/* =============================================================================
   ryderkatz.com — global design system
   -----------------------------------------------------------------------------
   Tokens → primitives → components → page-level overrides
   Per-section accent: body[data-accent] swaps --accent / --accent-2 with a
   transition so chrome (nav, glow, focus rings) shifts as the user scrolls.
============================================================================= */

/* === Tokens =============================================================== */
:root {
  /* Section accents — each pillar of the site has its own color identity. */
  --c-cyan-1:    #22d3ee;
  --c-cyan-2:    #2563eb;
  --c-violet-1:  #c084fc;
  --c-violet-2:  #7c3aed;
  --c-coral-1:   #ff8a5b;
  --c-coral-2:   #ef4444;
  --c-mint-1:    #6ee7b7;
  --c-mint-2:    #059669;
  --c-amber-1:   #fcd34d;
  --c-amber-2:   #d97706;
  /* Added 2026-08-12 for User Design (research field 04). Deliberately clear of
     violet: violet-1 is a purple-400, this is a pink-400, and the research index
     puts them two tiles apart. */
  --c-pink-1:    #f472b6;
  --c-pink-2:    #db2777;
  /* Added 2026-08-12 for Reason (research field 06). Indigo — bluer than
     violet-1 and far from cyan-1, so no two research tiles read as the same. */
  --c-iris-1:    #818cf8;
  --c-iris-2:    #4338ca;

  /* Admin green — deliberately separate from the section accents so the
     "you're signed in" signal is never recolored by body[data-accent]. */
  --c-admin:     #22c55e;
  --c-admin-2:   #15803d;
  --admin-banner-h: 0px;   /* becomes a real height under body[data-admin] */

  /* Active accent — overridden by body[data-accent]. */
  --accent:        var(--c-cyan-1);
  --accent-2:      var(--c-cyan-2);
  --accent-soft:   color-mix(in srgb, var(--accent) 16%, transparent);
  --accent-glow:   color-mix(in srgb, var(--accent) 36%, transparent);
  --accent-fg:     #0a0f1a;

  /* === THE PALETTE ========================================================
     This is the whole colour system. There is no second theme to keep in sync
     and no [data-theme] branch anywhere in the site — dark mode was removed
     2026-08-06 (unused in months of development; the switching machinery cost
     a parallel value for every token and a dark override for every component).

     Edit these eleven values and the entire site follows: every surface,
     border, shadow and material is derived from them via color-mix rather
     than hardcoded, so recolouring is a change HERE, not a search-and-replace.

     The homepage day cycle (body[data-scene], further down) re-tints these
     same tokens per scene. That is still this ONE system — the palette moved
     as a unit by scroll position — not a second theme to keep in sync.
     ====================================================================== */
  --bg:        #fbfbfd;
  --bg-elev:   #ffffff;
  --bg-soft:   #f3f4f7;
  --bg-deep:   #e7e9ee;
  --fg:        #0a0f1a;
  --fg-dim:    #4b5363;
  --fg-mute:   #8a93a3;
  --border:    rgba(10, 15, 26, .08);
  --border-hi: rgba(10, 15, 26, .16);
  --hairline:  rgba(10, 15, 26, .06);
  /* Ghost-numeral stroke (homepage chapters). A literal, never a --fg mix:
     the day-cycle tween must not move it per-frame — the giant stroked
     glyphs re-raster on every tint change, which is scroll-jank. The scene
     blocks override it; it snaps at the flip. */
  --ghost-ink: rgba(10, 15, 26, .085);

  /* Material — translucent layers used on cards, nav, palette */
  --mat-1:     color-mix(in srgb, var(--bg-elev) 92%, transparent);
  --mat-2:     color-mix(in srgb, var(--bg-elev) 78%, transparent);

  /* Elevation */
  --shadow-xs: 0 1px 2px rgba(10,15,26,.04);
  --shadow-sm: 0 2px 8px rgba(10,15,26,.06);
  --shadow-md: 0 12px 28px -10px rgba(10,15,26,.14);
  --shadow-lg: 0 40px 80px -24px rgba(10,15,26,.22);
  --shadow-accent: 0 24px 60px -16px var(--accent-glow);

  /* Type */
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Layout */
  --maxw: 1240px;
  --pad-x: clamp(1.25rem, 5vw, 4rem);

  /* === Header chrome — ONE system, not two lookalikes =====================
     The nav and the portal sub-header used to define their own material
     independently (--mat-1 + blur(20px) vs. color-mix(bg 60%) + blur(18px),
     different borders), so the two bands of the same header never quite
     matched. They both read from these now — change the bar here, both bands
     follow, and --nav-h/--subnav-h are the single knobs for header height. */
  --nav-h:       40px;
  --subnav-h:    32px;
  --chrome-bg:   color-mix(in srgb, var(--bg-elev) 78%, transparent);
  --chrome-blur: saturate(180%) blur(20px);
  --chrome-line: color-mix(in srgb, var(--fg) 10%, transparent);

  /* Shape */
  --radius:    28px;
  --radius-sm: 18px;
  --radius-xs: 12px;

  /* Motion — apple-flavored curves */
  --ease:        cubic-bezier(.2, .7, .2, 1);
  --ease-out:    cubic-bezier(.16, 1, .3, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --ease-in:     cubic-bezier(.55, 0, 1, .45);
  --t-fast:      .18s;
  --t-base:      .36s;
  --t-slow:      .8s;
}
/* Section accents (body[data-accent="…"]) ---------------------------------- */
body[data-accent="cyan"]   { --accent: var(--c-cyan-1);   --accent-2: var(--c-cyan-2); }
body[data-accent="violet"] { --accent: var(--c-violet-1); --accent-2: var(--c-violet-2); }
body[data-accent="coral"]  { --accent: var(--c-coral-1);  --accent-2: var(--c-coral-2); }
body[data-accent="mint"]   { --accent: var(--c-mint-1);   --accent-2: var(--c-mint-2); }
body[data-accent="amber"]  { --accent: var(--c-amber-1);  --accent-2: var(--c-amber-2); }
body[data-accent="pink"]   { --accent: var(--c-pink-1);   --accent-2: var(--c-pink-2); }
body[data-accent="iris"]   { --accent: var(--c-iris-1);   --accent-2: var(--c-iris-2); }
/* Admin/portal accent — P2 cool graphite (taste corpus ranked #1;
   ~/Developer/records/skills/taste). [RYDER-VERDICT 2026-07-08] the pale-green
   wash grammar is dead: admin GREEN survives only as the signed-in SIGNAL
   (admin edge / admin pill reference --c-admin directly) and the semantic
   --tone-green, never as the workspace accent that surfaces derive tints from. */
body[data-accent="admin"]  { --accent: oklch(0.55 0.16 255); --accent-2: oklch(0.44 0.15 257); --accent-fg: #fff; }

/* === The day cycle (homepage) ============================================= */
/* index.html only. Each homepage section declares data-scene (dawn → day →
   golden → dusk → night); initSectionAccent mirrors the most-visible one
   onto <body>. Two things then move together:
   1. The PALETTE ITSELF — these blocks re-tint the core tokens, and
      makePaletteTween (main.js) eases the live values toward whichever block
      is active, so the whole derived system glides: nav glass, tiles, chips,
      footer, everything. Dusk is a retint of the site, not a component skin.
      These blocks are the tween's SOURCE OF TRUTH (it reads them through
      computed style) as well as the no-tween fallback — keep every value a
      literal color.
   2. The SKY — five fixed gradient plates (#sky, markup in index.html)
      crossfading by opacity beneath everything else.
   Scoped by the attribute's existence: no other page sets data-scene, so
   none of this can leak into the portal or the other public pages. */
body[data-scene] {
  /* The tween is the ONLY animator of the palette. CSS transitions on top of
     its per-frame writes — including the base body background/color
     transition — would chase every frame and drag the ground behind the
     lines drawn on it. (v1 of the day cycle used @property-registered tokens
     with a 12-token transition here; fast scrolling restarted it on every
     section flip and the derived line colors visibly glitched.) */
  transition: none;
}
/* dawn is THE PALETTE unchanged — the rest of the site's exact ground, so the
   page opens looking like everywhere else and only then starts to travel. */
body[data-scene="day"] {
  --bg: #f8fbfd; --bg-soft: #eef4f8; --bg-deep: #e0e9f0;
}
body[data-scene="golden"] {
  --bg: #fdf8ef; --bg-elev: #fffdf7; --bg-soft: #f6eedd; --bg-deep: #ede0c8;
  --fg: #16120a; --fg-dim: #5a4f3c; --fg-mute: #988a72;
  --border: rgba(59, 42, 12, .09); --border-hi: rgba(59, 42, 12, .17);
  --hairline: rgba(59, 42, 12, .065);
  --ghost-ink: rgba(59, 42, 12, .09);
}
body[data-scene="dusk"] {
  --bg: #16131f; --bg-elev: #1e1a2b; --bg-soft: #262135; --bg-deep: #2e2841;
  --fg: #f1eef8; --fg-dim: #b3accb; --fg-mute: #7b7394;
  --border: rgba(241, 238, 248, .10); --border-hi: rgba(241, 238, 248, .19);
  --hairline: rgba(241, 238, 248, .07);
  --ghost-ink: rgba(241, 238, 248, .09);
}
body[data-scene="night"] {
  --bg: #090c12; --bg-elev: #0e131c; --bg-soft: #141a26; --bg-deep: #1a2230;
  --fg: #edf1f8; --fg-dim: #a4adc0; --fg-mute: #687184;
  --border: rgba(237, 241, 248, .10); --border-hi: rgba(237, 241, 248, .19);
  --hairline: rgba(237, 241, 248, .07);
  --ghost-ink: rgba(237, 241, 248, .09);
}

/* The sky plates. Fixed at z -4 — beneath the accent wash (body::before, -3)
   and the atmos canvas (-2), so glow and stars render ON the sky. */
#sky { position: fixed; inset: 0; z-index: -4; pointer-events: none; }
#sky i {
  position: absolute; inset: 0; opacity: 0;
  transition: opacity 1.8s var(--ease);
}
body[data-scene="dawn"]   #sky .sky-dawn,
body[data-scene="day"]    #sky .sky-day,
body[data-scene="golden"] #sky .sky-golden,
body[data-scene="dusk"]   #sky .sky-dusk,
body[data-scene="night"]  #sky .sky-night { opacity: 1; }
.sky-dawn {
  background:
    radial-gradient(60vw 42vh at 16% 100%, rgba(255, 193, 148, .45), transparent 66%),
    radial-gradient(50vw 40vh at 86% -10%, rgba(96, 190, 235, .28), transparent 62%),
    linear-gradient(180deg, #d9e7f6 0%, #f4f3f4 52%, #fbfbfd 100%);
}
.sky-day {
  background:
    radial-gradient(56vw 38vh at 78% -14%, rgba(255, 255, 255, .95), transparent 60%),
    radial-gradient(70vw 40vh at 20% 110%, rgba(190, 219, 240, .4), transparent 65%),
    linear-gradient(180deg, #dceefa 0%, #f0f7fb 55%, #f8fbfd 100%);
}
.sky-golden {
  background:
    radial-gradient(74vw 56vh at 24% 112%, rgba(255, 166, 87, .5), transparent 68%),
    radial-gradient(46vw 34vh at 72% 16%, rgba(255, 213, 158, .4), transparent 62%),
    linear-gradient(180deg, #f4dfc0 0%, #faefdb 55%, #fdf8ef 100%);
}
.sky-dusk {
  background:
    radial-gradient(72vw 50vh at 50% 114%, rgba(219, 39, 119, .30), transparent 70%),
    radial-gradient(58vw 46vh at 84% 92%, rgba(124, 58, 237, .32), transparent 66%),
    linear-gradient(180deg, #120f1c 0%, #1d1830 55%, #2a2342 100%);
}
.sky-night {
  background:
    radial-gradient(44vw 32vh at 82% 12%, rgba(160, 175, 200, .14), transparent 60%),
    radial-gradient(60vw 44vh at 18% 78%, rgba(124, 58, 237, .11), transparent 66%),
    radial-gradient(52vw 40vh at 72% 58%, rgba(34, 211, 238, .07), transparent 62%),
    linear-gradient(180deg, #04060b 0%, #090c13 58%, #0c1018 100%);
}

/* After dark, the "-2" (deep) accent variants lose their ground — chips and
   active nav pills read from the light variant instead. */
body[data-scene="dusk"] .status.live,  body[data-scene="night"] .status.live  { color: var(--c-mint-1); }
body[data-scene="dusk"] .status.wip,   body[data-scene="night"] .status.wip   { color: var(--c-amber-1); }
body[data-scene="dusk"] .status.idea,  body[data-scene="night"] .status.idea  { color: var(--c-violet-1); }
body[data-scene="dusk"] .nav-links a.active,
body[data-scene="night"] .nav-links a.active { color: var(--accent); }

/* === Base ================================================================= */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }

/* === Seamless navigation ==================================================
   This is a multi-page site: every nav click is a full document load, and the
   white flash between documents is what made moving around feel like leaving
   one place and arriving at another. Cross-document view transitions make the
   browser hold the outgoing frame and animate into the incoming one instead.

   Naming the chrome is the important half. A named element is matched across
   the two documents and treated as the SAME object, so the header does not
   cross-fade with the page — it stays put while the content beneath it
   changes, which is what makes navigation read as moving WITHIN one app.

   Progressive enhancement: browsers without @view-transition just navigate. */
@view-transition { navigation: auto; }

.nav           { view-transition-name: rk-nav; }
.portal-subnav { view-transition-name: rk-subnav; }
.admin-edge    { view-transition-name: rk-admin-edge; }

::view-transition-old(root) { animation: rk-vt-out .16s var(--ease-in) both; }
::view-transition-new(root) { animation: rk-vt-in  .26s var(--ease-out) both; }
@keyframes rk-vt-out { to   { opacity: 0; transform: translateY(-4px); } }
@keyframes rk-vt-in  { from { opacity: 0; transform: translateY(6px);  } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
body {
  /* The UA's default 8px was never reset. It put the header 8px down and 8px in
     from both edges, so a sticky bar pinning at top:0 jumped 8px the moment you
     scrolled — and no "full-bleed" surface on the site ever actually reached the
     window edge. */
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow-x: clip;
  transition: background-color var(--t-base) var(--ease),
              color var(--t-base) var(--ease);
}

::selection { background: var(--accent); color: var(--accent-fg); }

a { color: inherit; text-decoration: none; transition: color var(--t-fast) var(--ease); }
a.link {
  color: var(--accent);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  position: relative;
}
a.link::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: -2px;
  height: 1px; background: currentColor;
  transition: right var(--t-base) var(--ease-out);
}
a.link:hover::after { right: 0; }

button { font: inherit; cursor: pointer; }

main {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--pad-x);
  width: 100%;
}

/* === Background — sky, generative + scrim layers ========================== */
/* Bottom-up: #sky plates (-4, homepage only), the radial accent wash
   (body::before, -3), the atmos canvas (#fx-canvas, -2), then the page.
   The grain overlay (body::after) floats above everything at z 60. */
#fx-canvas {
  position: fixed; inset: 0;
  z-index: -2;
  pointer-events: none;
  opacity: .9;
}
body::before {
  /* Radial accent wash — tracks active accent, breathes */
  content: ""; position: fixed; inset: -20%;
  background:
    radial-gradient(60vw 60vw at 80% -10%, var(--accent-glow), transparent 60%),
    radial-gradient(50vw 50vw at -10% 110%, color-mix(in srgb, var(--accent-2) 28%, transparent), transparent 60%);
  filter: blur(40px);
  z-index: -3;
  pointer-events: none;
  transition: background var(--t-slow) var(--ease);
  animation: breath 18s var(--ease) infinite alternate;
}
body::after {
  /* Fine grain — barely there, breaks up flat fills */
  content: ""; position: fixed; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: .035;
  z-index: 60;
  pointer-events: none;
  mix-blend-mode: overlay;
}

@keyframes breath {
  0%   { transform: translate3d(0,0,0) scale(1);   opacity: .8; }
  100% { transform: translate3d(2%, -1%, 0) scale(1.08); opacity: 1; }
}

/* No atmosphere. A page that declares data-fx="none" gets a flat opaque ground:
   no generative canvas, no breathing accent wash, no film grain. This is the
   public-page counterpart of the portal's app-mode rule, and it exists for the
   same reason — some surfaces are instruments, and an instrument is read
   against still paper, not against something drifting behind it. The grain goes
   too: at z-index 60 with mix-blend-mode it sits OVER the canvases, and it was
   dusting every brain and chart on the page. (Neural Anomalies, 2026-08-12.) */
body[data-fx="none"]::before,
body[data-fx="none"]::after { content: none; animation: none; }
body[data-fx="none"] #fx-canvas { display: none; }
body[data-fx="none"] { background: var(--bg); }

/* Day-cycle pages: the wash holds still. blur(40px) over a 140%-viewport
   fixed layer re-rasters it on every accent repaint, and the breath drift is
   the "breathing gradient" ground-motion class ruled out in MM #2033 — the
   sky plates carry the atmosphere here; the wash is just color. The grain
   likewise drops its blend mode: compositing a blend group over the whole
   scrolling document costs scroll-time work its .035 opacity never shows. */
body[data-scene]::before { filter: none; animation: none; }
body[data-scene]::after { mix-blend-mode: normal; }

/* === Scroll progress bar ================================================== */
.scroll-progress {
  position: fixed; top: 0; left: 0; right: 0;
  height: 2px; z-index: 200;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transform-origin: 0 50%;
  transform: scaleX(var(--p, 0));
  transition: transform .1s linear, background var(--t-base) var(--ease);
  box-shadow: 0 0 12px var(--accent-glow);
}

/* === Navigation =========================================================== */
/* The nav is fully transparent at the top of the page so chrome reads as
   floating directly over the canvas — no visible "shelf" clashing with the
   generative pattern, and no edge for iOS overscroll bounce to reveal. Once
   the user scrolls past 8px, a blurred translucent surface fades in to give
   the nav its own ground. */
/* === Admin edge (§141 A2) ================================================= */
/* The demoted admin signal: a thin colored EDGE STRIP keyed to route, with a
   modular footprint (strip ↔ open) the user controls. Shown only when authed
   (layout.js toggles [hidden] + body[data-admin] and persists the mode; the
   strip's real height feeds --admin-banner-h so the nav offset stays exact). */
.admin-edge {
  position: sticky; top: 0; z-index: 120;
  --admin-edge-color: var(--c-violet-1);   /* off in the public site */
  --admin-edge-ink: #fff;
  --admin-strip-h: 16px;                    /* the footprint knob (Ryder's to tune) */
}
body[data-portal] .admin-edge {            /* inside the Portal app → admin green */
  --admin-edge-color: var(--c-admin-2);
  --admin-edge-ink: #04130a;
}
.admin-edge[hidden] { display: none; }

/* STRIP mode — a slim full-bleed colored edge; the handle label rides inside it */
.admin-edge[data-mode="strip"] {
  height: var(--admin-strip-h);
  background: var(--admin-edge-color);
  box-shadow: 0 2px 14px -4px color-mix(in srgb, var(--admin-edge-color) 65%, transparent);
}
.admin-edge[data-mode="strip"] .admin-edge-panel { display: none; }
.admin-edge-handle {
  display: flex; align-items: center; gap: .45rem; width: 100%; height: 100%;
  max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad-x);
  border: 0; cursor: pointer; background: transparent; color: var(--admin-edge-ink);
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em; line-height: 1;
}
.admin-edge-handle:hover { filter: brightness(1.06); }
.admin-edge-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--admin-edge-ink); flex: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--admin-edge-ink) 22%, transparent);
  animation: admin-pulse 2.4s var(--ease) infinite;
}
.admin-edge-chev { opacity: .85; }

/* OPEN mode — the strip becomes a top accent border above the full control bar */
.admin-edge[data-mode="open"] {
  border-top: 3px solid color-mix(in srgb, var(--admin-edge-color) 62%, #000);
  background: var(--admin-edge-color);   /* solid fill so the ink stays legible in dark theme */
  box-shadow: 0 8px 24px -14px color-mix(in srgb, var(--admin-edge-color) 60%, transparent);
}
.admin-edge[data-mode="open"] .admin-edge-handle { display: none; }
.admin-edge-panel {
  max-width: var(--maxw); margin: 0 auto; padding: .45rem var(--pad-x);
  display: flex; align-items: center; gap: 1rem;
  font-size: 13.5px; font-weight: 600; color: var(--admin-edge-ink);
}
.admin-banner-tag {
  display: inline-flex; align-items: center; gap: .5rem; color: var(--admin-edge-ink);
  font-family: var(--mono); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; font-size: 11.5px;
}
.admin-banner-tag .dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--admin-edge-ink);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--admin-edge-ink) 18%, transparent);
  animation: admin-pulse 2.4s var(--ease) infinite;
}
@keyframes admin-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
.admin-banner-msg { color: color-mix(in srgb, var(--admin-edge-ink) 80%, transparent); }
.admin-banner-link {
  font-weight: 700; padding: .25rem .7rem; border-radius: 8px; color: var(--admin-edge-ink);
  background: color-mix(in srgb, var(--admin-edge-ink) 14%, transparent);
  transition: background var(--t-fast) var(--ease);
}
.admin-banner-link:hover { background: color-mix(in srgb, var(--admin-edge-ink) 26%, transparent); }
.admin-banner-out {
  font: inherit; font-weight: 700; cursor: pointer; padding: .25rem .7rem; border-radius: 8px;
  color: var(--admin-edge-ink); background: transparent;
  border: 1px solid color-mix(in srgb, var(--admin-edge-ink) 30%, transparent);
  transition: background var(--t-fast) var(--ease);
}
.admin-banner-out:hover { background: color-mix(in srgb, var(--admin-edge-ink) 14%, transparent); }
.admin-edge-collapse {
  border: 0; background: transparent; cursor: pointer; color: var(--admin-edge-ink);
  font-size: 13px; line-height: 1; padding: .2rem .4rem; border-radius: 6px; opacity: .8;
}
.admin-edge-collapse:hover { opacity: 1; background: color-mix(in srgb, var(--admin-edge-ink) 12%, transparent); }
@media (max-width: 640px) {
  .admin-banner-msg { display: none; }
}

/* Admin-only surfaces are injected for everyone and un-hidden for admins
   (layout.js toggles [hidden]). [hidden] already removes them; the only thing
   that needs saying is that a revealed nav/footer LINK is inline-block.
   SCOPED ON PURPOSE: this was a blanket `.nav-admin-only:not([hidden])` rule,
   which silently overwrote the display of every other admin-gated element that
   carries the class — the Research drawer's LMS row (flex → inline-block, so
   its glyph and text stacked and the row broke), the TLR drawer group, the LMS
   tile on the homepage (.tile is a flex column), and the LMS field block on
   research.html. Reveal must never restyle; each component keeps its own
   display.

   The :not(.nav-private) is the same principle applied again (2026-08-06): the
   private cluster is a flex row, and without the exclusion it would match this
   rule at IDENTICAL specificity and be decided purely by which rule sits lower
   in the file — the exact order-dependence this block exists to prevent. */
.nav-links > .nav-admin-only:not([hidden]):not(.nav-private),
.footer-nav > .nav-admin-only:not([hidden]) { display: inline-block; }

/* layout.js injects THREE sticky bars into #nav-mount — the admin edge, the nav,
   and the section subnav. A sticky element can never travel outside its own
   containing block, and an unstyled #nav-mount is a box exactly as tall as those
   three bars: they came unstuck the instant you scrolled past their own height,
   on every page. `display: contents` removes the mount's box so each bar becomes
   a direct flex child of <body> and can stick against the whole document, which
   is what --admin-banner-h, --psub-top and .nav[data-stuck] have always assumed.
   Keep this rule: without it the header scrolls away and every surface that docks
   its own chrome against those variables reserves space for a bar that isn't
   there. */
#nav-mount { display: contents; }

/* DEFINITION IS ALWAYS ON (2026-08-06). The bar used to be fully transparent
   with a transparent border, growing a surface only once you scrolled past 8px
   (.nav[data-stuck]). At the top of every page — which is where you START — the
   header therefore had no ground of its own: the links floated directly on the
   content. It now carries its material at rest, and scrolling only deepens the
   shadow, so the bar reads as a fixed shelf that the page slides under. */
.nav {
  position: sticky; top: var(--admin-banner-h, 0); z-index: 100;
  background: var(--chrome-bg);
  backdrop-filter: var(--chrome-blur);
  -webkit-backdrop-filter: var(--chrome-blur);
  border-bottom: 1px solid var(--chrome-line);
  transition: box-shadow var(--t-base) var(--ease);
}
.nav[data-stuck] { box-shadow: 0 8px 24px -18px color-mix(in srgb, var(--fg) 55%, transparent); }
.nav-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  /* A FIXED height rather than padding around variable content: the bar's height
     used to be whatever the tallest child computed to (13px text × 1.6 line
     height + two paddings ≈ 44px), so it drifted whenever a control changed.
     --nav-h makes it exact and shorter, and gives the sticky offsets below
     something stable to measure. */
  height: var(--nav-h);
  padding: 0 var(--pad-x);
  display: flex; align-items: center; gap: .85rem;
  /* Lift the bar above the drawer backdrop (z-index 80, a child of .nav). Without
     this the fixed backdrop paints over the nav links (which are position:relative,
     z-auto) and swallows clicks on the Apps tab — so an open drawer would close
     on the click instead of navigating to apps.html. */
  position: relative; z-index: 90;
}
.brand {
  font-weight: 800; font-size: 15px; letter-spacing: -0.02em;
  display: flex; align-items: center; gap: .55rem;
}
.brand .mark {
  width: 20px; height: 20px; border-radius: 6px;
  object-fit: cover;
  display: block;
  box-shadow: 0 4px 12px -5px color-mix(in srgb, var(--fg) 40%, transparent);
  transition: transform var(--t-base) var(--ease-spring);
}
.brand:hover .mark { transform: rotate(-8deg) scale(1.08); }

.nav-spacer { flex: 1; }
.nav-links { display: flex; align-items: center; gap: .12rem; }
.nav-links a {
  padding: .3rem .6rem;
  border-radius: 8px;
  font-weight: 600; font-size: 13px; line-height: 1;
  color: var(--fg-dim);
  position: relative;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.nav-links a:hover { color: var(--fg); background: var(--bg-soft); }

/* ACTIVE STATE — a filled pill, not a floating dot. The dot hung 4px BELOW the
   link, outside the bar's shorter body, so at --nav-h it collided with the
   border. A pill also survives the shared-element transition below: it is the
   element that slides between tabs instead of blinking off and on. */
.nav-links a.active {
  color: var(--accent-2);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.nav-tools { display: flex; gap: .5rem; align-items: center; }

/* === The public / private split ===========================================
   This was a 1px hairline (.nav-div) between Music and Portal — a separator so
   quiet that the admin-only half read as five more public tabs. The private
   surfaces now sit in their own bounded zone: an inset well with the admin
   green as its edge, opened by the same green dot the admin edge strip uses.
   You can see where the site stops being public without reading a label.
   Green is the SIGNED-IN SIGNAL here (--c-admin direct), never a section accent
   and never a surface wash — portal visual law, KB #274. */
.nav-private {
  display: none;                /* revealed as flex by the rule below */
  align-items: center; gap: .12rem;
  margin-left: .5rem; padding: 2px 4px 2px 2px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--c-admin) 7%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c-admin) 22%, transparent);
}
.nav-links > .nav-private:not([hidden]) { display: flex; }
.nav-private-tag {
  width: 5px; height: 5px; border-radius: 50%; flex: none;
  margin: 0 .3rem 0 .35rem;
  background: var(--c-admin);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-admin) 18%, transparent);
}
.nav-private a.active {
  color: var(--c-admin-2);
  background: color-mix(in srgb, var(--c-admin) 16%, transparent);
}

/* App drawer --------------------------------------------------------------- */
/* Slides down anchored under the "Apps" tab (its left offset is set in JS), so
   the next actions appear right where the click was. Lives inside .nav so it
   tracks the sticky bar; a full-screen backdrop dismisses it. */
.nav-drawer {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 90;
  display: block; width: max-content; max-width: calc(100vw - 2 * var(--pad-x));
  opacity: 0; transform: translateY(-6px) scale(.985); transform-origin: top left; pointer-events: none;
  /* A menu is a response, not a page transition. --t-base (.36s) is the site's
     scene-change duration and read as lag on something that opens under the
     cursor — the drawer was still arriving after you had chosen. Opacity leads
     short so the content is readable early; the spring finishes underneath it. */
  transition: opacity .13s var(--ease-out), transform .22s var(--ease-spring);
}
.nav-drawer.open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
/* Transparent bridge across the 6px gap so hover survives the tab→panel move. */
#nav-drawer::before {
  content: ""; position: absolute; top: -10px; left: 0; right: 0; height: 10px;
}
.nav-drawer-inner { display: block; }
.nav-drawer-grid {
  width: 380px; max-width: 100%;
  display: grid; grid-template-columns: repeat(2, minmax(170px, 1fr)); gap: .4rem;
  padding: .6rem; border-radius: 18px;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg, 0 30px 80px -30px rgba(0,0,0,.6));
  backdrop-filter: saturate(160%) blur(20px);
  -webkit-backdrop-filter: saturate(160%) blur(20px);
}
.nav-app {
  display: flex; align-items: center; gap: .7rem;
  padding: .65rem .75rem; border-radius: 13px; color: inherit;
  transition: background var(--t-fast) var(--ease);
}
.nav-app:hover { background: var(--bg-soft); }
.nav-app-glyph {
  width: 38px; height: 38px; flex-shrink: 0; display: grid; place-items: center; font-size: 18px;
  border-radius: 11px; color: var(--tint, var(--accent));
  background: color-mix(in srgb, var(--tint, var(--accent)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--tint, var(--accent)) 26%, transparent);
}
.nav-app-txt { display: flex; flex-direction: column; min-width: 0; }
.nav-app-name { font-weight: 700; font-size: 14px; }
.nav-app-desc { font-size: 12px; color: var(--fg-mute); }
.nav-app-explore .nav-app-glyph {
  color: var(--fg-dim); background: var(--bg-soft);
  border-color: var(--hairline);
}
.nav-app-explore { border-top: 1px dashed var(--hairline); border-radius: 13px; }
.nav-drawer-backdrop { position: fixed; inset: 0; z-index: 80; background: transparent; }
.nav[data-drawer] .nav-links a[data-nav="apps"] { color: var(--fg); background: var(--bg-soft); }
@media (max-width: 560px) {
  .nav-drawer { width: calc(100vw - 2 * var(--pad-x)); }
  .nav-drawer-grid { grid-template-columns: 1fr; width: 100%; }
}

/* Hover menu drawers (Research, Music) ------------------------------------- */
/* Same drawer chrome as Apps, but grouped in a single column with per-group
   labels. A transparent bridge over the 6px gap keeps hover alive as the pointer
   crosses from the tab to the panel. */
.nav-drawer-menu::before {
  content: ""; position: absolute; top: -10px; left: 0; right: 0; height: 10px;
}
.nav-menu-inner {
  width: 320px; max-width: calc(100vw - 2 * var(--pad-x));
  display: flex; flex-direction: column; gap: .25rem;
  padding: .6rem; border-radius: 18px;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg, 0 30px 80px -30px rgba(0,0,0,.6));
  backdrop-filter: saturate(160%) blur(20px);
  -webkit-backdrop-filter: saturate(160%) blur(20px);
}
.nav-menu-group { display: flex; flex-direction: column; gap: .15rem; }
.nav-menu-group + .nav-menu-group { margin-top: .5rem; }
.nav-menu-label {
  display: flex; align-items: center; gap: .5rem;
  padding: .35rem .75rem .3rem;
  font-family: var(--mono);
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em;
  color: var(--tint, var(--accent));
}
.nav-menu-dot {
  width: 6px; height: 6px; border-radius: 99px;
  background: var(--tint, var(--accent));
  box-shadow: 0 0 8px var(--tint, var(--accent));
}
.nav-drawer-menu .nav-app-explore { margin-top: .5rem; }
@media (max-width: 560px) {
  .nav-drawer-menu { width: calc(100vw - 2 * var(--pad-x)); }
  .nav-menu-inner { width: 100%; }
}

/* Development directory (main header) -------------------------------------- */
/* The section's own drawer: the same two-column grid the Apps drawer uses (ten
   surfaces in a single column is a scroll, not a menu), with its hub leading as
   a full-width row so "the whole section" is the first thing you can click. */
.nav-dev-drawer .nav-drawer-grid { width: 480px; }
.nav-dev-drawer .nav-app-hub {
  grid-column: 1 / -1;
  margin-bottom: .1rem; padding-bottom: .7rem;
  border-bottom: 1px dashed var(--hairline); border-radius: 13px;
}
.nav-dev-drawer .nav-app.active { background: var(--bg-soft); }
.nav-dev-drawer .nav-app.active .nav-app-name { color: var(--tint, var(--accent)); }

/* Portal directory drawer (main header) ------------------------------------ */
/* Hovering the Portal tab opens a vertical list of sections; a grouped section
   expands its pages in a horizontal flyout to the side. Same drawer chrome +
   hover-bridge as the other menus. */
.nav-portal-drawer::before {
  content: ""; position: absolute; top: -10px; left: 0; right: 0; height: 10px;
}
.pnav-inner {
  width: 224px; max-width: calc(100vw - 2 * var(--pad-x));
  display: flex; flex-direction: column; gap: .1rem;
  padding: .5rem; border-radius: 16px;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg, 0 30px 80px -30px rgba(0,0,0,.6));
  backdrop-filter: saturate(160%) blur(20px);
  -webkit-backdrop-filter: saturate(160%) blur(20px);
}
.pnav-row {
  display: flex; align-items: center; gap: .5rem;
  padding: .5rem .65rem; border-radius: 10px; color: inherit;
  font-size: 14px; font-weight: 600;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.pnav-row:hover, .pnav-branch:hover > .pnav-row { background: var(--bg-soft); }
.pnav-row.active { color: var(--accent); }
.pnav-row-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pnav-branch { position: relative; }
.pnav-row-parent { cursor: default; }
.pnav-arrow { margin-left: auto; color: var(--fg-mute); font-size: 16px; line-height: 1; }
.pnav-branch:hover > .pnav-row-parent .pnav-arrow,
.pnav-branch:focus-within > .pnav-row-parent .pnav-arrow { color: var(--accent); }
.pnav-flyout {
  position: absolute; top: -.5rem; left: calc(100% + .35rem); z-index: 2;
  min-width: 184px;
  display: flex; flex-direction: column; gap: .1rem;
  padding: .5rem; border-radius: 16px;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg, 0 30px 80px -30px rgba(0,0,0,.6));
  backdrop-filter: saturate(160%) blur(20px);
  -webkit-backdrop-filter: saturate(160%) blur(20px);
  opacity: 0; transform: translateX(-6px); pointer-events: none;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease-spring);
}
/* Transparent bridge across the horizontal gap so hover survives row→flyout. */
.pnav-flyout::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: -.5rem; width: .5rem;
}
.pnav-branch:hover > .pnav-flyout,
.pnav-branch:focus-within > .pnav-flyout {
  opacity: 1; transform: translateX(0); pointer-events: auto;
}
/* Flip to the left edge when a right-side flyout would clip the viewport. */
.pnav-branch.flip-left > .pnav-flyout {
  left: auto; right: calc(100% + .35rem); transform: translateX(6px);
}
.pnav-branch.flip-left:hover > .pnav-flyout,
.pnav-branch.flip-left:focus-within > .pnav-flyout { transform: translateX(0); }
.pnav-branch.flip-left > .pnav-flyout::before { left: auto; right: -.5rem; }
.pnav-sub-item {
  display: block; padding: .45rem .6rem; border-radius: 9px; color: inherit;
  font-size: 13.5px; font-weight: 500; white-space: nowrap;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.pnav-sub-item:hover { background: var(--bg-soft); }
.pnav-sub-item.active { color: var(--accent); }
@media (max-width: 560px) {
  .nav-portal-drawer { width: calc(100vw - 2 * var(--pad-x)); }
  .pnav-inner { width: 100%; }
  /* No room to fly out sideways on a phone — stack the pages inline instead. */
  .pnav-flyout {
    position: static; opacity: 1; transform: none; pointer-events: auto;
    min-width: 0; margin: .1rem 0 .2rem .9rem; padding: .2rem .4rem;
    border: 0; box-shadow: none; background: transparent; backdrop-filter: none;
    border-left: 1px solid var(--hairline); border-radius: 0;
  }
  .pnav-flyout::before { display: none; }
  .pnav-arrow { transform: rotate(90deg); }
}

/* Buttons ------------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .55rem .9rem;
  border-radius: 12px;
  font-weight: 600; font-size: 14px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--fg);
  transition: all var(--t-base) var(--ease);
  white-space: nowrap;
}
.btn-bordered {
  background: var(--mat-1);
  border-color: var(--border);
  color: var(--fg-dim);
}
.btn-bordered:hover {
  border-color: var(--accent);
  color: var(--fg);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}
.btn-square {
  width: 36px; height: 36px; padding: 0;
  justify-content: center;
  border: 1px solid var(--border);
  background: var(--mat-1);
  color: var(--fg-dim);
}
.btn-square:hover { color: var(--accent); border-color: var(--accent); }
.btn-primary {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--accent-fg);
  border: none;
  box-shadow: 0 12px 28px -10px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,.3);
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 20px 40px -12px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,.4); }

.cmdk-btn {
  padding-right: .5rem;
}
/* Header tools follow the half-height band. */
.nav-tools .btn { padding: .3rem .6rem; font-size: 13px; border-radius: 9px; }
.nav-tools .btn-square { width: 27px; height: 27px; padding: 0; }
.nav-tools .cmdk-btn kbd { font-size: 10px; padding: 1px 5px; }
.cmdk-btn .label { color: var(--fg-mute); font-weight: 500; }
.cmdk-btn kbd {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  padding: 2px 6px; border-radius: 5px;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  color: var(--fg-dim);
}

/* === Hero ================================================================= */
.hero {
  min-height: clamp(520px, 78vh, 820px);
  padding: clamp(2rem, 6vh, 5rem) 0 clamp(2rem, 4vh, 4rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
}
/* Hero scrim — softens the canvas behind hero text so the dashboard reads
   distinctly from the rest of the page. Sits above the canvas, below content.
   Tuned to keep some pattern visible at the edges. */
.hero::after {
  content: "";
  position: absolute; inset: 0 -10%;
  background: radial-gradient(ellipse 55% 50% at 30% 55%,
              color-mix(in srgb, var(--bg) 88%, transparent) 0%,
              color-mix(in srgb, var(--bg) 55%, transparent) 35%,
              transparent 70%);
  z-index: 0;
  pointer-events: none;
}
/* Cursor-follow glow. Fixed to the viewport (not bound to the hero box) so
   the gradient runs cleanly to the screen edges and continues to track when
   the cursor moves over the nav. Sits at z-index 1 — below the nav (100) and
   nav controls (which have their own translucent backgrounds), so the nav
   chrome reads as floating on top of the glow rather than blocking it. */
.hero::before {
  content: "";
  position: fixed; inset: 0;
  background: radial-gradient(380px circle at var(--mx, -200px) var(--my, -200px),
              var(--accent-soft), transparent 60%);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--t-base) var(--ease);
  z-index: 1;
}
.hero[data-cursor]::before { opacity: 1; }
.hero > * { position: relative; z-index: 2; }

/* Scroll parallax — the copy lags the page at three rates, so the hero has
   layers of depth before the canvas even matters. --hp is scrollY in px (set
   in initInteractions, skipped under prefers-reduced-motion). `translate`,
   not transform: the entrance animations own transform. */
.hero-topline    { translate: 0 calc(var(--hp, 0) * .16px); }
.hero h1         { translate: 0 calc(var(--hp, 0) * .09px); }
.hero .currently { translate: 0 calc(var(--hp, 0) * .045px); }

.hero .eyebrow {
  display: inline-flex; align-items: center; gap: .6rem;
  font-family: var(--mono);
  font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .18em;
  color: var(--fg-dim);
  margin-bottom: 1.5rem;
  opacity: 0; transform: translateY(8px);
  animation: rise var(--t-slow) var(--ease-out) .1s forwards;
}
/* Hero top line — the status eyebrow plus the live date/weather readout
   (initHeroMeta). One flex row so the chips wrap cleanly on small screens. */
.hero-topline {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .5rem 1.75rem;
  margin-bottom: 1.5rem;
}
.hero-topline .eyebrow { margin-bottom: 0; }
.hero-meta { animation-delay: .25s; }
.hero-meta .hero-meta-sep { color: var(--fg-mute); }
#hero-wx:empty::after { content: "…"; }
.hero .eyebrow .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 12px var(--accent);
  animation: pulse 2.4s var(--ease) infinite;
}
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: .6; }
  50%      { transform: scale(1.4); opacity: 1; }
}

.hero h1 {
  font-size: clamp(3rem, 9.5vw, 7.5rem);
  font-weight: 900;
  letter-spacing: -0.055em;
  line-height: .92;
  margin: 0 0 2rem;
}
.hero h1 .line {
  display: block;
  opacity: 0; transform: translateY(20px);
  animation: rise 1s var(--ease-out) forwards;
}
.hero h1 .line:nth-child(2) { animation-delay: .15s; }
.hero h1 .accent {
  background: linear-gradient(120deg, var(--accent) 0%, var(--accent-2) 50%, var(--accent) 100%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: shimmer 6s linear infinite;
}
@keyframes shimmer { to { background-position: 200% 0; } }
@keyframes rise { to { opacity: 1; transform: translateY(0); } }

.hero .currently {
  display: inline-flex; align-items: center; gap: .9rem;
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--fg-dim);
  opacity: 0;
  animation: rise var(--t-slow) var(--ease-out) .4s forwards;
}
.hero .currently .label {
  background: var(--accent-soft);
  color: var(--accent);
  padding: 6px 12px;
  border-radius: 8px;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: .14em;
  font-weight: 700;
}
.hero .currently .word::after {
  content: "▍";
  margin-left: 2px;
  color: var(--accent);
  animation: blink 1.1s steps(2) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* Hero stat strip — four equal cards, each carrying its own color identity.
   Music remains the marquee with a stronger tint and a larger value, but
   every card now reads as part of the same dashboard rather than one bright
   slab + neutral fillers. */
.hero-strip {
  margin-top: 4rem;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  padding-top: 2rem;
  border-top: 1px solid var(--hairline);
  opacity: 0;
  animation: rise var(--t-slow) var(--ease-out) .6s forwards;
}
@media (max-width: 980px) { .hero-strip { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .hero-strip { grid-template-columns: 1fr; } }

.hero-stat {
  --tile-c:  var(--fg-mute);
  --tile-c2: var(--fg-dim);
  display: flex; flex-direction: column;
  padding: 1.25rem 1.4rem;
  border-radius: 18px;
  border: 1px solid color-mix(in srgb, var(--tile-c) 22%, var(--border-hi));
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--tile-c) 7%, transparent), transparent 65%),
    var(--bg-elev);
  box-shadow: var(--shadow-sm);
  transition: all var(--t-base) var(--ease);
  position: relative; overflow: hidden; isolation: isolate;
  color: inherit;
}
.hero-stat:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--tile-c) 50%, var(--border-hi));
  box-shadow: 0 22px 48px -20px color-mix(in srgb, var(--tile-c) 32%, transparent);
}
.hero-stat .k {
  display: inline-flex; align-items: center; gap: .5rem;
  font-family: var(--mono);
  font-size: 11px; font-weight: 700;
  line-height: 14px;
  text-transform: uppercase; letter-spacing: .14em;
  color: color-mix(in srgb, var(--tile-c) 55%, var(--fg-mute));
  margin-bottom: .6rem;
}
/* Constrain the equalizer to match the .k text height so the music card's
   label line is the same height as the other cards' — keeps "TLR" and the
   hint beneath aligned with the other cards' baselines. */
.hero-stat .k .eq { height: 12px; gap: 2px; }
.hero-stat .k .eq span { width: 2.5px; }
.hero-stat .v {
  font-size: 22px; font-weight: 800; letter-spacing: -.02em;
  line-height: 1.15;
}
.hero-stat .v .accent { color: var(--tile-c); }
.hero-stat .hint {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg-mute);
  margin-top: .5rem;
  text-transform: lowercase;
  letter-spacing: .04em;
}

/* Per-card color identities (TLR / education / software / research) */
.hero-stat-music { --tile-c: var(--c-coral-1);  --tile-c2: var(--c-coral-2); }
.hero-stat-edu   { --tile-c: var(--c-mint-1);   --tile-c2: var(--c-mint-2); }
.hero-stat-sw    { --tile-c: var(--c-cyan-1);   --tile-c2: var(--c-cyan-2); }
.hero-stat-res   { --tile-c: var(--c-violet-1); --tile-c2: var(--c-violet-2); }
.hero-stat-portal { --tile-c: var(--c-admin);   --tile-c2: var(--c-admin-2); }

/* Music — stronger tint, gradient text, larger value */
.hero-stat-music {
  border-color: color-mix(in srgb, var(--tile-c) 40%, var(--border));
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--tile-c) 18%, transparent), transparent 70%),
    var(--mat-1);
  box-shadow: 0 18px 40px -20px color-mix(in srgb, var(--tile-c) 36%, transparent);
}
.hero-stat-music:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--tile-c) 65%, var(--border));
  box-shadow: 0 28px 60px -20px color-mix(in srgb, var(--tile-c) 50%, transparent);
}
.hero-stat-music .v {
  font-size: 34px; font-weight: 900; letter-spacing: -.04em;
}
.hero-stat-music .v .accent {
  background: linear-gradient(120deg, var(--tile-c), var(--tile-c2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero-stat-music .eq span { background: var(--tile-c); }
/* === Sections ============================================================= */
section { padding: 8rem 0; position: relative; }
section:first-of-type { padding-top: 0; }

/* Ghost numerals — the chapter number at architectural scale, stroked rather
   than filled so it stays ground. z-index -1 paints above the fixed
   background layers (sky -4 / wash -3 / canvas -2) but beneath all in-flow
   content. Stroke reads --ghost-ink — a per-scene LITERAL that snaps at the
   flip — never a mix of the tweened --fg: these glyphs are the largest
   painted surface on the page, and re-rastering them on every animation
   frame while scrolling is visible jank. */
[data-ghost]::before {
  content: attr(data-ghost);
  position: absolute; top: 2.5rem; right: -1rem; z-index: -1;
  font-size: clamp(9rem, 23vw, 19rem);
  font-weight: 900; line-height: .78; letter-spacing: -.05em;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--ghost-ink);
  pointer-events: none; user-select: none;
}

.section-head { margin-bottom: 4rem; max-width: 800px; }
.section-num {
  font-family: var(--mono);
  font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .2em;
  color: var(--accent);
  margin-bottom: 1rem;
  display: inline-flex; align-items: center; gap: .6rem;
}
.section-num::before {
  content: ""; width: 24px; height: 1px; background: var(--accent);
}
.section-head h2 {
  font-size: clamp(2.25rem, 5vw, 4rem);
  font-weight: 900;
  letter-spacing: -0.04em;
  line-height: 1.02;
  margin: 0 0 1rem;
}
.section-head .sub {
  font-size: 18px;
  color: var(--fg-dim);
  max-width: 56ch;
}

/* Prose --------------------------------------------------------------------- */
.prose {
  max-width: 64ch;
  font-size: 19px; line-height: 1.65;
  color: var(--fg-dim);
}
.prose strong { color: var(--fg); font-weight: 700; }
.prose p { margin: 0 0 1.5rem; }
.prose p:last-child { margin-bottom: 0; }

/* === Tiles — multi-layer cards with edge-light ============================ */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 1.5rem;
}

.tile {
  --tint: var(--accent);
  position: relative;
  display: flex; flex-direction: column;
  padding: 2rem;
  background: var(--mat-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease),
              box-shadow var(--t-base) var(--ease);
  --gx: 50%; --gy: 50%;
}

/* gradient overlay that tracks cursor */
.tile::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: radial-gradient(420px circle at var(--gx) var(--gy),
              color-mix(in srgb, var(--tint) 18%, transparent), transparent 60%);
  opacity: 0; transition: opacity var(--t-base) var(--ease);
  pointer-events: none;
}
/* top edge-light */
.tile::after {
  content: ""; position: absolute; top: 0; left: 12%; right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent,
              color-mix(in srgb, var(--tint) 50%, transparent), transparent);
  opacity: .5;
  transition: opacity var(--t-base) var(--ease);
  z-index: 1;
}
.tile > * { position: relative; z-index: 2; }

.tile:hover {
  transform: translateY(-6px);
  border-color: color-mix(in srgb, var(--tint) 50%, var(--border));
  box-shadow: 0 28px 60px -20px color-mix(in srgb, var(--tint) 30%, transparent),
              0 1px 0 0 color-mix(in srgb, var(--tint) 20%, transparent) inset;
}
.tile:hover::before { opacity: 1; }
.tile:hover::after { opacity: 1; }

.tile-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 1.25rem;
}
.tile-glyph {
  font-size: 28px;
  width: 52px; height: 52px;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--tint) 12%, var(--bg-soft));
  color: var(--tint);
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--tint) 24%, var(--border));
  transition: transform var(--t-base) var(--ease-spring);
}
.tile-glyph img {
  width: 28px;
  height: 28px;
  object-fit: contain;
}.tile:hover .tile-glyph {
  transform: scale(1.08) rotate(-6deg);
}

.tile-title {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
  margin-bottom: .5rem;
  line-height: 1.2;
}
.tile-desc {
  color: var(--fg-dim);
  font-size: 15px;
  line-height: 1.55;
}
.tile-footer {
  margin-top: auto;
  padding-top: 1.5rem;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--fg-mute);
}
.tile-footer .arrow {
  transition: transform var(--t-base) var(--ease-out);
}
.tile:hover .tile-footer .arrow { transform: translateX(4px); color: var(--tint); }

/* Status pills */
.status {
  font-family: var(--mono);
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em;
  padding: 4px 9px;
  border-radius: 99px;
  border: 1px solid var(--border);
  background: var(--bg-soft);
  color: var(--fg-dim);
}
.status.live  { background: color-mix(in srgb, var(--c-mint-1) 15%, var(--bg-soft));  color: var(--c-mint-2); border-color: color-mix(in srgb, var(--c-mint-1) 30%, transparent); }
.status.wip   { background: color-mix(in srgb, var(--c-amber-1) 18%, var(--bg-soft)); color: var(--c-amber-2); border-color: color-mix(in srgb, var(--c-amber-1) 36%, transparent); }
.status.idea  { background: color-mix(in srgb, var(--c-violet-1) 14%, var(--bg-soft)); color: var(--c-violet-2); border-color: color-mix(in srgb, var(--c-violet-1) 30%, transparent); }
/* Featured tile (asymmetric) — used for "spotlight" entries */
.tile-feature {
  grid-column: span 2;
  min-height: 280px;
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--tint) 10%, transparent), transparent 60%),
    var(--mat-1);
}
.tile-feature .tile-title { font-size: 30px; }
.tile-feature .tile-desc  { font-size: 17px; max-width: 50ch; }
@media (max-width: 720px) {
  .tile-feature { grid-column: span 1; }
}

/* === Research fields — diverging branches on the Research hub ============= */
/* Each field is a self-contained branch (Psychology, Language Model Science)
   with its own tint (--field) and a single flagship instrument tile. The left
   rule + tag reads as a labeled section of an atlas. */
.research-field {
  position: relative;
  padding-left: 1.75rem;
  border-left: 2px solid color-mix(in srgb, var(--field, var(--accent)) 32%, var(--border));
}
.field-head { max-width: 62ch; margin-bottom: 1.75rem; }
/* A field holding more than one instrument: the tiles sit as a pair rather
   than stacking, so neither reads as the other's footnote. */
.field-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
  gap: 1rem;
}
.field-pair .tile-feature { grid-column: auto; min-height: 300px; }
.field-pair .tile-feature .tile-title { font-size: 25px; }
.field-pair .tile-feature .tile-desc  { font-size: 15px; max-width: 46ch; }
.field-tag {
  font-family: var(--mono);
  font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .18em;
  color: var(--field, var(--accent));
  display: inline-flex; align-items: center; gap: .55rem;
  margin-bottom: 1rem;
}
.field-dot {
  width: 8px; height: 8px; border-radius: 99px;
  background: var(--field, var(--accent));
  box-shadow: 0 0 10px var(--field, var(--accent));
}
.field-head h3 {
  font-size: clamp(1.6rem, 3.2vw, 2.4rem);
  font-weight: 850;
  letter-spacing: -0.03em;
  line-height: 1.08;
  margin: 0 0 .9rem;
}
.field-head p {
  color: var(--fg-dim);
  font-size: 17px; line-height: 1.6;
  margin: 0;
}
@media (max-width: 640px) {
  .research-field { padding-left: 1.1rem; }
}

/* === Bento — asymmetric layout ============================================ */
.bento {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 1.25rem;
}
.bento .tile { grid-column: span 2; }
.bento .tile.lg { grid-column: span 3; }
.bento .tile.xl { grid-column: span 4; }
.bento .tile.tall { grid-row: span 2; }
@media (max-width: 900px) {
  .bento { grid-template-columns: repeat(2, 1fr); }
  .bento .tile, .bento .tile.lg, .bento .tile.xl { grid-column: span 2; }
  .bento .tile.tall { grid-row: span 1; }
}

/* === Music hero =========================================================== */
.music-hero {
  position: relative;
  padding: 3.5rem;
  background: var(--mat-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 3rem;
  align-items: center;
  overflow: hidden;
  isolation: isolate;
}
.music-hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(60% 80% at 90% 10%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
    radial-gradient(50% 70% at 0% 100%, color-mix(in srgb, var(--accent-2) 22%, transparent), transparent 70%);
}
/* Night (homepage day cycle): the finale card burns brighter against the dark. */
body[data-scene="night"] .music-hero::before {
  background:
    radial-gradient(60% 80% at 90% 10%, color-mix(in srgb, var(--accent) 34%, transparent), transparent 70%),
    radial-gradient(50% 70% at 0% 100%, color-mix(in srgb, var(--accent-2) 30%, transparent), transparent 70%);
}
@media (max-width: 880px) {
  .music-hero { grid-template-columns: 1fr; gap: 2rem; padding: 2.5rem; }
}

/* Animated equalizer */
.eq {
  display: inline-flex; align-items: flex-end; gap: 3px;
  height: 18px; vertical-align: middle;
}
.eq span {
  width: 3px; background: var(--accent); border-radius: 2px;
  animation: eq 1s var(--ease) infinite;
}
.eq span:nth-child(2) { animation-delay: .15s; }
.eq span:nth-child(3) { animation-delay: .3s;  }
.eq span:nth-child(4) { animation-delay: .45s; }
.eq span:nth-child(5) { animation-delay: .6s;  }
@keyframes eq {
  0%, 100% { height: 30%; }
  50%      { height: 100%; }
}

/* Album cover — either real img (.album-cover-img) or procedural fallback */
.album-cover {
  position: relative;
  width: 100%; max-width: 420px;
  margin: 0 auto;
  aspect-ratio: 1;
  border-radius: 22px;
  overflow: hidden;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: var(--shadow-lg), 0 0 100px -20px var(--accent-glow);
  display: flex; align-items: center; justify-content: center;
  isolation: isolate;
  transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease);
}
.album-cover:hover {
  transform: translateY(-4px) rotate(-1deg);
  box-shadow: var(--shadow-lg), 0 0 140px -16px var(--accent-glow);
}
.album-cover .mark {
  position: relative; z-index: 1;
  font-size: 80px; font-weight: 900;
  letter-spacing: -.06em;
  color: rgba(255,255,255,.96);
  text-shadow: 0 4px 24px rgba(0,0,0,.2);
}

/* When wrapping an <img>, the image is the cover */
.album-cover-img { background: var(--bg-soft); }
.album-cover-img img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--t-slow) var(--ease-out);
}
.album-cover-img:hover img { transform: scale(1.04); }

/* === Command Palette ====================================================== */
.palette-backdrop {
  position: fixed; inset: 0;
  background: color-mix(in srgb, var(--bg) 60%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  z-index: 200;
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-base) var(--ease);
}
.palette-backdrop[data-open] { opacity: 1; pointer-events: auto; }

.palette {
  position: fixed;
  top: 14vh; left: 50%;
  transform: translate(-50%, -20px) scale(.96);
  width: min(640px, calc(100% - 2rem));
  background: var(--bg-elev);
  border: 1px solid var(--border-hi);
  border-radius: 22px;
  box-shadow: var(--shadow-lg);
  z-index: 210;
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-base) var(--ease-out),
              transform var(--t-base) var(--ease-spring);
  overflow: hidden;
}
.palette[data-open] {
  opacity: 1; pointer-events: auto;
  transform: translate(-50%, 0) scale(1);
}
.palette-input-row {
  display: flex; align-items: center; gap: .9rem;
  padding: 1.1rem 1.25rem;
  border-bottom: 1px solid var(--border);
  color: var(--fg-dim);
}
.palette-input-row input {
  flex: 1; background: none; border: none; outline: none;
  color: var(--fg); font-size: 16px; font-weight: 500;
}
.palette-output { padding: .5rem; max-height: 52vh; overflow-y: auto; }
.palette-item {
  padding: .75rem 1rem;
  border-radius: 12px;
  display: flex; align-items: center; gap: .9rem;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
  font-size: 14px;
}
.palette-item .kind {
  font-family: var(--mono); font-size: 10px;
  color: var(--fg-mute);
  width: 36px;
  text-transform: uppercase; letter-spacing: .1em;
}
.palette-item .palette-name { font-weight: 600; color: var(--fg); }
.palette-item .palette-desc { color: var(--fg-dim); font-size: 13px; margin-left: auto; }
.palette-item:hover,
.palette-item[aria-selected="true"] {
  background: var(--bg-soft);
}
.palette-item[aria-selected="true"] {
  outline: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}

/* === Reveal animations ==================================================== */
.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition: opacity var(--t-slow) var(--ease-out),
              transform var(--t-slow) var(--ease-out);
}
.reveal.in { opacity: 1; transform: translateY(0); }

/* Stagger children when parent has .stagger */
.stagger > * { transition-delay: 0s; }
.stagger.in > *:nth-child(1) { transition-delay: .04s; }
.stagger.in > *:nth-child(2) { transition-delay: .12s; }
.stagger.in > *:nth-child(3) { transition-delay: .2s;  }
.stagger.in > *:nth-child(4) { transition-delay: .28s; }
.stagger.in > *:nth-child(5) { transition-delay: .36s; }
.stagger.in > *:nth-child(6) { transition-delay: .44s; }

/* === Portal app chrome — the "second row" ================================= */
/* A tab strip that exists only inside the Portal app, pinned directly beneath
   the main header. --psub-top (set by layout.js) parks it just under the
   banner+nav stack; --portal-chrome-h drives the .portal-fit surfaces below. */
.portal-subnav {
  position: sticky; top: var(--psub-top, var(--admin-banner-h, 0));
  z-index: 99;
  /* SAME material as .nav, from the same tokens. These two bands are one
     header; they used to mix their own glass separately (bg 60% + blur(18px)
     here, --mat-1 + blur(20px) there) and the seam showed. */
  background: var(--chrome-bg);
  backdrop-filter: var(--chrome-blur);
  -webkit-backdrop-filter: var(--chrome-blur);
  border-bottom: 1px solid var(--chrome-line);
}
.portal-subnav[hidden] { display: none; }
/* The subnav carries .nav-admin-only, whose reveal rule is inline-block (right
   for nav links) — as a block-level band it must span the full width, or the
   frosted strip dies mid-screen after the last tab. */
.portal-subnav.nav-admin-only:not([hidden]) { display: block; }
.portal-subnav-inner {
  max-width: var(--maxw); margin: 0 auto;
  /* min-height, not height: the tab strip wraps to a second row on narrow
     viewports (.psub-tabs is flex-wrap: wrap) and a hard height would clip it. */
  min-height: var(--subnav-h);
  padding: 0 var(--pad-x);
  display: flex; align-items: center; gap: 1rem;
}
.psub-brand {
  display: inline-flex; align-items: center; gap: .45rem; flex-shrink: 0;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em; color: var(--fg-mute);
  padding-right: 1rem; border-right: 1px solid var(--hairline);
}
.psub-dot { width: 7px; height: 7px; border-radius: 999px; background: var(--accent-2); box-shadow: 0 0 8px var(--accent-2); }
.psub-tabs { display: flex; align-items: center; gap: .15rem; flex-wrap: wrap; }
.psub-tab {
  display: inline-flex; align-items: center; gap: .3rem;
  font: inherit; font-weight: 600; font-size: 12.5px; line-height: 1; white-space: nowrap;
  padding: .32rem .6rem; border-radius: 8px; cursor: pointer;
  color: var(--fg-dim); background: none; border: none;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.psub-tab:hover { color: var(--fg); background: var(--bg-soft); }
.psub-tab.active, .psub-group.active > .psub-tab {
  color: var(--accent-2); background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.psub-caret { font-size: 9px; opacity: .7; }

/* --- Edit mode: drag-reorder the tabs (the sub-nav's ✎, mirror of the
   Dashboard's) ----------------------------------------------------------- */
.psub-edit {
  /* Pinned to the far right of the sub-nav row — the same anatomy as the
     dashboard's ✎ (.dash-edit-btn), rightmost in its strip. When a back-link is
     present it owns the sole margin-left:auto and (ordered before this ✎) pulls
     the pair right, so the ✎ sits just to its right. On the dashboard there is
     no back-link, so `.psub-tabs + .psub-edit` below gives the ✎ its own auto. */
  flex-shrink: 0; margin-left: .35rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 8px;
  font-size: 13px; line-height: 1; cursor: pointer;
  color: var(--fg-mute); background: none; border: 1px solid transparent;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
/* Dashboard (no back-link): the ✎ follows the tabs directly, so it takes the
   auto margin itself and pins to the far right. */
.psub-tabs + .psub-edit { margin-left: auto; }
.psub-edit:hover { color: var(--fg); background: var(--bg-soft); }
.psub-edit[aria-pressed="true"] {
  color: var(--accent-2); background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.psub-edit-hint { display: none; }
.portal-subnav.editing .psub-edit-hint {
  display: block; max-width: var(--maxw); margin: 0 auto;
  padding: .3rem var(--pad-x) .45rem;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .04em; color: var(--accent-2);
}
.portal-subnav.editing { user-select: none; }
.portal-subnav.editing .psub-tab,
.portal-subnav.editing .psub-group,
.portal-subnav.editing .psub-item2 { cursor: grab; touch-action: none; }
.portal-subnav.editing .psub-tabs > .psub-tab,
.portal-subnav.editing .psub-tabs > .psub-group > .psub-trigger,
.portal-subnav.editing .psub-item2 {
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
}
.portal-subnav.editing .psub-caret { display: none; }
.psub-dragging { z-index: 2; opacity: .92; cursor: grabbing; }
.psub-dragging,
.psub-group.psub-dragging > .psub-trigger {
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-soft)) !important;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .28); border-radius: 9px;
}

.psub-group { position: relative; }
.psub-menu {
  /* top:100% (flush, no gap) so the cursor can travel from the tab into the menu
     without crossing dead space and collapsing the hover. Inner padding gives the
     visual separation a gap used to. */
  position: absolute; top: 100%; left: 0; z-index: 2;
  min-width: 12.5rem; display: flex; flex-direction: column; gap: .1rem;
  padding: .5rem .35rem .35rem; border-radius: 13px;
  background: var(--mat-1); border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg, 0 30px 80px -30px rgba(0,0,0,.6));
  backdrop-filter: saturate(160%) blur(20px); -webkit-backdrop-filter: saturate(160%) blur(20px);
  opacity: 0; transform: translateY(-6px) scale(.98); transform-origin: top left;
  pointer-events: none;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-base) var(--ease-spring);
}
.psub-group:hover .psub-menu,
.psub-group:focus-within .psub-menu,
.psub-group.open .psub-menu { opacity: 1; transform: none; pointer-events: auto; }
.psub-item {
  font-weight: 600; font-size: 13px; padding: .5rem .65rem; border-radius: 9px;
  color: var(--fg-dim); white-space: nowrap;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.psub-item:hover { color: var(--fg); background: var(--bg-soft); }
.psub-item.active { color: var(--accent-2); background: color-mix(in srgb, var(--accent) 12%, transparent); }

/* §141 A1 — the relocated back-link, pinned to the right end of the sub-nav row */
.psub-back {
  margin-left: auto; flex-shrink: 0;
  font-family: var(--mono); font-size: 11.5px; font-weight: 600; white-space: nowrap;
  color: var(--fg-mute); padding: .4rem .6rem; border-radius: 9px;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.psub-back:hover { color: var(--fg); background: var(--bg-soft); }

/* Second header level — the expanded pages of a grouped menu (User Data,
   Workspaces) when one of them is open. The active page carries the accent
   highlighted border: the same visual language as the active tab above it. */
.psub-row2 {
  display: flex; align-items: center; gap: .3rem; flex-wrap: wrap;
  max-width: var(--maxw); margin: 0 auto;
  padding: .3rem var(--pad-x) .5rem;
  border-top: 1px solid color-mix(in srgb, var(--hairline) 70%, transparent);
}
.psub-row2-tag {
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em; color: var(--fg-mute);
  padding-right: .8rem; margin-right: .3rem; border-right: 1px solid var(--hairline);
}
.psub-item2 {
  font-weight: 600; font-size: 12.5px; line-height: 1; white-space: nowrap;
  padding: .38rem .7rem; border-radius: 9px;
  color: var(--fg-dim); border: 1px solid transparent;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.psub-item2:hover { color: var(--fg); background: var(--bg-soft); }
.psub-item2.active {
  color: var(--accent-2);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}

/* App mode — portal pages own the viewport, edge to edge. No hero titles (the
   accent nav highlight is the location signal); the chrome and content run the
   full screen width with fixed app padding instead of a centered column. */
body[data-portal] {
  --maxw: 100%;
  --pad-x: .75rem;   /* fixed app padding — never a proportional website gutter */
}
/* No atmosphere in the app. The generative canvas, the fixed radial accent
   glow, and the film grain are website canvas, not workspace ground —
   [RYDER-VERDICT 2026-07-08] "i don't like the feeling of a background or
   gutter... i want the feeling of a portal/app workspace, not a website."
   The workspace sits on flat opaque --bg; nothing breathes behind it. The
   reading-progress bar is a document affordance and goes with it. */
body[data-portal]::before,
body[data-portal]::after { content: none; animation: none; }
body[data-portal] #fx-canvas { display: none; }
body[data-portal] .scroll-progress { display: none; }
/* Docked chrome: inside the app the header rows are opaque surfaces with
   rules between them — never frosted ghosts with the canvas bleeding through. */
body[data-portal] .nav {
  background: var(--bg-elev);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: var(--hairline);
}
body[data-portal] .portal-subnav {
  background: var(--bg);
  border-bottom-color: color-mix(in srgb, var(--border-hi) 40%, var(--hairline));
}
/* Portal pages carry no hero title block; the marketing 8rem section rhythm
   collapses to app spacing — content starts right under the header stack. */
body[data-portal] main > section { padding: .9rem 0 2.5rem; }
body[data-portal] main > section.portal-fit { padding-bottom: 0; }
/* Kill switch for any leftover in-page hero title: location lives in the nav. */
body[data-portal] .portal-head .section-num,
body[data-portal] .portal-head > h2,
body[data-portal] .portal-head .sub { display: none; }

/* A surface that fills the screen: header + content, overflow absorbed inside
   the module's own panes — the page scrolls normally (no viewport lock: the
   100dvh height lock half-broke sticky chrome and fought the widget canvas). */
.portal-fit {
  display: flex; flex-direction: column; min-height: 0;
}
@media (min-width: 901px) and (min-height: 600px) {
  .portal-fit { min-height: calc(100dvh - var(--portal-chrome-h, 120px) - 3.2rem); }
}

/* === Footer =============================================================== */
footer {
  margin-top: auto;
  padding: 5rem var(--pad-x) 3rem;
  border-top: 1px solid var(--hairline);
  background: var(--bg-elev);
}
.footer-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 4rem;
}
.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: 2.5rem;
}
.footer-brand {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.footer-brand .brand {
  font-weight: 800;
  font-size: 17px;
  letter-spacing: -0.02em;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--fg);
}
.footer-desc {
  font-size: 14px;
  color: var(--fg-dim);
  max-width: 28ch;
  line-height: 1.5;
}
.footer-column h4 {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-mute);
  margin: 0 0 1.25rem 0;
}
.footer-nav {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.footer-nav a {
  font-size: 14px;
  color: var(--fg-dim);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
  width: fit-content;
  display: inline-block;
}
.footer-nav a:hover {
  color: var(--accent);
  transform: translateX(4px);
}
.footer-bottom {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--hairline);
  font-size: 13px;
  color: var(--fg-mute);
  font-family: var(--mono);
}
.footer-bottom .mono {
  color: var(--fg-dim);
}

@media (max-width: 768px) {
  .footer-grid {
    grid-template-columns: 1fr;
    gap: 3rem;
  }
  .footer-inner {
    gap: 3rem;
  }
}

/* === Notes (API-backed entries, index only) ================================ */
.notes-list {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  margin-top: 2.5rem;
}
.note-card {
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 1.5rem;
}
.note-head {
  display: flex;
  align-items: center;
  gap: .75rem;
  font-size: 12px;
}
.note-type { color: var(--accent); text-transform: uppercase; letter-spacing: .08em; }
.note-vis  { color: var(--fg-mute); }
.note-vis.private { color: var(--accent-2); }
.note-del {
  margin-left: auto;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 8px;
  color: var(--fg-mute);
  font-size: 12px;
  padding: .2rem .6rem;
  cursor: pointer;
}
.note-del:hover { color: var(--accent); border-color: var(--accent); }
.note-title { margin: .6rem 0 .25rem; font-size: 1.15rem; font-weight: 700; }
.note-body  { color: var(--fg-dim); margin: 0 0 .5rem; white-space: pre-wrap; }
.note-date  { font-size: 12px; color: var(--fg-mute); margin: 0; }
.notes-form {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  margin-top: 2rem;
  padding: 1.5rem;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  border-radius: 14px;
}
.notes-form-row { display: flex; gap: .75rem; flex-wrap: wrap; align-items: center; }
.notes-form input,
.notes-form textarea,
.notes-form select {
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  color: var(--fg);
  font: inherit;
  padding: .55rem .8rem;
}
.notes-form input[name="title"] { flex: 1 1 14rem; }
.notes-form input:focus,
.notes-form textarea:focus,
.notes-form select:focus { outline: none; border-color: var(--accent); }

/* === Admin-gated chrome =================================================== */
/* Elements that should only appear with an authed session. layout.js sets
   body[data-admin]; until then these are display:none for everyone (including
   the brief pre-session-check window), so nothing flashes for the public. */
.profile-admin-only { display: none; }
body[data-admin] .profile-admin-only { display: block; }

.admin-pill {
  background: color-mix(in srgb, var(--c-admin) 18%, var(--bg-soft));
  color: var(--c-admin-2);
  border-color: color-mix(in srgb, var(--c-admin) 36%, transparent);
}
/* === Profile page ========================================================= */
.profile-section { padding: 3.5rem 0; border-top: 1px solid var(--hairline); }
.profile-section:first-of-type { border-top: none; }
.profile-section.section-dimmed { opacity: .5; }
.profile-section-head {
  display: flex; align-items: center; gap: 1rem;
  margin-bottom: 1.5rem;
}
.profile-h {
  font-size: clamp(1.4rem, 3vw, 2rem); font-weight: 800;
  letter-spacing: -.02em; margin: 0;
}
/* Live/Hidden toggle — only meaningful to an admin, hidden otherwise. */
.section-toggle { display: none; }
body[data-admin] .section-toggle {
  display: inline-flex; align-items: center; gap: .5rem;
  margin-left: auto;
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  padding: .3rem .7rem; border-radius: 999px; cursor: pointer;
  background: transparent;
  border: 1px solid var(--border-hi); color: var(--fg-dim);
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.section-toggle-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor;
}
.section-toggle[data-state="live"]   { color: var(--c-admin-2); border-color: color-mix(in srgb, var(--c-admin) 40%, transparent); }.section-toggle[data-state="hidden"] { color: var(--fg-mute); }
.section-toggle:hover { border-color: currentColor; }

/* === App drawer (apps.html) =============================================== */
.app-drawer { display: flex; flex-direction: column; gap: 3rem; }
.app-drawer-toolbar {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  margin-bottom: .5rem;
}
.app-filter {
  flex: 1 1 16rem; max-width: 26rem;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  color: var(--fg); font: inherit;
  padding: .6rem .9rem;
}
.app-filter:focus { outline: none; border-color: var(--accent); }
.app-group-title {
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em;
  color: var(--fg-mute); margin: 0 0 1.25rem;
}
.app-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 1rem;
}
.app-icon {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: .75rem; padding: 1.5rem 1rem;
  background: var(--mat-1);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  color: inherit;
  transition: transform var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease);
}
.app-icon:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--tint, var(--accent)) 50%, transparent);
  box-shadow: 0 18px 40px -22px color-mix(in srgb, var(--tint, var(--accent)) 70%, transparent);
}
.app-icon-glyph {
  width: 56px; height: 56px; display: grid; place-items: center;
  font-size: 26px; border-radius: 16px;
  color: var(--tint, var(--accent));
  background: color-mix(in srgb, var(--tint, var(--accent)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--tint, var(--accent)) 26%, transparent);
}
.app-icon-glyph img { width: 30px; height: 30px; object-fit: contain; }
.app-icon-name { font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
.app-icon-status {
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--fg-mute);
}
/* Homepage launcher variant: a one-line description under the name instead of
   the uppercase status tag (the launcher renders from NAV_APPS, which carries
   descs, not statuses). */
.app-icon-desc { font-size: 12px; color: var(--fg-mute); line-height: 1.35; }
.app-icon.is-hidden { display: none; }
.app-group.is-empty { display: none; }

/* === Back to top (homepage) =============================================== */
.to-top {
  position: fixed; right: 18px; bottom: 18px; z-index: 90;
  width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 18px; font-weight: 700; line-height: 1;
  color: var(--accent);
  background: color-mix(in srgb, var(--bg-soft) 92%, var(--accent));
  border: 1px solid color-mix(in srgb, var(--accent) 34%, var(--border));
  box-shadow: 0 12px 28px -14px color-mix(in srgb, var(--accent) 60%, #000);
  cursor: pointer;
  opacity: 0; transform: translateY(10px); pointer-events: none;
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}
.to-top.show { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { transform: translateY(-3px); }
/* Signed in, the admin ✎ edit pill owns the bottom-right corner — hop above it. */
body[data-admin] .to-top { bottom: 78px; }

/* === On-site text editing (edit.js, admin only) =========================== */
#rk-edit-pill {
  position: fixed; bottom: 18px; right: 18px; z-index: 998;
  display: flex; align-items: center; gap: .7rem;
  max-width: min(92vw, 560px);
  padding: .45rem .55rem;
  background: color-mix(in srgb, var(--bg-soft) 88%, var(--c-admin));
  border: 1px solid color-mix(in srgb, var(--c-admin) 38%, var(--border));
  border-radius: 999px;
  box-shadow: 0 14px 34px -16px color-mix(in srgb, var(--c-admin) 55%, #000);
}
#rk-edit-pill button {
  border: 0; cursor: pointer; border-radius: 999px;
  padding: .35rem .85rem; font-size: 12px; font-weight: 700;
  letter-spacing: .04em;
  color: var(--c-admin); background: color-mix(in srgb, var(--c-admin) 14%, transparent);
}
#rk-edit-pill.on button { color: #04130a; background: var(--c-admin); }
#rk-edit-pill .rk-edit-hint { font-size: 11px; color: var(--fg-mute); padding-right: .35rem; }
#rk-edit-pill .rk-edit-flash { font-size: 11px; font-weight: 700; color: var(--c-admin); padding-right: .35rem; }
#rk-edit-pill .rk-edit-flash.err { color: var(--c-coral-1); }

/* Rich-format toolbar — sits just above the edit pill while editing. */
#rk-fmt {
  position: fixed; bottom: 68px; right: 18px; z-index: 998;
  display: flex; align-items: center; gap: .25rem;
  padding: .3rem .35rem;
  background: color-mix(in srgb, var(--bg-soft) 90%, var(--c-admin));
  border: 1px solid color-mix(in srgb, var(--c-admin) 38%, var(--border));
  border-radius: 12px;
  box-shadow: 0 14px 34px -16px color-mix(in srgb, var(--c-admin) 55%, #000);
}
#rk-fmt button {
  border: 0; cursor: pointer; border-radius: 8px; min-width: 30px;
  padding: .3rem .5rem; font-size: 12px; font-weight: 700; line-height: 1;
  color: var(--c-admin); background: color-mix(in srgb, var(--c-admin) 14%, transparent);
}
#rk-fmt button:hover { color: #04130a; background: var(--c-admin); }

body.rk-editing [data-edit] {
  outline: 1.5px dashed color-mix(in srgb, var(--c-admin) 55%, transparent);
  outline-offset: 3px; border-radius: 4px; cursor: text;
}
/* Empty editable regions (e.g. a not-yet-written entry body) show a prompt so
   they're visibly fillable rather than collapsed to nothing. */
body.rk-editing [data-edit].lms-body:empty::before {
  content: "Empty — type here. ⇥ nests a bullet, ⌘B bolds.";
  color: var(--fg-mute); font-style: italic;
}
body.rk-editing [data-edit]:hover {
  outline-style: solid;
  background: color-mix(in srgb, var(--c-admin) 7%, transparent);
}
body.rk-editing [data-edit]:focus {
  outline: 2px solid var(--c-admin);
  background: color-mix(in srgb, var(--c-admin) 9%, transparent);
}

/* === Responsive tweaks ==================================================== */
@media (max-width: 720px) {
  section { padding: 5rem 0; }
  .hero { min-height: 70vh; padding-top: 4vh; }
  .nav-inner { gap: .75rem; }
  .nav-links { display: none; }
  .cmdk-btn .label { display: none; }
  .tile { padding: 1.5rem; }
  .music-hero { padding: 2rem; }
}
