/* ==========================================================================
   Alfredo Studio — Design Tokens Core
   Semantic tokens (colors, spacing, shadows, motion, materials)
   Font loading kept separate per context (fonts in tokens.css for demos,
   in globals.css for React app).
   ========================================================================== */

/* ---------- Root Tokens (Dark theme — default) --------------------------- */
:root {

  /* ---- Typography Scales -------------------------------------------------- */
  --text-2xs:  0.625rem;   /* 10px */
  --text-xs:   0.75rem;    /* 12px */
  --text-sm:   0.8125rem;  /* 13px */
  --text-base: 0.875rem;   /* 14px */
  --text-md:   1rem;       /* 16px */
  --text-lg:   1.125rem;   /* 18px */
  --text-xl:   1.25rem;    /* 20px */
  --text-2xl:  1.5rem;     /* 24px */
  --text-3xl:  1.875rem;   /* 30px */
  --text-4xl:  2.5rem;     /* 40px */
  --text-5xl:  3.25rem;    /* 52px */

  --fw-light:    300;
  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;

  --lh-tight:    1.2;
  --lh-snug:     1.35;
  --lh-normal:   1.6;
  --lh-relaxed:  1.75;

  --ls-tight:   -0.02em;
  --ls-normal:   0;
  --ls-wide:     0.04em;
  --ls-wider:    0.08em;
  --ls-ultra:    0.15em;

  /* ---- Palette (Primitives) --------------------------------------------- */
  /* Neutrals — dark-first */
  --palette-neutral-50:   #e9e9ea;
  --palette-neutral-100:  #d7d8d9;
  --palette-neutral-200:  #c5c6c8;
  --palette-neutral-300:  #b3b5b7;
  --palette-neutral-400:  #a1a3a6;
  --palette-neutral-500:  #8f9295;
  --palette-neutral-600:  #7d8083;
  --palette-neutral-700:  #6b6e70;
  --palette-neutral-800:  #595b5e;
  --palette-neutral-900:  #444648;
  --palette-neutral-1000: #242526;

  /* Dark backgrounds */
  --palette-dark:  #0a0f1a;
  --palette-void:  #060a12;
  --palette-alt:   #1a1525;

  /* Cyan (brand primary accent) */
  --palette-cyan-400:  #76ffff;
  --palette-cyan-500:  #41d9d9;
  --palette-cyan-600:  #2bb8b8;
  --palette-cyan-700:  #1f9999;

  /* Pink (secondary accent) */
  --palette-pink-400:  #d9a0c9;
  --palette-pink-500:  #c87db5;
  --palette-pink-600:  #a85d96;

  /* Gold (tertiary accent) */
  --palette-gold-400:  #f5d76e;
  --palette-gold-500:  #e5c44e;
  --palette-gold-600:  #c4a530;

  /* Status */
  --palette-red-500:    #f75942;
  --palette-red-600:    #dc2626;
  --palette-green-400:  #22c55e;
  --palette-green-500:  #16a34a;
  --palette-blue-500:   #3b82f6;
  --palette-orange-400: #ffb464;

  /* ---- Semantic Colors -------------------------------------------------- */
  --color-bg:              var(--palette-dark);
  --color-bg-alt:          var(--palette-alt);
  --color-surface-1:       rgba(255, 255, 255, 0.03);
  --color-surface-2:       rgba(255, 255, 255, 0.06);
  --color-surface-3:       rgba(255, 255, 255, 0.10);
  --color-surface-nav:     rgba(10, 15, 26, 0.95);

  --color-text:            #e0e0e0;
  --color-text-strong:     #ffffff;
  --color-text-secondary:  rgba(255, 255, 255, 0.7);
  --color-text-tertiary:   rgba(255, 255, 255, 0.5);
  --color-text-muted:      rgba(255, 255, 255, 0.4);
  --color-text-faint:      rgba(255, 255, 255, 0.3);

  --color-accent:           var(--palette-cyan-500);
  --color-accent-bright:    var(--palette-cyan-400);
  --color-accent-dim:       var(--palette-cyan-700);
  --color-accent-bg:        rgba(65, 217, 217, 0.15);
  --color-accent-bg-subtle: rgba(65, 217, 217, 0.08);
  --color-accent-secondary: var(--palette-pink-400);
  --color-accent-tertiary:  var(--palette-gold-400);

  --color-border:          rgba(255, 255, 255, 0.08);
  --color-border-hover:    rgba(255, 255, 255, 0.15);
  --color-border-accent:   rgba(65, 217, 217, 0.3);
  --color-border-accent-strong: rgba(65, 217, 217, 0.4);

  --color-error:   var(--palette-red-500);
  --color-success: var(--palette-green-400);
  --color-warning: var(--palette-orange-400);
  --color-info:    var(--palette-blue-500);

  /* ---- shadcn bridge tokens (no direct demo equivalent) ----------------- */
  /* Card is a solid elevated surface — not a glass layer like surface-1/2   */
  --color-card:        oklch(0.2077 0.0398 265.7549);
  --color-popover:     var(--color-card);
  --color-ring:        var(--color-accent);
  --color-input:       var(--color-border);
  --color-destructive: oklch(0.4437 0.1613 26.8994);
  --color-secondary:   var(--color-surface-2);

  /* ---- Visualizer palette tokens --------------------------------------- */
  /* Equalizer bar gradients (top → bottom) */
  --color-viz-eq-cyan-hi:    var(--palette-cyan-400);  /* #76ffff */
  --color-viz-eq-cyan-lo:    var(--palette-cyan-700);  /* #1f9999 */
  --color-viz-eq-warm-hi:    #ffd23c;
  --color-viz-eq-warm-lo:    #ff5a5a;
  --color-viz-eq-violet-hi:  #b388ff;
  --color-viz-eq-violet-lo:  #7d5cff;
  --color-viz-eq-mono-hi:    #ffffff;
  --color-viz-eq-mono-lo:    #7a7a7a;

  /* Smiley-grid palette swatches (3 representative colors each) */
  --color-viz-sg-vivid-1:    #f2553a;
  --color-viz-sg-vivid-2:    #39f264;
  --color-viz-sg-vivid-3:    #5239f2;
  --color-viz-sg-pastel-1:   #ffaaaa;
  --color-viz-sg-pastel-2:   #a0ffbe;
  --color-viz-sg-pastel-3:   #c8b4ff;
  --color-viz-sg-warm-1:     #ff5a3c;
  --color-viz-sg-warm-2:     #ffd23c;
  --color-viz-sg-warm-3:     #c83c50;
  --color-viz-sg-sky-1:      #50c8ff;
  --color-viz-sg-sky-2:      #3ca0dc;
  --color-viz-sg-sky-3:      #64b9f5;

  /* Brand gradient */
  --gradient-brand: linear-gradient(90deg,
    var(--palette-cyan-500),
    var(--palette-pink-400),
    var(--palette-gold-400)
  );

  /* ---- Spacing (adapted from Figma spacing-mode-1) ---------------------- */
  --space-xxxs:  0.125rem;  /*  2px */
  --space-xxs:   0.25rem;   /*  4px */
  --space-xs:    0.5rem;    /*  8px */
  --space-sm:    0.75rem;   /* 12px */
  --space-md:    1rem;      /* 16px */
  --space-lg:    1.5rem;    /* 24px */
  --space-xl:    2rem;      /* 32px */
  --space-2xl:   3rem;      /* 48px */
  --space-3xl:   4rem;      /* 64px */
  --space-4xl:   6rem;      /* 96px */
  --space-5xl:   8rem;      /* 128px */

  /* ---- Border Radius (adapted from Figma radius-mode-1) ----------------- */
  --radius-xs:   0.25rem;   /*  4px */
  --radius-sm:   0.5rem;    /*  8px */
  --radius-md:   0.75rem;   /* 12px */
  --radius-lg:   1rem;      /* 16px */
  --radius-xl:   1.5rem;    /* 24px */
  --radius-2xl:  2rem;      /* 32px */
  --radius-full: 9999px;

  /* ---- Shadows (adapted from Figma shadows-mode-1) ---------------------- */
  --shadow-sm:    0 1px 2px rgba(1, 2, 37, 0.25);
  --shadow-md:    0 4px 8px rgba(1, 2, 37, 0.35);
  --shadow-lg:    0 8px 24px rgba(1, 2, 37, 0.45);
  --shadow-xl:    0 16px 48px rgba(1, 2, 37, 0.55);
  --shadow-glass: 0 4px 30px rgba(0, 0, 0, 0.3);

  /* ---- Glassmorphism Materials ------------------------------------------ */
  --mat-glass-1: blur(8px);   /* Nav, toolbars — subtle */
  --mat-glass-2: blur(10px);  /* Cards, panels — standard */
  --mat-glass-3: blur(50px);  /* Widgets, modals — dense */

  --mat-glass-1-bg: rgba(10, 15, 26, 0.85);
  --mat-glass-2-bg: rgba(10, 15, 26, 0.95);
  --mat-glass-3-bg: rgba(10, 15, 26, 0.72);

  /* ---- LCD Dither Surface -------------------------------------------------- */
  --lcd-black:            #000000;
  --lcd-screen:           #B5C9B0;
  --font-lcd:             'Courier New', Courier, monospace;
  --font-size-label:      9px;   /* chrome text at DISPLAY scale (panels render 3x) */
  --font-size-value:      12px;
  --font-size-header:     10px;
  --letter-spacing-lcd:   2px;
  --lcd-unit:             4px;

  /* ---- Motion ----------------------------------------------------------- */
  --motion-instant:  50ms;
  --motion-fast:     100ms;
  --motion-base:     200ms;
  --motion-moderate: 350ms;
  --motion-slow:     500ms;
  --motion-glacial:  1000ms;

  --motion-ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --motion-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --motion-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  --transition-fast: all var(--motion-fast) ease;
  --transition-base: all var(--motion-base) ease;
  --transition-slow: all var(--motion-slow) var(--motion-ease-out);

  /* ---- Layout ----------------------------------------------------------- */
  --layout-container:      1000px;
  --layout-container-wide: 1200px;
  --layout-container-narrow: 700px;
  --layout-touch:          44px;
  --layout-nav-height:     50px;

  /* ---- Purpose-specific UI colors --------------------------------------- */
  /* Absolute values — do not override in theme blocks.                      */
  /* Piano keys must be these exact values to read as keys regardless of     */
  /* the page theme; wrapping them in tokens documents the intent.           */
  --color-piano-key-white: #f5f5f5;
  --color-piano-key-black: #1a1a1a;

  /* ---- Groovebox hardware surface ---------------------------------------
     A moulded-plastic keycap is a MATERIAL, not a UI surface, and nothing else
     in this file describes one: --palette-dark is blue-tinted for the app
     background and the neutral scale bottoms out at #242526, lighter than the
     whole dark keycap. So the groovebox gets its own scale.

     Structure, which is what makes these reusable rather than a pile of hex:
       key/key-alt  the cap face, hi → lo running with the light
       well         the recessed dimple in the cap
       panel        the chassis the caps sit on
       lift/fall    ALPHAS, not colours — lift catches the light (top-left),
                    fall turns away from it (bottom-right). Layer over any face.
       cast         the shadow the cap throws
       *-down       the same shading, weaker, for a pressed key
       glow         stops of the top-left highlight, held off the rim
       ink          markings etched into the plastic (note label, selection)

     ONE LIGHT SOURCE, TOP-LEFT, everywhere. A raised face lifts top-left and
     falls bottom-right; a recess does the exact opposite. That inversion is
     what makes a dimple read as cut into the cap rather than sat on top of it.

     Geometry sits below the colours because the grid is LOCKED at 17 × 48px
     with a 3px gap (groovebox-sequencer-row.tsx) — the key and everything in
     it derive from the cell so they cannot drift out of it. */

  --gb-key-hi:      #232326;
  --gb-key-mid:     #1d1d20;
  --gb-key-lo:      #171719;
  --gb-key-alt-hi:  #2a2a2e;
  --gb-key-alt-mid: #232326;
  --gb-key-alt-lo:  #1c1c1f;

  --gb-well-lit:    #2c2c30;
  --gb-well-mid:    #242427;
  --gb-well-shade:  #1d1d20;
  --gb-well-rim:    #161618;

  --gb-panel-hi:    #151517;
  --gb-panel-mid:   #121214;
  --gb-panel-lo:    #0e0e10;
  --gb-panel-edge:  #202024;

  --gb-lift-1:      rgba(255, 255, 255, 0.055);
  --gb-lift-2:      rgba(255, 255, 255, 0.028);
  --gb-lift-well:   rgba(255, 255, 255, 0.050);
  --gb-lift-well-2: rgba(255, 255, 255, 0.035);
  --gb-fall-1:      rgba(0, 0, 0, 0.55);
  --gb-fall-2:      rgba(0, 0, 0, 0.38);
  --gb-fall-well-1: rgba(0, 0, 0, 0.62);
  --gb-fall-well-2: rgba(0, 0, 0, 0.32);
  --gb-cast-1:      rgba(0, 0, 0, 0.55);
  --gb-cast-2:      rgba(0, 0, 0, 0.45);

  --gb-lift-1-down: rgba(255, 255, 255, 0.035);
  --gb-lift-2-down: rgba(255, 255, 255, 0.018);
  --gb-fall-1-down: rgba(0, 0, 0, 0.58);
  --gb-fall-2-down: rgba(0, 0, 0, 0.42);
  --gb-cast-down:   rgba(0, 0, 0, 0.50);

  --gb-glow-0:      rgba(255, 255, 255, 0.115);
  --gb-glow-1:      rgba(255, 255, 255, 0.055);
  --gb-glow-2:      rgba(255, 255, 255, 0.015);
  --gb-glow-3:      rgba(255, 255, 255, 0);

  --gb-ink:         rgba(206, 211, 224, 0.58);
  --gb-ink-hover:   rgba(206, 211, 224, 0.26);

  --gb-tie:         #8affa0;

  /* Geometry — theme-independent. --gb-cell is the ONE input; everything else
     about a key is derived from it in the block just below this rule. */
  --gb-cell:        48px;
  --gb-muted-alpha: 0.35;
  --gb-tie-alpha:   0.50;
  --gb-glow-down:   0.55;
}

/* ==========================================================================
   A KEY'S GEOMETRY, derived from --gb-cell

   These were absolute pixels with their ratios written in the comments —
   "~23% of the cell", "~62% of the cell", "key-radius − glow-inset, to stay
   concentric". The relationships were known and documented; they just were not
   expressed. So overriding --gb-cell scaled the CAP and left the dimple and the
   radii behind: measured at a 24px cell the well came out 0.80× instead of
   0.50×, big enough to stop being a recess and start being the whole face.
   groovebox-discover-section.tsx had to hand-scale --gb-well-size alongside
   --gb-cell to work around exactly that; it no longer does.

   Ratios are written as n/48 against the default cell, so at 48px every value
   resolves to the integer it always was. Exact, not close — the machine is
   unchanged, it is only now able to be a different size.

   The two radii state their RELATIONSHIP rather than a fraction, because "stay
   concentric" is the actual rule and two independent fractions could drift.

   WHY THIS SELECTOR LIST, and it is the whole trick: a custom property is
   substituted where it is DECLARED. Declared only on :root, --gb-key-radius
   resolves against :root's 48px cell and inherits down as a frozen 11px — so
   setting --gb-cell on a subtree scales nothing, which is exactly what the
   first version of this change did (verified in the browser: every ratio came
   back x1.00). Re-declaring on each key makes it re-resolve there, against
   whatever --gb-cell that key inherits.

   :root keeps the defaults so nothing outside a key can lose a value. The two
   key selectors were chosen by measuring: of 826 elements under the panel, 160
   are .gb-keycap-surface and the only geometry consumers outside one are
   .gb-die-shimmer and .gb-keyface-die, both children of .gb-keyface-slot.
   Descendants inherit the resolved value, so covering the roots covers them.

   .gbv4-row-wave IS THE FOURTH ROOT, and it is here because it is the one
   geometry consumer that is NOT inside a key and cannot be. The row's waveform
   spans sixteen keycaps as a single sibling element — it has to, because a cap
   carries `contain: layout` and nothing painted inside one can appear outside
   it — and it stands its drawing off the cap's inner stroke, so it needs that
   stroke's inset and width.

   MEASURED, not assumed. Without this line the canvas reads --gb-ring-inset as
   3px while the key beside it reads 3.375px: --gb-cell resolves correctly at
   the row (54px on a desktop machine), but --gb-ring-inset was already
   substituted at :root against 48px and inherits down frozen. That is this
   comment's own rule seen from outside a key, and it is worth one more selector
   rather than a second copy of the ratios in the waveform's sheet.

   .gb-tape-wave-over IS THE SAME CASE AGAIN — the tape's take, drawn over its
   own keys and standing off their stroke by the same rule. Added here rather
   than given a literal for exactly the reason above: it reads the ring's inset
   and width, and outside this list both would freeze at a 48px cell and be
   correct only on a machine whose cell happens to be 48.

   .gb-slide-nudge IS THE THIRD, and the same case a third time — the first-run
   chain's sliding shimmer, one band spanning the four steps a tie is taught
   across, standing off the caps' inner stroke exactly as the two drawings above
   do. It is outside a key for the same unavoidable reason they are: a cap
   carries `contain: layout`, so nothing painted inside one can appear over its
   neighbour, and this band's entire job is to cross from one key to the next. */
:root,
.gb-keycap-surface,
.gb-keyface-slot,
.gbv4-row-wave,
.gb-tape-wave-over,
.gb-slide-nudge {
  --gb-gap:         calc(var(--gb-cell) *  3 / 48);   /*  3px */
  --gb-key-radius:  calc(var(--gb-cell) * 11 / 48);   /* 11px — ~23% of the cell */
  --gb-well-size:   calc(var(--gb-cell) * 30 / 48);   /* 30px — ~62% of the cell */
  --gb-glow-inset:  calc(var(--gb-cell) *  2 / 48);   /*  2px */
  --gb-glow-radius: calc(var(--gb-key-radius) - var(--gb-glow-inset));  /* 9px, concentric */
  --gb-ring-inset:  calc(var(--gb-cell) *  3 / 48);   /*  3px */
  --gb-ring-radius: calc(var(--gb-key-radius) - var(--gb-ring-inset));  /* 8px, same reason */
  /* STROKES scale up but never below their drawn width. A rule that carries
     edge is 1px at any size — halve it and it either disappears or turns into
     a grey smear on a non-retina screen, a worse failure than being slightly
     heavy on a small cap. max() gives growth without that floor. */
  --gb-ring-width:  max(1px, calc(var(--gb-cell) * 1 / 48));
  /* The value ring at the knob's rim. 2px, not the 1px the other strokes use:
     this one carries information rather than edge, and at 1px a partial arc on
     a 48px circle was a hairline you had to hunt for — which is also why its
     floor is 2px and not 1. */
  --gb-knob-arc-w:  max(2px, calc(var(--gb-cell) * 2 / 48));
}

/* ---------- Light Theme (next-themes uses .dark class) ------------------- */
html:not(.dark) {
  --color-bg:              #f5f3f0;
  --color-bg-alt:          #eae6e1;
  --color-surface-1:       rgba(0, 0, 0, 0.08);
  --color-surface-2:       rgba(0, 0, 0, 0.15);
  --color-surface-3:       rgba(0, 0, 0, 0.20);
  --color-surface-nav:     rgba(255, 255, 255, 0.95);

  --color-text:            #242526;
  --color-text-strong:     #000000;
  --color-text-secondary:  rgba(0, 0, 0, 0.7);
  --color-text-tertiary:   rgba(0, 0, 0, 0.5);
  --color-text-muted:      rgba(0, 0, 0, 0.4);
  --color-text-faint:      rgba(0, 0, 0, 0.3);

  --color-accent:           #0066cc;
  --color-accent-bright:    #0088ff;
  --color-accent-dim:       #004c99;
  --color-accent-bg:        rgba(0, 102, 204, 0.1);
  --color-accent-bg-subtle: rgba(0, 102, 204, 0.05);

  --color-border:          rgba(0, 0, 0, 0.1);
  --color-border-hover:    rgba(0, 0, 0, 0.2);
  --color-border-accent:   rgba(0, 102, 204, 0.3);
  --color-border-accent-strong: rgba(0, 102, 204, 0.5);

  --color-card:        oklch(1.0000 0 0);
  --color-destructive: oklch(0.6368 0.2078 25.3313);
  --color-secondary:   rgba(0, 0, 0, 0.06);

  --gradient-brand: linear-gradient(90deg, #0066cc, #8b5cf6, #ec4899);

  --shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.08);
  --shadow-md:    0 4px 8px rgba(0, 0, 0, 0.12);
  --shadow-lg:    0 8px 24px rgba(0, 0, 0, 0.16);
  --shadow-xl:    0 16px 48px rgba(0, 0, 0, 0.20);
  --shadow-glass: 0 4px 30px rgba(0, 0, 0, 0.1);

  --mat-glass-1-bg: rgba(235, 230, 225, 0.7);
  --mat-glass-2-bg: rgba(245, 240, 235, 0.85);
  --mat-glass-3-bg: rgba(255, 250, 245, 0.9);

  /* ---- Groovebox hardware surface, light ---------------------------------
     Not a tint of the dark values — a different material. Light-grey moulded
     plastic, which is what the groovebox has always been (the chassis ships at
     #dedfe4). The STRUCTURE is identical and the light still comes from the
     top-left; only the substance changes, so every rule written against these
     names works in both themes untouched.

     The alphas flip character rather than value. On a dark face the highlight
     is a faint 5% white and the shadow does the work; on a light face the
     highlight is nearly opaque white and the shadow is a soft blue-grey. Pure
     black shadows on light plastic read as dirt, which is why fall/cast are
     rgba(88,94,112) here and rgba(0,0,0) above. */

  --gb-key-hi:      #e6e7ed;
  --gb-key-mid:     #d9dae2;
  --gb-key-lo:      #cbccd6;
  --gb-key-alt-hi:  #dcdde5;
  --gb-key-alt-mid: #cfd0da;
  --gb-key-alt-lo:  #c2c3ce;

  --gb-well-lit:    #eef0f5;
  --gb-well-mid:    #e0e2ea;
  --gb-well-shade:  #d0d2dc;
  --gb-well-rim:    #c2c4d0;

  --gb-panel-hi:    #e4e5ea;
  --gb-panel-mid:   #d6d8de;
  --gb-panel-lo:    #c7cad2;
  --gb-panel-edge:  #c4c4c2;

  --gb-lift-1:      rgba(255, 255, 255, 0.95);
  --gb-lift-2:      rgba(255, 255, 255, 0.45);
  --gb-lift-well:   rgba(255, 255, 255, 0.85);
  --gb-lift-well-2: rgba(255, 255, 255, 0.55);
  --gb-fall-1:      rgba(88, 94, 112, 0.45);
  --gb-fall-2:      rgba(88, 94, 112, 0.26);
  --gb-fall-well-1: rgba(96, 102, 120, 0.42);
  --gb-fall-well-2: rgba(96, 102, 120, 0.24);
  --gb-cast-1:      rgba(88, 94, 112, 0.34);
  --gb-cast-2:      rgba(88, 94, 112, 0.26);

  --gb-lift-1-down: rgba(255, 255, 255, 0.55);
  --gb-lift-2-down: rgba(255, 255, 255, 0.24);
  --gb-fall-1-down: rgba(88, 94, 112, 0.50);
  --gb-fall-2-down: rgba(88, 94, 112, 0.30);
  --gb-cast-down:   rgba(88, 94, 112, 0.30);

  /* Much weaker than they look like they should be. The glow is a radial that
     STOPS at 62%, and on a near-white face a strong last stop makes that stop
     point a visible edge — it drew a hard oval across every tile, offset up
     and left exactly where the gradient origin sits. A light surface needs
     less white to register, not more: there is barely any contrast left to
     use. Still ~3x the dark values, which is the real difference between the
     two materials. */
  --gb-glow-0:      rgba(255, 255, 255, 0.34);
  --gb-glow-1:      rgba(255, 255, 255, 0.16);
  --gb-glow-2:      rgba(255, 255, 255, 0.05);
  --gb-glow-3:      rgba(255, 255, 255, 0);

  --gb-ink:         rgba(48, 54, 72, 0.62);
  --gb-ink-hover:   rgba(48, 54, 72, 0.28);

  /* The dark theme's #8affa0 is near-invisible on light plastic. */
  --gb-tie:         #35b45b;
}

/* ---------- Dark Theme (next-themes adds .dark class) --------------------- */
html.dark {
  --color-bg:              var(--palette-dark);
  --color-bg-alt:          var(--palette-alt);
  --color-surface-1:       rgba(255, 255, 255, 0.03);
  --color-surface-2:       rgba(255, 255, 255, 0.06);
  --color-surface-3:       rgba(255, 255, 255, 0.10);
  --color-surface-nav:     rgba(10, 15, 26, 0.95);

  --color-text:            #e0e0e0;
  --color-text-strong:     #ffffff;
  --color-text-secondary:  rgba(255, 255, 255, 0.7);
  --color-text-tertiary:   rgba(255, 255, 255, 0.5);
  --color-text-muted:      rgba(255, 255, 255, 0.4);
  --color-text-faint:      rgba(255, 255, 255, 0.3);

  --color-accent:           var(--palette-cyan-500);
  --color-accent-bright:    var(--palette-cyan-400);
  --color-accent-dim:       var(--palette-cyan-700);
  --color-accent-bg:        rgba(65, 217, 217, 0.15);
  --color-accent-bg-subtle: rgba(65, 217, 217, 0.08);

  --color-border:          rgba(255, 255, 255, 0.08);
  --color-border-hover:    rgba(255, 255, 255, 0.15);
  --color-border-accent:   rgba(65, 217, 217, 0.3);
  --color-border-accent-strong: rgba(65, 217, 217, 0.4);

  --color-card:        oklch(0.2077 0.0398 265.7549);
  --color-destructive: oklch(0.70 0.22 25);
  --color-secondary:   var(--color-surface-2);

  --shadow-sm:    0 1px 2px rgba(1, 2, 37, 0.25);
  --shadow-md:    0 4px 8px rgba(1, 2, 37, 0.35);
  --shadow-lg:    0 8px 24px rgba(1, 2, 37, 0.45);
  --shadow-xl:    0 16px 48px rgba(1, 2, 37, 0.55);
  --shadow-glass: 0 4px 30px rgba(0, 0, 0, 0.3);

  --mat-glass-1-bg: rgba(10, 15, 26, 0.85);
  --mat-glass-2-bg: rgba(10, 15, 26, 0.95);
  --mat-glass-3-bg: rgba(10, 15, 26, 0.72);

  /* The --gb-* groovebox hardware tokens are NOT repeated here on purpose.
     Their dark values already live in :root, and html:not(.dark) — which is
     more specific — supplies the light ones. With .dark present that selector
     stops matching and :root stands, so restating forty-odd values here would
     add a second place to forget to update rather than any behaviour. */
}

/* ==========================================================================
   The groovebox CHASSIS palette (--gbx-*)

   The machine is dressed by TWO token families, and they must move together
   or the panel is half-themed:

     --gb-*   the KEYCAPS  — cap faces, wells, glows, ink, tie/mute states
     --gbx-*  the CHASSIS  — the box, section plates, headers, fields, screens

   Overriding one and not the other is not hypothetical: v3 shipped broken in
   light mode for a long stretch because the chassis was pinned while the caps
   were paired, and nobody had looked.

   These lived inside groovebox-chassis.tsx until now, which made adding a skin
   mean editing a component's template literal. They are here because this file
   is what CLAUDE.md calls the single source of truth for colour, and --gbx-*
   was the one family that wasn't in it.

   SCOPED, not on :root. The scope is exactly {v3, v3's lab mode, standalone
   /cartridge-lab}. Every consumer reads var(--gbx-NAME, the-old-literal), so
   the same components rendered OUTSIDE the scope take the fallback and are
   untouched — today that means /component-gallery, which shows the knob in its
   classic skin and the chassis in its bezel variant. Promoting the palette to
   :root would leak the whole machine's colours onto the rest of the site.

   NOTE ON THE PROSE BELOW: several comments in these two blocks say "v1 and
   v2" for that outside-the-scope role. Both routes were retired (see
   docs/backlog.md); the gallery is what is left of it. The comments are
   otherwise the originals, moved verbatim.

   Custom properties set on an element beat anything inherited from html
   regardless of specificity, which is what lets one class answer
   html:not(.dark).
   ========================================================================== */

/* .lab-root is the standalone lab's OUTERMOST element, outside the two chassis
   anchors rather than inside them — without it listed, every var(--gbx-*) in
   its own token block falls back to the dark literal it carries for v1/v2. In
   dark mode those fallbacks happen to be right, which is exactly why the page
   was white-on-white in light and nobody saw it: the failure is invisible in
   the theme the page is developed in. Measured in the browser, not read off
   the CSS. */
.lab-root,
.gb-chassis-flat,
.gb-panel-root,
/* Every GrooveboxSelect menu is PORTALLED to <body>, so it sits outside both
   anchors above and would inherit nothing. Listed explicitly rather than
   promoted to :root. See the theme hook at the foot of this section — the
   portal is also the one place a per-instance theme cannot reach. */
.gb-select-content {
  --gbx-surface:      #141416;
  --gbx-edge:         rgba(255,255,255,0.16);
  --gbx-edge-soft:    rgba(255,255,255,0.13);
  --gbx-inset-hi:     rgba(255,255,255,0.08);
  --gbx-inset-lo:     rgba(0,0,0,0.45);
  /* The title bar is always the INVERSE of the surface it sits on, in both
     themes — dark machine, pale plate; pale machine, dark plate. It reads as a
     printed label riveted to the panel rather than a slightly different shade
     of the panel, and it means a section's name never has to compete with the
     controls under it for contrast.

     Opaque, not a translucent lift. The lift is what this used to be, and at
     5% white over a near-black surface the "bar" was a suggestion — you could
     lose a section header entirely against a busy row of keycaps. */
  --gbx-header-top:   #eceef4;
  --gbx-header-bot:   #dcdfe8;
  --gbx-ink:          rgba(255,255,255,0.86);
  --gbx-ink-dim:      rgba(255,255,255,0.62);
  /* 0.55, not the 0.45 this started at: measured, 0.45 landed on exactly
     4.52:1 against this surface, and a token sitting on the line fails the
     moment anything under it shifts a shade. */
  --gbx-ink-faint:    rgba(255,255,255,0.55);
  /* Dark type, because the plate under it is pale. */
  --gbx-header-ink:     rgba(18,20,28,0.94);
  --gbx-header-ink-dim: rgba(18,20,28,0.72);
  /* Controls that sit ON the plate — the bar selector and the key trigger —
     need fills and rules relative to the PLATE, not to the surface. The
     surface set is the wrong side of the inversion: --gbx-fill-* is a white
     lift here, and a white lift on a pale plate is invisible. */
  --gbx-header-fill:    rgba(18,20,28,0.10);
  --gbx-header-fill-2:  rgba(18,20,28,0.17);
  --gbx-header-line:    rgba(18,20,28,0.30);
  --gbx-header-line-2:  rgba(18,20,28,0.52);
  --gbx-divider:      rgba(255,255,255,0.14);
  --gbx-field-bg:     rgba(255,255,255,0.10);
  --gbx-field-edge:   rgba(255,255,255,0.38);
  --gbx-field-ink:    #ffffff;
  --gbx-btn-bg:       #d8dbe3;
  --gbx-btn-ink:      #26262a;
  --gbx-btn-off-bg:   #6d7078;
  --gbx-btn-off-ink:  #b6b8bd;
  --gbx-field-focus:  rgba(255,255,255,0.75);
  --gbx-field-bg-focus: rgba(255,255,255,0.16);
  --gbx-warn:         rgba(255,180,120,0.85);
  /* "Copied" confirmation. Status colours keep their hue in both themes and
     only shift value, so the meaning survives the theme. */
  --gbx-ok:           #8fe3a8;
  /* Ink for a control FILLED with --gbx-ok — the cartridge header's confirm.
     Paired the way --gbx-armed/--gbx-armed-ink is, and for the same reason: a
     status colour used as a BACKGROUND needs its own ink, and the ink has to
     flip with the theme because --gbx-ok flips from pale to dark.

     A filled chip also sidesteps the title plate entirely. The plate is the
     inverse of the surface, so --gbx-ok as TEXT is unreadable on it in both
     themes at once — pale green on a pale plate in dark, dark green on a dark
     plate in light. A chip that paints its own ground does not care what it
     sits on. */
  --gbx-ok-ink:       #0b0b0c;
  /* "This one, out of several" — the Take switcher's A1..A8 chips and the bar
     selector's active bar (border + a fixed-alpha tint, never a filled
     background, so no paired -ink). Deliberately not --gbx-ok: that already
     means Confirmed, and reusing it for "currently showing" collided with
     groovebox-v4-design-language.md's own one-colour-one-state rule. See
     app/(studio)/groovebox-v4/skin/skin.ts's Skin.current doc comment for the
     full reasoning — this is v3/fallback's copy of the same decision v4
     makes through the skin system. */
  --gbx-current:      #4ade80;
  /* The hold latch. Amber means "this one is pinned" on both materials, but
     the dark theme's #fde047 is 1.10:1 on a pale panel — invisible, and it was
     being used as TEXT. The light value is the same hue taken down until it
     reads. */
  --gbx-hold:         #fde047;
  /* Ink for a control FILLED with --gbx-hold — the layer row's held latch,
     which sits on a title plate. As TEXT there it measures 1.14:1 in dark and
     1.98:1 in light: invisible in both, because the plate is the inverse of
     the surface and --gbx-hold flips with the surface. Filled it is 14.92:1
     here. Same argument as --gbx-ok-ink and --gbx-select-ink. */
  --gbx-hold-ink:     #0b0b0c;
  /* The menu's "this one" — the item under the pointer and the one already
     chosen. The DARK value is left exactly as it was; only light needed
     fixing, and changing a colour that already works to make a token tidy is
     how you break the half that was fine. */
  --gbx-select:       #f5c518;
  /* Ink for a control FILLED with --gbx-select — the save header's UPDATE
     chip. Same argument as --gbx-ok-ink: the chip sits on the title plate,
     which is the inverse of the surface, so --gbx-select as TEXT is
     unreadable there in both themes at once (yellow on a pale plate in dark,
     dark olive on a dark plate in light). Filling sidesteps the plate; the
     pair is what keeps the word legible on the fill. 12.07:1 here. */
  --gbx-select-ink:   #0b0b0c;
  /* The layer row's switch: an empty ring with a knob, on a title plate. The
     ring is the plate's own ink, so it flips with the plate for free.

     THE KNOB IS THE MACHINE'S GROUND YELLOW, #FFC400 — the same yellow the
     chassis sits on. It was green, which was a colour this machine uses nowhere
     else; "on" now says it in the machine's own voice.

     The KNOB is plate-relative and therefore theme-paired the OPPOSITE way
     round to a surface colour. Here the plate is PALE, so the knob is the same
     hue taken right down: 7.14:1 against the darker end of the plate's
     gradient, which is the end that matters. #FFC400 itself measures 1.20:1
     here — the same hue, and invisible — which is the whole reason this is a
     pair and not a constant. Exactly the trap the green pair was built to
     avoid, and the yellow falls into it harder: the green read 1.48.

     Hue held at 46 across the pair, so the two states are one colour at two
     lightnesses rather than yellow in one theme and brown in the other.

     7:1 rather than the 3:1 a control's graphics owe you: this switch was asked
     to be AAA. WCAG has no AAA tier for non-text contrast, so the AAA number
     for text is what that means here, applied to every part of it. */
  --gbx-switch-knob:  #574300;
  /* The guide screen: a shade off the chassis, so it reads as something
     lowered over the machine rather than the machine changing colour. */
  --gbx-screen:       #0d0d0f;
  --gbx-screen-ink:   rgba(255,255,255,0.92);
  /* Translucent overlays. On a dark face these are white lifts; on a light one
     they have to invert to dark shades or they simply disappear — which is the
     whole reason a "just flip the surface" change does not work. Three fill
     steps and three line steps, because the lab alone used a dozen ad-hoc
     alphas and they collapse cleanly onto this scale. */
  --gbx-fill-1:       rgba(255,255,255,0.05);
  --gbx-fill-2:       rgba(255,255,255,0.09);
  --gbx-fill-3:       rgba(255,255,255,0.12);
  --gbx-line-1:       rgba(255,255,255,0.22);
  --gbx-line-2:       rgba(255,255,255,0.34);
  --gbx-line-3:       rgba(255,255,255,0.50);
  /* The armed-bar chip. A status colour, so it keeps its orange in both
     themes; only the ink on it flips. */
  --gbx-armed:        #ff6d27;
  --gbx-armed-ink:    #0b0b0c;
  /* The wordmark. Four inks it alternates through, from the knob palette. */
  --gbx-mark-1:       #5fbaf2;
  --gbx-mark-2:       #00cd73;
  --gbx-mark-3:       #ff6d27;
  --gbx-mark-4:       #fcfbfc;

  /* ---- The knobs ---------------------------------------------------------
     The four signature inks, and they are the machine's identity: a theme that
     cannot reach them reads as the same instrument in a different coat. They
     were literals in groovebox-knob.tsx (KNOB_RECIPES plus a per-colour
     ::after recipe), which is what made theming chassis-only.

     Declared ONCE, in the default block, and deliberately NOT restated in the
     light block below — so they resolve identically in both themes, which is
     exactly what they did as literals. That keeps this move a pure refactor.
     A light MATERIAL for the knobs is real design work and has not been done;
     it is the first thing a skin should reach for now that it can.

     fill/border dress the dial; bar/lift/fall are the handle laid across it —
     `bar` its face, `lift` the outer glow, `fall` the inset shade. Only the
     roles each colour actually uses are declared. */
  --gbx-knob-blue-fill:        #5fbaf2;
  --gbx-knob-blue-border:      #8bd0f9;
  --gbx-knob-blue-bar:         #5bb3ee;
  --gbx-knob-blue-bar-lift:    #89d9ff;
  --gbx-knob-blue-bar-fall:    #0292d4;

  --gbx-knob-green-fill:       #00cd73;
  --gbx-knob-green-border:     #00f1ab;
  --gbx-knob-green-bar:        #00cf77;
  --gbx-knob-green-bar-lift:   rgba(0,241,171,0.75);
  --gbx-knob-green-bar-lift-2: rgba(0,241,171,0.5);
  --gbx-knob-green-bar-fall:   #00b34d;
  --gbx-knob-green-bar-fall-2: #00bf66;

  --gbx-knob-white-fill:       #fcfbfc;
  --gbx-knob-white-border:     rgba(255,255,255,0.2);
  --gbx-knob-white-bar:        #f3f3f3;
  --gbx-knob-white-bar-lift:   rgba(255,255,255,0.5);
  --gbx-knob-white-bar-fall:   rgba(209,205,199,0.5);

  --gbx-knob-orange-fill:       #ff6d27;
  --gbx-knob-orange-border:     #ffa361;
  --gbx-knob-orange-bar:        #ff6e2b;
  --gbx-knob-orange-bar-lift:   #ffaa70;
  --gbx-knob-orange-bar-fall:   #f74b26;
  --gbx-knob-orange-bar-fall-2: #ff6423;

  /* The name and value printed on the tile, in BOTH skins. Grey on the classic
     light plate, and the same grey on v3's dark keycap tile — which is where a
     themed knob would want to say something different. */
  --gbx-knob-label:            #6c6d72;
}

/* Light. Not a tint of the above — a different material, following the same
   reasoning this file gives for the light keycaps: on a pale face the
   highlight does the work and the shadow goes soft blue-grey, because pure
   black shadows on light plastic read as dirt.

   The inks are darkened past the point of merely inverting them: #fcfbfc and
   #ff6d27 both fail against a pale nameplate, so the wordmark's fourth ink
   becomes near-black and its orange becomes a burnt one. Every value here
   clears 4.5:1 on --gbx-surface. */
html:not(.dark) :is(.lab-root, .gb-chassis-flat, .gb-panel-root, .gb-select-content) {
  --gbx-surface:      #e9eaef;
  --gbx-edge:         rgba(60,66,84,0.24);
  --gbx-edge-soft:    rgba(60,66,84,0.16);
  --gbx-inset-hi:     rgba(255,255,255,0.90);
  --gbx-inset-lo:     rgba(88,94,112,0.20);
  /* Inverted the other way: pale machine, dark plate. Same rule as above. */
  --gbx-header-top:   #2f3444;
  --gbx-header-bot:   #232838;
  --gbx-ink:          rgba(26,30,42,0.92);
  /* Measured against --gbx-surface: 0.70 gave 4.78:1 and 0.55 gave 3.19:1,
     which failed outright. At 9-10px these want headroom, not a pass mark. */
  --gbx-ink-dim:      rgba(40,45,62,0.78);
  --gbx-ink-faint:    rgba(40,45,62,0.72);
  /* Light type, because the bar underneath it is now dark. */
  --gbx-header-ink:     rgba(255,255,255,0.94);
  --gbx-header-ink-dim: rgba(255,255,255,0.72);
  --gbx-header-fill:    rgba(255,255,255,0.14);
  --gbx-header-fill-2:  rgba(255,255,255,0.22);
  --gbx-header-line:    rgba(255,255,255,0.34);
  --gbx-header-line-2:  rgba(255,255,255,0.58);
  --gbx-divider:      rgba(60,66,84,0.20);
  --gbx-field-bg:     #ffffff;
  --gbx-field-edge:   rgba(60,66,84,0.38);
  --gbx-field-ink:    #1a1e2c;
  --gbx-btn-bg:       #3a4054;
  --gbx-btn-ink:      #f2f3f7;
  --gbx-btn-off-bg:   #c3c6cf;
  --gbx-btn-off-ink:  #71757f;
  --gbx-field-focus:  rgba(40,45,62,0.62);
  --gbx-field-bg-focus: #ffffff;
  --gbx-warn:         #9a4a00;
  --gbx-ok:           #14713a;
  --gbx-ok-ink:       #ffffff;
  /* Darkened from the dark theme's #4ade80 for border contrast on a pale
     plate, the same move --gbx-select makes for the same reason — this is a
     border colour, not measured to the same text-contrast rigor as the -ink
     pairs above since nothing is printed on top of it. */
  --gbx-current:      #15803d;
  --gbx-hold:         #7a5c00;
  /* Flipped with its fill: white on the dark amber, 6.25:1. */
  --gbx-hold-ink:     #ffffff;
  /* #f5c518 measured 1.54:1 on the light dropdown, and 1.41:1 for the
     highlighted row — so the item you were hovering and the one already
     selected were the two you could not read. Same hue, taken down until both
     clear AA with headroom: 6.92:1 plain, 5.26:1 on its own wash. */
  --gbx-select:       #6d5200;
  /* Flipped, because the fill did: white on the dark olive, 7.35:1. */
  --gbx-select-ink:   #ffffff;
  /* Pale machine, DARK plate — so the knob goes the other way: the ground
     yellow at full strength, 7.75:1 on the LIGHTER end of that plate, which is
     the end that matters when the knob is the pale thing. Better than the green
     it replaces managed (7.21), and it is the machine's own colour. */
  --gbx-switch-knob:  #FFC400;
  --gbx-screen:       #f7f8fb;
  --gbx-screen-ink:   rgba(22,26,36,0.94);
  --gbx-fill-1:       rgba(40,45,62,0.05);
  --gbx-fill-2:       rgba(40,45,62,0.09);
  --gbx-fill-3:       rgba(40,45,62,0.13);
  --gbx-line-1:       rgba(40,45,62,0.26);
  --gbx-line-2:       rgba(40,45,62,0.40);
  --gbx-line-3:       rgba(40,45,62,0.58);
  /* 3% darker than the #d1500f it shipped as. White on that measured 4.32:1 —
     under AA, and on the one control whose whole job is to say "this press
     deletes something". Found by the filled-chip pair test, which did not
     exist until the cartridge header needed a green confirm on the same
     plate. Same hue, the smallest step that clears 4.5. */
  --gbx-armed:        #cb4e0f;
  --gbx-armed-ink:    #ffffff;
  --gbx-mark-1:       #1d6fa8;
  --gbx-mark-2:       #0a7a46;
  --gbx-mark-3:       #b8430f;
  --gbx-mark-4:       #2a2f3d;
  /* --gbx-knob-* is deliberately absent — see the note in the block above. */
}

/* ---------- The theme hook ------------------------------------------------
   A skin is one more scope, not a new mechanism. Stamp data-gb-theme on (or
   above) a chassis root and add a block in the same shape as the two above:

     [data-gb-theme="industrial"]:is(.lab-root, .gb-chassis-flat, .gb-panel-root),
     [data-gb-theme="industrial"] :is(.lab-root, .gb-chassis-flat, .gb-panel-root) {
       --gbx-surface: …;
     }

   BOTH selectors, with and without the descendant space, or the attribute
   works on a wrapper and silently does nothing on the chassis itself.

   Two things to know before writing one:

   1. BOTH FAMILIES OR NEITHER. --gb-* (the keycaps, at the top of this file)
      and --gbx-* dress the same machine. Theming one is the bug that already
      shipped — a light chassis under pinned dark caps.

   2. THE PORTALLED MENU CANNOT SEE IT. .gb-select-content is rendered to
      <body>, so no attribute on a chassis root is an ancestor of it. The two
      blocks above reach it only because they name the class globally. A
      per-instance theme would need GrooveboxSelect to copy the theme name onto
      its portalled content; nothing does that yet. Until it does, a theme is
      effectively page-wide — put the attribute on <html> or the page root and
      the dropdown comes along.

   Geometry is safe either way: the 13 fixed --gb-* tokens (cell, gap, radii,
   ring and glow geometry) are declared once and never overridden, so a skin
   cannot deform the 17×8 grid. Colour is all a theme can reach.

   A theme is a MATERIAL, not a palette. The keycap surface enforces one light
   source, top-left — raised faces lift top-left and fall bottom-right, recesses
   invert exactly that. Going from dark anodised to light plastic means
   different --gb-glow-* stops and different lift/fall alphas, not a hue shift.
   ------------------------------------------------------------------------- */
