From f0cbe5b5d0bf03c2c70d4f923cac2f714737d4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=BChn?= Date: Thu, 30 Apr 2026 10:35:29 +0200 Subject: [PATCH] Add header stopwatch and asset cache-busting - Stopwatch between logo and menu: tap to start, tap to stop, tap to reset - AppController passes md5-based version hash for app.js and style.css to prevent browser caching issues Co-Authored-By: Claude Sonnet 4.6 --- public/app.js | 35 ++++++++++++++++++++++++++++++++ public/style.css | 2 ++ src/Controller/AppController.php | 6 +++++- templates/app.html.twig | 5 +++-- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/public/app.js b/public/app.js index 97a60e2..159d41e 100644 --- a/public/app.js +++ b/public/app.js @@ -714,3 +714,38 @@ document.addEventListener('visibilitychange',function(){ loadGoals().then(function(g){goals=g;render();}).catch(function(){}); } }); + +(function(){ + var swEl = document.getElementById('sw'); + var state = 0; // 0=stopped, 1=running, 2=paused + var start = 0, elapsed = 0, raf = null; + + function fmt(ms){ + var s = ms / 1000; + return s.toFixed(2) + 's'; + } + + function tick(){ + swEl.textContent = fmt(Date.now() - start + elapsed); + raf = requestAnimationFrame(tick); + } + + swEl.addEventListener('click', function(){ + if(state === 0){ + start = Date.now(); elapsed = 0; + swEl.classList.add('running'); + state = 1; tick(); + } else if(state === 1){ + cancelAnimationFrame(raf); + elapsed += Date.now() - start; + swEl.textContent = fmt(elapsed); + swEl.classList.remove('running'); + state = 2; + } else { + cancelAnimationFrame(raf); + elapsed = 0; swEl.textContent = '0.00s'; + swEl.classList.remove('running'); + state = 0; + } + }); +})(); diff --git a/public/style.css b/public/style.css index 6252ca9..3ca65bd 100644 --- a/public/style.css +++ b/public/style.css @@ -8,6 +8,8 @@ body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);min .hdr-logo{height:100px;width:auto;mix-blend-mode:multiply;display:block} .hdr-sub{font-size:12px;color:var(--text3);margin-top:1px;font-family:'DM Mono',monospace} .hdr-btns{display:flex;gap:8px;align-items:center} +.sw{font-family:'DM Mono',monospace;font-size:15px;color:var(--text3);cursor:pointer;user-select:none;letter-spacing:-.5px;min-width:52px;text-align:center} +.sw.running{color:var(--text1)} .btn-add{width:38px;height:38px;border-radius:50%;background:var(--text);color:var(--bg);border:none;cursor:pointer;font-size:22px;display:flex;align-items:center;justify-content:center;font-weight:300;transition:transform .15s} .btn-add:active{transform:scale(.92)} .btn-menu{width:38px;height:38px;border-radius:50%;background:var(--bg3);color:var(--text2);border:1px solid var(--border);cursor:pointer;font-size:20px;display:flex;align-items:center;justify-content:center;transition:transform .15s} diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 3dbb872..e4a7ca6 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -11,6 +11,10 @@ class AppController extends AbstractController #[Route('/{path}', name: 'app', requirements: ['path' => '.*'], priority: -10)] public function index(): Response { - return $this->render('app.html.twig'); + $public = $this->getParameter('kernel.project_dir') . '/public/'; + return $this->render('app.html.twig', [ + 'jsv' => substr(md5_file($public . 'app.js'), 0, 8), + 'cssv' => substr(md5_file($public . 'style.css'), 0, 8), + ]); } } diff --git a/templates/app.html.twig b/templates/app.html.twig index b3a7514..3022790 100644 --- a/templates/app.html.twig +++ b/templates/app.html.twig @@ -7,7 +7,7 @@ Dudi - +
@@ -16,6 +16,7 @@
+ 0.00s
@@ -324,6 +325,6 @@
- +