From adb164c8e186d8c9d803c6fa712166f854b32959 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 8 Mar 2026 00:06:50 +0000 Subject: [PATCH] Improve frontend performance and caching behavior --- hdyc-svelte/src/app.css | 36 +------------- hdyc-svelte/src/app.html | 24 +--------- hdyc-svelte/src/hooks.server.ts | 25 ++++++++++ hdyc-svelte/src/lib/components/Sidebar.svelte | 8 ++-- hdyc-svelte/src/routes/+layout.svelte | 47 +++++++++++++++---- 5 files changed, 69 insertions(+), 71 deletions(-) diff --git a/hdyc-svelte/src/app.css b/hdyc-svelte/src/app.css index 5fb1295..46a0407 100644 --- a/hdyc-svelte/src/app.css +++ b/hdyc-svelte/src/app.css @@ -62,14 +62,6 @@ src: url('/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2') format('woff2'); } -body { - visibility: visible; -} - -:root[data-fonts='loading'] body { - visibility: hidden; -} - :root { /* ─── Colors (Dark Theme) ─────────────────────────────── */ --bg: #0c0f14; @@ -467,11 +459,7 @@ a:focus-visible { .palette-dots { display: flex; gap: 0.25rem; - max-width: 38px; - overflow-x: hidden; - overflow-y: visible; flex: 0 0 auto; - transition: max-width 0.2s ease, gap 0.2s ease; } .palette-dot { width: 30px; @@ -482,11 +470,10 @@ a:focus-visible { background-size: 160%; background-position: center; cursor: pointer; - transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s, opacity 0.2s; + transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05); overflow: hidden; flex-shrink: 0; - order: 0; transform-origin: center; } .palette-dot:hover { @@ -500,27 +487,6 @@ a:focus-visible { .palette-dot:focus-visible { outline: none; } -.floating-palette-controls:not(:hover):not(:focus-within) .palette-dots { - gap: 0; -} -.floating-palette-controls:not(:hover):not(:focus-within) .palette-dot:not(.active) { - opacity: 0; - pointer-events: none; -} -.floating-palette-controls:not(:hover):not(:focus-within) .palette-dot.active { - order: -1; -} -.floating-palette-controls:hover .palette-dots, -.floating-palette-controls:focus-within .palette-dots { - max-width: min(280px, calc(100vw - 2rem)); - gap: 0.25rem; -} -.floating-palette-controls:hover .palette-dot:not(.active), -.floating-palette-controls:focus-within .palette-dot:not(.active) { - opacity: 1; - pointer-events: auto; -} - @media (max-width: 520px) { .floating-palette-controls { gap: 0.2rem; diff --git a/hdyc-svelte/src/app.html b/hdyc-svelte/src/app.html index 1837ea7..e88d3f9 100644 --- a/hdyc-svelte/src/app.html +++ b/hdyc-svelte/src/app.html @@ -1,5 +1,5 @@ - + @@ -62,9 +62,6 @@ %sveltekit.head% diff --git a/hdyc-svelte/src/hooks.server.ts b/hdyc-svelte/src/hooks.server.ts index 1c4a2cb..1bd509c 100644 --- a/hdyc-svelte/src/hooks.server.ts +++ b/hdyc-svelte/src/hooks.server.ts @@ -14,7 +14,25 @@ const MIME_TYPES: Record = { }; const HTML_CACHE_CONTROL = 'public, max-age=0, must-revalidate'; +const IMMUTABLE_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable'; const ASSET_404_CACHE_CONTROL = 'no-store'; +const LONG_CACHE_EXTENSIONS = new Set([ + '.js', + '.mjs', + '.css', + '.json', + '.svg', + '.png', + '.jpg', + '.jpeg', + '.webp', + '.avif', + '.ico', + '.woff2', + '.woff', + '.ttf', + '.otf' +]); export const handle: Handle = async ({ event, resolve }) => { const response = await resolve(event); @@ -36,11 +54,18 @@ export const handle: Handle = async ({ event, resolve }) => { // keep pointing to already-rotated files long after a deployment. if (response.status >= 400) { response.headers.set('cache-control', ASSET_404_CACHE_CONTROL); + } else if (pathname.startsWith('/_app/immutable/')) { + response.headers.set('cache-control', IMMUTABLE_ASSET_CACHE_CONTROL); } return response; } + const extension = path.extname(pathname).toLowerCase(); + if (LONG_CACHE_EXTENSIONS.has(extension) && !contentType.includes('text/html')) { + response.headers.set('cache-control', IMMUTABLE_ASSET_CACHE_CONTROL); + } + // HTML documents should revalidate so they can reference the latest client // bundle hashes after each deployment. if (contentType.includes('text/html')) { diff --git a/hdyc-svelte/src/lib/components/Sidebar.svelte b/hdyc-svelte/src/lib/components/Sidebar.svelte index 360be04..8cdb80b 100644 --- a/hdyc-svelte/src/lib/components/Sidebar.svelte +++ b/hdyc-svelte/src/lib/components/Sidebar.svelte @@ -412,14 +412,16 @@ .sidebar { position: fixed; top: 0; - left: -300px; + left: 0; z-index: 100; height: 100vh; - transition: left 0.3s ease; + transform: translateX(-100%); + transition: transform 0.3s ease; + will-change: transform; box-shadow: 4px 0 24px rgba(0, 0, 0, 0.2); } .sidebar.open { - left: 0; + transform: translateX(0); } .close-btn { display: block; diff --git a/hdyc-svelte/src/routes/+layout.svelte b/hdyc-svelte/src/routes/+layout.svelte index 8ebb7c4..2f07863 100644 --- a/hdyc-svelte/src/routes/+layout.svelte +++ b/hdyc-svelte/src/routes/+layout.svelte @@ -252,6 +252,13 @@ }, }, ]; + const matomoContainerSrc = 'https://matomo.howdoyouconvert.com/js/container_B3r877Kn.js'; + + type WindowWithAnalytics = Window & { + _mtm?: Array>; + requestIdleCallback?: (callback: () => void, options?: { timeout: number }) => number; + cancelIdleCallback?: (handle: number) => void; + }; let sidebarOpen = false; let headerSearchOpen = false; @@ -303,6 +310,22 @@ } }; + const loadMatomoContainer = () => { + if (!browser) return; + if (document.querySelector(`script[src="${matomoContainerSrc}"]`)) return; + + const appWindow = window as WindowWithAnalytics; + const queue = appWindow._mtm ?? []; + appWindow._mtm = queue; + queue.push({ 'mtm.startTime': Date.now(), event: 'mtm.Start' }); + + const script = document.createElement('script'); + script.async = true; + script.src = matomoContainerSrc; + script.setAttribute('data-cfasync', 'false'); + document.head.appendChild(script); + }; + afterNavigate(() => { sidebarOpen = false; headerSearchOpen = false; @@ -310,6 +333,9 @@ onMount(() => { if (!browser) return; + const appWindow = window as WindowWithAnalytics; + let idleCallbackId: number | null = null; + let fallbackTimeoutId: number | null = null; const savedTheme = window.localStorage.getItem('theme') as ThemeMode | null; const savedPalette = window.localStorage.getItem('palette'); @@ -356,6 +382,11 @@ sidebarOpen = false; } updateHeaderBreakpoint(); + if (typeof appWindow.requestIdleCallback === 'function') { + idleCallbackId = appWindow.requestIdleCallback(loadMatomoContainer, { timeout: 3000 }); + } else { + fallbackTimeoutId = window.setTimeout(loadMatomoContainer, 1200); + } const cleanup = () => { if ('removeEventListener' in mediaQuery) { @@ -373,6 +404,12 @@ } else { headerBreakpoint.removeListener(handleHeaderBreakpoint); } + if (idleCallbackId !== null && typeof appWindow.cancelIdleCallback === 'function') { + appWindow.cancelIdleCallback(idleCallbackId); + } + if (fallbackTimeoutId !== null) { + window.clearTimeout(fallbackTimeoutId); + } window.removeEventListener('keydown', handleEscape); }; @@ -400,16 +437,6 @@ - - -