loop-benchmarking

Controlled experiments across agentic coding configurations. Same task, one variable, what actually works.
git clone https://git.shiptheloop.com/loop-benchmarking.git
Log | Files | Refs | README

commit 111fd13e8ec810c3ae1455b4a632bde9de285994
parent 7c654ac66b26f774aa6c918fed7e20e1ad3bf1c1
Author: Brian Graham <brian@buildingbetterteams.de>
Date:   Sat,  4 Apr 2026 09:32:38 +0200

Align theme with SMUI, add light/dark mode toggle

Theme aligned with ~/smui design system:
- Raw HSL triplet variables for alpha support
- Full SMUI semantic tokens (background, foreground, card, primary, etc.)
- Frost palette (frost-1 through frost-4)
- Chart colors, kbd styling, alert variants, skeleton animation

Light mode:
- Snow Storm palette for light theme
- data-theme toggle saved to localStorage
- Sun/moon toggle button in header nav
- prefers-color-scheme media query fallback

Legacy aliases preserved for all existing components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Mdashboard/src/layouts/Base.astro | 46++++++++++++++++++++++++++++++++++++++++------
Mdashboard/src/styles/global.css | 422++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
2 files changed, 392 insertions(+), 76 deletions(-)

diff --git a/dashboard/src/layouts/Base.astro b/dashboard/src/layouts/Base.astro @@ -17,7 +17,7 @@ try { --- <!doctype html> -<html lang="en"> +<html lang="en" data-theme="dark"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> @@ -25,29 +25,63 @@ try { <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> <title>{title} | Loop Benchmarking</title> + <script is:inline> + (function() { + var saved = localStorage.getItem('theme'); + if (saved === 'light' || saved === 'dark') { + document.documentElement.setAttribute('data-theme', saved); + } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { + document.documentElement.setAttribute('data-theme', 'light'); + } else { + document.documentElement.setAttribute('data-theme', 'dark'); + } + })(); + </script> </head> <body> - <header style="border-bottom: 1px solid var(--border); padding: 16px 0; margin-bottom: 32px;"> + <header style="border-bottom: 1px solid hsl(var(--border)); padding: 16px 0; margin-bottom: 32px;"> <div class="container" style="display: flex; align-items: center; gap: 24px;"> - <a href="/" style="font-weight: 700; font-size: 1.1rem; color: var(--text);"> + <a href="/" style="font-weight: 700; font-size: 1.1rem; color: hsl(var(--foreground));"> Loop Benchmarking </a> - <nav style="display: flex; gap: 16px; font-size: 0.875rem;"> + <nav style="display: flex; gap: 16px; font-size: 0.875rem; align-items: center;"> <a href="/">Grid</a> <a href="/insights">Insights</a> <a href="/explore">Explore</a> <a href="/compare">Compare</a> </nav> + <button + id="theme-toggle" + class="theme-toggle" + type="button" + aria-label="Toggle theme" + style="margin-left: auto;" + > + <svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg> + <svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg> + </button> </div> </header> <main class="container"> <slot /> </main> - <footer style="border-top: 1px solid var(--border); padding: 24px 0; margin-top: 48px;"> - <div class="container" style="text-align: center; color: var(--text-muted); font-size: 0.75rem;"> + <footer style="border-top: 1px solid hsl(var(--border)); padding: 24px 0; margin-top: 48px;"> + <div class="container" style="text-align: center; color: hsl(var(--muted-foreground)); font-size: 0.75rem;"> Loop Benchmarking - Open agentic loop benchmark data <span style="margin-left: 12px; font-family: var(--font-mono);">{gitCommit}</span> </div> </footer> + <script is:inline> + (function() { + var btn = document.getElementById('theme-toggle'); + if (!btn) return; + btn.addEventListener('click', function() { + var current = document.documentElement.getAttribute('data-theme'); + var next = current === 'dark' ? 'light' : 'dark'; + document.documentElement.setAttribute('data-theme', next); + localStorage.setItem('theme', next); + }); + })(); + </script> </body> </html> diff --git a/dashboard/src/styles/global.css b/dashboard/src/styles/global.css @@ -1,36 +1,65 @@ /* ============================================================ Loop Benchmarking - Ship the Loop Design System + Built on SMUI (SpaceMolt UI) -- terminal-aesthetic, Nord-inspired Font: JetBrains Mono (monospace everywhere) - Palette: Nord-inspired HSL with aurora accents + Modes: dark (default), light (Snow Storm) ============================================================ */ @import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap"); +/* ============================================================ + Dark mode (default) + ============================================================ */ + :root { - /* Surfaces */ - --surface-0: hsl(213 16% 12%); - --surface-1: hsl(217 16% 15.5%); - --surface-2: hsl(216 15% 19%); - --surface-3: hsl(215 14% 22%); - - /* Text */ - --text: hsl(213 27% 88%); - --text-muted: hsl(213 14% 65%); - - /* Borders */ - --border: hsl(217 17% 28%); - --border-hover: hsl(217 17% 36%); - - /* Accent / frost */ - --accent: hsl(193 44% 67%); - --accent-hover: hsl(193 50% 75%); - - /* Aurora */ - --green: hsl(92 28% 65%); - --yellow: hsl(40 71% 73%); - --orange: hsl(14 51% 63%); - --red: hsl(355 52% 64%); - --purple: hsl(311 24% 63%); + /* shadcn/ui base variables (raw HSL triplets for alpha support) */ + --background: 213 16% 12%; + --foreground: 213 27% 88%; + --card: 217 16% 15.5%; + --card-foreground: 213 27% 88%; + --popover: 217 16% 15.5%; + --popover-foreground: 213 27% 88%; + --primary: 193 44% 67%; + --primary-foreground: 213 16% 12%; + --secondary: 216 15% 19%; + --secondary-foreground: 213 27% 88%; + --muted: 216 15% 19%; + --muted-foreground: 213 14% 65%; + --destructive: 355 52% 65%; + --destructive-foreground: 219 28% 94%; + --border: 217 17% 28%; + --input: 217 17% 28%; + --ring: 193 44% 67%; + --radius: 0rem; + + /* Chart colors */ + --chart-1: 193 44% 67%; + --chart-2: 213 32% 52%; + --chart-3: 92 28% 65%; + --chart-4: 40 71% 73%; + --chart-5: 311 24% 63%; + + /* Frost blues */ + --smui-frost-1: 176 25% 65%; + --smui-frost-2: 193 44% 67%; + --smui-frost-3: 210 34% 63%; + --smui-frost-4: 213 32% 52%; + + /* Aurora status colors */ + --smui-green: 92 28% 65%; + --smui-yellow: 40 71% 73%; + --smui-orange: 14 51% 63%; + --smui-red: 355 52% 64%; + --smui-purple: 311 24% 63%; + + /* Surface hierarchy (Polar Night) */ + --smui-surface-0: 213 16% 12%; + --smui-surface-1: 217 16% 15.5%; + --smui-surface-2: 216 15% 19%; + --smui-surface-3: 215 14% 22%; + + /* Interactive */ + --smui-border-hover: 216 12% 37%; /* Typography */ --font-mono: "JetBrains Mono", monospace; @@ -38,13 +67,127 @@ --text-ui: 13px; --text-heading: 22px; --text-stat: 26px; - - /* Legacy aliases so inline styles in Base.astro keep working */ + --text-hero: 42px; + + /* Legacy aliases so existing components keep working. + Components use var(--accent) to mean "frost blue text color", + but SMUI uses --accent for a different semantic role. + These resolved-color aliases bridge the gap. */ + --surface-0: hsl(var(--smui-surface-0)); + --surface-1: hsl(var(--smui-surface-1)); + --surface-2: hsl(var(--smui-surface-2)); + --surface-3: hsl(var(--smui-surface-3)); + --text: hsl(var(--foreground)); + --text-muted: hsl(var(--muted-foreground)); + --border-hover: hsl(var(--smui-border-hover)); + /* --accent in legacy components means the frost blue accent color */ + --accent: hsl(var(--primary)); + --accent-hover: hsl(var(--primary) / 0.8); + --green: hsl(var(--smui-green)); + --yellow: hsl(var(--smui-yellow)); + --orange: hsl(var(--smui-orange)); + --red: hsl(var(--smui-red)); + --purple: hsl(var(--smui-purple)); --bg: var(--surface-0); --bg-card: var(--surface-1); --bg-hover: var(--surface-2); } +/* ============================================================ + Light mode (Snow Storm) + ============================================================ */ + +[data-theme="light"] { + --background: 218 27% 94%; + --foreground: 220 16% 22%; + --card: 218 27% 89%; + --card-foreground: 220 16% 22%; + --popover: 218 27% 89%; + --popover-foreground: 220 16% 22%; + --primary: 213 32% 40%; + --primary-foreground: 0 0% 100%; + --secondary: 219 28% 84%; + --secondary-foreground: 220 16% 22%; + --muted: 219 28% 84%; + --muted-foreground: 220 17% 28%; + --destructive: 355 60% 45%; + --destructive-foreground: 0 0% 100%; + --border: 219 18% 72%; + --input: 219 18% 72%; + --ring: 213 32% 40%; + + /* Chart colors (darkened for light backgrounds) */ + --chart-1: 213 32% 44%; + --chart-2: 210 34% 42%; + --chart-3: 92 30% 36%; + --chart-4: 40 70% 35%; + --chart-5: 311 28% 42%; + + /* Frost (darkened for light backgrounds) */ + --smui-frost-1: 176 30% 35%; + --smui-frost-2: 193 40% 38%; + --smui-frost-3: 210 34% 40%; + --smui-frost-4: 213 32% 36%; + + /* Aurora (darkened for light backgrounds) */ + --smui-green: 92 35% 34%; + --smui-yellow: 40 70% 34%; + --smui-orange: 14 55% 44%; + --smui-red: 355 55% 44%; + --smui-purple: 311 28% 40%; + + /* Surface hierarchy (Snow Storm) */ + --smui-surface-0: 218 27% 94%; + --smui-surface-1: 218 27% 89%; + --smui-surface-2: 219 28% 84%; + --smui-surface-3: 219 20% 76%; + + /* Interactive */ + --smui-border-hover: 220 17% 50%; +} + +/* Also support prefers-color-scheme for users without explicit toggle */ +@media (prefers-color-scheme: light) { + :root:not([data-theme="dark"]):not([data-theme="light"]) { + --background: 218 27% 94%; + --foreground: 220 16% 22%; + --card: 218 27% 89%; + --card-foreground: 220 16% 22%; + --popover: 218 27% 89%; + --popover-foreground: 220 16% 22%; + --primary: 213 32% 40%; + --primary-foreground: 0 0% 100%; + --secondary: 219 28% 84%; + --secondary-foreground: 220 16% 22%; + --muted: 219 28% 84%; + --muted-foreground: 220 17% 28%; + --destructive: 355 60% 45%; + --destructive-foreground: 0 0% 100%; + --border: 219 18% 72%; + --input: 219 18% 72%; + --ring: 213 32% 40%; + --chart-1: 213 32% 44%; + --chart-2: 210 34% 42%; + --chart-3: 92 30% 36%; + --chart-4: 40 70% 35%; + --chart-5: 311 28% 42%; + --smui-frost-1: 176 30% 35%; + --smui-frost-2: 193 40% 38%; + --smui-frost-3: 210 34% 40%; + --smui-frost-4: 213 32% 36%; + --smui-green: 92 35% 34%; + --smui-yellow: 40 70% 34%; + --smui-orange: 14 55% 44%; + --smui-red: 355 55% 44%; + --smui-purple: 311 28% 40%; + --smui-surface-0: 218 27% 94%; + --smui-surface-1: 218 27% 89%; + --smui-surface-2: 219 28% 84%; + --smui-surface-3: 219 20% 76%; + --smui-border-hover: 220 17% 50%; + } +} + /* ---- Reset ---- */ * { @@ -53,12 +196,19 @@ box-sizing: border-box; } +/* ---- Selection ---- */ + +::selection { + background: hsl(var(--primary) / 0.2); + color: hsl(var(--primary)); +} + /* ---- Base ---- */ body { font-family: var(--font-mono); - background: var(--surface-0); - color: var(--text); + background: hsl(var(--background)); + color: hsl(var(--foreground)); font-size: var(--text-ui); line-height: 1.5; -webkit-font-smoothing: antialiased; @@ -66,13 +216,13 @@ body { } a { - color: var(--accent); + color: hsl(var(--primary)); text-decoration: none; transition: color 0.15s; } a:hover { - color: var(--accent-hover); + color: hsl(var(--primary) / 0.8); text-decoration: underline; } @@ -111,15 +261,24 @@ h3 { /* ---- Cards ---- */ .card { - background: var(--surface-1); - border: 1px solid var(--border); - border-radius: 2px; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); padding: 20px; transition: border-color 0.15s; } .card:hover { - border-color: var(--border-hover); + border-color: hsl(var(--smui-border-hover)); +} + +/* Card glow -- SMUI standard hover effect */ +.card-glow { + transition: border-color 0.15s; +} + +.card-glow:hover { + border-color: hsl(var(--smui-border-hover)); } /* ---- Badges ---- */ @@ -128,7 +287,7 @@ h3 { display: inline-block; padding: 1px 6px; border: 1px solid transparent; - border-radius: 2px; + border-radius: var(--radius); font-size: var(--text-label); font-weight: 500; font-family: var(--font-mono); @@ -137,20 +296,32 @@ h3 { } .badge-pass { - color: var(--green); - border-color: var(--green); + color: hsl(var(--smui-green)); + border-color: hsl(var(--smui-green) / 0.3); background: transparent; } .badge-fail { - color: var(--red); - border-color: var(--red); + color: hsl(var(--smui-red)); + border-color: hsl(var(--smui-red) / 0.3); background: transparent; } .badge-neutral { - color: var(--accent); - border-color: var(--accent); + color: hsl(var(--primary)); + border-color: hsl(var(--primary) / 0.3); + background: transparent; +} + +.badge-warn { + color: hsl(var(--smui-yellow)); + border-color: hsl(var(--smui-yellow) / 0.3); + background: transparent; +} + +.badge-info { + color: hsl(var(--smui-purple)); + border-color: hsl(var(--smui-purple) / 0.3); background: transparent; } @@ -165,12 +336,12 @@ table { th, td { padding: 8px 12px; text-align: left; - border-bottom: 1px solid var(--border); + border-bottom: 1px solid hsl(var(--border)); } th { - background: var(--surface-2); - color: var(--text-muted); + background: hsl(var(--smui-surface-2)); + color: hsl(var(--muted-foreground)); font-weight: 500; font-size: var(--text-label); text-transform: uppercase; @@ -182,7 +353,7 @@ tr { } tr:hover { - background: var(--surface-2); + background: hsl(var(--smui-surface-2)); } /* ---- Score cells ---- */ @@ -192,41 +363,41 @@ tr:hover { font-weight: 600; } -.score-high { color: var(--green); } -.score-mid { color: var(--yellow); } -.score-low { color: var(--red); } +.score-high { color: hsl(var(--smui-green)); } +.score-mid { color: hsl(var(--smui-yellow)); } +.score-low { color: hsl(var(--smui-red)); } /* ---- Form controls ---- */ select, input { font-family: var(--font-mono); - background: var(--surface-1); - border: 1px solid var(--border); - border-radius: 2px; - color: var(--text); + background: hsl(var(--card)); + border: 1px solid hsl(var(--input)); + border-radius: var(--radius); + color: hsl(var(--foreground)); padding: 6px 10px; font-size: var(--text-ui); transition: border-color 0.15s; } select:hover, input:hover { - border-color: var(--border-hover); + border-color: hsl(var(--smui-border-hover)); } select:focus, input:focus { outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 2px hsla(193, 44%, 67%, 0.25); + border-color: hsl(var(--ring)); + box-shadow: 0 0 0 2px hsl(var(--ring) / 0.25); } /* ---- Buttons ---- */ button { font-family: var(--font-mono); - background: var(--surface-2); - border: 1px solid var(--border); - border-radius: 2px; - color: var(--text); + background: hsl(var(--smui-surface-2)); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + color: hsl(var(--foreground)); padding: 6px 14px; font-size: var(--text-ui); font-weight: 500; @@ -237,13 +408,13 @@ button { } button:hover { - border-color: var(--border-hover); - background: var(--surface-3); + border-color: hsl(var(--smui-border-hover)); + background: hsl(var(--smui-surface-3)); } button:focus { outline: none; - box-shadow: 0 0 0 2px hsla(193, 44%, 67%, 0.25); + box-shadow: 0 0 0 2px hsl(var(--ring) / 0.25); } /* ---- Filters ---- */ @@ -263,7 +434,7 @@ button:focus { .filter-group label { font-size: var(--text-label); - color: var(--text-muted); + color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 1px; font-weight: 500; @@ -279,30 +450,141 @@ button:focus { } .stat-card { - background: var(--surface-1); - border: 1px solid var(--border); - border-radius: 2px; + background: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); padding: 16px; text-align: center; transition: border-color 0.15s; } .stat-card:hover { - border-color: var(--border-hover); + border-color: hsl(var(--smui-border-hover)); } .stat-value { font-size: var(--text-stat); font-weight: 700; font-family: var(--font-mono); - color: var(--text); + color: hsl(var(--foreground)); + letter-spacing: -0.5px; } .stat-label { font-size: var(--text-label); - color: var(--text-muted); + color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 1.5px; font-weight: 500; margin-top: 4px; } + +/* ---- Status dots ---- */ + +.status-dot { + display: inline-block; + width: 5px; + height: 5px; + border-radius: 9999px; +} + +.status-dot-green { background: hsl(var(--smui-green)); } +.status-dot-yellow { background: hsl(var(--smui-yellow)); } +.status-dot-orange { background: hsl(var(--smui-orange)); } +.status-dot-red { background: hsl(var(--smui-red)); } +.status-dot-purple { background: hsl(var(--smui-purple)); } + +/* ---- Keyboard shortcuts ---- */ + +kbd { + font-family: var(--font-mono); + font-size: var(--text-label); + color: hsl(var(--muted-foreground)); + border: 1px solid hsl(var(--border)); + padding: 1px 4px; + background: hsl(var(--background)); +} + +/* ---- Alerts ---- */ + +.alert { + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + padding: 12px 16px; + font-size: var(--text-ui); +} + +.alert-info { + border-color: hsl(var(--smui-frost-2) / 0.25); + background: hsl(var(--smui-frost-2) / 0.04); + color: hsl(var(--smui-frost-2)); +} + +.alert-success { + border-color: hsl(var(--smui-green) / 0.25); + background: hsl(var(--smui-green) / 0.04); + color: hsl(var(--smui-green)); +} + +.alert-warning { + border-color: hsl(var(--smui-yellow) / 0.25); + background: hsl(var(--smui-yellow) / 0.04); + color: hsl(var(--smui-yellow)); +} + +.alert-error { + border-color: hsl(var(--smui-red) / 0.25); + background: hsl(var(--smui-red) / 0.04); + color: hsl(var(--smui-red)); +} + +/* ---- Skeleton shimmer ---- */ + +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +.skeleton { + background: linear-gradient(90deg, + hsl(var(--smui-surface-2)) 25%, + hsl(var(--smui-surface-3)) 50%, + hsl(var(--smui-surface-2)) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +/* ---- Theme toggle button ---- */ + +.theme-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + background: transparent; + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + color: hsl(var(--muted-foreground)); + cursor: pointer; + transition: border-color 0.15s, color 0.15s; +} + +.theme-toggle:hover { + border-color: hsl(var(--smui-border-hover)); + color: hsl(var(--foreground)); + background: hsl(var(--smui-surface-2)); +} + +.theme-toggle svg { + width: 14px; + height: 14px; +} + +/* Hide the inactive icon */ +.theme-toggle .icon-sun { display: none; } +.theme-toggle .icon-moon { display: block; } + +[data-theme="light"] .theme-toggle .icon-sun { display: block; } +[data-theme="light"] .theme-toggle .icon-moon { display: none; }

Impressum · Datenschutz