mirror of
https://github.com/exo-explore/exo.git
synced 2026-02-23 09:47:47 -05:00
Compare commits
2 Commits
meta-insta
...
feat/dashb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab427f1b75 | ||
|
|
412f66d523 |
@@ -16,9 +16,10 @@
|
||||
/* Gotham-inspired accent colors */
|
||||
--exo-grid: oklch(0.25 0 0);
|
||||
--exo-scanline: oklch(0.15 0 0);
|
||||
--exo-glow-yellow: 0 0 20px oklch(0.85 0.18 85 / 0.3);
|
||||
--exo-glow-yellow-strong: 0 0 40px oklch(0.85 0.18 85 / 0.5);
|
||||
|
||||
--exo-glow-yellow: oklch(0.85 0.18 85 / 0.3);
|
||||
--exo-glow-yellow-strong: oklch(0.85 0.18 85 / 0.5);
|
||||
--exo-bg-hover: oklch(0.18 0 0);
|
||||
|
||||
/* Theme Variables */
|
||||
--radius: 0.375rem;
|
||||
--background: var(--exo-black);
|
||||
@@ -41,6 +42,237 @@
|
||||
--ring: var(--exo-yellow);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LIGHT THEME — "Mission Control, Dawn Shift"
|
||||
Warm parchment + deep amber. Applied when <html> has .light class.
|
||||
============================================================ */
|
||||
html.light {
|
||||
/* EXO brand palette — warm amber shift */
|
||||
--exo-black: oklch(0.97 0.015 80);
|
||||
--exo-dark-gray: oklch(0.92 0.012 80);
|
||||
--exo-medium-gray: oklch(0.83 0.009 78);
|
||||
--exo-light-gray: oklch(0.50 0.018 75);
|
||||
--exo-yellow: oklch(0.50 0.14 65);
|
||||
--exo-yellow-darker: oklch(0.40 0.13 65);
|
||||
--exo-yellow-glow: oklch(0.60 0.14 65);
|
||||
|
||||
--exo-grid: oklch(0.88 0.009 80);
|
||||
--exo-scanline: oklch(0.93 0.010 80);
|
||||
--exo-glow-yellow: oklch(0.50 0.14 65 / 0.12);
|
||||
--exo-glow-yellow-strong: oklch(0.50 0.14 65 / 0.22);
|
||||
--exo-bg-hover: oklch(0.89 0.010 80);
|
||||
|
||||
/* Semantic tokens */
|
||||
--background: oklch(0.97 0.015 80);
|
||||
--foreground: oklch(0.13 0.015 75);
|
||||
--card: oklch(0.92 0.012 80);
|
||||
--card-foreground: oklch(0.13 0.015 75);
|
||||
--popover: oklch(0.95 0.012 80);
|
||||
--popover-foreground: oklch(0.13 0.015 75);
|
||||
--primary: oklch(0.50 0.14 65);
|
||||
--primary-foreground: oklch(0.97 0.015 80);
|
||||
--secondary: oklch(0.88 0.008 80);
|
||||
--secondary-foreground: oklch(0.15 0.012 75);
|
||||
--muted: oklch(0.90 0.009 80);
|
||||
--muted-foreground: oklch(0.50 0.018 75);
|
||||
--accent: oklch(0.88 0.008 80);
|
||||
--accent-foreground: oklch(0.15 0.012 75);
|
||||
--destructive: oklch(0.52 0.22 25);
|
||||
--border: oklch(0.84 0.007 78);
|
||||
--input: oklch(0.87 0.008 80);
|
||||
--ring: oklch(0.50 0.14 65);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LIGHT MODE UTILITY OVERRIDES
|
||||
============================================================ */
|
||||
html.light {
|
||||
& .text-white,
|
||||
& .text-white\/90,
|
||||
& .text-white\/80,
|
||||
& .text-white\/70 {
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
& .text-white\/60,
|
||||
& .text-white\/50 {
|
||||
color: color-mix(in oklch, var(--foreground) 60%, transparent) !important;
|
||||
}
|
||||
& .text-white\/40,
|
||||
& .text-white\/30 {
|
||||
color: color-mix(in oklch, var(--foreground) 38%, transparent) !important;
|
||||
}
|
||||
|
||||
& .bg-black\/80,
|
||||
& .bg-black\/60,
|
||||
& .bg-black\/50,
|
||||
& .bg-black\/40 {
|
||||
background-color: oklch(0.90 0.010 80 / 0.7) !important;
|
||||
}
|
||||
& [class*="bg-exo-black/"] {
|
||||
background-color: oklch(0.90 0.010 80 / 0.6) !important;
|
||||
}
|
||||
& [class*="shadow-black"] {
|
||||
--tw-shadow-color: oklch(0.30 0.010 75 / 0.10) !important;
|
||||
}
|
||||
|
||||
& ::-webkit-scrollbar-track {
|
||||
background: oklch(0.93 0.010 80) !important;
|
||||
}
|
||||
& ::-webkit-scrollbar-thumb {
|
||||
background: oklch(0.76 0.010 78) !important;
|
||||
}
|
||||
& ::-webkit-scrollbar-thumb:hover {
|
||||
background: oklch(0.50 0.14 65 / 0.6) !important;
|
||||
}
|
||||
|
||||
& .command-panel {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
oklch(0.94 0.012 80 / 0.96) 0%,
|
||||
oklch(0.91 0.010 80 / 0.98) 100%
|
||||
) !important;
|
||||
border-color: oklch(0.82 0.008 78) !important;
|
||||
box-shadow:
|
||||
inset 0 1px 0 oklch(1 0 0 / 0.6),
|
||||
0 4px 20px oklch(0.30 0.010 75 / 0.08) !important;
|
||||
}
|
||||
|
||||
& .glow-text {
|
||||
text-shadow:
|
||||
0 0 12px oklch(0.50 0.14 65 / 0.20),
|
||||
0 1px 3px oklch(0.30 0.010 75 / 0.12) !important;
|
||||
}
|
||||
|
||||
& .grid-bg {
|
||||
background-image:
|
||||
linear-gradient(oklch(0.75 0.008 78 / 0.25) 1px, transparent 1px),
|
||||
linear-gradient(90deg, oklch(0.75 0.008 78 / 0.25) 1px, transparent 1px) !important;
|
||||
}
|
||||
|
||||
& .scanlines::before {
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
oklch(0.50 0.010 78 / 0.018) 2px,
|
||||
oklch(0.50 0.010 78 / 0.018) 4px
|
||||
) !important;
|
||||
}
|
||||
|
||||
& .crt-screen {
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
oklch(0.95 0.012 80) 0%,
|
||||
oklch(0.92 0.010 80) 50%,
|
||||
oklch(0.89 0.009 80) 100%
|
||||
) !important;
|
||||
box-shadow:
|
||||
inset 0 0 60px oklch(0.30 0.010 75 / 0.04),
|
||||
0 0 30px oklch(0.50 0.14 65 / 0.04) !important;
|
||||
}
|
||||
|
||||
& .graph-link {
|
||||
stroke: oklch(0.50 0.018 75 / 0.45) !important;
|
||||
filter: none !important;
|
||||
}
|
||||
& .graph-link-active {
|
||||
stroke: oklch(0.50 0.14 65 / 0.75) !important;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
& .shooting-stars {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
& img[alt="EXO"] {
|
||||
filter: brightness(0) drop-shadow(0 0 6px oklch(0.30 0.010 75 / 0.10)) !important;
|
||||
}
|
||||
|
||||
& .text-red-400 { color: oklch(0.52 0.22 25) !important; }
|
||||
& .text-green-400 { color: oklch(0.48 0.17 155) !important; }
|
||||
& .text-blue-200,
|
||||
& .text-blue-300,
|
||||
& .text-blue-400 { color: oklch(0.48 0.17 250) !important; }
|
||||
|
||||
& .bg-red-500\/10 { background-color: oklch(0.52 0.22 25 / 0.07) !important; }
|
||||
& .bg-red-500\/20 { background-color: oklch(0.52 0.22 25 / 0.11) !important; }
|
||||
& .bg-red-500\/30 { background-color: oklch(0.52 0.22 25 / 0.14) !important; }
|
||||
|
||||
& textarea,
|
||||
& input[type="text"] { color: var(--foreground) !important; }
|
||||
& textarea::placeholder,
|
||||
& input::placeholder { color: oklch(0.50 0.012 78 / 0.55) !important; }
|
||||
|
||||
& .code-block-wrapper,
|
||||
& .math-display-wrapper {
|
||||
background: oklch(0.95 0.010 80) !important;
|
||||
border-color: oklch(0.83 0.007 78) !important;
|
||||
}
|
||||
& .code-block-header,
|
||||
& .math-display-header {
|
||||
background: oklch(0.91 0.009 80) !important;
|
||||
border-color: oklch(0.85 0.007 78) !important;
|
||||
}
|
||||
& .inline-code {
|
||||
background: oklch(0.89 0.009 80) !important;
|
||||
color: oklch(0.20 0.012 75) !important;
|
||||
}
|
||||
|
||||
& blockquote { background: oklch(0.93 0.010 80) !important; }
|
||||
& th {
|
||||
background: oklch(0.90 0.009 80) !important;
|
||||
border-color: oklch(0.80 0.007 78) !important;
|
||||
}
|
||||
& td { border-color: oklch(0.84 0.007 78) !important; }
|
||||
& hr { border-color: oklch(0.84 0.007 78) !important; }
|
||||
|
||||
& .hljs { color: oklch(0.22 0.012 75) !important; }
|
||||
& .hljs-keyword, & .hljs-selector-tag, & .hljs-literal, & .hljs-section, & .hljs-link {
|
||||
color: oklch(0.45 0.18 300) !important;
|
||||
}
|
||||
& .hljs-string, & .hljs-title, & .hljs-name, & .hljs-type,
|
||||
& .hljs-attribute, & .hljs-symbol, & .hljs-bullet, & .hljs-addition,
|
||||
& .hljs-variable, & .hljs-template-tag, & .hljs-template-variable {
|
||||
color: oklch(0.45 0.14 65) !important;
|
||||
}
|
||||
& .hljs-comment, & .hljs-quote, & .hljs-deletion, & .hljs-meta {
|
||||
color: oklch(0.55 0.010 78) !important;
|
||||
}
|
||||
& .hljs-number, & .hljs-regexp, & .hljs-built_in {
|
||||
color: oklch(0.45 0.15 160) !important;
|
||||
}
|
||||
& .hljs-function, & .hljs-class .hljs-title {
|
||||
color: oklch(0.42 0.17 240) !important;
|
||||
}
|
||||
|
||||
& .katex, & .katex .mord, & .katex .minner, & .katex .mop,
|
||||
& .katex .mbin, & .katex .mrel, & .katex .mpunct {
|
||||
color: oklch(0.15 0.012 75) !important;
|
||||
}
|
||||
& .katex .frac-line, & .katex .overline-line, & .katex .underline-line,
|
||||
& .katex .hline, & .katex .rule {
|
||||
border-color: oklch(0.25 0.012 75) !important;
|
||||
background: oklch(0.25 0.012 75) !important;
|
||||
}
|
||||
& .katex svg { fill: oklch(0.25 0.012 75) !important; stroke: oklch(0.25 0.012 75) !important; }
|
||||
& .katex svg path { stroke: oklch(0.25 0.012 75) !important; }
|
||||
& .katex .mopen, & .katex .mclose,
|
||||
& .katex .delimsizing, & [class^="katex .delim-size"] {
|
||||
color: oklch(0.35 0.012 75) !important;
|
||||
}
|
||||
|
||||
& .latex-proof { background: oklch(0.96 0.010 80) !important; border-left-color: oklch(0.72 0.010 78) !important; }
|
||||
& .latex-proof-header { color: oklch(0.22 0.012 75) !important; }
|
||||
& .latex-proof-content { color: oklch(0.15 0.012 75) !important; }
|
||||
& .latex-proof-content::after { color: oklch(0.48 0.012 75) !important; }
|
||||
& .latex-theorem { background: oklch(0.94 0.010 80) !important; border-color: oklch(0.80 0.008 78) !important; }
|
||||
& .latex-diagram-placeholder {
|
||||
background: oklch(0.96 0.010 80) !important;
|
||||
border-color: oklch(0.80 0.008 78) !important;
|
||||
color: oklch(0.38 0.012 75) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 2px);
|
||||
--radius-md: var(--radius);
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script>
|
||||
try {
|
||||
if (localStorage.getItem('exo-theme') === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.add('light');
|
||||
}
|
||||
} catch (_) {}
|
||||
</script>
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>EXO</title>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { theme } from "$lib/stores/theme.svelte";
|
||||
|
||||
export let showHome = true;
|
||||
export let onHome: (() => void) | null = null;
|
||||
@@ -79,10 +80,48 @@
|
||||
/>
|
||||
</button>
|
||||
|
||||
<!-- Right: Home + Downloads -->
|
||||
<!-- Right: Theme toggle + Home + Downloads -->
|
||||
<div
|
||||
class="absolute right-6 top-1/2 -translate-y-1/2 flex items-center gap-4"
|
||||
>
|
||||
<button
|
||||
onclick={() => theme.toggle()}
|
||||
class="p-2 rounded border border-exo-medium-gray/40 hover:border-exo-yellow/50 transition-colors cursor-pointer"
|
||||
title={theme.isLight ? "Switch to dark mode" : "Switch to light mode"}
|
||||
aria-label={theme.isLight
|
||||
? "Switch to dark mode"
|
||||
: "Switch to light mode"}
|
||||
>
|
||||
{#if theme.isLight}
|
||||
<svg
|
||||
class="w-4 h-4 text-exo-light-gray"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21 12.79A9 9 0 1111.21 3a7 7 0 009.79 9.79z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class="w-4 h-4 text-exo-light-gray"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{#if showHome}
|
||||
<button
|
||||
onclick={handleHome}
|
||||
|
||||
@@ -6,6 +6,27 @@
|
||||
TopologyEdge,
|
||||
} from "$lib/stores/app.svelte";
|
||||
import { debugMode, topologyData } from "$lib/stores/app.svelte";
|
||||
import { theme } from "$lib/stores/theme.svelte";
|
||||
|
||||
// Theme-aware colors for SVG elements
|
||||
let tc = $derived({
|
||||
accent: theme.isLight ? "#374151" : "#FFD700",
|
||||
accentDim: theme.isLight ? "rgba(26,26,26,0.06)" : "rgba(255,215,0,0.1)",
|
||||
accentGlow: theme.isLight ? "rgba(0,0,0,0.03)" : "rgba(255,215,0,0.1)",
|
||||
outlineActive: theme.isLight ? "#374151" : "#FFD700",
|
||||
outlineInactive: theme.isLight ? "#d1d5db" : "#4B5563",
|
||||
strokeActive: theme.isLight ? "#374151" : "#FFD700",
|
||||
strokeInactive: theme.isLight ? "#d1d5db" : "#4B5563",
|
||||
caseFill: theme.isLight ? "#f3f4f6" : "#0a0a0a",
|
||||
caseDetail: theme.isLight ? "#d1d5db" : "#374151",
|
||||
errorDot: theme.isLight ? "#dc2626" : "#f87171",
|
||||
statusInactive: theme.isLight ? "#9ca3af" : "#4B5563",
|
||||
connGood: theme.isLight ? "rgba(30,30,30,0.85)" : "rgba(255,255,255,0.85)",
|
||||
connBad: theme.isLight ? "rgba(220,38,38,0.85)" : "rgba(248,113,113,0.85)",
|
||||
scanlineColor: theme.isLight
|
||||
? "rgba(0,0,0,0.01)"
|
||||
: "rgba(255,215,0,0.02)",
|
||||
});
|
||||
|
||||
interface Props {
|
||||
model: { id: string; name?: string; storage_size_megabytes?: number };
|
||||
@@ -491,7 +512,7 @@
|
||||
<div
|
||||
class="bg-exo-dark-gray/60 border {canFit
|
||||
? 'border-exo-yellow/20 group-hover:border-exo-yellow/40'
|
||||
: 'border-red-500/20'} p-3 transition-all duration-200 group-hover:shadow-[0_0_15px_rgba(255,215,0,0.1)]"
|
||||
: 'border-red-500/20'} p-3 transition-all duration-200 group-hover:shadow-[0_0_15px_var(--exo-glow-yellow)]"
|
||||
>
|
||||
<!-- Model Name & Memory Required -->
|
||||
<div class="flex items-start justify-between gap-2 mb-2">
|
||||
@@ -589,7 +610,8 @@
|
||||
>
|
||||
<!-- Scanline effect -->
|
||||
<div
|
||||
class="absolute inset-0 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(255,215,0,0.02)_2px,rgba(255,215,0,0.02)_4px)] pointer-events-none"
|
||||
style="background: repeating-linear-gradient(0deg, transparent, transparent 2px, {tc.scanlineColor} 2px, {tc.scanlineColor} 4px)"
|
||||
class="absolute inset-0 pointer-events-none"
|
||||
></div>
|
||||
|
||||
<svg
|
||||
@@ -670,7 +692,9 @@
|
||||
y1={node.y}
|
||||
x2={node2.x}
|
||||
y2={node2.y}
|
||||
stroke={node.isUsed && node2.isUsed ? "#FFD700" : "#374151"}
|
||||
stroke={node.isUsed && node2.isUsed
|
||||
? tc.strokeActive
|
||||
: tc.strokeInactive}
|
||||
stroke-width="1"
|
||||
stroke-dasharray={node.isUsed && node2.isUsed ? "4,2" : "2,4"}
|
||||
opacity={node.isUsed && node2.isUsed ? 0.4 : 0.15}
|
||||
@@ -706,9 +730,7 @@
|
||||
dominant-baseline="hanging"
|
||||
font-size="6"
|
||||
font-family="SF Mono, Monaco, monospace"
|
||||
fill={conn.iface
|
||||
? "rgba(255,255,255,0.85)"
|
||||
: "rgba(248,113,113,0.85)"}
|
||||
fill={conn.iface ? tc.connGood : tc.connBad}
|
||||
>
|
||||
{conn.arrow}
|
||||
{isRdma
|
||||
@@ -725,9 +747,7 @@
|
||||
dominant-baseline="hanging"
|
||||
font-size="6"
|
||||
font-family="SF Mono, Monaco, monospace"
|
||||
fill={conn.iface
|
||||
? "rgba(255,255,255,0.85)"
|
||||
: "rgba(248,113,113,0.85)"}
|
||||
fill={conn.iface ? tc.connGood : tc.connBad}
|
||||
>
|
||||
{conn.arrow}
|
||||
{isRdma
|
||||
@@ -746,9 +766,7 @@
|
||||
dominant-baseline="auto"
|
||||
font-size="6"
|
||||
font-family="SF Mono, Monaco, monospace"
|
||||
fill={conn.iface
|
||||
? "rgba(255,255,255,0.85)"
|
||||
: "rgba(248,113,113,0.85)"}
|
||||
fill={conn.iface ? tc.connGood : tc.connBad}
|
||||
>
|
||||
{conn.arrow}
|
||||
{isRdma
|
||||
@@ -767,9 +785,7 @@
|
||||
dominant-baseline="auto"
|
||||
font-size="6"
|
||||
font-family="SF Mono, Monaco, monospace"
|
||||
fill={conn.iface
|
||||
? "rgba(255,255,255,0.85)"
|
||||
: "rgba(248,113,113,0.85)"}
|
||||
fill={conn.iface ? tc.connGood : tc.connBad}
|
||||
>
|
||||
{conn.arrow}
|
||||
{isRdma
|
||||
@@ -801,7 +817,7 @@
|
||||
height={node.iconSize * 0.65}
|
||||
rx="2"
|
||||
fill="none"
|
||||
stroke={node.isUsed ? "#FFD700" : "#4B5563"}
|
||||
stroke={node.isUsed ? tc.strokeActive : tc.outlineInactive}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- Screen area (memory fill container) -->
|
||||
@@ -810,7 +826,7 @@
|
||||
y="2"
|
||||
width={node.iconSize - 8}
|
||||
height={node.screenHeight}
|
||||
fill="#0a0a0a"
|
||||
fill={tc.caseFill}
|
||||
/>
|
||||
<!-- Current memory fill (gray) -->
|
||||
<rect
|
||||
@@ -818,7 +834,7 @@
|
||||
y={2 + node.screenHeight - node.currentFillHeight}
|
||||
width={node.iconSize - 8}
|
||||
height={node.currentFillHeight}
|
||||
fill="#374151"
|
||||
fill={tc.caseDetail}
|
||||
/>
|
||||
<!-- New model memory fill (glowing yellow) -->
|
||||
{#if node.modelUsageGB > 0 && node.isUsed}
|
||||
@@ -830,7 +846,7 @@
|
||||
node.modelFillHeight}
|
||||
width={node.iconSize - 8}
|
||||
height={node.modelFillHeight}
|
||||
fill="#FFD700"
|
||||
fill={tc.accent}
|
||||
filter="url(#memGlow-{filterId})"
|
||||
class="animate-pulse-slow"
|
||||
/>
|
||||
@@ -842,7 +858,7 @@
|
||||
0.68} L {node.iconSize - 2} {node.iconSize *
|
||||
0.78} L 2 {node.iconSize * 0.78} Z"
|
||||
fill="none"
|
||||
stroke={node.isUsed ? "#FFD700" : "#4B5563"}
|
||||
stroke={node.isUsed ? tc.strokeActive : tc.outlineInactive}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</g>
|
||||
@@ -859,7 +875,7 @@
|
||||
height={node.iconSize - 4}
|
||||
rx="4"
|
||||
fill="none"
|
||||
stroke={node.isUsed ? "#FFD700" : "#4B5563"}
|
||||
stroke={node.isUsed ? tc.strokeActive : tc.outlineInactive}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- Memory fill background -->
|
||||
@@ -868,7 +884,7 @@
|
||||
y="4"
|
||||
width={node.iconSize - 8}
|
||||
height={node.iconSize - 8}
|
||||
fill="#0a0a0a"
|
||||
fill={tc.caseFill}
|
||||
/>
|
||||
<!-- Current memory fill -->
|
||||
<rect
|
||||
@@ -877,7 +893,7 @@
|
||||
(node.iconSize - 8) * (1 - node.currentPercent / 100)}
|
||||
width={node.iconSize - 8}
|
||||
height={(node.iconSize - 8) * (node.currentPercent / 100)}
|
||||
fill="#374151"
|
||||
fill={tc.caseDetail}
|
||||
/>
|
||||
<!-- New model memory fill -->
|
||||
{#if node.modelUsageGB > 0 && node.isUsed}
|
||||
@@ -887,7 +903,7 @@
|
||||
width={node.iconSize - 8}
|
||||
height={(node.iconSize - 8) *
|
||||
((node.newPercent - node.currentPercent) / 100)}
|
||||
fill="#FFD700"
|
||||
fill={tc.accent}
|
||||
filter="url(#memGlow-{filterId})"
|
||||
class="animate-pulse-slow"
|
||||
/>
|
||||
@@ -906,7 +922,7 @@
|
||||
height={node.iconSize * 0.4}
|
||||
rx="3"
|
||||
fill="none"
|
||||
stroke={node.isUsed ? "#FFD700" : "#4B5563"}
|
||||
stroke={node.isUsed ? tc.strokeActive : tc.outlineInactive}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- Memory fill background -->
|
||||
@@ -915,7 +931,7 @@
|
||||
y={node.iconSize * 0.32}
|
||||
width={node.iconSize - 8}
|
||||
height={node.iconSize * 0.36}
|
||||
fill="#0a0a0a"
|
||||
fill={tc.caseFill}
|
||||
/>
|
||||
<!-- Current memory fill -->
|
||||
<rect
|
||||
@@ -924,7 +940,7 @@
|
||||
node.iconSize * 0.36 * (1 - node.currentPercent / 100)}
|
||||
width={node.iconSize - 8}
|
||||
height={node.iconSize * 0.36 * (node.currentPercent / 100)}
|
||||
fill="#374151"
|
||||
fill={tc.caseDetail}
|
||||
/>
|
||||
<!-- New model memory fill -->
|
||||
{#if node.modelUsageGB > 0 && node.isUsed}
|
||||
@@ -936,7 +952,7 @@
|
||||
height={node.iconSize *
|
||||
0.36 *
|
||||
((node.newPercent - node.currentPercent) / 100)}
|
||||
fill="#FFD700"
|
||||
fill={tc.accent}
|
||||
filter="url(#memGlow-{filterId})"
|
||||
class="animate-pulse-slow"
|
||||
/>
|
||||
@@ -955,8 +971,8 @@
|
||||
0.75} {node.iconSize /
|
||||
2},{node.iconSize} 0,{node.iconSize *
|
||||
0.75} 0,{node.iconSize * 0.25}"
|
||||
fill={node.isUsed ? "rgba(255,215,0,0.1)" : "#0a0a0a"}
|
||||
stroke={node.isUsed ? "#FFD700" : "#4B5563"}
|
||||
fill={node.isUsed ? tc.accentDim : tc.caseFill}
|
||||
stroke={node.isUsed ? tc.strokeActive : tc.outlineInactive}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</g>
|
||||
@@ -970,9 +986,9 @@
|
||||
font-family="SF Mono, Monaco, monospace"
|
||||
fill={node.isUsed
|
||||
? node.newPercent > 90
|
||||
? "#f87171"
|
||||
: "#FFD700"
|
||||
: "#4B5563"}
|
||||
? tc.errorDot
|
||||
: tc.accent
|
||||
: tc.statusInactive}
|
||||
>
|
||||
{node.newPercent.toFixed(0)}%
|
||||
</text>
|
||||
|
||||
@@ -10,6 +10,53 @@
|
||||
nodeIdentities,
|
||||
type NodeInfo,
|
||||
} from "$lib/stores/app.svelte";
|
||||
import { theme } from "$lib/stores/theme.svelte";
|
||||
|
||||
// Theme-aware colors for D3-rendered SVG elements
|
||||
let themeColors = $derived({
|
||||
accent: theme.isLight ? "oklch(0.50 0.14 65)" : "oklch(0.85 0.18 85)",
|
||||
accentRgb: theme.isLight ? "55,40,15" : "255,215,0",
|
||||
deviceCase: theme.isLight ? "#e8e8e8" : "#1a1a1a",
|
||||
deviceCaseDark: theme.isLight ? "#d4d4d4" : "#2c2c2c",
|
||||
deviceScreen: theme.isLight ? "#f0f0f5" : "#0a0a12",
|
||||
deviceScreenFill: theme.isLight
|
||||
? "rgba(240,242,248,0.95)"
|
||||
: "rgba(0,20,40,0.9)",
|
||||
labelWhite: theme.isLight ? "#1a1a1a" : "#FFFFFF",
|
||||
labelMuted: theme.isLight ? "rgba(80,80,80,0.9)" : "rgba(179,179,179,0.9)",
|
||||
labelDim: theme.isLight ? "rgba(100,100,100,0.7)" : "rgba(179,179,179,0.7)",
|
||||
wireDefault: theme.isLight
|
||||
? "rgba(120,120,120,0.6)"
|
||||
: "rgba(179,179,179,0.8)",
|
||||
wireBright: theme.isLight ? "rgba(30,30,30,0.9)" : "rgba(255,255,255,0.9)",
|
||||
wireFiltered: theme.isLight
|
||||
? "rgba(160,160,160,0.5)"
|
||||
: "rgba(140,140,140,0.6)",
|
||||
gridStroke: theme.isLight
|
||||
? "var(--exo-light-gray, #888888)"
|
||||
: "var(--exo-light-gray, #B3B3B3)",
|
||||
errorText: theme.isLight
|
||||
? "rgba(220,38,38,0.9)"
|
||||
: "rgba(248,113,113,0.9)",
|
||||
normalText: theme.isLight
|
||||
? "rgba(30,30,30,0.85)"
|
||||
: "rgba(255,255,255,0.85)",
|
||||
gpuChip: theme.isLight
|
||||
? "rgba(180, 180, 190, 0.7)"
|
||||
: "rgba(80, 80, 90, 0.7)",
|
||||
detailOverlay: theme.isLight ? "rgba(0,0,0,0.08)" : "rgba(0,0,0,0.35)",
|
||||
deviceShadow: theme.isLight ? "rgba(0,0,0,0.06)" : "rgba(0,0,0,0.2)",
|
||||
deviceHighlight: theme.isLight
|
||||
? "rgba(255,255,255,0.5)"
|
||||
: "rgba(255,255,255,0.08)",
|
||||
tbActive: theme.isLight
|
||||
? "rgba(30,28,20,0.9)"
|
||||
: "rgba(234,179,8,0.9)",
|
||||
tbInactive: theme.isLight
|
||||
? "rgba(160,160,160,0.7)"
|
||||
: "rgba(100,100,100,0.7)",
|
||||
deviceDetail: theme.isLight ? "#c0c0c0" : "#374151",
|
||||
});
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
@@ -127,7 +174,7 @@
|
||||
|
||||
function getTemperatureColor(temp: number): string {
|
||||
// Default for N/A temp - light gray
|
||||
if (isNaN(temp) || temp === null) return "rgba(179, 179, 179, 0.8)";
|
||||
if (isNaN(temp) || temp === null) return themeColors.wireDefault;
|
||||
|
||||
const coolTemp = 45; // Temp for pure blue
|
||||
const midTemp = 57.5; // Temp for pure yellow
|
||||
@@ -208,7 +255,7 @@
|
||||
.append("path")
|
||||
.attr("d", "M 0 0 L 10 5 L 0 10")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "var(--exo-light-gray, #B3B3B3)")
|
||||
.attr("stroke", themeColors.gridStroke)
|
||||
.attr("stroke-width", "1.6")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("stroke-linejoin", "round")
|
||||
@@ -221,7 +268,7 @@
|
||||
.attr("y", centerY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("fill", "rgba(255,215,0,0.4)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.4)`)
|
||||
.attr("font-size", isMinimized ? 10 : 12)
|
||||
.attr("font-family", "SF Mono, monospace")
|
||||
.attr("letter-spacing", "0.1em")
|
||||
@@ -505,8 +552,8 @@
|
||||
.attr(
|
||||
"fill",
|
||||
conn.missingIface
|
||||
? "rgba(248,113,113,0.9)"
|
||||
: "rgba(255,255,255,0.85)",
|
||||
? themeColors.errorText
|
||||
: themeColors.normalText,
|
||||
)
|
||||
.text(label);
|
||||
currentY += isTop ? lineHeight : -lineHeight;
|
||||
@@ -563,24 +610,24 @@
|
||||
filteredNodes.size > 0 && !filteredNodes.has(nodeInfo.id);
|
||||
const isHovered = hoveredNodeId === nodeInfo.id && !isInFilter;
|
||||
|
||||
// Holographic wireframe colors - bright yellow for filter, subtle yellow for hover, grey for filtered out
|
||||
// Holographic wireframe colors - accent for filter, subtle for hover, grey for filtered out
|
||||
const wireColor = isInFilter
|
||||
? "rgba(255,215,0,1)" // Bright yellow for filter selection
|
||||
? `rgba(${themeColors.accentRgb},1)`
|
||||
: isHovered
|
||||
? "rgba(255,215,0,0.7)" // Subtle yellow for hover
|
||||
? `rgba(${themeColors.accentRgb},0.7)`
|
||||
: isHighlighted
|
||||
? "rgba(255,215,0,0.9)" // Yellow for instance highlight
|
||||
? `rgba(${themeColors.accentRgb},0.9)`
|
||||
: isFilteredOut
|
||||
? "rgba(140,140,140,0.6)" // Grey for filtered out
|
||||
: "rgba(179,179,179,0.8)"; // Default
|
||||
const wireColorBright = "rgba(255,255,255,0.9)";
|
||||
? themeColors.wireFiltered
|
||||
: themeColors.wireDefault;
|
||||
const wireColorBright = themeColors.wireBright;
|
||||
const fillColor = isInFilter
|
||||
? "rgba(255,215,0,0.25)"
|
||||
? `rgba(${themeColors.accentRgb},0.25)`
|
||||
: isHovered
|
||||
? "rgba(255,215,0,0.12)"
|
||||
? `rgba(${themeColors.accentRgb},0.12)`
|
||||
: isHighlighted
|
||||
? "rgba(255,215,0,0.15)"
|
||||
: "rgba(255,215,0,0.08)";
|
||||
? `rgba(${themeColors.accentRgb},0.15)`
|
||||
: `rgba(${themeColors.accentRgb},0.08)`;
|
||||
const strokeWidth = isInFilter
|
||||
? 3
|
||||
: isHovered
|
||||
@@ -588,8 +635,8 @@
|
||||
: isHighlighted
|
||||
? 2.5
|
||||
: 1.5;
|
||||
const screenFill = "rgba(0,20,40,0.9)";
|
||||
const glowColor = "rgba(255,215,0,0.3)";
|
||||
const screenFill = themeColors.deviceScreenFill;
|
||||
const glowColor = `rgba(${themeColors.accentRgb},0.3)`;
|
||||
|
||||
const nodeG = nodesGroup
|
||||
.append("g")
|
||||
@@ -653,7 +700,7 @@
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("rx", cornerRadius)
|
||||
.attr("fill", "#1a1a1a")
|
||||
.attr("fill", themeColors.deviceCase)
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth);
|
||||
|
||||
@@ -671,12 +718,12 @@
|
||||
)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", memFillActualHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.75)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.75)`)
|
||||
.attr("clip-path", `url(#${studioClipId})`);
|
||||
}
|
||||
|
||||
// Front panel details - vertical slots
|
||||
const detailColor = "rgba(0,0,0,0.35)";
|
||||
const detailColor = themeColors.detailOverlay;
|
||||
const slotHeight = iconBaseHeight * 0.14;
|
||||
const vSlotWidth = iconBaseWidth * 0.05;
|
||||
const vSlotY =
|
||||
@@ -736,7 +783,7 @@
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("rx", cornerRadius)
|
||||
.attr("fill", "#1a1a1a")
|
||||
.attr("fill", themeColors.deviceCase)
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth);
|
||||
|
||||
@@ -754,12 +801,12 @@
|
||||
)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", memFillActualHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.75)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.75)`)
|
||||
.attr("clip-path", `url(#${miniClipId})`);
|
||||
}
|
||||
|
||||
// Front panel details - vertical slots (no horizontal slot for Mini)
|
||||
const detailColor = "rgba(0,0,0,0.35)";
|
||||
const detailColor = themeColors.detailOverlay;
|
||||
const slotHeight = iconBaseHeight * 0.2;
|
||||
const vSlotWidth = iconBaseWidth * 0.045;
|
||||
const vSlotY =
|
||||
@@ -814,7 +861,7 @@
|
||||
.attr("width", screenWidth)
|
||||
.attr("height", screenHeight)
|
||||
.attr("rx", 3)
|
||||
.attr("fill", "#1a1a1a")
|
||||
.attr("fill", themeColors.deviceCase)
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth);
|
||||
|
||||
@@ -826,7 +873,7 @@
|
||||
.attr("width", screenWidth - screenBezel * 2)
|
||||
.attr("height", screenHeight - screenBezel * 2)
|
||||
.attr("rx", 2)
|
||||
.attr("fill", "#0a0a12");
|
||||
.attr("fill", themeColors.deviceScreen);
|
||||
|
||||
// Memory fill on screen (fills from bottom up - classic style)
|
||||
if (ramUsagePercent > 0) {
|
||||
@@ -842,7 +889,7 @@
|
||||
)
|
||||
.attr("width", screenWidth - screenBezel * 2)
|
||||
.attr("height", memFillActualHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.85)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.85)`)
|
||||
.attr("clip-path", `url(#${screenClipId})`);
|
||||
}
|
||||
|
||||
@@ -859,7 +906,7 @@
|
||||
"transform",
|
||||
`translate(${logoX}, ${logoY}) scale(${logoScale})`,
|
||||
)
|
||||
.attr("fill", "#FFFFFF")
|
||||
.attr("fill", themeColors.labelWhite)
|
||||
.attr("opacity", 0.9);
|
||||
|
||||
// Base (keyboard) - trapezoidal
|
||||
@@ -875,7 +922,7 @@
|
||||
"d",
|
||||
`M ${baseTopX} ${baseY} L ${baseTopX + baseTopWidth} ${baseY} L ${baseBottomX + baseBottomWidth} ${baseY + baseHeight} L ${baseBottomX} ${baseY + baseHeight} Z`,
|
||||
)
|
||||
.attr("fill", "#2c2c2c")
|
||||
.attr("fill", themeColors.deviceCaseDark)
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", 1);
|
||||
|
||||
@@ -890,7 +937,7 @@
|
||||
.attr("y", keyboardY)
|
||||
.attr("width", keyboardWidth)
|
||||
.attr("height", keyboardHeight)
|
||||
.attr("fill", "rgba(0,0,0,0.2)")
|
||||
.attr("fill", themeColors.deviceShadow)
|
||||
.attr("rx", 2);
|
||||
|
||||
// Trackpad
|
||||
@@ -904,7 +951,7 @@
|
||||
.attr("y", trackpadY)
|
||||
.attr("width", trackpadWidth)
|
||||
.attr("height", trackpadHeight)
|
||||
.attr("fill", "rgba(255,255,255,0.08)")
|
||||
.attr("fill", themeColors.deviceHighlight)
|
||||
.attr("rx", 2);
|
||||
} else {
|
||||
// Default/Unknown - holographic hexagon
|
||||
@@ -942,7 +989,7 @@
|
||||
.attr("y", gpuBarY)
|
||||
.attr("width", gpuBarWidth)
|
||||
.attr("height", gpuBarHeight)
|
||||
.attr("fill", "rgba(80, 80, 90, 0.7)")
|
||||
.attr("fill", themeColors.gpuChip)
|
||||
.attr("rx", 2);
|
||||
|
||||
// GPU Bar Fill (from bottom up, colored by temperature)
|
||||
@@ -979,7 +1026,7 @@
|
||||
.attr("y", gpuTextY - lineSpacing)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("fill", "#FFFFFF")
|
||||
.attr("fill", themeColors.labelWhite)
|
||||
.attr("font-size", gpuTextFontSize)
|
||||
.attr("font-weight", "700")
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
@@ -992,7 +1039,7 @@
|
||||
.attr("y", gpuTextY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("fill", "#FFFFFF")
|
||||
.attr("fill", themeColors.labelWhite)
|
||||
.attr("font-size", gpuTextFontSize)
|
||||
.attr("font-weight", "700")
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
@@ -1005,7 +1052,7 @@
|
||||
.attr("y", gpuTextY + lineSpacing)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("fill", "#FFFFFF")
|
||||
.attr("fill", themeColors.labelWhite)
|
||||
.attr("font-size", gpuTextFontSize)
|
||||
.attr("font-weight", "700")
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
@@ -1033,7 +1080,7 @@
|
||||
.attr("y", nameY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("fill", "#FFD700")
|
||||
.attr("fill", themeColors.accent)
|
||||
.attr("font-size", fontSize)
|
||||
.attr("font-weight", 500)
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
@@ -1050,15 +1097,15 @@
|
||||
.attr("font-family", "SF Mono, Monaco, monospace");
|
||||
memText
|
||||
.append("tspan")
|
||||
.attr("fill", "rgba(255,215,0,0.9)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.9)`)
|
||||
.text(`${formatBytes(ramUsed)}`);
|
||||
memText
|
||||
.append("tspan")
|
||||
.attr("fill", "rgba(179,179,179,0.9)")
|
||||
.attr("fill", themeColors.labelMuted)
|
||||
.text(`/${formatBytes(ramTotal)}`);
|
||||
memText
|
||||
.append("tspan")
|
||||
.attr("fill", "rgba(179,179,179,0.7)")
|
||||
.attr("fill", themeColors.labelDim)
|
||||
.text(` (${ramUsagePercent.toFixed(0)}%)`);
|
||||
} else if (showCompactLabels) {
|
||||
// COMPACT MODE: Just name and basic info (4+ nodes)
|
||||
@@ -1075,7 +1122,7 @@
|
||||
.attr("x", nodeInfo.x)
|
||||
.attr("y", nameY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "#FFD700")
|
||||
.attr("fill", themeColors.accent)
|
||||
.attr("font-size", fontSize)
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
.text(shortName);
|
||||
@@ -1087,7 +1134,7 @@
|
||||
.attr("x", nodeInfo.x)
|
||||
.attr("y", statsY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "rgba(255,215,0,0.7)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.7)`)
|
||||
.attr("font-size", fontSize * 0.85)
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
.text(
|
||||
@@ -1108,7 +1155,7 @@
|
||||
.attr("x", nodeInfo.x)
|
||||
.attr("y", nameY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "#FFD700")
|
||||
.attr("fill", themeColors.accent)
|
||||
.attr("font-size", fontSize)
|
||||
.attr("font-weight", "500")
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
@@ -1125,15 +1172,15 @@
|
||||
.attr("font-family", "SF Mono, Monaco, monospace");
|
||||
memTextMini
|
||||
.append("tspan")
|
||||
.attr("fill", "rgba(255,215,0,0.9)")
|
||||
.attr("fill", `rgba(${themeColors.accentRgb},0.9)`)
|
||||
.text(`${formatBytes(ramUsed)}`);
|
||||
memTextMini
|
||||
.append("tspan")
|
||||
.attr("fill", "rgba(179,179,179,0.9)")
|
||||
.attr("fill", themeColors.labelMuted)
|
||||
.text(`/${formatBytes(ramTotal)}`);
|
||||
memTextMini
|
||||
.append("tspan")
|
||||
.attr("fill", "rgba(179,179,179,0.7)")
|
||||
.attr("fill", themeColors.labelDim)
|
||||
.text(` (${ramUsagePercent.toFixed(0)}%)`);
|
||||
}
|
||||
|
||||
@@ -1149,8 +1196,8 @@
|
||||
const tbStatus = tbBridgeData[nodeInfo.id];
|
||||
if (tbStatus) {
|
||||
const tbColor = tbStatus.enabled
|
||||
? "rgba(234,179,8,0.9)"
|
||||
: "rgba(100,100,100,0.7)";
|
||||
? themeColors.tbActive
|
||||
: themeColors.tbInactive;
|
||||
const tbText = tbStatus.enabled ? "TB:ON" : "TB:OFF";
|
||||
nodeG
|
||||
.append("text")
|
||||
@@ -1168,7 +1215,7 @@
|
||||
if (rdmaStatus !== undefined) {
|
||||
const rdmaColor = rdmaStatus.enabled
|
||||
? "rgba(74,222,128,0.9)"
|
||||
: "rgba(100,100,100,0.7)";
|
||||
: themeColors.tbInactive;
|
||||
const rdmaText = rdmaStatus.enabled ? "RDMA:ON" : "RDMA:OFF";
|
||||
nodeG
|
||||
.append("text")
|
||||
@@ -1189,7 +1236,7 @@
|
||||
.attr("x", nodeInfo.x)
|
||||
.attr("y", debugLabelY)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("fill", "rgba(179,179,179,0.7)")
|
||||
.attr("fill", themeColors.labelDim)
|
||||
.attr("font-size", debugFontSize)
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
.text(
|
||||
@@ -1206,6 +1253,7 @@
|
||||
const _hoveredNodeId = hoveredNodeId;
|
||||
const _filteredNodes = filteredNodes;
|
||||
const _highlightedNodes = highlightedNodes;
|
||||
const _themeColors = themeColors;
|
||||
if (_data) {
|
||||
renderGraph();
|
||||
}
|
||||
|
||||
28
dashboard/src/lib/stores/theme.svelte.ts
Normal file
28
dashboard/src/lib/stores/theme.svelte.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
let _isLight = $state(false);
|
||||
|
||||
export const theme = {
|
||||
get isLight() {
|
||||
return _isLight;
|
||||
},
|
||||
|
||||
init() {
|
||||
if (!browser) return;
|
||||
_isLight = document.documentElement.classList.contains("light");
|
||||
},
|
||||
|
||||
toggle() {
|
||||
if (!browser) return;
|
||||
_isLight = !_isLight;
|
||||
if (_isLight) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.documentElement.classList.add("light");
|
||||
localStorage.setItem("exo-theme", "light");
|
||||
} else {
|
||||
document.documentElement.classList.remove("light");
|
||||
document.documentElement.classList.add("dark");
|
||||
localStorage.setItem("exo-theme", "dark");
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,13 @@
|
||||
<script lang="ts">
|
||||
import "../app.css";
|
||||
import { onMount } from "svelte";
|
||||
import { theme } from "$lib/stores/theme.svelte";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
onMount(() => {
|
||||
theme.init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
Reference in New Issue
Block a user