tunecamp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.env.local +2 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/LICENSE +22 -0
  5. package/README.md +554 -0
  6. package/dist/cli.d.ts +6 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +172 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/generator/embedGenerator.d.ts +38 -0
  11. package/dist/generator/embedGenerator.d.ts.map +1 -0
  12. package/dist/generator/embedGenerator.js +92 -0
  13. package/dist/generator/embedGenerator.js.map +1 -0
  14. package/dist/generator/feedGenerator.d.ts +50 -0
  15. package/dist/generator/feedGenerator.d.ts.map +1 -0
  16. package/dist/generator/feedGenerator.js +167 -0
  17. package/dist/generator/feedGenerator.js.map +1 -0
  18. package/dist/generator/podcastFeedGenerator.d.ts +54 -0
  19. package/dist/generator/podcastFeedGenerator.d.ts.map +1 -0
  20. package/dist/generator/podcastFeedGenerator.js +173 -0
  21. package/dist/generator/podcastFeedGenerator.js.map +1 -0
  22. package/dist/generator/proceduralCoverGenerator.d.ts +51 -0
  23. package/dist/generator/proceduralCoverGenerator.d.ts.map +1 -0
  24. package/dist/generator/proceduralCoverGenerator.js +228 -0
  25. package/dist/generator/proceduralCoverGenerator.js.map +1 -0
  26. package/dist/generator/siteGenerator.d.ts +55 -0
  27. package/dist/generator/siteGenerator.d.ts.map +1 -0
  28. package/dist/generator/siteGenerator.js +539 -0
  29. package/dist/generator/siteGenerator.js.map +1 -0
  30. package/dist/generator/templateEngine.d.ts +13 -0
  31. package/dist/generator/templateEngine.d.ts.map +1 -0
  32. package/dist/generator/templateEngine.js +146 -0
  33. package/dist/generator/templateEngine.js.map +1 -0
  34. package/dist/index.d.ts +12 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +32 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/parser/catalogParser.d.ts +13 -0
  39. package/dist/parser/catalogParser.d.ts.map +1 -0
  40. package/dist/parser/catalogParser.js +120 -0
  41. package/dist/parser/catalogParser.js.map +1 -0
  42. package/dist/tools/generate-codes.d.ts +14 -0
  43. package/dist/tools/generate-codes.d.ts.map +1 -0
  44. package/dist/tools/generate-codes.js +274 -0
  45. package/dist/tools/generate-codes.js.map +1 -0
  46. package/dist/tools/generate-sea-pair.d.ts +14 -0
  47. package/dist/tools/generate-sea-pair.d.ts.map +1 -0
  48. package/dist/tools/generate-sea-pair.js +111 -0
  49. package/dist/tools/generate-sea-pair.js.map +1 -0
  50. package/dist/types/index.d.ts +117 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/index.js +5 -0
  53. package/dist/types/index.js.map +1 -0
  54. package/dist/utils/audioUtils.d.ts +9 -0
  55. package/dist/utils/audioUtils.d.ts.map +1 -0
  56. package/dist/utils/audioUtils.js +67 -0
  57. package/dist/utils/audioUtils.js.map +1 -0
  58. package/dist/utils/configUtils.d.ts +11 -0
  59. package/dist/utils/configUtils.d.ts.map +1 -0
  60. package/dist/utils/configUtils.js +50 -0
  61. package/dist/utils/configUtils.js.map +1 -0
  62. package/dist/utils/fileUtils.d.ts +14 -0
  63. package/dist/utils/fileUtils.d.ts.map +1 -0
  64. package/dist/utils/fileUtils.js +73 -0
  65. package/dist/utils/fileUtils.js.map +1 -0
  66. package/examples/artist-free/README.md +36 -0
  67. package/examples/artist-paycurtain/README.md +49 -0
  68. package/examples/label/README.md +33 -0
  69. package/gundb-keypair.json +8 -0
  70. package/logo.svg +30 -0
  71. package/package-lock.json +1176 -0
  72. package/package.json +42 -0
  73. package/public/assets/community-registry.js +291 -0
  74. package/public/assets/download-stats.js +263 -0
  75. package/public/assets/player.js +219 -0
  76. package/public/assets/style.css +1170 -0
  77. package/public/assets/theme-widget.js +353 -0
  78. package/public/assets/unlock-codes.js +225 -0
  79. package/public/atom.xml +22 -0
  80. package/public/catalog.m3u +3 -0
  81. package/public/feed.xml +22 -0
  82. package/public/image.png +0 -0
  83. package/public/index.html +249 -0
  84. package/public/logo.svg +30 -0
  85. package/public/releases/chirichetto/Homologo - Chirichetto.wav +0 -0
  86. package/public/releases/chirichetto/cover.png +0 -0
  87. package/public/releases/chirichetto/embed-code.txt +16 -0
  88. package/public/releases/chirichetto/embed-compact.txt +8 -0
  89. package/public/releases/chirichetto/embed.html +39 -0
  90. package/public/releases/chirichetto/index.html +389 -0
  91. package/public/releases/chirichetto/playlist.m3u +3 -0
  92. package/templates/dark/assets/community-registry.js +291 -0
  93. package/templates/dark/assets/download-stats.js +263 -0
  94. package/templates/dark/assets/player.js +219 -0
  95. package/templates/dark/assets/style.css +740 -0
  96. package/templates/dark/index.hbs +73 -0
  97. package/templates/dark/layout.hbs +84 -0
  98. package/templates/dark/release.hbs +212 -0
  99. package/templates/default/assets/community-registry.js +291 -0
  100. package/templates/default/assets/download-stats.js +263 -0
  101. package/templates/default/assets/player.js +219 -0
  102. package/templates/default/assets/style.css +1170 -0
  103. package/templates/default/assets/theme-widget.js +353 -0
  104. package/templates/default/assets/unlock-codes.js +225 -0
  105. package/templates/default/index.hbs +188 -0
  106. package/templates/default/layout.hbs +117 -0
  107. package/templates/default/release.hbs +553 -0
  108. package/templates/minimal/assets/community-registry.js +291 -0
  109. package/templates/minimal/assets/download-stats.js +263 -0
  110. package/templates/minimal/assets/player.js +219 -0
  111. package/templates/minimal/assets/style.css +796 -0
  112. package/templates/minimal/index.hbs +73 -0
  113. package/templates/minimal/layout.hbs +84 -0
  114. package/templates/minimal/release.hbs +212 -0
  115. package/templates/retro/assets/community-registry.js +291 -0
  116. package/templates/retro/assets/download-stats.js +263 -0
  117. package/templates/retro/assets/player.js +219 -0
  118. package/templates/retro/assets/style.css +872 -0
  119. package/templates/retro/index.hbs +73 -0
  120. package/templates/retro/layout.hbs +84 -0
  121. package/templates/retro/release.hbs +212 -0
  122. package/templates/translucent/assets/community-registry.js +291 -0
  123. package/templates/translucent/assets/download-stats.js +263 -0
  124. package/templates/translucent/assets/player.js +219 -0
  125. package/templates/translucent/assets/style.css +1352 -0
  126. package/templates/translucent/index.hbs +73 -0
  127. package/templates/translucent/layout.hbs +84 -0
  128. package/templates/translucent/release.hbs +212 -0
  129. package/website/community.html +492 -0
  130. package/website/index.html +195 -0
  131. package/website/styles.css +396 -0
  132. package/website/tunecamp.svg +30 -0
@@ -0,0 +1,492 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tunecamp Community - Sites Using Tunecamp</title>
7
+ <meta name="description" content="Discover artists and music labels using Tunecamp. A decentralized directory of independent music sites.">
8
+ <link rel="stylesheet" href="styles.css">
9
+ <style>
10
+ .community-header {
11
+ text-align: center;
12
+ margin-bottom: 60px;
13
+ }
14
+
15
+ .community-header h1 {
16
+ font-size: 2.5rem;
17
+ margin-bottom: 20px;
18
+ }
19
+
20
+ .community-stats {
21
+ display: flex;
22
+ justify-content: center;
23
+ gap: 40px;
24
+ margin: 30px 0;
25
+ }
26
+
27
+ .stat-item {
28
+ text-align: center;
29
+ }
30
+
31
+ .stat-number {
32
+ font-size: 2.5rem;
33
+ font-weight: bold;
34
+ color: #007bff;
35
+ }
36
+
37
+ .stat-label {
38
+ font-size: 0.9rem;
39
+ color: #666;
40
+ }
41
+
42
+ .sites-grid {
43
+ display: grid;
44
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
45
+ gap: 25px;
46
+ margin: 40px 0;
47
+ }
48
+
49
+ .site-card {
50
+ background: #f8f9fa;
51
+ border-radius: 12px;
52
+ overflow: hidden;
53
+ transition: transform 0.2s, box-shadow 0.2s;
54
+ text-decoration: none;
55
+ color: inherit;
56
+ display: block;
57
+ }
58
+
59
+ .site-card:hover {
60
+ transform: translateY(-4px);
61
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
62
+ opacity: 1;
63
+ border-bottom: none;
64
+ }
65
+
66
+ .site-cover {
67
+ width: 100%;
68
+ height: 180px;
69
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ font-size: 4rem;
74
+ color: rgba(255,255,255,0.3);
75
+ }
76
+
77
+ .site-cover img {
78
+ width: 100%;
79
+ height: 100%;
80
+ object-fit: cover;
81
+ }
82
+
83
+ .site-info {
84
+ padding: 20px;
85
+ }
86
+
87
+ .site-title {
88
+ font-size: 1.1rem;
89
+ font-weight: 600;
90
+ margin-bottom: 5px;
91
+ color: #333;
92
+ }
93
+
94
+ .site-artist {
95
+ font-size: 0.9rem;
96
+ color: #666;
97
+ margin-bottom: 10px;
98
+ }
99
+
100
+ .site-url {
101
+ font-size: 0.8rem;
102
+ color: #007bff;
103
+ word-break: break-all;
104
+ }
105
+
106
+ .site-meta {
107
+ font-size: 0.75rem;
108
+ color: #999;
109
+ margin-top: 10px;
110
+ }
111
+
112
+ .loading {
113
+ text-align: center;
114
+ padding: 60px;
115
+ color: #666;
116
+ }
117
+
118
+ .loading-spinner {
119
+ font-size: 2rem;
120
+ animation: spin 1s linear infinite;
121
+ margin-bottom: 20px;
122
+ }
123
+
124
+ @keyframes spin {
125
+ from { transform: rotate(0deg); }
126
+ to { transform: rotate(360deg); }
127
+ }
128
+
129
+ .empty-state {
130
+ text-align: center;
131
+ padding: 60px;
132
+ color: #666;
133
+ }
134
+
135
+ .empty-state i {
136
+ font-size: 4rem;
137
+ color: #ddd;
138
+ margin-bottom: 20px;
139
+ }
140
+
141
+ .register-cta {
142
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
143
+ color: white;
144
+ padding: 40px;
145
+ border-radius: 12px;
146
+ text-align: center;
147
+ margin: 40px 0;
148
+ }
149
+
150
+ .register-cta h3 {
151
+ color: white;
152
+ margin-bottom: 15px;
153
+ }
154
+
155
+ .register-cta p {
156
+ opacity: 0.9;
157
+ margin-bottom: 20px;
158
+ }
159
+
160
+ .register-cta .btn {
161
+ background: white;
162
+ color: #6366f1;
163
+ }
164
+
165
+ .register-cta .btn:hover {
166
+ background: #f0f0f0;
167
+ }
168
+
169
+ .how-it-works {
170
+ background: #f8f9fa;
171
+ padding: 30px;
172
+ border-radius: 12px;
173
+ margin: 40px 0;
174
+ }
175
+
176
+ .how-it-works h3 {
177
+ margin-bottom: 20px;
178
+ }
179
+
180
+ .how-it-works ol {
181
+ padding-left: 20px;
182
+ }
183
+
184
+ .how-it-works li {
185
+ margin-bottom: 10px;
186
+ color: #666;
187
+ }
188
+
189
+ .back-link {
190
+ margin-bottom: 30px;
191
+ }
192
+
193
+ .back-link a {
194
+ color: #666;
195
+ text-decoration: none;
196
+ border-bottom: none;
197
+ }
198
+
199
+ .back-link a:hover {
200
+ color: #007bff;
201
+ }
202
+
203
+ .filter-bar {
204
+ display: flex;
205
+ gap: 15px;
206
+ margin-bottom: 30px;
207
+ flex-wrap: wrap;
208
+ }
209
+
210
+ .filter-bar input {
211
+ flex: 1;
212
+ min-width: 200px;
213
+ padding: 12px 16px;
214
+ border: 2px solid #dee2e6;
215
+ border-radius: 8px;
216
+ font-family: inherit;
217
+ font-size: 1rem;
218
+ }
219
+
220
+ .filter-bar input:focus {
221
+ outline: none;
222
+ border-color: #007bff;
223
+ }
224
+
225
+ .realtime-indicator {
226
+ display: inline-flex;
227
+ align-items: center;
228
+ gap: 8px;
229
+ font-size: 0.85rem;
230
+ color: #28a745;
231
+ }
232
+
233
+ .realtime-dot {
234
+ width: 8px;
235
+ height: 8px;
236
+ background: #28a745;
237
+ border-radius: 50%;
238
+ animation: pulse 2s infinite;
239
+ }
240
+
241
+ @keyframes pulse {
242
+ 0%, 100% { opacity: 1; }
243
+ 50% { opacity: 0.5; }
244
+ }
245
+ </style>
246
+ </head>
247
+ <body>
248
+ <div class="container">
249
+ <div class="back-link">
250
+ <a href="index.html">← Back to Tunecamp</a>
251
+ </div>
252
+
253
+ <header class="community-header">
254
+ <img src="tunecamp.svg" alt="Tunecamp" class="logo" style="width: 150px; height: 150px;">
255
+ <h1>Tunecamp Community</h1>
256
+ <p class="tagline">Discover independent artists and labels using Tunecamp</p>
257
+
258
+ <div class="community-stats">
259
+ <div class="stat-item">
260
+ <div class="stat-number" id="siteCount">-</div>
261
+ <div class="stat-label">Sites Registered</div>
262
+ </div>
263
+ <div class="stat-item">
264
+ <div class="realtime-indicator">
265
+ <span class="realtime-dot"></span>
266
+ Live Updates
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </header>
271
+
272
+ <div class="filter-bar">
273
+ <input type="text" id="searchInput" placeholder="Search artists, titles..." oninput="filterSites()">
274
+ </div>
275
+
276
+ <div id="sitesContainer">
277
+ <div class="loading">
278
+ <div class="loading-spinner">⟳</div>
279
+ <p>Loading community sites from decentralized network...</p>
280
+ </div>
281
+ </div>
282
+
283
+ <div class="register-cta">
284
+ <h3>🎵 Join the Community!</h3>
285
+ <p>Your Tunecamp site is automatically registered when visitors browse it.<br>
286
+ No sign-up needed - it's fully decentralized via GunDB.</p>
287
+ <a href="index.html" class="btn">Get Started with Tunecamp</a>
288
+ </div>
289
+
290
+ <div class="how-it-works">
291
+ <h3>How does this work?</h3>
292
+ <ol>
293
+ <li><strong>Build your site</strong> with Tunecamp and deploy it anywhere</li>
294
+ <li><strong>Automatic registration</strong> - when someone visits your site, it gets added to this directory</li>
295
+ <li><strong>Decentralized</strong> - all data is stored on public GunDB peers, no central server</li>
296
+ <li><strong>Real-time</strong> - new sites appear here instantly as they're discovered</li>
297
+ </ol>
298
+ <p style="margin-top: 15px; color: #666; font-size: 0.9rem;">
299
+ <strong>Privacy:</strong> Only your public site URL, title, and artist name are shared.
300
+ No personal data or analytics are collected.
301
+ </p>
302
+ </div>
303
+
304
+ <footer>
305
+ <div class="footer-links">
306
+ <a href="https://github.com/scobru/tunecamp">GitHub</a>
307
+ <a href="https://www.npmjs.com/package/tunecamp">NPM</a>
308
+ <a href="index.html">Home</a>
309
+ </div>
310
+ <div class="footer-note">
311
+ Powered by GunDB - a decentralized database.
312
+ </div>
313
+ </footer>
314
+ </div>
315
+
316
+ <!-- GunDB -->
317
+ <script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
318
+
319
+ <script>
320
+ // GunDB configuration
321
+ const REGISTRY_PEERS = [
322
+ 'https://gun.defucc.me/gun',
323
+ 'https://gun.o8.is/gun',
324
+ 'https://shogun-relay.scobrudot.dev/gun',
325
+ 'https://relay.peer.ooo/gun',
326
+ ];
327
+ const REGISTRY_ROOT = 'shogun';
328
+ const REGISTRY_NAMESPACE = 'tunecamp-community';
329
+
330
+ let gun;
331
+ let allSites = new Map();
332
+
333
+ // Initialize
334
+ document.addEventListener('DOMContentLoaded', function() {
335
+ gun = Gun({ peers: REGISTRY_PEERS, localStorage: true });
336
+ loadSites();
337
+ });
338
+
339
+ function loadSites() {
340
+ const container = document.getElementById('sitesContainer');
341
+
342
+ // Subscribe to all sites
343
+ gun.get(REGISTRY_ROOT).get(REGISTRY_NAMESPACE).get('sites').map().on((data, key) => {
344
+ if (data && data.url) {
345
+ allSites.set(key, {
346
+ id: key,
347
+ url: data.url,
348
+ title: data.title || 'Untitled',
349
+ description: data.description || '',
350
+ artistName: data.artistName || '',
351
+ coverImage: data.coverImage || '',
352
+ registeredAt: data.registeredAt,
353
+ lastSeen: data.lastSeen,
354
+ });
355
+ renderSites();
356
+ }
357
+ });
358
+
359
+ // Initial render after timeout (in case no sites yet)
360
+ setTimeout(() => {
361
+ if (allSites.size === 0) {
362
+ container.innerHTML = `
363
+ <div class="empty-state">
364
+ <i>🎵</i>
365
+ <h3>No sites registered yet</h3>
366
+ <p>Be the first! Create a Tunecamp site and visitors will automatically register it here.</p>
367
+ </div>
368
+ `;
369
+ }
370
+ updateCount();
371
+ }, 3000);
372
+ }
373
+
374
+ function renderSites() {
375
+ const container = document.getElementById('sitesContainer');
376
+ const searchTerm = document.getElementById('searchInput').value.toLowerCase();
377
+
378
+ // Convert to array
379
+ let sitesArray = Array.from(allSites.values());
380
+
381
+ // Deduplicate by title+artist (keep the most recently seen)
382
+ const dedupMap = new Map();
383
+ sitesArray.forEach(site => {
384
+ const key = `${(site.title || '').toLowerCase().trim()}::${(site.artistName || '').toLowerCase().trim()}`;
385
+ const existing = dedupMap.get(key);
386
+ if (!existing || (site.lastSeen || 0) > (existing.lastSeen || 0)) {
387
+ dedupMap.set(key, site);
388
+ }
389
+ });
390
+ sitesArray = Array.from(dedupMap.values());
391
+
392
+ // Filter by search term
393
+ if (searchTerm) {
394
+ sitesArray = sitesArray.filter(site =>
395
+ site.title.toLowerCase().includes(searchTerm) ||
396
+ site.artistName.toLowerCase().includes(searchTerm) ||
397
+ site.url.toLowerCase().includes(searchTerm)
398
+ );
399
+ }
400
+
401
+ // Sort by lastSeen (most recent first)
402
+ sitesArray.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0));
403
+
404
+ if (sitesArray.length === 0) {
405
+ container.innerHTML = `
406
+ <div class="empty-state">
407
+ <i>🔍</i>
408
+ <h3>No sites found</h3>
409
+ <p>${searchTerm ? 'Try a different search term.' : 'No sites registered yet.'}</p>
410
+ </div>
411
+ `;
412
+ return;
413
+ }
414
+
415
+ container.innerHTML = `
416
+ <div class="sites-grid">
417
+ ${sitesArray.map(site => renderSiteCard(site)).join('')}
418
+ </div>
419
+ `;
420
+
421
+ updateCount();
422
+ }
423
+
424
+ function renderSiteCard(site) {
425
+ const coverHtml = site.coverImage
426
+ ? `<img src="${escapeHtml(site.coverImage)}" alt="${escapeHtml(site.title)}" onerror="this.parentElement.innerHTML='♪'">`
427
+ : '♪';
428
+
429
+ const displayUrl = site.url.replace(/^https?:\/\//, '').replace(/\/$/, '');
430
+ const lastSeenDate = site.lastSeen ? formatDate(site.lastSeen) : 'Unknown';
431
+
432
+ return `
433
+ <a href="${escapeHtml(site.url)}" target="_blank" rel="noopener" class="site-card">
434
+ <div class="site-cover">${coverHtml}</div>
435
+ <div class="site-info">
436
+ <div class="site-title">${escapeHtml(site.title)}</div>
437
+ ${site.artistName ? `<div class="site-artist">by ${escapeHtml(site.artistName)}</div>` : ''}
438
+ <div class="site-url">${escapeHtml(displayUrl)}</div>
439
+ <div class="site-meta">Last seen: ${lastSeenDate}</div>
440
+ </div>
441
+ </a>
442
+ `;
443
+ }
444
+
445
+ function filterSites() {
446
+ renderSites();
447
+ }
448
+
449
+ function updateCount() {
450
+ // Count unique sites (deduplicated by title+artist)
451
+ const dedupMap = new Map();
452
+ allSites.forEach(site => {
453
+ const key = `${(site.title || '').toLowerCase().trim()}::${(site.artistName || '').toLowerCase().trim()}`;
454
+ if (!dedupMap.has(key)) {
455
+ dedupMap.set(key, true);
456
+ }
457
+ });
458
+ document.getElementById('siteCount').textContent = dedupMap.size;
459
+ }
460
+
461
+ function formatDate(timestamp) {
462
+ const date = new Date(timestamp);
463
+ const now = new Date();
464
+ const diffMs = now - date;
465
+ const diffMins = Math.floor(diffMs / 60000);
466
+ const diffHours = Math.floor(diffMs / 3600000);
467
+ const diffDays = Math.floor(diffMs / 86400000);
468
+
469
+ if (diffMins < 1) return 'Just now';
470
+ if (diffMins < 60) return `${diffMins}m ago`;
471
+ if (diffHours < 24) return `${diffHours}h ago`;
472
+ if (diffDays < 7) return `${diffDays}d ago`;
473
+
474
+ return date.toLocaleDateString('en-US', {
475
+ month: 'short',
476
+ day: 'numeric',
477
+ year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined,
478
+ });
479
+ }
480
+
481
+ function escapeHtml(str) {
482
+ if (!str) return '';
483
+ return str
484
+ .replace(/&/g, '&amp;')
485
+ .replace(/</g, '&lt;')
486
+ .replace(/>/g, '&gt;')
487
+ .replace(/"/g, '&quot;')
488
+ .replace(/'/g, '&#039;');
489
+ }
490
+ </script>
491
+ </body>
492
+ </html>
@@ -0,0 +1,195 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tunecamp</title>
7
+ <meta name="description" content="A modern static site generator for musicians and music labels, inspired by Faircamp. Point Tunecamp at a folder hierarchy containing your audio files and generate a complete, database-free website.">
8
+ <link rel="stylesheet" href="styles.css">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <header>
13
+ <img src="tunecamp.svg" alt="Tunecamp" class="logo">
14
+ <h1>Tunecamp</h1>
15
+ <p class="tagline">A modern static site generator for musicians and music labels.</p>
16
+ <p class="tagline">Point Tunecamp at a folder hierarchy containing your audio files.<br>Within minutes, Tunecamp builds a complete website presenting your work.<br>The resulting site requires no database, no programming, and minimal maintenance.</p>
17
+ </header>
18
+
19
+ <div class="download-section">
20
+ <h2>Install</h2>
21
+ <p>Install via npm on Windows, macOS, and Linux</p>
22
+ <div class="download-buttons">
23
+ <a href="https://www.npmjs.com/package/tunecamp" class="btn">NPM</a>
24
+ <a href="https://github.com/scobru/tunecamp" class="btn btn-secondary">GitHub</a>
25
+ <a href="https://github.com/scobru/tunecamp#quick-start" class="btn btn-secondary">Docs</a>
26
+ </div>
27
+ <p><strong>Current version: 0.1.0</strong> | MIT licensed</p>
28
+ </div>
29
+
30
+ <div class="features">
31
+ <div class="feature-section">
32
+ <h2>Audio-First</h2>
33
+ <p>Built specifically for musicians and music labels with automatic metadata extraction.</p>
34
+ <ul class="feature-list">
35
+ <li><strong>Multi-format Support</strong> MP3, FLAC, OGG, WAV, M4A, and more</li>
36
+ <li><strong>Automatic Metadata</strong> Reads tags from your audio files</li>
37
+ <li><strong>Built-in Player</strong> Modern HTML5 audio player</li>
38
+ <li><strong>Zero Database</strong> Pure static HTML generation</li>
39
+ </ul>
40
+ </div>
41
+
42
+ <div class="feature-section">
43
+ <h2>Download Models</h2>
44
+ <p>Flexible download options to match your distribution style.</p>
45
+ <ul class="feature-list">
46
+ <li><strong>Free Downloads</strong> Make all tracks immediately available</li>
47
+ <li><strong>Soft Paycurtain (Honor System)</strong> Show PayPal/Stripe links with a suggested price, but downloads remain always available - it's up to the listener to support you</li>
48
+ <li><strong>Unlock Codes</strong> Protect downloads with codes validated via GunDB (requires self-hosting to generate codes)</li>
49
+ <li><strong>Download Stats</strong> Track download counts via decentralized GunDB</li>
50
+ </ul>
51
+ </div>
52
+
53
+ <div class="feature-section">
54
+ <h2>For Musicians & Labels</h2>
55
+ <p>Perfect for solo artists, bands, and music labels of all sizes.</p>
56
+ <ul class="feature-list">
57
+ <li><strong>Solo Artists</strong> Showcase your complete discography</li>
58
+ <li><strong>Bands & Collectives</strong> Represent your musical projects</li>
59
+ <li><strong>Music Labels</strong> Multi-artist catalog support</li>
60
+ <li><strong>Flexible Structure</strong> Organize releases, tracks, and metadata</li>
61
+ </ul>
62
+ </div>
63
+
64
+ <div class="feature-section">
65
+ <h2>Modern & Fast</h2>
66
+ <p>Built with modern web standards for optimal performance.</p>
67
+ <ul class="feature-list">
68
+ <li><strong>Responsive Design</strong> Works on all devices</li>
69
+ <li><strong>Fast Loading</strong> Static sites that load instantly</li>
70
+ <li><strong>Mobile Friendly</strong> Optimized for mobile devices</li>
71
+ <li><strong>SEO Ready</strong> Search engine optimized</li>
72
+ </ul>
73
+ </div>
74
+
75
+ <div class="feature-section">
76
+ <h2>Beautiful Themes</h2>
77
+ <p>Five ready-to-use themes with customizable options.</p>
78
+ <ul class="feature-list">
79
+ <li><strong>Default</strong> Modern dark theme with purple/blue gradients</li>
80
+ <li><strong>Minimal</strong> Clean light theme with lots of white space</li>
81
+ <li><strong>Dark</strong> Aggressive dark theme with red accents</li>
82
+ <li><strong>Retro</strong> 80s-inspired theme with neon colors</li>
83
+ <li><strong>Translucent</strong> Glassmorphism with blur effects</li>
84
+ </ul>
85
+ </div>
86
+
87
+ <div class="feature-section">
88
+ <h2>Easy Configuration</h2>
89
+ <p>Simple YAML-based configuration for maximum flexibility.</p>
90
+ <ul class="feature-list">
91
+ <li><strong>YAML Configuration</strong> Easy-to-edit config files</li>
92
+ <li><strong>Artist Profiles</strong> Bio, links, and social media</li>
93
+ <li><strong>Release Metadata</strong> Dates, descriptions, and credits</li>
94
+ <li><strong>Custom Themes</strong> Create your own theme templates</li>
95
+ </ul>
96
+ </div>
97
+
98
+ <div class="feature-section">
99
+ <h2>Advanced Features</h2>
100
+ <p>Everything you need for modern distribution and sharing.</p>
101
+ <ul class="feature-list">
102
+ <li><strong>RSS & Atom</strong> Automatic feed generation for releases</li>
103
+ <li><strong>Podcast RSS</strong> Generate podcast feeds from your catalog</li>
104
+ <li><strong>Embed Widgets</strong> Share releases with embeddable players</li>
105
+ <li><strong>M3U Playlists</strong> Playlists per release and for the whole catalog</li>
106
+ <li><strong>Procedural Covers</strong> Auto-generate cover art when missing</li>
107
+ <li><strong>Unlock Codes</strong> Download protection via GunDB (requires self-hosting for code generation)</li>
108
+ </ul>
109
+ </div>
110
+
111
+ <div class="feature-section">
112
+ <h2>🌐 Community Directory</h2>
113
+ <p>Join the decentralized Tunecamp community!</p>
114
+ <ul class="feature-list">
115
+ <li><strong>Auto-Discovery</strong> Your site is automatically registered when visitors browse it</li>
116
+ <li><strong>Decentralized</strong> No sign-up, no central server - powered by GunDB</li>
117
+ <li><strong>Real-time</strong> New sites appear instantly in the community directory</li>
118
+ <li><strong>Privacy-First</strong> Only public info shared (URL, title, artist name)</li>
119
+ </ul>
120
+ <p style="margin-top: 15px;"><a href="community.html" class="btn btn-secondary">Browse Community →</a></p>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- <div class="example-sites">
125
+ <h3>Sites Using Tunecamp</h3>
126
+ <p>These are some example sites - but many more are out there!</p>
127
+ <div class="site-links">
128
+ <a href="#" class="site-link">example1.com</a>
129
+ <a href="#" class="site-link">example2.com</a>
130
+ <a href="#" class="site-link">example3.com</a>
131
+ <a href="#" class="site-link">example4.com</a>
132
+ <a href="#" class="site-link">example5.com</a>
133
+ <a href="#" class="site-link">example6.com</a>
134
+ </div>
135
+ </div> -->
136
+
137
+ <div class="principles">
138
+ <h3>Tunecamp adheres to these principles:</h3>
139
+ <ul class="principles-list">
140
+ <li>Personal</li>
141
+ <li>Plain</li>
142
+ <li>Small</li>
143
+ <li>Simple</li>
144
+ <li>Light</li>
145
+ <li>Fast</li>
146
+ <li>Reduced</li>
147
+ <li>Elegant</li>
148
+ <li>Stable</li>
149
+ <li>Low/No-Maintenance</li>
150
+ <li>Free</li>
151
+ <li>Independent</li>
152
+ <li>Privacy-Respecting</li>
153
+ <li>Standards-Conforming</li>
154
+ </ul>
155
+ </div>
156
+
157
+ <div class="code-block">
158
+ <h3>Quick Start</h3>
159
+ <pre><code><span class="comment"># Install Tunecamp</span>
160
+ npm install -g tunecamp
161
+
162
+ <span class="comment"># Create your music catalog</span>
163
+ mkdir my-music
164
+ cd my-music
165
+
166
+ <span class="comment"># Initialize catalog structure</span>
167
+ tunecamp init .
168
+
169
+ <span class="comment"># Add your audio files</span>
170
+ cp *.mp3 releases/example-album/tracks/
171
+ <span class="string"># (optional) rename example-album to your release slug</span>
172
+
173
+ <span class="comment"># Configure your catalog</span>
174
+ <span class="string"># Edit catalog.yaml, artist.yaml, and release.yaml files</span>
175
+
176
+ <span class="comment"># Generate your website</span>
177
+ tunecamp build . --output ./public
178
+
179
+ <span class="comment"># Deploy to any static host</span>
180
+ <span class="string"># Upload the public folder to Netlify, Vercel, GitHub Pages, etc.</span></code></pre>
181
+ </div>
182
+
183
+ <footer>
184
+ <div class="footer-links">
185
+ <a href="https://github.com/scobru/tunecamp">GitHub</a>
186
+ <a href="https://www.npmjs.com/package/tunecamp">NPM</a>
187
+ <a href="community.html">🌐 Community</a>
188
+ </div>
189
+ <div class="footer-note">
190
+ This page uses no cookies and no client-side scripts.
191
+ </div>
192
+ </footer>
193
+ </div>
194
+ </body>
195
+ </html>