wip pwa support
Hunter
Sunday, November 2nd, 2025 at 11:39 PM
22 *.swp
23 *.swo
24 *~
25 +
26 +# auto-generated PWA files
27 +manifest.json
28 +resource-manifest.json
29 +service-worker.js
30 +
31 +# Deployment script (personal)
32 +push.py
1 +#!/usr/bin/env python3
2 +"""
3 +Creates manifest.json, resource-manifest.json, and service-worker.js
4 +based on the contents of tracks.json
5 +"""
6 +
7 +import json
8 +from pathlib import Path
9 +
10 +SCRIPT_DIR = Path(__file__).parent.absolute()
11 +TRACKS_JSON = SCRIPT_DIR / "tracks.json"
12 +
13 +
14 +def generate_pwa_manifests():
15 + """Generate PWA manifest files based on tracks.json"""
16 + print("Generating PWA manifests...")
17 +
18 + # Load tracks.json
19 + if not TRACKS_JSON.exists():
20 + print("Error: tracks.json not found. Run scan.py first.")
21 + return
22 +
23 + with open(TRACKS_JSON, 'r', encoding='utf-8') as f:
24 + tracks = json.load(f)
25 +
26 + # Generate manifest.json
27 + manifest = {
28 + "id": "/vibe_capsule/",
29 + "name": "vibe capsule",
30 + "short_name": "vibe capsule",
31 + "description": "mixtape as artifact",
32 + "start_url": "/vibe_capsule/",
33 + "scope": "/vibe_capsule/",
34 + "display": "standalone",
35 + "background_color": "#1a1a1a",
36 + "theme_color": "#1a1a1a",
37 + "icons": [
38 + {
39 + "src": "resources/icon.png",
40 + "sizes": "640x640",
41 + "type": "image/png",
42 + "purpose": "any maskable"
43 + }
44 + ]
45 + }
46 +
47 + with open(SCRIPT_DIR / "manifest.json", 'w', encoding='utf-8') as f:
48 + json.dump(manifest, f, indent=2)
49 + print("✓ Generated manifest.json")
50 +
51 + # Generate resource-manifest.json
52 + resource_manifest = {
53 + "static_files": [
54 + "./",
55 + "index.html",
56 + "styles.css",
57 + "script.js",
58 + "tracks.json",
59 + "resources/icon.png",
60 + "resources/play.png",
61 + "resources/pause.png",
62 + "resources/prev.png",
63 + "resources/next.png",
64 + "resources/album_art.jpg"
65 + ],
66 + "tracks": [f"tracks/{track['filename']}" for track in tracks]
67 + }
68 +
69 + with open(SCRIPT_DIR / "resource-manifest.json", 'w', encoding='utf-8') as f:
70 + json.dump(resource_manifest, f, indent=2)
71 + print("✓ Generated resource-manifest.json")
72 +
73 + # Generate service-worker.js
74 + all_files = resource_manifest["static_files"] + resource_manifest["tracks"]
75 + service_worker_content = f'''// Auto-generated service worker for vibe capsule PWA
76 +const CACHE_NAME = 'vibe-capsule-v3';
77 +const urlsToCache = {json.dumps(all_files, indent=2)};
78 +
79 +// Get the base path from the service worker location
80 +const getBasePath = () => {{
81 + const swPath = self.location.pathname;
82 + return swPath.substring(0, swPath.lastIndexOf('/') + 1);
83 +}};
84 +
85 +const basePath = getBasePath();
86 +
87 +// Install event - cache all resources
88 +self.addEventListener('install', (event) => {{
89 + console.log('Service Worker installing...', 'Base path:', basePath);
90 + event.waitUntil(
91 + caches.open(CACHE_NAME)
92 + .then((cache) => {{
93 + console.log('Opened cache');
94 + // Make URLs absolute relative to service worker location
95 + const absoluteUrls = urlsToCache.map(url => {{
96 + if (url === './') return basePath;
97 + return new URL(url, basePath + 'index.html').href;
98 + }});
99 + console.log('Caching', absoluteUrls.length, 'resources');
100 + console.log('URLs to cache:', absoluteUrls);
101 +
102 + // Cache files one by one with better error handling
103 + return Promise.all(
104 + absoluteUrls.map(url =>
105 + cache.add(url)
106 + .then(() => console.log('✓ Cached:', url))
107 + .catch(err => console.error('✗ Failed to cache:', url, err))
108 + )
109 + );
110 + }})
111 + .then(() => {{
112 + console.log('All resources cached successfully');
113 + return self.skipWaiting();
114 + }})
115 + .catch(error => {{
116 + console.error('Service Worker installation failed:', error);
117 + }})
118 + );
119 +}});
120 +
121 +// Activate event - clean up old caches
122 +self.addEventListener('activate', (event) => {{
123 + console.log('Service Worker activating...');
124 + event.waitUntil(
125 + caches.keys().then((cacheNames) => {{
126 + return Promise.all(
127 + cacheNames.map((cacheName) => {{
128 + if (cacheName !== CACHE_NAME) {{
129 + console.log('Deleting old cache:', cacheName);
130 + return caches.delete(cacheName);
131 + }}
132 + }})
133 + );
134 + }}).then(() => self.clients.claim())
135 + );
136 +}});
137 +
138 +// Fetch event - serve from cache, fallback to network
139 +self.addEventListener('fetch', (event) => {{
140 + event.respondWith(
141 + caches.match(event.request)
142 + .then((response) => {{
143 + // Cache hit - return response
144 + if (response) {{
145 + console.log('Serving from cache:', event.request.url);
146 + return response;
147 + }}
148 +
149 + // Cache miss - try network
150 + console.log('Fetching from network:', event.request.url);
151 + return fetch(event.request).then((response) => {{
152 + // Check if valid response
153 + if (!response || response.status !== 200) {{
154 + return response;
155 + }}
156 +
157 + // Clone the response for caching
158 + const responseToCache = response.clone();
159 +
160 + caches.open(CACHE_NAME)
161 + .then((cache) => {{
162 + cache.put(event.request, responseToCache);
163 + }});
164 +
165 + return response;
166 + }}).catch((error) => {{
167 + console.error('Fetch failed; returning offline page if available:', error);
168 + // If fetch fails, try to return from cache one more time
169 + return caches.match(event.request);
170 + }});
171 + }})
172 + );
173 +}});
174 +'''
175 +
176 + with open(SCRIPT_DIR / "service-worker.js", 'w', encoding='utf-8') as f:
177 + f.write(service_worker_content)
178 + print("✓ Generated service-worker.js")
179 + print()
180 + print("PWA manifests generated successfully!")
181 +
182 +
183 +if __name__ == "__main__":
184 + generate_pwa_manifests()
1 #!/usr/bin/env python3
2 """
-Simple HTTP Server for vibe capsule MP3 player
-Starts server and opens browser automatically
3 +Starts HTTP server for local testing
4 Automatically manages a virtual environment for dependencies
5 """
6
7 import http.server
8 import socketserver
-import webbrowser
9 import socket
10 import sys
11 import os
4 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>💿</text></svg>">
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 + <base href="/vibe_capsule/">
8 + <meta name="theme-color" content="#1a1a1a">
9 + <meta name="apple-mobile-web-app-capable" content="yes">
10 + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
11 + <link rel="apple-touch-icon" href="resources/icon.png">
12 + <link rel="manifest" href="manifest.json">
13 <title>vibe capsule</title>
14 <link rel="stylesheet" href="styles.css">
15 </head>
Binary files differ.
1 +// Register service worker for PWA functionality
2 +if ('serviceWorker' in navigator) {
3 + window.addEventListener('load', () => {
4 + navigator.serviceWorker.register('service-worker.js')
5 + .then(registration => {
6 + console.log('Service Worker registered successfully:', registration.scope);
7 + })
8 + .catch(error => {
9 + console.log('Service Worker registration failed:', error);
10 + });
11 + });
12 +}
13 +
14 const playPauseBtn = document.getElementById('playPause');
15 const prevBtn = document.getElementById('prev');
16 const nextBtn = document.getElementById('next');
69 })
70 .catch(error => {
71 console.error('Error loading tracks:', error);
- updateCurrentSongDisplay('Error: tracks.json not found. Run scan.py first.');
72 + updateCurrentSongDisplay('Unable to load tracks. Please check your connection.');
73 });
74
75 // Audio event listeners