Add batched status endpoint and optimize frontend performance

Server-side batched /api/v1/services/status endpoint replaces N
individual browser probes with a single API call (HEAD-first with
GET fallback, concurrency-limited, CA-aware HTTPS agent).

Frontend: clock reuses DOM instead of rebuilding innerHTML every
second with drift-correcting timer that pauses on hidden tabs.
Card animations use CSS transitionDelay + requestAnimationFrame.
Internet dot blink moved from JS intervals to CSS keyframes with
prefers-reduced-motion support. Service worker rewritten with
network-first navigation, stale-while-revalidate assets, and
navigation preload. Font faces drop TTF fallbacks, use font-display
swap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 22:39:29 -07:00
parent 063bf948b1
commit 0f4bd419e1
6 changed files with 476 additions and 150 deletions

View File

@@ -1,75 +1,67 @@
/* Sami Grotesk Custom Font Family */
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Light.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-Light.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: block;
font-display: swap;
}
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Regular.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-Regular.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: block;
font-display: swap;
}
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Medium.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-Medium.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: block;
font-display: swap;
}
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Bold.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-Bold.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: block;
font-display: swap;
}
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Black.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-Black.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
font-display: block;
font-display: swap;
}
/* Italic variants */
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-RegularItalic.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-RegularItalic.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-RegularItalic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: block;
font-display: swap;
}
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-MediumItalic.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-MediumItalic.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-MediumItalic.woff2') format('woff2');
font-weight: 500;
font-style: italic;
font-display: block;
font-display: swap;
}
@font-face {
font-family: 'Sami Grotesk';
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-BoldItalic.woff2') format('woff2'),
url('/assets/fonts/sami-grotesk/SamiGrotesk-BoldItalic.ttf') format('truetype');
src: url('/assets/fonts/sami-grotesk/SamiGrotesk-BoldItalic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: block;
font-display: swap;
}
/* Theme variables and transitions are in themes.css */
@@ -2018,15 +2010,37 @@ button:focus-visible {
/* Internet card packet blink */
.card[data-app="internet"] .dot {
transition: all 0.1s ease;
transition: background-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
.card[data-app="internet"] .dot.packet-rx {
box-shadow: 0 0 8px 2px #4caf50;
background: #4caf50;
.card[data-app="internet"][data-status="on"] .dot {
animation: internet-traffic 1.35s ease-in-out infinite;
}
.card[data-app="internet"] .dot.packet-tx {
box-shadow: 0 0 8px 2px #2196f3;
background: #2196f3;
@keyframes internet-traffic {
0%, 100% {
transform: scale(1);
background: var(--dot-ok);
box-shadow: 0 0 0 rgba(39, 174, 96, 0);
}
38% {
transform: scale(1.06);
background: #4caf50;
box-shadow: 0 0 10px 2px rgba(76, 175, 80, 0.55);
}
68% {
transform: scale(0.98);
background: #2196f3;
box-shadow: 0 0 10px 2px rgba(33, 150, 243, 0.5);
}
}
@media (prefers-reduced-motion: reduce) {
.card[data-app="internet"][data-status="on"] .dot {
animation: none;
}
}
/* Restart button styling */