/* ============================================================
   CraftUI — dark shell theme (panels on black), blue accent
============================================================ */
:root {
  --accent: #1E22E6;        /* Hyper Blue — fills: chips, buttons, checks */
  --accent-soft: #5B6BFF;   /* bright electric — text / hairlines on jet */
  --primary: var(--accent);
  --nocturne: #0C1152;      /* deep indigo — glows / gradient depth */
  --background: #060608;    /* Jet Black */
  --panel: #0C0C12;
  --panel-2: #14141F;
  --foreground: #F4F5F7;    /* Glacier White */
  --muted: rgba(244, 245, 247, 0.56);
  --muted-2: rgba(244, 245, 247, 0.36);
  --line: rgba(255, 255, 255, 0.08);
  --pattern-fg: rgba(255, 255, 255, 0.07);
  --card-bg: #ffffff;
  --card-border: rgba(255, 255, 255, 0.06);
  --sidebar-active: rgba(255, 255, 255, 0.07);

  --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SFMono-Regular", monospace;
  --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
  --shadow-card: 0 2px 6px rgba(0, 0, 0, 0.35), 0 10px 28px rgba(0, 0, 0, 0.28);
}

/* ============================================================
   LIGHT THEME: token remap only (dark :root above stays the default).
   Applied by nav.js via document.documentElement[data-theme="light"].
   Higher specificity ([data-theme="light"] adds an attribute selector) means
   these win over the base rules regardless of source order. This is the
   FOUNDATION: component-level hardcoded-dark surfaces (hero/title text, canvas
   renders, per-page inline dark panels) are addressed separately in Phase 2.
============================================================ */
:root[data-theme="light"] {
  --accent: #1E22E6;             /* Hyper Blue fill: white content reads on light */
  --accent-soft: #1A1DC8;        /* darker hyper mark/text accent, AA on #fff */
  --nocturne: #0C1152;
  --background: #F4F5F7;
  --panel: #ffffff;
  --panel-2: #eeeef1;
  --foreground: #14141a;
  --muted: rgba(20, 20, 26, 0.64);   /* ~4.9:1 on #f4f4f6 (AA body) */
  --muted-2: rgba(20, 20, 26, 0.60); /* ~4.6:1 on #f4f4f6, AA on #fff/#eeeef1 too (secondary labels) */
  --line: rgba(0, 0, 0, 0.10);
  --pattern-fg: rgba(0, 0, 0, 0.06);
  --card-bg: #ffffff;
  --card-border: rgba(0, 0, 0, 0.08);
  --sidebar-active: rgba(0, 0, 0, 0.05);
  /* soften shadows: the deep-black dark shadows read as grey smudges on white */
  --shadow-2xl: 0 20px 44px -14px rgba(20, 20, 40, 0.18);
  --shadow-card: 0 1px 3px rgba(20, 20, 40, 0.08), 0 8px 24px rgba(20, 20, 40, 0.10);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  color: var(--foreground);
  background: var(--background);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
a { color: inherit; text-decoration: none; }
::selection { background: rgba(91, 107, 255, 0.35); }
:root[data-theme="light"] ::selection { background: rgba(30, 34, 230, 0.18); }

/* ============================================================
   Glass nav CAPSULE navigation - BOTTOM-CENTER cylinder, SINGLE ROW bloom
   (replaces the floating dock and the standalone orb)

   The capsule is fixed at bottom-center, sticky across scroll. Its left cell is
   the MENU button (constant app-grid glyph) and its right cell is the theme
   toggle, split by a hairline divider. Tapping MENU blooms all 8 petals into
   ONE horizontal row ABOVE the capsule, centered as a group on the capsule
   center line, in site order
   (Home Icons UI Loaders Interaction Studio Glass About) with a subtle smile arc.
   Petals emerge from and collapse into the MENU button. The capsule stays
   visible below the row as the anchor / close control. The wordmark is a
   non-link label centered directly above the row.

   Stacking: capsule + bloom + petals + head at z-index 116, mobile scrim 115.
   Above ALL regular page content (top-most ~95, incl. the /ui DialKit FAB at
   95) yet below the /icons drawer (120)+scrim (118) so the OPEN DRAWER covers
   the capsule, and below every full-screen modal (ui dialog 120, command
   palette 130, loaders modal 200) so those correctly cover it too.

   Collision: the row's bottom is ~92px (capsule bottom 20 + capsule 58 + 14 gap)
   and it is centered, so it sits in a HIGHER vertical band than the bottom-right
   social icons (24-60px on /, /about), clearing them vertically at all widths.
   The /ui DialKit FAB (90-142px) shares the row's band, so full-label rows (wide)
   are only shown where they also clear the FAB horizontally: labels collapse to
   icon circles at <=1024px (see below). On mobile the open scrim (z115) covers
   the FAB anyway. The capsule itself is a compact centered pill (~120px wide),
   well clear of the right-edge socials and FAB in both themes.
============================================================ */
.nav-capsule {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 116;
  view-transition-name: nav-capsule; /* persists across page loads as the anchor */
  display: flex;
  align-items: center;
  gap: 7px;
  height: 58px;
  padding: 7px;
  border-radius: 999px; /* true cylinder / pill */
  color: var(--foreground);
  background: rgba(18, 18, 24, 0.55);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  backdrop-filter: blur(22px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  -webkit-tap-highlight-color: transparent;
  /* NO overflow:hidden -> the ::before presence glow and the ::after beckon ring
     must bleed past the capsule edge; backdrop-filter stays clipped to the pill
     by the capsule's own border-radius, so the frosted glass never leaks. */
}

/* ambient presence: a soft accent halo BEHIND the capsule (z-index -1) that
   gently breathes (scale only; opacity is theme-controlled) so the closed nav
   reads as alive and findable over any page. Brightens on pointer proximity. */
.nav-capsule::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -14px -10px -10px;
  border-radius: 999px;
  background: radial-gradient(60% 130% at 32% 30%, var(--accent-soft) 0%, rgba(91, 107, 255, 0) 70%);
  filter: blur(22px);
  opacity: 0.34;
  pointer-events: none;
  animation: navCapsuleBreath 8s ease-in-out infinite alternate;
  transition: opacity 0.3s ease;
}
@keyframes navCapsuleBreath {
  0%   { transform: scale(0.98); }
  100% { transform: scale(1.06); }
}
.nav-capsule.near::before { opacity: 0.6; }

/* periodic beckon: nav.js toggles .beckon on an idle cadence, firing exactly one
   soft accent ring pulse (no loop) from the pill outline. The ::after overlay
   owns ONLY this ring, so the capsule's own shadow is never disturbed. */
.nav-capsule::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 0 0 rgba(91, 107, 255, 0);
}
.nav-capsule.beckon::after { animation: navCapsuleBeckon 0.9s ease-out; }
@keyframes navCapsuleBeckon {
  0%   { box-shadow: 0 0 0 0 rgba(91, 107, 255, 0.4); }
  100% { box-shadow: 0 0 0 16px rgba(91, 107, 255, 0); }
}

/* hairline divider between the two controls. Neutral, never a colored stroke. */
.nav-capsule-divider {
  width: 1px;
  height: 26px;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.10);
}

/* MENU button (bloom trigger, left cell). A 44px circular glass button carrying
   the CONSTANT app-grid glyph. Motion owns its press-scale transform; it has no
   baseline transform, so nothing conflicts with the capsule's own centering. */
.bloom-orb {
  position: relative;
  z-index: 1;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  border-radius: 50%;
  color: var(--foreground);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.10);
  cursor: pointer;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s ease, border-color 0.18s ease, padding 0.3s cubic-bezier(.22, 1, .36, 1), border-radius 0.3s ease;
}
.bloom-orb:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.2); }
.bloom-orb:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 3px; }
.bloom-orb > svg { position: relative; z-index: 1; display: block; flex-shrink: 0; }
/* menu button expands to reveal the wordmark "Drift" while the bloom is open,
   then collapses back to an icon-only circle. overflow:hidden on .bloom-orb clips
   the label as it retracts on close; width:auto lets the orb hug the label. */
.bloom-orb.is-open { width: auto; padding: 0 14px 0 11px; border-radius: 22px; justify-content: flex-start; }
.bloom-orb-label {
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  opacity: 0;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--foreground);
  transition: max-width 0.32s cubic-bezier(.22, 1, .36, 1), opacity 0.24s ease, margin-left 0.32s cubic-bezier(.22, 1, .36, 1);
}
.bloom-orb.is-open .bloom-orb-label { max-width: 64px; opacity: 1; margin-left: 8px; }
@media (prefers-reduced-motion: reduce) { .bloom-orb-label { transition: none; } }

/* anchor vignette: a fixed full-width darkened landing strip BEHIND the orb
   (below scrim 115 / orb 116). Gives the orb consistent contrast over any page
   content - panels, white cards, canvases. Subtle, not a bar: no border, no blur.
   Deliberately carries NO view-transition-name, so it never flashes on page nav. */
.bloom-vignette {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(110px + env(safe-area-inset-bottom, 0px));
  z-index: 114;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0.28) 55%, transparent);
}

/* bloom container: a 0x0 point anchored at the orb's CENTER; petals translate
   UP from here into the row. The 3D perspective lives HERE (never as
   perspective() inside a transform string, and never on a parent that also
   rotates a backdrop-filter element). */
.bloom {
  position: fixed;
  left: 50%;        /* the point sits on the capsule center line (= page center) */
  bottom: 49px;     /* capsule bottom (20) + capsule half (29) = capsule center */
  z-index: 116;
  width: 0;
  height: 0;
  pointer-events: none;
  perspective: 900px;
  perspective-origin: 50% 100%;
}

.bloom-head {
  position: fixed;
  left: 50%;
  transform: translateX(-50%); /* Motion animates only opacity, so this stays */
  bottom: 146px;               /* row bottom (92) + petal height (44) + 10 */
  z-index: 116;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  opacity: 0; /* fades in / out with the row */
  pointer-events: none;
}
.bloom-head-dot { width: 7px; height: 7px; border-radius: 2px; background: var(--accent-soft); }
.bloom-head-txt {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: -0.02em;
  color: var(--foreground);
}

.bloom-petal {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  gap: 9px;
  height: 44px;               /* identical across all petals — no size variation */
  padding: 0 16px 0 12px;
  border-radius: 14px;
  color: var(--muted);
  background: rgba(20, 20, 20, 0.6);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  white-space: nowrap;
  opacity: 0; /* revealed by the open choreography */
  transform: translateZ(0); /* own layer: dodges the Chromium glass crop */
  transform-origin: 50% 100%; /* scale up out of the orb */
  will-change: transform, opacity;
  pointer-events: none;
}
.bloom.open .bloom-petal { pointer-events: auto; }
.bloom-petal > svg { flex-shrink: 0; display: block; }
.bloom-petal-label {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: -0.01em;
}
.bloom-petal.active { background: var(--accent); color: #fff; border-color: transparent; }
.bloom-petal:not(.active):hover { color: #fff; border-color: rgba(255, 255, 255, 0.3); }
.bloom-petal:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }

/* When the bloom opens, this full-screen scrim dims + blurs everything behind
   the petals (page content, hero, socials, the capsule stays crisp above) so
   the open menu floats clearly on top with nothing overlapping through it.
   Shown on ALL sizes now (was desktop-hidden). z-index 115: under the petals +
   capsule + head (116), over page content + the vignette (114). */
.bloom-scrim {
  position: fixed;
  inset: 0;
  z-index: 115;
  background: rgba(6, 6, 12, 0.55);
  -webkit-backdrop-filter: blur(13px) saturate(0.9);
  backdrop-filter: blur(13px) saturate(0.9);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
.bloom-scrim.show { opacity: 1; pointer-events: auto; }
:root[data-theme="light"] .bloom-scrim { background: rgba(18, 18, 28, 0.42); }

/* ---- Labels -> icon circles at <=1024px ------------------------------------
   The 8-petal full-label row is wide (~840px, half ~420px). The /ui DialKit FAB
   shares the row's vertical band, so a wide row must clear it horizontally:
   full labels clear only when rowRight (vw/2 + ~420) < FAB left (vw - 78), i.e.
   vw > ~996px. Collapsing to icon circles at <=1024px keeps a comfortable
   margin at the boundary; below it petals become 44px icon circles (aria-labels
   retained) that always clear the FAB and the bottom-right socials. */
@media (max-width: 1024px) {
  .bloom-petal {
    width: 44px;
    padding: 0;
    gap: 0;
    justify-content: center;
    border-radius: 50%;
  }
  .bloom-petal-label { display: none; }
}
@media (max-width: 680px) {
  .nav-capsule { height: 54px; padding: 5px; gap: 6px; bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
  .bloom { bottom: calc(41px + env(safe-area-inset-bottom, 0px)); }        /* capsule center: 14 + 27 */
  .bloom-head { bottom: calc(136px + env(safe-area-inset-bottom, 0px)); }  /* row 82 + petal 44 + 10 */
}
/* ---- 8-petal mobile fit: step petal size down so the whole icon row + margins
   stay inside the viewport. GAP stays 8px here and drops to 6px at <=400px
   (see gapFor() in nav.js). Row width = 8*p + 7*gap:
     44px/8gap = 408px  (needs vw >= ~432; fine for 461-680)
     40px/8gap = 376px  (needs vw >= ~400; covers 401-460)
     36px/6gap = 330px  (needs vw >= ~354; covers <=400, 30px slack at 360) */
@media (max-width: 460px) {
  .bloom-petal { width: 40px; height: 40px; }
  .bloom-head { bottom: calc(132px + env(safe-area-inset-bottom, 0px)); }  /* row 82 + 40 + 10 */
}
@media (max-width: 400px) {
  .bloom-petal { width: 36px; height: 36px; }  /* 36px >= 34px tap floor */
  .bloom-head { bottom: calc(128px + env(safe-area-inset-bottom, 0px)); }  /* row 82 + 36 + 10 */
}

@media (prefers-reduced-motion: reduce) {
  .nav-capsule::before { animation: none; transform: none; transition: none; }
  .nav-capsule.beckon::after { animation: none; }
  .bloom-petal { transition: opacity 0.08s linear; }
  .bloom-head { transition: opacity 0.08s linear; }
}

/* ============================================================
   Theme toggle: sun/moon control, the RIGHT cell inside the nav capsule.
   Built + owned by nav.js so it appears on EVERY page. Native <button>
   (Enter/Space toggle for free). A 44px circular button; the capsule provides
   the glass, so this stays a light-touch surface with a hover tint. Dark
   styling here is the default; the light variant is remapped below.
============================================================ */
.theme-toggle {
  position: relative;
  z-index: 1;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  border-radius: 50%;
  color: var(--foreground);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s ease, border-color 0.18s ease;
}
.theme-toggle:hover { background: rgba(255, 255, 255, 0.10); border-color: rgba(255, 255, 255, 0.18); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 3px; }
.theme-toggle > .theme-toggle-ico { display: flex; align-items: center; justify-content: center; }
.theme-toggle-ico { will-change: transform, opacity; }
.theme-toggle-ico > svg { display: block; }

/* ============================================================
   LIGHT THEME SHELL: the small set of shared shell surfaces that are
   hardcoded-dark glass (not token-driven) and must adapt with the theme:
   the orb glass, the theme-toggle glass, the bloom petals/pills, and the
   anchor vignette. Component-level dark surfaces stay for Phase 2.
============================================================ */
:root[data-theme="light"] .nav-capsule {
  background: rgba(255, 255, 255, 0.72);
  border-color: rgba(0, 0, 0, 0.10);
  box-shadow: 0 14px 34px rgba(20, 20, 40, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
/* soften the accent halo on the light field so it reads as a hint, not a wash */
:root[data-theme="light"] .nav-capsule::before { opacity: 0.2; }
:root[data-theme="light"] .nav-capsule.near::before { opacity: 0.4; }
:root[data-theme="light"] .nav-capsule-divider { background: rgba(0, 0, 0, 0.10); }
:root[data-theme="light"] .bloom-orb {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.08);
}
:root[data-theme="light"] .bloom-orb:hover { background: rgba(0, 0, 0, 0.09); border-color: rgba(0, 0, 0, 0.18); }
:root[data-theme="light"] .theme-toggle:hover { background: rgba(0, 0, 0, 0.06); border-color: rgba(0, 0, 0, 0.16); }
:root[data-theme="light"] .bloom-petal {
  background: rgba(255, 255, 255, 0.8);
  border-color: rgba(0, 0, 0, 0.1);
}
/* The white-glass rule above (specificity 0,3,0) otherwise overrides the base
   .bloom-petal.active fill (0,2,0), leaving the active petal's white label on a
   white pill (~1:1, invisible). Restore the accent fill in light too — #1E22E6
   + white = 10.6:1, matching dark. */
:root[data-theme="light"] .bloom-petal.active {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
:root[data-theme="light"] .bloom-petal:not(.active):hover {
  color: #111118;
  border-color: rgba(0, 0, 0, 0.28);
}
:root[data-theme="light"] .bloom-vignette {
  background: linear-gradient(to top, rgba(244, 244, 246, 0.85), rgba(244, 244, 246, 0.35) 55%, transparent);
}

/* ============================================================
   Cross-document View Transitions (whole-MPA page navigation)
   Subtle + fast + premium. transform/opacity only (never filter/blur —
   those repaint the full-page snapshot). The capsule carries
   view-transition-name: nav-capsule (above) so it persists as the anchor.
============================================================ */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vtOut 0.14s ease-in both; }
::view-transition-new(root) { animation: vtIn 0.26s cubic-bezier(0.22, 0.68, 0.2, 1) both; }
@keyframes vtOut { to { opacity: 0; transform: scale(0.985); } }
@keyframes vtIn { from { opacity: 0; transform: translateY(12px); } }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}

/* Firefox / unsupported: a light entrance so there's never a hard flash.
   Guarded so it does NOT run where view transitions are supported. */
@supports not (view-transition-name: none) {
  body { animation: pageFadeIn 0.22s ease-out both; }
  @keyframes pageFadeIn { from { opacity: 0; } }
}

/* ============================================================
   Legacy top bar (hidden by nav.js; kept for markup compat)
============================================================ */
.topbar {
  /* Legacy hanging badge is fully replaced by the bloom-orb nav. nav.js also
     display:none's it at runtime, but that runs at end-of-body, so the badge
     FLASHED on first paint before the JS hid it. Hiding it here kills the flash
     from first paint; the JS hide becomes a harmless redundant fallback. Applies
     in both themes (no theme override needed). */
  display: none;
  position: sticky;
  top: 0;
  z-index: 50;
  width: 100%;
  height: 24px;
  background: #000;
  overflow: visible;
}
.topbar-inner { position: relative; height: 24px; width: 100%; }
.badge {
  position: absolute;
  top: -2px;
  left: 0;
  right: 0;
  margin: 0 auto;
  z-index: 20;
  width: 180px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 16px;
  background: #121212;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-top: none;
  color: #fff;
  border-radius: 0 0 18px 18px;
  box-shadow: var(--shadow-2xl);
  cursor: pointer;
  user-select: none;
}
.badge svg { display: block; }
.badge-text {
  font-family: var(--font-sans);
  font-size: 14px;
  letter-spacing: -0.03em;
  color: #fff;
}

/* Badge dropdown menu */
.badge.has-menu {
  flex-direction: column;
  justify-content: flex-start;
  align-items: stretch;
  overflow: hidden;
  padding: 0 10px;
}
.badge-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 48px;
  flex-shrink: 0;
}
.badge-menu { opacity: 0; padding: 2px 0 12px; }
.badge-menu-sep { height: 1px; margin: 0 8px 8px; background: rgba(255, 255, 255, 0.12); }
.badge-menu-item { border-radius: 13px; padding: 10px 12px; cursor: pointer; }
.badge-menu-item:hover { background: #262626; }
.badge-menu-item a { display: flex; align-items: center; gap: 12px; width: 100%; }
.mi-icon { color: rgba(255, 255, 255, 0.25); flex-shrink: 0; display: flex; transition: color 0.15s ease; }
.mi-icon.active, .badge-menu-item:hover .mi-icon { color: #fff; }
.mi-label { font-family: var(--font-sans); font-size: 14px; letter-spacing: -0.03em; color: #fff; }

/* ============================================================
   Framed layout
============================================================ */
.frame { background: var(--background); }
/* full-bleed shell: no 80rem cap, background stretches to the viewport edges */
.frame-max { max-width: none; margin: 0 auto; }
.frame-grid {
  position: relative;
  display: grid;
  /* decorative side rails retired at ALL widths: content column spans full-bleed */
  grid-template-columns: 0 1fr 0;
  grid-template-rows: 1px auto 1px;
  background: var(--background);
}
/* class definitions kept for markup compat, but the rails no longer render */
.pattern-col {
  display: none;
  grid-row: 1 / -1;
  background-image: repeating-linear-gradient(
    315deg,
    var(--pattern-fg) 0,
    var(--pattern-fg) 1px,
    transparent 0,
    transparent 50%
  );
  background-size: 10px 10px;
  background-attachment: fixed;
}
.pattern-col.left { grid-column: 1; border-right: 1px solid var(--pattern-fg); }
.pattern-col.right { grid-column: 3; border-left: 1px solid var(--pattern-fg); }
.frame-content { grid-column: 2; grid-row: 2; min-width: 0; }

/* ============================================================
   HERO (homepage)
============================================================ */
.hero-center {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
  gap: 24px;
  min-height: 100vh;
  padding: 24px 0 104px; /* reserve the fixed bottom-nav zone so the pane never overlaps it */
  box-sizing: border-box;
  text-align: center;
}
.hero-title {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  letter-spacing: -0.045em;
  line-height: 1.05;
  font-size: clamp(2.75rem, 6vw, 3.75rem);
  text-wrap: balance;
  color: #f2f2f2;
}
.hero-title .line { display: block; overflow: hidden; }
.hero-title .reveal {
  display: block;
  will-change: transform, opacity;
}
.hero-title .primary { color: var(--accent-soft); }

/* On-load reveal: ONE coherent Motion v12 cascade drives title -> subtitle ->
   category rows (see the inline script in index.html). ROBUSTNESS: the content
   is VISIBLE BY DEFAULT here; the JS applies the hidden state only in the instant
   before it animates, so if Motion is slow/blocked/absent the hero is never left
   invisible. Reduced motion / missing Motion => content simply stays visible. */
.hero-sub {
  margin: 16px 0 0;
  font-family: var(--font-mono);
  font-size: 13.5px;
  letter-spacing: -0.01em;
  color: var(--muted);
  max-width: 52ch;
}
/* NOTE: #ditherBg (below) is the SHARED detonation canvas rule still used by
   about.html; left untouched. The homepage now uses #eragonBg (the WebGL2
   shader), which has no element with #ditherBg, so this rule no longer applies
   on the homepage but keeps about.html rendering exactly as before. */
#ditherBg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0.35;
  z-index: 0;
  mask-image: radial-gradient(ellipse 90% 70% at 50% 45%, transparent 22%, #000 78%);
  -webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 45%, transparent 22%, #000 78%);
}

/* Homepage WebGL2 dithered-starburst shader canvas. Bold, high-contrast, fills
   the hero behind the frosted glass pane. A CSS radial-gradient fallback shows
   only if WebGL2 is unavailable (the shader paints over it otherwise). */
#eragonBg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
  opacity: 0.92;
  display: block;
  background: radial-gradient(120% 90% at 44% 40%, rgba(30, 34, 230, 0.5), rgba(0, 0, 0, 0) 46%), #000;
}
:root[data-theme="light"] #eragonBg {
  opacity: 0.85;
  background: radial-gradient(120% 90% at 44% 40%, rgba(30, 34, 230, 0.28), rgba(244, 244, 246, 0) 46%), #f4f4f6;
}

.frame-content { position: relative; }
.hero-center { position: relative; z-index: 1; }

/* ============================================================
   FROSTED GLASS PANE (homepage hero)
   Sits behind the hero content block (title + subtitle + category list) so the
   text stays AA-legible over the busy high-contrast shader. Built on the Drift
   house glass recipe: backdrop-filter blur + saturate, a translucent tint
   (dark-glass in dark, light-glass in light), a neutral 1px rim highlight (NO
   colored border stroke), and a soft drop shadow. Emphasis is a soft one-sided
   accent glow BEHIND the pane (breathing, transform/opacity only) plus a slow
   specular sheen drifting across the surface. Both animations are transform/
   opacity only (blur is never animated) and disabled under reduced motion.
============================================================ */
.hero-pane {
  position: relative;
  box-sizing: border-box;
  width: min(34rem, 100%);
  max-width: 100%;
  margin: 0 auto;
  padding: 30px 40px 26px;
  border-radius: 28px;
}
/* Ambient accent bloom behind the pane. A radial-gradient that fades FULLY to
   transparent, so it reads as a soft edgeless glow with NO visible circle/box.
   Inset horizontally (8% each side) so it stays within the pane width and can
   never cause horizontal overflow; it bleeds only vertically. Breathes on scale
   + opacity (never blur). */
.hero-pane::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -14% 8%;
  border-radius: 50%;
  background: radial-gradient(58% 52% at 50% 44%, rgba(91, 107, 255, 0.26), rgba(91, 107, 255, 0.09) 48%, rgba(91, 107, 255, 0) 78%);
  opacity: 0.6;
  pointer-events: none;
  animation: heroGlowDrift 9s ease-in-out infinite alternate;
  will-change: transform, opacity;
}
@keyframes heroGlowDrift {
  from { transform: translateY(-3px) scale(0.98); opacity: 0.42; }
  to   { transform: translateY(5px) scale(1.05); opacity: 0.6; }
}
/* the frosted glass surface itself: clips the sheen, carries the tint + blur +
   rim + shadow. Absolute so the content box defines the pane's size. */
.hero-pane__glass {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  overflow: hidden;
  background: linear-gradient(180deg, rgba(16, 17, 24, 0.58), rgba(9, 10, 16, 0.70));
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  backdrop-filter: blur(22px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: 0 30px 80px -22px rgba(0, 0, 0, 0.66), inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
/* slow specular sheen: a soft diagonal light band drifting across, clipped by
   the glass overflow. Transform only. */
.hero-pane__sheen {
  position: absolute;
  top: -60%;
  left: -30%;
  width: 42%;
  height: 220%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.14), transparent);
  transform: rotate(8deg) translateX(-160%);
  animation: heroSheen 9s ease-in-out infinite;
  will-change: transform;
  pointer-events: none;
}
@keyframes heroSheen {
  0%   { transform: rotate(8deg) translateX(-160%); }
  55%  { transform: rotate(8deg) translateX(420%); }
  100% { transform: rotate(8deg) translateX(420%); }
}
.hero-pane__content { position: relative; z-index: 1; }

/* Light theme: flip to Apple-style light glass (bright white frost, light rim,
   the dark theme tokens already switch the text ink to dark for AA). */
:root[data-theme="light"] .hero-pane__glass {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.80), rgba(255, 255, 255, 0.68));
  border-color: rgba(0, 0, 0, 0.06);
  box-shadow: 0 26px 64px -22px rgba(28, 28, 60, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.92);
}
:root[data-theme="light"] .hero-pane::before {
  background: radial-gradient(58% 52% at 50% 44%, rgba(30, 34, 230, 0.16), rgba(30, 34, 230, 0.06) 48%, rgba(30, 34, 230, 0) 78%);
}
:root[data-theme="light"] .hero-pane__sheen {
  background: linear-gradient(100deg, transparent, rgba(120, 120, 255, 0.12), transparent);
}

/* Category list */
.cat-list {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 28rem;
  margin: 0 auto;
  padding-top: 16px;
}
/* .cat-item entrance is owned by the Motion cascade (visible by default; JS
   applies the hidden state only just before animating). No CSS keyframe here. */
.cat-item { will-change: transform, opacity; }
.cat-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 6px 6px;
  border-radius: 10px;
}
.cat-row:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 3px;
}
.cat-name {
  font-family: var(--font-mono);
  letter-spacing: -0.04em;
  font-size: 16px;
  color: var(--foreground);
  flex-shrink: 0;
}
.cat-line { flex: 1; height: 24px; min-width: 32px; color: rgba(255, 255, 255, 0.5); }
/* base connector: a faint always-visible hairline. On hover it re-draws left->
   right (stroke-dashoffset) and brightens to the accent, then holds. */
.cat-line path {
  opacity: 0.25;
  stroke-dasharray: 220;
  stroke-dashoffset: 0;
  transition: opacity 0.25s ease;
}
.cat-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  font-family: var(--font-mono);
  letter-spacing: -0.04em;
  font-size: 14px;
  color: var(--muted);
}
.cat-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  padding: 4px;
  border-radius: 6px;
  color: #fff;
  transition: transform 0.18s ease, background 0.18s ease;
}
.cat-row:hover .cat-badge, .cat-row:focus-visible .cat-badge { transform: translate(2px, -2px); background: var(--accent-soft); }
.cat-row:hover .cat-line, .cat-row:focus-visible .cat-line { color: var(--accent-soft); }
.cat-row:hover .cat-line path, .cat-row:focus-visible .cat-line path {
  opacity: 0.85;
  animation: catLineDraw 0.5s cubic-bezier(0.22, 0.68, 0.2, 1);
}
@keyframes catLineDraw {
  from { stroke-dashoffset: 220; }
  to   { stroke-dashoffset: 0; }
}
.cat-item[aria-disabled="true"] .cat-row,
.cat-row.disabled { cursor: default; }
.badge-soon {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 9999px;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}

/* Creator socials: centered directly below the category list, inside the pane
   content, so the dock is a prominent, obviously-visible part of the hero. */
.creator {
  display: flex;
  justify-content: center;
  margin-top: 24px;
}
/* ---- Floating GLASS SOCIAL DOCK ----------------------------------------------
   Four frosted-glass orbs, CLEARLY VISIBLE AT REST (glass + rim + soft accent
   glow + brand-blue glyph), gently bobbing on a staggered breathe. Hover/focus:
   springy magnify + lift, the orb warms to the accent fill (white glyph), and a
   glass label chip pops above. Brand-mark shapes kept; tint stays on-palette.
   transform/opacity only (blur never animated); reduced motion freezes to a
   static, clearly-visible state. The bob lives on the .social-orb anchor and the
   magnify on the inner .social-orb__glass, so the two transforms compose without
   fighting. */
.social-dock {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: center;
  gap: 14px;
}
.social-orb {
  position: relative;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  -webkit-tap-highlight-color: transparent;
  animation: socBob 4.2s ease-in-out infinite;
  will-change: transform;
}
.social-orb:nth-child(1) { animation-delay: 0s; }
.social-orb:nth-child(2) { animation-delay: 0.35s; }
.social-orb:nth-child(3) { animation-delay: 0.7s; }
.social-orb:nth-child(4) { animation-delay: 1.05s; }
@keyframes socBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}
.social-orb__glass {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  /* high-contrast at REST: an opaque dark glass chip with a WHITE glyph (~15:1),
     so the four icons are immediately visible without hover. Hover warms the
     chip to the accent fill (glyph stays white) for the tactile pop. */
  color: #f5f6ff;
  background: rgba(34, 36, 54, 0.78);
  -webkit-backdrop-filter: blur(14px) saturate(1.35);
  backdrop-filter: blur(14px) saturate(1.35);
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.42), 0 4px 16px rgba(91, 107, 255, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.22);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.5, 1), background 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
  will-change: transform;
}
.social-orb__glass svg { display: block; }
.social-orb:hover .social-orb__glass,
.social-orb:focus-visible .social-orb__glass {
  transform: translateY(-8px) scale(1.2);
  background: var(--accent);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 18px 36px rgba(0, 0, 0, 0.5), 0 8px 26px rgba(30, 34, 230, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.28);
}
.social-orb:focus-visible { outline: none; }
.social-orb:focus-visible .social-orb__glass {
  box-shadow: 0 18px 36px rgba(0, 0, 0, 0.5), 0 0 0 3px rgba(91, 107, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.28);
}
/* label chip pops above on hover/focus (content pulled from the aria-label) */
.social-orb::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  padding: 5px 10px;
  border-radius: 9px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: #fff;
  background: rgba(16, 17, 24, 0.72);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transform: translate(-50%, 6px);
  transition: opacity 0.2s ease, transform 0.28s cubic-bezier(0.34, 1.56, 0.5, 1);
  pointer-events: none;
}
.social-orb:hover::after,
.social-orb:focus-visible::after {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* ============================================================
   GALLERY pages — dark shell: rounded panels on black
============================================================ */
.gallery-body {
  display: grid;
  grid-template-columns: minmax(220px, 268px) 1fr;
  gap: 14px;
  padding: 14px;
  background: var(--background);
  min-height: 100vh;
}
.sidebar {
  background: var(--panel);
  border-radius: 23px;
  padding: 24px 16px;
  align-self: start;
  position: sticky;
  top: 14px;                       /* match the 14px .gallery-body gutter (was
                                      38px clearance for the removed top badge) */
  max-height: calc(100vh - 28px);
  overflow-y: auto;
}
.sidebar-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px 14px;
  color: var(--muted-2);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.nav-item, .nav-folder, .nav-leaf {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 12px;
  font-family: var(--font-sans);
  font-size: 14.5px;
  letter-spacing: -0.01em;
  color: var(--muted);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.nav-item .chev, .nav-folder .chev { width: 14px; color: var(--muted-2); }
.nav-item.active { background: var(--sidebar-active); color: var(--foreground); font-weight: 500; }
.nav-item.active .grid-ico { color: var(--accent-soft); }
.nav-folder:hover, .nav-leaf:hover, .nav-item:hover:not(.active) { background: rgba(255, 255, 255, 0.04); color: var(--foreground); }
.nav-folder .folder-ico { color: var(--accent-soft); }
.nav-leaf { padding-left: 12px; }
.nav-leaf .file-ico { color: var(--muted-2); }

.main { background: var(--panel); border-radius: 23px; padding: 0; min-width: 0; align-self: start; }
.main-inner { padding: clamp(16px, 2.6vw, 26px) clamp(14px, 2.6vw, 26px) 120px; }
.main-header { display: flex; align-items: center; gap: 16px; margin-bottom: 22px; }
.search {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 44px;
  padding: 0 14px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.search svg { color: var(--muted-2); flex-shrink: 0; }
.search input {
  flex: 1;
  border: 0;
  outline: 0;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 15px;
  color: var(--foreground);
}
.search input::placeholder { color: var(--muted-2); }
.search .kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 22px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--muted-2);
  font-family: var(--font-mono);
  font-size: 12px;
}
.count {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: -0.02em;
  color: var(--muted);
}

/* cards stay light so the artwork pops against the dark shell */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 14px; }
.card {
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card-bg);
  border: 1px solid rgba(255, 255, 255, 0.04);
  border-radius: 16px;
  box-shadow: var(--shadow-card);
  color: #111;
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow 0.18s ease, transform 0.18s ease;
}
.card:hover { transform: translateY(-2px); box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5); }
.card .see {
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translate(-50%, 6px);
  display: block;
  max-width: calc(100% - 12px);
  padding: 3px 9px;
  border-radius: 9999px;
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #fff;
  background: rgba(17, 17, 17, 0.82);
  backdrop-filter: blur(4px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.card:hover .see { opacity: 1; transform: translate(-50%, 0); }
.card > div:first-child { transition: transform 0.18s ease; }
.card:hover > div:first-child { transform: translateY(-6px); }

@media (max-width: 900px)  { .gallery-body { grid-template-columns: 200px 1fr; } }
@media (max-width: 680px)  {
  /* stack: sidebar becomes a horizontal scrolling chip bar above the grid */
  .gallery-body { grid-template-columns: 1fr; padding: 10px; gap: 10px; }
  .sidebar {
    position: static;
    max-height: none;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 10px 12px;
    scrollbar-width: none;
  }
  .sidebar::-webkit-scrollbar { display: none; }
  .sidebar-header { display: none; }
  .sidebar-nav { flex-direction: row; gap: 8px; }
  .nav-item, .nav-folder, .nav-leaf {
    flex-shrink: 0;
    white-space: nowrap;
    padding: 8px 14px;
    background: var(--panel-2);
    border-radius: 9999px;
  }
  .nav-item .chev, .nav-folder .chev { display: none; }
  .hu-count, .nav-folder [class*="count"] { margin-left: 6px; }
  .creator { margin-top: 20px; }
  .block-detail { padding: 20px 18px 120px; }
  .block-title { font-size: 32px; }
  .hero-center { padding: 16px 18px 132px; }  /* extra bottom clearance so the pane's last rows + socials never sit under the fixed capsule on phones */
  .main-inner { padding: 14px 12px 120px; }
  /* homepage category rows: tighter type so long labels fit a phone */
  .cat-name { font-size: 13.5px; }
  .cat-meta { font-size: 12px; gap: 6px; }
  .cat-row { gap: 8px; }
  .cat-line { min-width: 18px; }
  .hero-sub { font-size: 12.5px; padding: 0 8px; }
  /* trim the pane's side padding on phones so the category rows never overflow the viewport */
  .hero-pane { padding: 26px 22px 22px; }
}

/* ============================================================
   BLOCKS detail page
============================================================ */
.block-detail { padding: 28px 40px 60px; max-width: 62rem; }
.block-eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 14px;
}
.block-title {
  margin: 0 0 14px;
  font-family: var(--font-sans);
  font-weight: 500;
  letter-spacing: -0.03em;
  font-size: 44px;
  color: #f0f0f0;
}
.block-desc { margin: 0 0 22px; max-width: 60ch; font-size: 18px; line-height: 1.55; color: var(--muted); }
.block-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 40px; }
.block-tag {
  padding: 4px 12px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.06);
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
}
.block-section-label {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 16px;
}
.block-stage {
  width: 460px;
  max-width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 20px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 40px;
}

@media (prefers-reduced-motion: reduce) {
  .reveal, .cat-item { animation: none !important; opacity: 1 !important; filter: none !important; transform: none !important; }
  /* freeze the hero pane's premium touches to their resting state */
  .hero-pane::before { animation: none; transform: none; opacity: 0.5; }
  .hero-pane__sheen { animation: none; display: none; }
  .cat-row:hover .cat-line path, .cat-row:focus-visible .cat-line path { animation: none; }
  /* social dock: no bob, no magnify/lift. Orbs stay static + clearly visible;
     the accent warm + label (opacity) still give non-motion feedback. */
  .social-orb { animation: none; }
  .social-orb__glass { transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; }
  .social-orb:hover .social-orb__glass, .social-orb:focus-visible .social-orb__glass { transform: none; }
  .social-orb::after { transition: opacity 0.15s ease; transform: translate(-50%, 0); }
}

/* ============================================================
   LIGHT THEME / PHASE 2 COMPONENT SURFACES
   Component-level values that hardcode dark ink / white washes and so
   bypass the theme tokens: homepage hero + blocks titles, homepage connector
   lines, blocks tag chips, social-icon hover, gallery sidebar hover, and
   gallery card border / hover shadow / caption pill. The dark rules above stay
   the default and are untouched; every rule here is prefixed with
   :root[data-theme="light"] so it (a) only applies in light and (b) outranks
   the base rule on specificity regardless of source order. WCAG AA verified.
============================================================ */

/* Homepage hero + blocks detail titles: hardcoded near-white (#f2f2f2 / #f0f0f0)
   are invisible on the light field -> theme foreground ink (~17:1 on the bg). */
:root[data-theme="light"] .hero-title { color: var(--foreground); }
:root[data-theme="light"] .block-title { color: var(--foreground); }

/* Homepage connector lines: white hairline (currentColor) disappears on light.
   Dark ink at a matching low alpha; path opacity nudged up so the product of
   the two stays legible (mirrors the white-on-black faintness in dark). */
:root[data-theme="light"] .cat-line { color: rgba(20, 20, 26, 0.55); }
:root[data-theme="light"] .cat-line path { opacity: 0.35; }

/* Blocks tag chips: white .06 fill vanishes on light -> subtle dark tint. */
:root[data-theme="light"] .block-tag { background: rgba(0, 0, 0, 0.06); }

/* Creator social icons: the dark hover set the glyph to #fff on --panel-2
   (near-white in light) = invisible. Light hover fills the accent instead,
   white glyph on #1E22E6 (~10.6:1), a clean on-brand inversion. */
/* Light: the social orbs flip to light glass; the dark --accent-soft token is
   already #1E22E6 (~8:1 on white) so the brand glyphs stay AA over the light
   frost at rest. Hover warms to the accent fill with a white glyph. */
:root[data-theme="light"] .social-orb__glass {
  /* opaque light glass chip + accent glyph #1E22E6 (~10.6:1 on the chip), so the
     icons are clearly visible at rest on the light pane. */
  color: var(--accent);
  background: rgba(255, 255, 255, 0.92);
  border-color: rgba(0, 0, 0, 0.10);
  box-shadow: 0 10px 26px rgba(28, 28, 60, 0.16), 0 4px 16px rgba(30, 34, 230, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.95);
}
:root[data-theme="light"] .social-orb:hover .social-orb__glass,
:root[data-theme="light"] .social-orb:focus-visible .social-orb__glass {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 18px 36px rgba(28, 28, 60, 0.2), 0 8px 26px rgba(30, 34, 230, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
:root[data-theme="light"] .social-orb:focus-visible .social-orb__glass {
  box-shadow: 0 18px 36px rgba(28, 28, 60, 0.2), 0 0 0 3px rgba(30, 34, 230, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
:root[data-theme="light"] .social-orb::after {
  color: #14141a;
  background: rgba(255, 255, 255, 0.86);
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 8px 22px rgba(28, 28, 60, 0.14);
}

/* Gallery sidebar hover: white .04 wash is invisible on the white --panel. */
:root[data-theme="light"] .nav-folder:hover,
:root[data-theme="light"] .nav-leaf:hover,
:root[data-theme="light"] .nav-item:hover:not(.active) {
  background: rgba(0, 0, 0, 0.04);
}

/* Gallery cards: the white .04 edge is invisible on the light shell (the card
   stays white in both themes) -> the tokenized dark card border. The hover
   shadow (rgba(0,0,0,.5)) reads as a hard black smudge on light -> softened,
   tinted lift matching the light --shadow tokens. */
:root[data-theme="light"] .card { border-color: var(--card-border); }
:root[data-theme="light"] .card:hover {
  box-shadow: 0 14px 34px -8px rgba(20, 20, 40, 0.20);
}

/* Card caption pill: the near-black pill is too heavy for the light field.
   Light frosted-glass pill with dark ink; border + soft shadow keep it
   delineated even over pale card artwork. Backdrop blur carries over. */
:root[data-theme="light"] .card .see {
  color: #14141a;
  background: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(0, 0, 0, 0.08);
  box-shadow: 0 2px 10px rgba(20, 20, 40, 0.12);
}

/* home hero brand lockup (rebrand: drift* wordmark over the hero) */
.hero-brand { display: flex; align-items: flex-start; justify-content: center; gap: 4px; margin: 0 0 16px; color: var(--foreground); }
.hero-brand__wm { font-family: var(--font-sans); font-weight: 700; font-size: 38px; letter-spacing: 0.02em; line-height: 1; }
.hero-brand__ast { width: 21px; height: 21px; margin-top: 1px; flex: none; transform-origin: 50% 50%; animation: driftAstSpin 6s linear infinite; }
@keyframes driftAstSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .hero-brand__ast { animation: none; } }
:root[data-theme="light"] .hero-brand__ast { color: var(--accent); }

/* brand logo at the top of the sidebar (content pages: ui, icons, loaders, interaction, glass) */
.drift-brand { display: flex; align-items: center; gap: 10px; padding: 2px 8px 16px; text-decoration: none; }
.drift-brand__tile { width: 30px; height: 30px; border-radius: 50%; flex: none; background: var(--accent); display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 18px rgba(30, 34, 230, 0.40); }
.drift-brand__tile svg { width: 17px; height: 17px; display: block; transform-origin: 50% 50%; animation: driftAstSpin 6s linear infinite; }
.drift-brand__wm { font-family: var(--font-mono); font-weight: 600; font-size: 14.5px; letter-spacing: 0.18em; color: var(--foreground); line-height: 1; }
@media (prefers-reduced-motion: reduce) { .drift-brand__tile svg { animation: none; } }
.drift-brand:hover .drift-brand__tile { box-shadow: 0 6px 22px rgba(30, 34, 230, 0.6); }

/* ===== drift-brand as a DRIFTING WHEEL: tyre ring + hover spin-up/steer ===== */
.drift-brand__tile { box-shadow: inset 0 0 0 3px rgba(255,255,255,.28), inset 0 2px 3px rgba(255,255,255,.5), inset 0 -3px 6px rgba(12,17,82,.5), 0 6px 18px rgba(30,34,230,.40); transition: transform .5s cubic-bezier(.34,1.56,.64,1); }
:root[data-theme="light"] .drift-brand__tile { box-shadow: inset 0 0 0 3px rgba(255,255,255,.5), inset 0 2px 3px rgba(255,255,255,.75), inset 0 -3px 6px rgba(12,17,82,.38), 0 6px 18px rgba(30,34,230,.28); }
.drift-brand:hover .drift-brand__tile { transform: rotate(18deg) translate(3px,1px); }
.drift-brand:hover .drift-brand__tile svg { animation-duration: .5s; }
.drift-brand__wm { display: inline-block; transition: transform .5s cubic-bezier(.34,1.56,.64,1); }
.drift-brand:hover .drift-brand__wm { transform: skewX(-8deg) translateX(3px); }
@media (prefers-reduced-motion: reduce) { .drift-brand:hover .drift-brand__tile, .drift-brand:hover .drift-brand__wm { transform: none; } }

/* ============================================================
   GLASS DOCK RAIL (persistent nav v3)
   Desktop (>860px): fixed LEFT frosted rail, vertically centered — wheel
   brand, five section icons, theme toggle, compact London clock. Mobile
   (<=860px): fixed BOTTOM glass tab bar (6 cells, safe-area aware).
   Built by nav.js; Motion springs drive the entrance stagger, the morphing
   accent pill, the label flyouts and the press pops — this section is the
   static glass. The bloom-capsule styles above are retired (dead, kept for
   compat). Stacking: rail + tab bar at z-index 116, below the /icons drawer
   (120) and every modal (120/130/200).
============================================================ */

/* content clears the fixed rail on desktop */
/* Rail clearance on CONTENT shells only. The old body{padding-left} shifted
   EVERYTHING right - section hairlines stopped short of the viewport edge and
   the hero dither canvas left a dead unpainted strip behind the rail. Backgrounds
   and rules now run full-bleed; the glass rail floats over them. */
@media (min-width: 861px) {
  .gallery-body { padding-left: 84px; }
  .st-app { padding-left: 84px; }
}


.nav-rail {
  position: fixed;
  left: 14px;
  top: 50%;
  translate: 0 -50%; /* CSS translate property: composes with Motion's entrance scale */
  z-index: 116;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  width: 64px;
  padding: 12px 0 10px;
  border-radius: 20px;
  color: var(--foreground);
  background: rgba(18, 18, 24, 0.55);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  backdrop-filter: blur(22px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  -webkit-tap-highlight-color: transparent;
}
:root[data-theme="light"] .nav-rail {
  background: rgba(255, 255, 255, 0.6);
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 8px 32px rgba(20, 20, 40, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}

/* drift wheel brand: 30px accent circle + white asterisk; the wheel spins up
   on hover (reuses the sitewide driftAstSpin keyframes) */
.rail-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 40px;
  border-radius: 12px;
  -webkit-tap-highlight-color: transparent;
}
.rail-brand__tile {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex: none;
  background: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: inset 0 0 0 3px rgba(6, 6, 8, 0.45), 0 6px 18px rgba(30, 34, 230, 0.4);
  transition: box-shadow 0.2s ease, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
:root[data-theme="light"] .rail-brand__tile {
  box-shadow: inset 0 0 0 3px rgba(6, 6, 8, 0.28), 0 6px 18px rgba(30, 34, 230, 0.3);
}
.rail-brand__tile svg {
  width: 17px;
  height: 17px;
  display: block;
  transform-origin: 50% 50%;
  animation: driftAstSpin 6s linear infinite;
}
.rail-brand:hover .rail-brand__tile {
  transform: rotate(14deg);
  box-shadow: inset 0 0 0 3px rgba(6, 6, 8, 0.45), 0 6px 22px rgba(30, 34, 230, 0.6);
}
.rail-brand:hover .rail-brand__tile svg { animation-duration: 0.5s; }
.rail-brand:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }

.nav-rail-div { width: 28px; height: 1px; flex: none; background: rgba(255, 255, 255, 0.1); }
:root[data-theme="light"] .nav-rail-div { background: rgba(0, 0, 0, 0.08); }

.nav-rail-group {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
}

/* ONE shared glass chip that MORPHS (translateY spring) between icon slots:
   the glass itself flows to the hovered / active section. Accent kept subtle. */
.rail-pill {
  position: absolute;
  top: 0;
  left: calc(50% - 22px);
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: rgba(91, 107, 255, 0.16);
  border: 1px solid rgba(91, 107, 255, 0.28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  will-change: transform, opacity;
}
:root[data-theme="light"] .rail-pill {
  background: rgba(30, 34, 230, 0.09);
  border-color: rgba(30, 34, 230, 0.22);
}

.rail-btn {
  position: relative;
  z-index: 1;
  width: 44px;
  height: 44px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.16s ease;
  will-change: transform, opacity;
}
.rail-btn:hover, .rail-btn.active { color: var(--foreground); }
.rail-btn:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; color: var(--foreground); }
.rail-btn svg { display: block; }

/* magnetic magnification layer: nav.js sets scale() here on mousemove
   (cosine falloff); the transition supplies the ~160ms settle. A separate
   layer from .rail-pop so Motion's press pop never fights it. */
.rail-mag {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  transform-origin: 50% 50%;
  transition: transform 0.16s cubic-bezier(0.3, 0.7, 0.4, 1);
  will-change: transform;
}
.rail-pop { display: flex; align-items: center; justify-content: center; }

/* glass label flyout to the right of the icon (Motion springs x/scale in
   after a 150ms hover-intent delay) */
.rail-flyout {
  position: absolute;
  left: calc(100% + 16px);
  top: 50%;
  translate: 0 -50%; /* composes with Motion's x / scale transform */
  padding: 8px 13px;
  border-radius: 11px;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: -0.01em;
  color: var(--foreground);
  background: rgba(18, 18, 24, 0.62);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  backdrop-filter: blur(12px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
:root[data-theme="light"] .rail-flyout {
  background: rgba(255, 255, 255, 0.78);
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 8px 24px rgba(20, 20, 40, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}

/* compact London clock: HH over MM, tight mono stack */
.rail-clock {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding-top: 2px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  -webkit-user-select: none;
  user-select: none;
}

/* ---- mobile bottom glass tab bar (same glass recipe as the rail) ---- */
.nav-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 116;
  display: flex;
  align-items: stretch;
  height: calc(62px + env(safe-area-inset-bottom, 0px));
  padding: 0 8px env(safe-area-inset-bottom, 0px);
  background: rgba(18, 18, 24, 0.55);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  backdrop-filter: blur(22px) saturate(1.3);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  -webkit-tap-highlight-color: transparent;
  will-change: transform, opacity;
}
:root[data-theme="light"] .nav-tabbar {
  background: rgba(255, 255, 255, 0.6);
  border-top-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 -8px 32px rgba(20, 20, 40, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.tab-cell {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  -webkit-tap-highlight-color: transparent;
  will-change: transform, opacity;
}
.tab-pop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  max-width: 100%;
}
.tab-cell svg { display: block; width: 19px; height: 19px; }
.tab-cell-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tab-cell.active { color: var(--accent-soft); }
:root[data-theme="light"] .tab-cell.active { color: var(--accent); }
.tab-cell:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: -3px; border-radius: 12px; }
/* accent dot that springs between cells (translateX set by nav.js / Motion) */
.tab-dot {
  position: absolute;
  top: 7px;
  left: 0;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent-soft);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
:root[data-theme="light"] .tab-dot { background: var(--accent); }

@media (max-width: 860px) { .nav-rail { display: none; } }
@media (min-width: 861px) { .nav-tabbar { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .rail-mag { transition: none; }
  .rail-brand__tile { transition: none; }
  .rail-brand__tile svg { animation: none; }
  .rail-brand:hover .rail-brand__tile { transform: none; }
}
