/* ============================================================
   SKIN — non-color UI tokens (Tier 3)
   ------------------------------------------------------------
   Orthogonal to theme. Theme = color; skin = shape, type, size,
   spacing, MOTION. Swapping [data-skin="..."] changes the whole
   feel of the site without touching a single color.

   This is the web port of the DeetsMusic skin system: the app's
   window-chrome tokens (titlebar, traffic lights, bento, np-cover)
   are dropped; the type / shape / material / canvas / motion slice
   that a document actually uses is kept, one-to-one in spirit.

   A shared [data-skin] BASE holds every token constant across skins
   (these ARE the "Vanilla" values). Each named skin overrides only
   what it changes. A skin NEVER names a color — where it restyles a
   surface it points at a theme ROLE (var(--surface)) or DERIVES one
   (color-mix); the theme still owns the hex.
   ============================================================ */

[data-skin] {
  /* -- typography -------------------------------------------- */
  --font-title: "Liberation Serif", Georgia, "Times New Roman", serif;
  --font-body:  system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --fs-title: 1.6rem;   /* the "Deets" wordmark + page headings */
  --fs-nav:   1rem;
  --fw-title: 700;
  --fw-nav:   600;   /* the header nav labels — bolder than body text (his call, 2026-09-14) */
  --lh-text:  1.6;

  /* Editorial title rule — an underline under the wordmark / headings.
     Ink is currentColor (--title), so the theme owns the color; these
     knobs are pure geometry. Base is a NO-OP so a new skin never inherits
     it by accident — a skin opts in (Vanilla does). */
  --title-underline:        none;
  --title-underline-w:      auto;
  --title-underline-offset: auto;

  /* -- shape ------------------------------------------------- */
  --radius-control: 8px;   /* nav links, chips, buttons */
  --radius-panel:   10px;  /* floating menus */
  --radius-card:    12px;  /* future content cards */

  /* -- floating panels (the settings menu / flyouts) --------- */
  /* Menu material — base is opaque theme surface + no frost; Glass opts
     into a translucent mix + blur. Points at roles, never a hex. */
  --menu-surface:  var(--surface);
  --menu-backdrop: none;
  --shadow-panel:  0 8px 24px rgba(0, 0, 0, 0.18);

  /* -- content cards (future section pages) ------------------ */
  --card:              var(--canvas);   /* flush with the canvas by default */
  --card-border-width: 0;
  --card-border:       var(--border);   /* the keyline's ink; Press strengthens it */
  --card-backdrop:     none;            /* frosted-glass blur; a skin opts in */
  --shadow-card:       none;            /* skins add a real card shadow */

  /* -- canvas pattern (Press halftone / Cyber grid) ---- */
  /* A STILL background-image painted on <body>, BEHIND the content. Its
     color comes from theme roles; the pattern is skin. Vanilla = none. A
     canvas that MOVES draws on an ambient layer instead (Glass → aurora),
     because a moving background repaints every frame. */
  --canvas-bg:        none;
  --canvas-bg-size:   auto;
  --canvas-bg-repeat: repeat;

  /* -- aurora layer (drifting blobs behind content) ----------- */
  /* Three <div>s injected by controls.js that a skin opts into; each
     carries one gradient and drifts by its own vector (transform only).
     Base is inert. */
  --aurora-display: none;             /* glass opts in */
  --aurora-1:       none;
  --aurora-2:       none;
  --aurora-3:       none;
  --aurora-drift-1-x: 0px;            /* offset at the loop's midpoint */
  --aurora-drift-1-y: 0px;
  --aurora-drift-2-x: 0px;
  --aurora-drift-2-y: 0px;
  --aurora-drift-3-x: 0px;
  --aurora-drift-3-y: 0px;
  --aurora-slack:   0px;              /* oversize per side; ≥ the largest drift */
  --aurora-dur:     60s;

  /* -- ambient motion rate ------------------------------------ */
  /* The endless decorative loops (ocean / aurora / storm) step at this
     rate instead of the display's, so the compositor draws a new frame
     only when a layer moves (DeetsMusic measured Ocean at 36% → 15% of a
     core, 60 → 30 fps). A unitless integer. */
  --ambient-fps: 30;

  /* -- ocean layer (the sea behind content) -------------------- */
  /* Injected into <body> by controls.js, that a skin opts into; js/ocean.js
     paints the three depth bands from these colors (DeetsMusic's skin.css
     ocean block, values one to one). The swell, the glow and the neon
     album light are ported: no heave or ripples (docs/deetsmusic-page-pass.md §7). */
  --ocean-display:      none;             /* ocean opts in */
  --ocean-water-top:    var(--canvas);    /* the water at the horizon … */
  --ocean-water-bottom: var(--canvas);    /* … and at the bottom (the deepest) */
  --ocean-deep:         linear-gradient(to bottom, var(--ocean-water-top), var(--ocean-water-bottom));
  --ocean-swell-ink:    var(--border);    /* the crest line */
  --ocean-trough:       0.35;             /* how much darker a trough is under its crest, on dark water */
  --ocean-trough-light: 0.12;             /* the same on light water (more reads as sand dunes) */
  --ocean-roll-far:     90;               /* s for a band to roll one tile (unitless: steps() needs a number); */
  --ocean-roll-mid:     90;               /*   the tiles are 360 / 720 / 1200 px (ocean-worker.js BANDS), */
  --ocean-roll-near:    100;              /*   so the near swell moves fastest */
  --ocean-bob-far:      13;               /* s for one rise of a band */
  --ocean-bob-mid:      11;
  --ocean-bob-near:     9;
  --ocean-bob-amp-far:  1px;              /* how far each band bobs; the signs alternate so the sea breathes */
  --ocean-bob-amp-mid:  -2px;
  --ocean-bob-amp-near: 3px;
  --ocean-ease:         cubic-bezier(0.37, 0, 0.63, 1);
  /* The glow is stronger and reaches higher than the app's (0.28, 80% 60%
     at 115%, 75%): his call 2026-10-06, the app's level was invisible here. */
  --ocean-glow:         0.5;              /* the cover's glow from the deep */
  --ocean-glow-fade:    2.4s;             /* a band or a new cover's glow fades in */
  --ocean-glow-w:       95%;              /* the glow's ellipse, rising from below the bottom edge */
  --ocean-glow-h:       75%;
  --ocean-glow-at:      50% 108%;
  --ocean-glow-reach:   85%;              /* where the glow has faded out */
  /* The album light (Settings › Album light; the app's OCEAN.md §7): controls.js
     sets --ocean-light (0–100) on <html>, and the fallback is its default. */
  --ocean-light-glow:   0.7;              /* the glow at full Album light (--ocean-glow at 0) */
  --ocean-light-reach:  35%;              /* how much taller the glow rises at full Album light */
  --ocean-neon-fade:    1.1s;             /* the neon crossfades to a new cover's color */
  /* The classic sea (the Tanks level designer only, data-ocean-sea="classic"):
     masked wave trains whose ink fades with distance via these roles. */
  --ocean-ink-1:   var(--border);
  --ocean-ink-2:   var(--border);
  --ocean-ink-3:   var(--border);

  /* -- storm layer (decorative strokes behind content) ------- */
  /* An inline-SVG layer (two <path pathLength="1"> bolts) injected into
     <body> by controls.js, that a skin opts into. The GEOMETRY is a skin
     token (CSS d: path(...)); the ink is a theme ROLE. Base is inert. */
  --storm-display: none;             /* cyber opts in */
  --storm-ink:     var(--title);
  --storm-glow:    none;
  --storm-w:       2px;
  --storm-path-1:  none;
  --storm-path-2:  none;
  --storm-cycle-1: 8s;
  --storm-cycle-2: 12s;

  /* -- motion ------------------------------------------------ */
  /* Base durations match DeetsMusic's so both products feel the same
     (his call, 2026-09-15; the site ran .14/.22 before). */
  --dur-fast: 0.12s;
  --dur-med:  0.18s;
  --dur-spin: 0.9s;   /* busy spinners (radio account button) */
  --ease-ui:  cubic-bezier(0.2, 0, 0.2, 1);
  --hover-lift: none;                 /* nav links / chips rise on hover; a skin opts in */
  --seam-shift: 0.5s;                 /* a phase change's dividers dissolving open or filling in (games/stage.js) */
  --seam-ease:  cubic-bezier(0.37, 0, 0.63, 1); /* sine in-out: no edge to the start or the stop */
  --wave-span:  0.5s;                 /* a phase change's or deal's nested wave, first piece to last (games/stage.js) */
  /* mahjong's opening (docs/mahjong.md, "The opening"): the seating pads
     morph into the seat boxes, the wall goes up in two layers, and after
     the break the hands are dealt 4-4-4-1 from the wall */
  --morph:       0.6s cubic-bezier(0.2, 0.8, 0.2, 1); /* a pad flying into its seat box */
  --build-layer: 0.5s;                /* one layer of the wall, all four sides at once */
  --build-stack: 0.16s;               /* one stack settling into place */
  --deal-step:   0.15s;               /* one chunk after the next, round the table */
  --deal-fly:    0.38s cubic-bezier(0.3, 0.7, 0.3, 1); /* a chunk's flight, wall to hand */
  --deal-gap:    0.03s;               /* the tiles of one chunk, one after the next */
  /* cities' opening (docs/cities.md, "The opening"): the seating pads morph
     into the player strips (--morph above), then the board is laid out —
     every tile one by one, the number tokens, the harbours, the robber */
  --lay-tile:    0.055s;              /* one terrain tile after the next */
  --lay-token:   0.035s;              /* one number token after the next */
  --lay-port:    0.045s;              /* one harbour after the next, round the coast */
  --lay-drop:    0.36s;               /* one piece setting down */
  --lay-gap:     0.15s;               /* the pause between one kind of piece and the next */
  --lay-ease:    cubic-bezier(0.2, 0.9, 0.3, 1.15); /* lands with a small overshoot */
  /* a mahjong tile's flight (docs/mahjong.md, "Tile flights"). One turn is a
     discard (lift + glide + settle), the beat, then a draw at 0.6/0.7/0.6 of
     the same three, and it must finish inside the worker's 700 ms bot step:
     these come to 680 ms. A skin that retunes them keeps that sum. */
  --tile-lift:   70ms;                /* rising off its spot */
  --tile-glide:  240ms;               /* crossing the table */
  --tile-settle: 70ms;                /* setting down into its slot */
  --tile-beat:   60ms;                /* the pause after a discard lands, before the next draw */
  --tile-ease:   cubic-bezier(0.4, 0, 0.2, 1); /* the glide: a gentle start and a soft stop */
  --tile-rise:   6px;                 /* how high the lift goes */
  --tile-drop:   3px;                 /* the last stretch the settle sets down */
  --tile-hop:    0.06;                /* scale-up at mid-flight: the tile's height off the table */

  /* -- toolbar icons: DeetsMusic's title-bar glyph standard (DeetsMusicWindows
        styles.css › The cog; styles/updates.css) -- */
  --icon-glyph:  1rem;                /* 16px — DeetsMusic's --traffic-size */
  --icon-stroke: 1.6;                 /* in the glyph's 24-unit grid */
  --icon-spring: 0.42s cubic-bezier(0.34, 1.26, 0.64, 1); /* DeetsMusic's --cog-spin / --updates-roll */
  --icon-fly:    22px;                /* an arrow's flight: out of the 24-unit box */
  --icon-part:   2px;              /* how far the invite link's two links part */
  --cog-step:    120deg;              /* one click's turn: six teeth, lands on an identical tooth */
  --icon-swap-turn: 90deg;            /* Close Table: the power glyph turns away as the check draws in */

  /* -- pop: floating panels arrive and leave (DeetsMusic's .pop) -- */
  --pop-in:      var(--dur-med);      /* a panel and new rows arriving */
  --pop-out:     var(--dur-fast);     /* a panel leaving */
  --pop-grow:    var(--dur-med);      /* a panel's height following a new row */
  --pop-stagger: 0.03s;               /* one arriving row after the next */
  --pop-shift:   var(--space-1);      /* how far a panel and a new row travel in */
  --pop-scale:   0.97;                /* a panel's size as it starts to arrive */
  --pop-ease:    cubic-bezier(0.2, 0.8, 0.2, 1); /* fast start, soft stop */

  /* -- a table arriving (games/table.js, the open button): the panel
        unfurls top-down, then its rows come in one after the next -- */
  --unfurl:      0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
  --wave-in:     0.36s cubic-bezier(0.2, 0.8, 0.2, 1); /* one row arriving */
  --wave-step:   0.045s;              /* the next row's head start */

  /* -- cover wave: a first screen of covers arrives (js/covers.js, DeetsMusic's) -- */
  --reveal-cap:  1.5s;                /* the longest the covers are waited for */
  --reveal-span: 0.5s;                /* the whole wave, first tile to last, at any count */

  /* -- settings panel (docs/ui-direction.md; not built yet) --- */
  --set-section-gap: var(--space-3);
  --set-row-h:       1.5rem;          /* one consistent row height */
  --set-range-w:     7.5rem;          /* a range row's slider */
  --set-range-val-w: 3.5em;           /* its value, "100%" fits */
  --set-pill-pad:    var(--space-1) var(--space-2);
  --set-pill-radius: 999px;           /* the split pill's outer radius */
  --set-menu-min-w:  7.5rem;          /* the small menu behind a menu half */
  --set-menu-max-h:  17.5rem;         /* longer lists scroll */
  --set-flash-dur:   1600ms;          /* a row opened from a toast: its highlight fades over this */
  --set-panel-w:     20rem;           /* the Vibe menu's width while Settings is open */
  --set-clearance:   9rem;            /* viewport height the open panel leaves for the header + menu rows */

  /* -- range slider (settings rows; handle is a skin-swappable SVG mask) -- */
  /* The handle is a --title-filled box masked by this SVG, so the shape is
     arbitrary and still themes via --title. Skins override the url() only. */
  --scrubber-track-h:     3px;
  --scrubber-handle:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='10' fill='%23000'/%3E%3C/svg%3E");
  --scrubber-handle-size: 12px;

  /* -- canvas dim (Glass › Dim canvas) ------------------------ */
  /* A black layer over the canvas and ambient layers, under the content
     (body::after in chrome.css), as an alpha 0–1. Base 0; Glass drives it. */
  --canvas-dim:       0;
  --canvas-dim-layer: rgb(0 0 0 / var(--canvas-dim));

  /* -- look-change cover (controls.js; DeetsMusic appearance.ts) -- */
  /* A theme or skin change: an opaque --canvas cover fades in over the old
     look (--cover-in-*, the OUTGOING skin's), the look swaps under it, then
     the cover fades and the page blocks rise in (--boot-*, the INCOMING
     skin's). --boot-safety lifts the cover by CSS alone if the script never
     does. */
  --boot-dur:       0.8s;
  --boot-cover-out: var(--boot-dur);  /* the cover's fade; a skin may clear it sooner */
  --boot-ease:      cubic-bezier(0.22, 1, 0.36, 1);  /* a long, soft settle */
  --boot-rise:      translateY(12px);
  --boot-stagger:   70ms;
  --boot-safety:    4s;
  --cover-in-dur:   0.22s;
  --cover-in-ease:  cubic-bezier(0.4, 0, 1, 1);      /* eases in, so the cover lands softly */

  /* -- first-run walk (js/walk.js; docs/onboarding.md) -------- */
  /* Deets and Happy lead a first visitor round the home page. The speech
     card wears the toast material; these are its geometry. The sprites'
     own frame sizes are the art's, not a choice, and live with the walker
     rules in main.css. Values match DeetsMusic's walk tokens. */
  --walk-card-w:   19rem;             /* the toast host's width */
  --walk-card-pad: var(--space-2) var(--space-3);
  --walk-card-gap: var(--space-2);    /* between the sprites and the card */
  --walk-pair-gap: var(--space-1);    /* between Deets and Happy */
  --walk-gap:      10px;              /* clearance between the group and its target */
  --walk-point:    14px;              /* the arrow that names the target */
  --walk-scale:    2;                 /* pixel art doubled, like walkers.js */
  --walk-cycle:    0.571s;            /* the walkers' rate: steps(4) at 7 fps */
  --walk-travel:   0.75s;             /* one stop to the next */
  --walk-fs:       0.9rem;
  --walk-fs-small: 0.8rem;            /* the step count and the buttons */
  --walk-stripe:   3px;               /* the card's left stripe, like a toast's */

  /* -- focus ring (one place to restyle every focus state) --- */
  --focus-ring-w:   2px;
  --focus-ring-off: 2px;
}

/* Vanilla — the reference skin. It IS the base, so this holds almost
   nothing. Exception, by design: a VANILLA-ONLY expression other skins
   shouldn't inherit lives here — the editorial underline under the
   wordmark, the one line vanilla keeps. */
[data-skin="vanilla"] {
  --title-underline:        underline;
  --title-underline-w:      1px;
  --title-underline-offset: 5px;
}

/* ============================================================
   PRESS — a risograph print shop. Ink on stock, not light on
   glass: a poster grotesque over a spec-sheet mono, every corner
   trimmed square, and NO blur anywhere. Where the other skins
   cast a shadow, Press prints a SECOND PLATE slightly out of
   register — a hard offset block of --ink-2, the theme-owned
   spot color (themes.css). Blur radius is 0 in every shadow
   here, on purpose; a soft edge would break the illusion.
   ============================================================ */
[data-skin="press"] {
  --font-title: "Anton", "Haettenschweiler", "Arial Narrow", sans-serif;
  --font-body:  "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  --fw-title:   400;              /* Anton ships one weight, and it is heavy */
  --fs-title:   1.7rem;           /* condensed caps take the extra size well */
  --fs-nav:     0.9rem;           /* mono runs wide — size the nav down so the
                                     desktop row still clears the breakpoint */
  --lh-text:    1.7;              /* mono needs the extra leading to breathe */

  /* shape — everything comes off a trimmer */
  --radius-control: 0;
  --radius-panel:   0;
  --radius-card:    0;

  /* cards — opaque stock, a printed keyline, and the offset plate. The
     keyline is --panel-border (the theme's stronger hairline), not the
     usual --border: at 0 radius a faint edge reads as a rendering
     artifact rather than a deliberate rule. */
  --card:              var(--surface);
  --card-border-width: 1px;
  --card-border:       var(--panel-border);
  --card-backdrop:     none;
  --shadow-card:       4px 4px 0 var(--ink-2);

  /* menus print too — same plate, one step further off register */
  --menu-surface: var(--surface);
  --shadow-panel: 5px 5px 0 var(--ink-2);

  /* stock — a halftone screen, dotted in the theme's hairline. Tighter
     and finer than a dot grid: at 7px it reads as paper tooth, not as
     a pattern you are meant to look at. */
  --canvas-bg:        radial-gradient(circle, var(--panel-border) 1.1px, transparent 1.4px);
  --canvas-bg-size:   7px 7px;
  --canvas-bg-repeat: repeat;

  /* motion — a press stroke, not a settle. The lift moves on BOTH axes
     (every other skin only rises): the sheet slides up-left off its ink,
     widening the misregistration to 5px instead of floating above it. */
  --dur-fast:   0.1s;
  --dur-med:    0.16s;
  --hover-lift: translate(-1px, -1px);

  /* look change — sheets set down firmly: short, a small rise, tight order */
  --boot-dur:     0.5s;
  --boot-ease:    cubic-bezier(0.3, 0, 0.1, 1);
  --boot-rise:    translateY(6px);
  --boot-stagger: 40ms;
  --wave-span:    0.35s;          /* the table's pieces too: tight order */
  --tile-lift:    50ms;           /* tiles too: picked up and put down briskly (550 ms a turn) */
  --tile-glide:   200ms;
  --tile-settle:  50ms;
  --tile-beat:    50ms;
  --cover-in-dur: 0.16s;

  /* slider handle — a trimmed block riding the rail, corners square */
  --scrubber-handle:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='6' y='2' width='12' height='20' fill='%23000'/%3E%3C/svg%3E");
  --scrubber-handle-size: 12px;
  --scrubber-track-h:     4px;
  --set-pill-radius:      0;
}

/* ============================================================
   OCEAN — deep/abyssal. Engraved Roman caps title on a serene
   serif body, over a surface of gently rolling waves.
   ============================================================ */
[data-skin="ocean"] {
  --font-title: "Cinzel", Georgia, "Times New Roman", serif;
  --font-body:  "Spectral", Georgia, "Times New Roman", serif;
  --fw-title:   600;
  --fs-title:   1.5rem;      /* all-caps reads large; size down */

  --radius-card: 14px;       /* softer, water-smoothed corners */
  /* the recessed card, also the sand fill. Settings › Card opacity sets
     --ocean-card-opacity (0–100) on <html>; the fallback is the default.
     100 is the solid sunken card, lower lets the swell through (the app's
     --panel). */
  --ocean-card:  color-mix(in srgb, color-mix(in srgb, var(--surface), black 18%) calc(var(--ocean-card-opacity, 60) * 1%), transparent);
  --card:        var(--ocean-card);

  /* look change — cards come up from beneath the surface: long, deep, unhurried */
  --boot-dur:      1.05s;
  --boot-ease:     cubic-bezier(0.25, 0.8, 0.35, 1);
  --boot-rise:     translateY(22px) scale(0.99);
  --boot-stagger:  95ms;
  --wave-span:     0.7s;           /* the table's pieces surface one after another, unhurried */
  --tile-glide:    250ms;          /* tiles drift a touch longer and settle softer, */
  --tile-settle:   75ms;           /* paid for with a shorter beat (697 ms a turn) */
  --tile-beat:     40ms;
  --cover-in-dur:  0.3s;
  --cover-in-ease: cubic-bezier(0.37, 0, 0.63, 1);

  /* slider handle — a lens swell: the rail bulges to a fat middle and tapers
     back at the pointed tips */
  --scrubber-handle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1 12Q12 5 23 12Q12 19 1 12Z' fill='%23000'/%3E%3C/svg%3E");
  --shadow-card: 0 0 8px 1px var(--border), 0 12px 32px rgba(0, 0, 0, 0.16);

  /* surface — the sea (.ocean in chrome.css, painted by js/ocean.js).
     A heavy swell in perspective: rows crowd into haze at the horizon
     (the top) and grow tall and sharp toward the bottom, each wave's dark
     body hiding the water behind it. The water is darkest at the bottom,
     where today's Song of the Day cover glows. The other values are the
     base block's. DeetsMusic's Ocean, one to one (its skin.css). */
  --ocean-display:      block;
  --ocean-water-top:    color-mix(in srgb, var(--canvas), var(--border) 10%);
  --ocean-water-bottom: color-mix(in srgb, var(--canvas), black 22%);
  /* The crest line. On a light theme, lifted toward the title color so it
     reads on pale water. On a dark theme, sunk toward the canvas: the border
     lifted toward the title glowed on dark water (his word in the app,
     2026-09-23: "too bright for dark modes"). light-dark() follows the
     theme's color-scheme. */
  --ocean-swell-ink:    light-dark(color-mix(in srgb, var(--border) 80%, var(--title)),
                                   color-mix(in srgb, var(--border) 55%, var(--canvas)));
  /* The classic sea (Tanks designer): the front crest is full --border;
     middle/back fade toward --canvas (70% / 45% ink), so far waves recede
     into haze. */
  --ocean-ink-1:   var(--border);
  --ocean-ink-2:   color-mix(in srgb, var(--border) 70%, var(--canvas));
  --ocean-ink-3:   color-mix(in srgb, var(--border) 45%, var(--canvas));

  /* micro-motion — cards float on the surface; a touch buoys them up */
  --hover-lift: translateY(-1px);
}
/* Ocean's sand edges (Settings › Draw card edges: Sand; controls.js sets
   data-ocean-edges; Soft is the default, so a first frame without the
   attribute is soft) — a dark beach: each card's edge breaks into grains over
   --sand-reach, and light specks (--border ink) scatter through that band.
   The card box paints nothing; the fill moves to its ::before (main.css
   §Ocean sand edges). No glow ring or drop shadow: an outer shadow would
   trace the hard box edge the grains remove. The grain tiles are SVG noise
   thresholded to hard 0/1 alpha, so each grain is a crisp ~1px dot. */
:root[data-skin="ocean"][data-ocean-edges="sand"] {
  --card:         transparent;
  --shadow-card:  none;
  --sand-fill:    var(--ocean-card);
  /* Settings › Sand width sets --ocean-sand (0–100) on <html>; the fallback
     is the default. It maps onto the min…max reach. */
  --sand-reach-min: 4px;
  --sand-reach-max: 40px;
  --sand-reach:   calc(var(--sand-reach-min) + (var(--sand-reach-max) - var(--sand-reach-min)) * var(--ocean-sand, 20) / 100);
  --sand-ink:     color-mix(in srgb, var(--border) 70%, transparent);
  --sand-tile:    128px 128px;
  --sand-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='0 0 1 1 1 1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='128' height='128' filter='url(%23g)'/%3E%3C/svg%3E");
  --sand-speck: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='s' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='0 0 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='128' height='128' filter='url(%23s)'/%3E%3C/svg%3E");
}

/* Ocean's swell — motion for the injected layer. Roll owns the horizontal
   drift (linear, on .ocean__train; the classic sea's .ocean__roll), bob the
   rise-and-fall (on the wrapping .ocean__bob) — separate elements, so the
   two transforms compose. A band rolls by exactly one of its own tiles per
   loop (ocean.js sets --tw), so the wrap never shows. The bob's ease is
   sampled into the keyframes, because the stepped timing (--ambient-fps)
   replaces any easing. Per-layer distances come from --roll-dist /
   --bob-amp, set on the elements in chrome.css. */
@keyframes ocean-roll {
  to { transform: translateX(var(--roll-dist, 0px)); }
}
/* (1 − cos πt) / 2 sampled at fifths ≈ ease-in-out */
@keyframes ocean-bob {
  0%   { transform: translateY(0); }
  20%  { transform: translateY(calc(var(--bob-amp, 0px) * 0.0955)); }
  40%  { transform: translateY(calc(var(--bob-amp, 0px) * 0.3455)); }
  60%  { transform: translateY(calc(var(--bob-amp, 0px) * 0.6545)); }
  80%  { transform: translateY(calc(var(--bob-amp, 0px) * 0.9045)); }
  100% { transform: translateY(var(--bob-amp, 0px)); }
}

/* ============================================================
   GLASS — frosted glassmorphism. A crisp light sans over soft
   accent "aurora" blobs the frost refracts. The frost is the one
   new primitive; every color is still a theme role.
   ============================================================ */
[data-skin="glass"] {
  --font-title: var(--font-body);   /* drop the serif for a modern sans */
  --fw-title:   600;
  --fs-title:   1.55rem;

  --radius-control: 12px;
  --radius-panel:   16px;
  --radius-card:    18px;

  /* frosted menus — translucent theme surface + a real backdrop blur. Kept
     fairly opaque (85%) since menus carry dense text and must stay readable
     over the busy content behind them. */
  --menu-surface:  color-mix(in srgb, var(--surface) 85%, transparent);
  --menu-backdrop: blur(16px) saturate(1.4);
  --shadow-panel:  0 8px 32px rgba(0, 0, 0, 0.18);

  /* ── the layers, back to front ──────────────────────────────
     Four Settings sliders (Look and feel, shown only under Glass;
     controls.js writes each as a custom property on <html>, and each
     fallback below is its default). They read in the same order as the rows:
       1. Canvas glow  --glass-canvas      the aurora's strength (--aurora-* stops)
       2. Dim canvas   --glass-canvas-dim  black over the background, undone inside cards
       3. Backlight    --glass-backlight   a light inside each card, under its tint
       4. Tint cards   --glass-tint        the card color, over the backlight
     No hex is named anywhere: every color is a theme role or an alpha of one. */

  /* 1. canvas glow — scales every aurora stop: 50 = the values as written,
     0 = no glow, 100 = double, each stop capped at 100%. */
  --glass-glow: calc(var(--glass-canvas, 74) / 50);

  /* 2. dim canvas — 0–100 maps to a 0–0.9 dim (body::after). The frost sees
     that dim too, so its brightness() multiplies it back out (1 / (1 − dim)):
     only the gaps get darker. The 0.9 cap keeps the ×10 restore inside 8-bit
     precision. */
  --glass-dim:         calc(var(--glass-canvas-dim, 21) / 100 * 0.9);
  --canvas-dim:        var(--glass-dim);
  --card-backdrop:     blur(14px) saturate(1.5) brightness(calc(1 / (1 - var(--glass-dim))));
  --card-border-width: 1px;

  /* 3 + 4. The card paints two background layers, top first: the tint (an
     alpha of --surface) over the backlight (a soft glow of the theme's accent
     roles). At tint 0 the backlight shows fully. A faint halo of the same
     light leaks past the edge (the first --shadow-card entry). */
  --glass-light: calc(var(--glass-backlight, 65) / 100);
  --glass-tint-ink: color-mix(in srgb, var(--surface) var(--glass-tint, 26%), transparent);
  --card:
    linear-gradient(var(--glass-tint-ink), var(--glass-tint-ink)),
    radial-gradient(140% 100% at 50% 0%,
      color-mix(in srgb, var(--go) calc(60% * var(--glass-light)), transparent),
      color-mix(in srgb, var(--stop) calc(40% * var(--glass-light)), transparent) 55%,
      color-mix(in srgb, var(--pause) calc(30% * var(--glass-light)), transparent));
  --shadow-card:
    0 0 28px color-mix(in srgb, var(--go) calc(30% * var(--glass-light)), transparent),
    0 8px 32px rgba(0, 0, 0, 0.18),
    inset 0 1px 0 color-mix(in srgb, var(--surface) 70%, transparent);

  /* look change — panes settle forward into place: soft and slow, a slight scale */
  --boot-dur:     0.9s;
  --boot-ease:    cubic-bezier(0.4, 0, 0.2, 1);
  --boot-rise:    translateY(6px) scale(0.985);
  --boot-stagger: 60ms;
  --cover-in-dur: 0.26s;

  /* slider handle — a glass ring (evenodd hollow): the track shows through */
  --scrubber-handle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill-rule='evenodd' d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 4.5a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11z' fill='%23000'/%3E%3C/svg%3E");

  /* aurora — soft accent blobs the frost refracts, drawn on the injected
     .aurora layer (chrome.css). Hues are the theme's OWN accent roles
     (go / stop / pause) at low alpha, so Glass recolors per theme while
     naming no color. Each blob is OVERSIZED by --aurora-slack per side so
     its drift never drags a gradient's cut edge into view. */
  --aurora-display: block;
  --aurora-1:
    radial-gradient(42% 52% at 16% 10%,
      color-mix(in srgb, var(--go) min(100%, 50% * var(--glass-glow)), transparent),
      color-mix(in srgb, var(--go) min(100%, 20% * var(--glass-glow)), transparent) 45%, transparent 80%);
  --aurora-2:
    radial-gradient(46% 56% at 86% 22%,
      color-mix(in srgb, var(--stop) min(100%, 44% * var(--glass-glow)), transparent),
      color-mix(in srgb, var(--stop) min(100%, 18% * var(--glass-glow)), transparent) 45%, transparent 80%);
  --aurora-3:
    radial-gradient(52% 52% at 60% 96%,
      color-mix(in srgb, var(--pause) min(100%, 38% * var(--glass-glow)), transparent),
      color-mix(in srgb, var(--pause) min(100%, 15% * var(--glass-glow)), transparent) 45%, transparent 82%);
  --aurora-slack: 64px;               /* every drift below stays within ±64 */
  /* each blob wanders a different way and returns */
  --aurora-drift-1-x:  48px;  --aurora-drift-1-y:  32px;
  --aurora-drift-2-x: -56px;  --aurora-drift-2-y:  24px;
  --aurora-drift-3-x:  36px;  --aurora-drift-3-y: -44px;
  --aurora-dur:   60s;

  --dur-fast:   0.2s;
  --hover-lift: translateY(-1px);
}
/* Glass's aurora blobs wander and return: each blob drifts by its own
   --drift-x / --drift-y (--aurora-drift-N-*) at the midpoint and comes
   back, so the loop is seamless. Every vector stays within --aurora-slack,
   so a blob's cut edge never enters the viewport. The ease is sampled into
   the keyframes — (1 − cos 2πt) / 2 at tenths — because the stepped timing
   (--ambient-fps) replaces any easing. */
@keyframes aurora-drift {
  0%, 100% { transform: translate(0, 0); }
  10%, 90% { transform: translate(calc(var(--drift-x) * 0.0955), calc(var(--drift-y) * 0.0955)); }
  20%, 80% { transform: translate(calc(var(--drift-x) * 0.3455), calc(var(--drift-y) * 0.3455)); }
  30%, 70% { transform: translate(calc(var(--drift-x) * 0.6545), calc(var(--drift-y) * 0.6545)); }
  40%, 60% { transform: translate(calc(var(--drift-x) * 0.9045), calc(var(--drift-y) * 0.9045)); }
  50%      { transform: translate(var(--drift-x), var(--drift-y)); }
}

/* ============================================================
   CYBER — electric/futuristic. Machined sci-fi type, every
   corner cut square, lightning bolts slow-drawing down a circuit
   grid behind the content.
   ============================================================ */
[data-skin="cyber"] {
  --font-title: "Orbitron", "Segoe UI", system-ui, sans-serif;
  --font-body:  "Rajdhani", "Segoe UI", system-ui, sans-serif;
  --fw-title:   700;
  --fs-title:   1.4rem;
  --lh-text:    1.55;

  /* shape — blades, not pebbles: everything square or near-square */
  --radius-control: 2px;
  --radius-panel:   0;
  --radius-card:    0;

  /* cards — smoked-glass plates with a hard 1px edge, no soft shadow */
  --card:              color-mix(in srgb, var(--canvas) 86%, transparent);
  --card-border-width: 1px;
  --shadow-card:       none;

  /* storm — the flagship expression. Two --title bolts, ~5s and ~7s draws,
     glowing in a soft halo of the same role. */
  --storm-display: block;
  --storm-glow:    drop-shadow(0 0 6px color-mix(in srgb, var(--title) 60%, transparent));
  --storm-path-1:  path("M 0 -2 L 5 14 L -3 27 L -9 36 L -12 45 L -9 36 L -3 27 L 4 41 L -2 55 L 3 70 L 10 80 L 16 92 L 10 80 L 3 70 L -4 85 L 1 102");
  --storm-path-2:  path("M 0 -2 L -4 15 L 3 28 L 9 37 L 13 47 L 9 37 L 3 28 L -2 42 L 5 56 L -3 71 L -10 80 L -15 90 L -10 80 L -3 71 L 2 86 L -1 102");

  /* canvas — a faint circuit grid under the bolts, ruled in the hairline */
  --canvas-bg:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  --canvas-bg-size:   28px 28px;
  --canvas-bg-repeat: repeat;

  /* micro-motion — twitch-fast */
  --dur-fast:   0.08s;
  --hover-lift: translateY(-1px);

  /* look change — an electric snap: the cover clears almost at once, then the
     page blocks jolt in hard-skewed and straighten, one after the next */
  --boot-dur:       0.6s;
  --boot-cover-out: 0.18s;
  --boot-ease:      cubic-bezier(0.2, 0.9, 0.3, 1);
  --boot-rise:      translateX(18px) skewX(-10deg);
  --boot-stagger:   50ms;
  --cover-in-dur:   0.1s;

  /* slider handle — the thumb is a lightning bolt */
  --scrubber-handle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14 2 L6 14 h5 l-2 8 L18 9 h-5 l3 -7 Z' fill='%23000'/%3E%3C/svg%3E");
  --scrubber-handle-size: 14px;
  --set-pill-radius:      2px;
}

/* ============================================================
   METAL — finely brushed steel (ported from DeetsMusic 0.31.0's
   sixth skin, docs/features/metal.md there). Sharp machined
   corners, Bahnschrift titles, plates lit a little at the top
   with a crisp contact shadow. Each theme names its metal and
   floor (themes.css, "Metal"): steels on the dark themes,
   aluminum on the light ones, brass on Sepia. The app draws its
   grain in a canvas; here it is a static SVG noise stretched
   along the brush, white and black streaks at low alpha over the
   theme's metal, so it recolors per theme while naming no color.
   ============================================================ */
[data-skin="metal"] {
  --font-title: "Bahnschrift", "DIN Alternate", "Segoe UI", system-ui, sans-serif;
  --font-body:  "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
  --fw-title:   600;
  --fs-title:   1.5rem;

  /* shape: sharp, a machined corner barely broken */
  --radius-control: 2px;
  --radius-panel:   4px;
  --radius-card:    4px;

  /* the material: the floor under the plates, controls a step lighter than the metal */
  --canvas:        var(--metal-floor);
  --surface:       light-dark(color-mix(in srgb, var(--metal), white 40%), color-mix(in srgb, var(--metal), white 3%));
  --surface-hover: light-dark(color-mix(in srgb, var(--metal), white 62%), color-mix(in srgb, var(--metal), white 9%));
  --border:        var(--metal-edge);
  --menu-surface:  var(--surface);

  /* the brush: one tile of each grain, stretched along x */
  --metal-grain-hi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='480'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.003 0.9' numOctaves='3' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.3 -0.12'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  --metal-grain-lo: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='480'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.004 0.7' numOctaves='3' seed='19' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.26 -0.1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");

  /* cards: the grain over a plate lit a little at the top (no diagonal reflection: the
     app's "old school app design" call); a lit top edge, a shaded bottom one, a short cast */
  --card:
    var(--metal-grain-hi),
    var(--metal-grain-lo),
    linear-gradient(180deg, color-mix(in srgb, var(--metal), white 5%), var(--metal) 45%, color-mix(in srgb, var(--metal), black 5%));
  --card-border-width: 1px;
  --card-border:       var(--metal-edge);
  --shadow-card:
    inset 0 1px 0 var(--metal-edge-hi),
    inset 0 -1px 0 var(--metal-edge-lo),
    0 1px 0 var(--metal-contact),
    0 4px 10px var(--metal-contact);
  --shadow-panel: 0 1px 0 var(--metal-contact), 0 6px 14px var(--metal-contact), inset 0 1px 0 var(--metal-edge-hi);

  /* canvas: the floor brushed at the same grain, so a card reads as its own plate on it */
  --canvas-bg:        var(--metal-grain-hi), var(--metal-grain-lo);
  --canvas-bg-size:   480px 480px;
  --canvas-bg-repeat: repeat;

  /* micro-motion — short and firm */
  --dur-fast:   0.12s;
  --hover-lift: translateY(-1px);

  /* look change — the plates slide a few px into place on the app's drum curve */
  --boot-dur:     0.56s;
  --boot-ease:    cubic-bezier(0.65, 0, 0.35, 1);
  --boot-rise:    translateY(4px);
  --boot-stagger: 40ms;
  --cover-in-dur: 0.16s;

  /* slider handle — the app's thumbwheel: a small knurled wheel (evenodd ridges) */
  --scrubber-handle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill-rule='evenodd' d='M7 2h10v20H7z M9 5.5h6v1.3H9z M9 9h6v1.3H9z M9 12.5h6v1.3H9z M9 16h6v1.3H9z' fill='%23000'/%3E%3C/svg%3E");
  --scrubber-handle-size: 16px;
  --set-pill-radius:      2px;
}

/* Settings › Animate backgrounds: Reduced halves the ambient rate for every
   skin. A user choice, so it outranks the skin's --ambient-fps. */
:root[data-bg-motion="reduced"] { --ambient-fps: 15; }
/* Cyber's strike cycle: the bolt is wiped in top→bottom over ~60% of
   its period (the strike box slides 0→full reach while storm-hold
   counter-slides, so the bolt itself never moves), flickers once at full
   reach, cuts to dark, and rests until the next strike. Every bolt runs
   THESE keyframes at its own --storm-cycle-* duration. transform + opacity
   only, so the compositor runs them. */
@keyframes storm-strike {
  0%        { transform: translateY(-100%); opacity: 0; }
  2%        { opacity: 1; }
  60%       { transform: translateY(0); opacity: 1; }
  64%       { opacity: 0.4; }   /* flicker at full reach */
  68%       { opacity: 1; }
  74%, 100% { transform: translateY(0); opacity: 0; }
}
@keyframes storm-hold {
  0%        { transform: translateY(100%); }
  60%, 100% { transform: translateY(0); }
}

/* Reduced motion (OS setting). Last in the file so it outranks every
   skin's own value: no hover rise, and busy spinners slow down rather than
   stop (a stopped spinner reads as a hang). The ambient layers and the pop
   panels handle their own reduced-motion rules in chrome.css. */
@media (prefers-reduced-motion: reduce) {
  [data-skin] {
    --hover-lift: none;
    --dur-spin:   2.4s;
  }
}
