3 Commits

5 changed files with 165 additions and 0 deletions

22
icons/192.svg Normal file
View File

@ -0,0 +1,22 @@
<svg width="192" height="192" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#16213e"/>
<circle cx="96" cy="96" r="70" fill="none" stroke="#0f3460" stroke-width="3"/>
<!-- Swerve modules (4 corners) -->
<circle cx="56" cy="56" r="12" fill="#e94560"/>
<circle cx="136" cy="56" r="12" fill="#e94560"/>
<circle cx="56" cy="136" r="12" fill="#e94560"/>
<circle cx="136" cy="136" r="12" fill="#e94560"/>
<!-- Velocity arrows -->
<line x1="56" y1="56" x2="56" y2="40" stroke="#ff6b6b" stroke-width="3" stroke-linecap="round"/>
<line x1="136" y1="56" x2="136" y2="40" stroke="#ff6b6b" stroke-width="3" stroke-linecap="round"/>
<line x1="56" y1="136" x2="56" y2="152" stroke="#ff6b6b" stroke-width="3" stroke-linecap="round"/>
<line x1="136" y1="136" x2="136" y2="152" stroke="#ff6b6b" stroke-width="3" stroke-linecap="round"/>
<!-- Center point -->
<circle cx="96" cy="96" r="6" fill="#4ecca3"/>
<!-- Robot frame -->
<polygon points="56,56 136,56 136,136 56,136" fill="none" stroke="#4ecca3" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

41
icons/512.svg Normal file
View File

@ -0,0 +1,41 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="#16213e"/>
<circle cx="256" cy="256" r="190" fill="none" stroke="#0f3460" stroke-width="8"/>
<!-- Swerve modules (4 corners) -->
<circle cx="150" cy="150" r="32" fill="#e94560"/>
<circle cx="362" cy="150" r="32" fill="#e94560"/>
<circle cx="150" cy="362" r="32" fill="#e94560"/>
<circle cx="362" cy="362" r="32" fill="#e94560"/>
<!-- Velocity arrows -->
<line x1="150" y1="150" x2="150" y2="100" stroke="#ff6b6b" stroke-width="8" stroke-linecap="round"/>
<polygon points="150,90 140,110 160,110" fill="#ff6b6b"/>
<line x1="362" y1="150" x2="362" y2="100" stroke="#ff6b6b" stroke-width="8" stroke-linecap="round"/>
<polygon points="362,90 352,110 372,110" fill="#ff6b6b"/>
<line x1="150" y1="362" x2="150" y2="412" stroke="#ff6b6b" stroke-width="8" stroke-linecap="round"/>
<polygon points="150,422 140,402 160,402" fill="#ff6b6b"/>
<line x1="362" y1="362" x2="362" y2="412" stroke="#ff6b6b" stroke-width="8" stroke-linecap="round"/>
<polygon points="362,422 352,402 372,402" fill="#ff6b6b"/>
<!-- Center point -->
<circle cx="256" cy="256" r="16" fill="#4ecca3"/>
<!-- Robot frame -->
<polygon points="150,150 362,150 362,362 150,362" fill="none" stroke="#4ecca3" stroke-width="6"/>
<!-- Grid lines in background -->
<g opacity="0.2" stroke="#4ecca3" stroke-width="1">
<line x1="100" y1="100" x2="100" y2="412"/>
<line x1="200" y1="100" x2="200" y2="412"/>
<line x1="300" y1="100" x2="300" y2="412"/>
<line x1="400" y1="100" x2="400" y2="412"/>
<line x1="100" y1="100" x2="412" y2="100"/>
<line x1="100" y1="200" x2="412" y2="200"/>
<line x1="100" y1="300" x2="412" y2="300"/>
<line x1="100" y1="400" x2="412" y2="400"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -4,7 +4,15 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#16213e">
<meta name="description"
content="Interactive simulation of a swerve drive robot with configurable size, speeds, and number of wheels">
<link rel="icon" type="image/svg+xml" href="/icons/512.svg">
<title>Swerve Drive Visualizer</title> <title>Swerve Drive Visualizer</title>
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
@ -313,6 +321,45 @@ if scale &lt; 1:
<script type="module" src="vendor/lucio/graham-scan.mjs"></script> <script type="module" src="vendor/lucio/graham-scan.mjs"></script>
<script type="module" src="script.js"></script> <script type="module" src="script.js"></script>
<!-- Register Service Worker for PWA -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service Worker registered successfully:', registration.scope);
// Check for updates periodically
setInterval(() => {
registration.update();
}, 60000); // Check every minute
// Handle updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New service worker available, prompt user to reload
if (confirm('A new version is available! Reload to update?')) {
newWorker.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
}
}
});
});
})
.catch((error) => {
console.log('Service Worker registration failed:', error);
});
// Handle controller change (new service worker activated)
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
});
}
</script>
</body> </body>
</html> </html>

22
manifest.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "MuneBase Swerve Drive Sim",
"short_name": "Swerve Sim",
"start_url": "/",
"display": "standalone",
"theme_color": "#16213e",
"background_color": "#16213e",
"icons": [
{
"src": "/icons/192.svg",
"sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/icons/512.svg",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any"
}
]
}

33
sw.js Normal file
View File

@ -0,0 +1,33 @@
const CACHE_NAME = 'v1';
const ASSETS = [
'/',
'/index.html',
'/script.js',
'/styles.css',
'/manifest.json',
'/icons/192.svg',
'/icons/512.svg',
'/vendor/lucio/graham-scan.mjs'
];
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
);
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then(response => response || fetch(e.request))
);
});