diff --git a/dashboard/src/app.css b/dashboard/src/app.css index fc532578..a55c64af 100644 --- a/dashboard/src/app.css +++ b/dashboard/src/app.css @@ -198,8 +198,10 @@ stroke: oklch(0.85 0.18 85 / 0.4); stroke-width: 1.5px; stroke-dasharray: 8, 8; - animation: flowAnimation 1s linear infinite; + animation: flowAnimation 1.5s linear infinite; filter: drop-shadow(0 0 3px oklch(0.85 0.18 85 / 0.5)); + /* GPU optimization - hint to browser this element will animate */ + will-change: stroke-dashoffset; } .graph-link-active { @@ -208,6 +210,24 @@ filter: drop-shadow(0 0 6px oklch(0.85 0.18 85 / 0.8)); } +/* Reduce motion for users who prefer it - also saves GPU */ +@media (prefers-reduced-motion: reduce) { + .graph-link { + animation: none; + } + + .shooting-star { + animation: none; + display: none; + } + + .status-pulse, + .cursor-blink, + .animate-pulse { + animation: none; + } +} + /* CRT Screen effect for topology */ .crt-screen { position: relative; @@ -266,13 +286,15 @@ input:focus, textarea:focus { box-shadow: none; } -/* Shooting Stars Animation */ +/* Shooting Stars Animation - GPU optimized */ .shooting-stars { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; + /* Only render when visible */ + content-visibility: auto; } .shooting-star { @@ -285,6 +307,9 @@ input:focus, textarea:focus { animation: shootingStar var(--duration, 3s) linear infinite; animation-delay: var(--delay, 0s); opacity: 0; + /* GPU optimization */ + will-change: transform, opacity; + transform: translateZ(0); } .shooting-star::before { @@ -320,3 +345,13 @@ input:focus, textarea:focus { transform: translate(400px, 400px); } } + +/* Pause animations when page is hidden to save resources */ +:root:has(body[data-page-hidden="true"]) { + .shooting-star, + .graph-link, + .status-pulse, + .cursor-blink { + animation-play-state: paused; + } +} diff --git a/dashboard/src/lib/components/TopologyGraph.svelte b/dashboard/src/lib/components/TopologyGraph.svelte index 319dc547..99d3f854 100644 --- a/dashboard/src/lib/components/TopologyGraph.svelte +++ b/dashboard/src/lib/components/TopologyGraph.svelte @@ -1,5 +1,5 @@ @@ -9,7 +27,7 @@ -
+
{@render children?.()}
diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte index 9f75eac8..759ba89a 100644 --- a/dashboard/src/routes/+page.svelte +++ b/dashboard/src/routes/+page.svelte @@ -99,17 +99,35 @@ function toggleInstanceDownloadDetails(nodeId: string): void { } // Compute highlighted nodes from hovered instance or hovered preview + // Memoized to avoid creating new Sets on every render + let lastHighlightedNodesKey = ''; + let cachedHighlightedNodes: Set = new Set(); + const highlightedNodes = $derived(() => { + // Create a key for the current state to enable memoization + const previewKey = Array.from(hoveredPreviewNodes).sort().join(','); + const currentKey = `${hoveredInstanceId || 'null'}:${previewKey}`; + + // Return cached value if nothing changed + if (currentKey === lastHighlightedNodesKey) { + return cachedHighlightedNodes; + } + + lastHighlightedNodesKey = currentKey; + // First check instance hover if (hoveredInstanceId) { const instanceWrapped = instanceData[hoveredInstanceId]; - return unwrapInstanceNodes(instanceWrapped); + cachedHighlightedNodes = unwrapInstanceNodes(instanceWrapped); + return cachedHighlightedNodes; } // Then check preview hover if (hoveredPreviewNodes.size > 0) { - return hoveredPreviewNodes; + cachedHighlightedNodes = hoveredPreviewNodes; + return cachedHighlightedNodes; } - return new Set(); + cachedHighlightedNodes = new Set(); + return cachedHighlightedNodes; }); // Helper to estimate memory from model ID (mirrors ModelCard logic) @@ -516,12 +534,13 @@ function toggleInstanceDownloadDetails(nodeId: string): void { }; } - // Debug: Log downloads data when it changes - $effect(() => { - if (downloadsData && Object.keys(downloadsData).length > 0) { - console.log('[Download Debug] Current downloads:', downloadsData); - } - }); + // Debug: Log downloads data when it changes (disabled in production for performance) + // Uncomment for debugging: + // $effect(() => { + // if (downloadsData && Object.keys(downloadsData).length > 0) { + // console.log('[Download Debug] Current downloads:', downloadsData); + // } + // }); // Helper to get download status for an instance function getInstanceDownloadStatus(instanceId: string, instanceWrapped: unknown): {