/* ============================================================
   shared.css — chrome shared between home.html and subpages.
   Canonical source: home.html (more recent comments + tighter
   formatting). Drift absorbed during extraction:
     * tulpas's @media(max-width:700px) overlay-mobile rules were
       a subset of home's; home's fuller rule set kept.
     * tulpas's GDPR sub-rules used hardcoded font-size 11/12px,
       home's uses var(--fs-small) — home's version kept.
     * tulpas declares html{overflow-x:hidden;} body{...}; home
       declares html,body{...}. We adopt tulpas's looser version
       so subpage long scrolling works; home re-imposes its own
       hard-overflow inline next to its hero rules.
     * .mstack[data-overlay]/.mstack-close/.bg-preview/.preview-label
       are tulpas-only but harmless when included site-wide (they
       only activate when the matching markup attribute is present
       in chrome.js's "sub" mode).
     * Same applies to html.video-fs-landscape suppressor.
   ============================================================ */

*{box-sizing:border-box;margin:0;padding:0;font-family:'Safiro',sans-serif!important;text-transform:none!important;}
:root {
  --r:95; --g:90; --b:85;
  --acc: rgb(var(--r),var(--g),var(--b));
  --acc-muted: rgba(var(--r),var(--g),var(--b),0.12);
  --acc-border: rgba(var(--r),var(--g),var(--b),0.28);
  --acc-strong: rgba(var(--r),var(--g),var(--b),0.45);
  --bg-solid: #fafaf9;
  --bg-panel: rgba(250,250,249,0.80);
  --bg-panel-strong: rgba(252,252,251,0.90);
  --border: rgba(var(--r),var(--g),var(--b),0.12);
  --border-mid: rgba(var(--r),var(--g),var(--b),0.22);
  --text-dim: rgba(40,38,36,0.40);
  --text-mid: rgba(40,38,36,0.65);
  --text-bright: rgba(20,18,16,0.92);
  --gdpr-h: 0px; --bar-w:380px; --sliver:52px;
  --fs-hero: 50px;
  --fs-display: 48px;
  --fs-h1: 32px;
  --fs-h2: 18px;
  --fs-body: 16px;
  --fs-small: 12px;

  /* ── LIQUID-GLASS TOKENS ────────────────────────────────────────
     Single source of truth for the "Apple-style liquid glass" look
     used across pill buttons, close discs, sticky chrome surfaces,
     hover affordances, etc. Change a token here and every glass
     element in the site updates. */

  /* Saturated/brightened backdrop-filter shared by all variants. */
  --glass-filter: blur(24px) saturate(200%) brightness(1.05);

  /* LIGHT variant — frosted cream pill on a light surface. */
  --glass-light-bg:        rgba(255,255,255,0.30);
  --glass-light-bg-hover:  rgba(255,255,255,0.42);
  --glass-light-bg-active: rgba(255,255,255,0.55);
  --glass-light-border:    rgba(255,255,255,0.55);
  --glass-light-border-hover: rgba(255,255,255,0.75);
  --glass-light-shadow:
    0 8px 24px rgba(20,18,16,0.08),
    0 2px 4px rgba(20,18,16,0.04),
    inset 0 1.4px 0 rgba(255,255,255,0.85),
    inset 0 3px 4px rgba(255,255,255,0.35),
    inset 0 -1px 0 rgba(20,18,16,0.06),
    inset 0 0 0 0.5px rgba(255,255,255,0.40);
  --glass-light-shadow-active:
    0 2px 6px rgba(20,18,16,0.06),
    inset 0 1px 0 rgba(255,255,255,0.55),
    inset 0 -1px 0 rgba(20,18,16,0.10);

  /* PRIMARY variant — luminous "important" affirmative CTA.
     A bright chrome-cream glass with an OUTER glow so primary
     actions look lit-up and lifted against the page chrome. */
  --glass-primary-bg:        rgba(255,255,255,0.65);
  --glass-primary-bg-hover:  rgba(255,255,255,0.82);
  --glass-primary-bg-active: rgba(255,255,255,0.92);
  --glass-primary-border:    rgba(255,255,255,0.95);
  --glass-primary-shadow:
    0 0 0 4px rgba(255,255,255,0.18),
    0 0 24px rgba(255,255,255,0.30),
    0 10px 28px rgba(20,18,16,0.10),
    0 2px 6px rgba(20,18,16,0.05),
    inset 0 1.6px 0 rgba(255,255,255,0.95),
    inset 0 4px 6px rgba(255,255,255,0.45),
    inset 0 -1px 0 rgba(20,18,16,0.06),
    inset 0 0 0 0.5px rgba(255,255,255,0.55);

  /* ON-DARK variant — light tint pill over photographic backgrounds. */
  --glass-dark-bg:        rgba(240,238,235,0.16);
  --glass-dark-bg-hover:  rgba(240,238,235,0.28);
  --glass-dark-bg-active: rgba(240,238,235,0.42);
  --glass-dark-border:    rgba(240,238,235,0.35);
  --glass-dark-shadow:
    0 6px 18px rgba(0,0,0,0.30),
    0 1px 2px rgba(0,0,0,0.18),
    inset 0 1.2px 0 rgba(255,255,255,0.40),
    inset 0 2px 3px rgba(255,255,255,0.12),
    inset 0 -1px 0 rgba(0,0,0,0.18),
    inset 0 0 0 0.5px rgba(255,255,255,0.18);

  /* SURFACE variant — applied to large chrome panels (sticky header,
     gdpr banner, sidebar). Just the saturate-blur + a top edge
     specular; each panel keeps its own bg/border. */
  --glass-surface-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.75),
    inset 0 4px 6px rgba(255,255,255,0.18),
    0 1px 2px rgba(20,18,16,0.03);
}
@media(max-width:700px){
  :root{
    --fs-hero: 52px;
    --fs-display: 58px;
    --fs-h1: 38px;
    --fs-h2: 22px;
    --fs-body: 18px;
    --fs-small: 14px;
  }
}

/* All subpages share the same large hero title size for .series-title.
   Originally each subpage defined this inline; hoisted here so any new
   subpage automatically gets it without per-page CSS. Mobile keeps the
   38px from the @media block above. */
body[data-page="sub"]{ --fs-h1: 52px; }
@media(max-width:700px){ body[data-page="sub"]{ --fs-h1: 38px; } }

.sub-link {
  display:inline-block;
  width:auto;
  font-size:var(--fs-h2); letter-spacing:0.14em;
  color:var(--text-bright);
  text-decoration:none; background:none; border:none;
  cursor:pointer; padding:0;
  position:relative; transition:color 0.3s;
  white-space:nowrap;
}
.sub-link::after {
  content:''; position:absolute; bottom:-3px; left:0;
  width:100%; height:2px; background:var(--acc);
  transform-origin:left;
  animation:pulse-ul 3s ease-in-out infinite;
}
/* DRIFT NOTE: tulpas's pulse-ul keyframe used 0.2/0.7 opacity stops;
   home's uses 0.15/0.75. Home is canonical. */
@keyframes pulse-ul{0%{opacity:0.15;transform:scaleX(0.2);}55%{opacity:0.75;transform:scaleX(1);}100%{opacity:0.15;transform:scaleX(0.2);}}
.sub-link:hover{color:var(--text-bright);}
.sub-link:hover::after{animation:none;opacity:0.9;transform:scaleX(1);}

/* ─────────────────────────────────────────────────────────────────
   LIQUID-GLASS BUTTON VOCABULARY (shared)
   ─────────────────────────────────────────────────────────────────
   Apple-style "liquid glass" treatment: low-opacity white tint,
   saturated/brightened backdrop-blur (so colour passes through
   the pill rather than being dulled), strong specular top edge,
   subtle bottom inset and refractive inner ring, plus a soft
   outer shadow to lift the surface. Applied to every round / pill
   chrome element via the .liquid-glass utility class AND to the
   matching button selectors directly (so existing pages don't need
   markup changes to inherit the look). Per-button rules layered
   on top control size, padding, radius, and any colour overrides.
   ───────────────────────────────────────────────────────────────── */
/* ── LIGHT pill variant (default frosted glass on light surfaces) ── */
.liquid-glass,
.ov-close,
.mstack-close,
.mjump-close,
.mob-menu-btn,
.choice-strip-decline,
.choice-strip-customize,
.cookie-save-btn,
.lb-close{
  background:var(--glass-light-bg) !important;
  border:1px solid var(--glass-light-border) !important;
  backdrop-filter:var(--glass-filter) !important;
  -webkit-backdrop-filter:var(--glass-filter) !important;
  box-shadow:var(--glass-light-shadow) !important;
  color:var(--text-mid);
  -webkit-text-fill-color:var(--text-mid);
  -webkit-tap-highlight-color:transparent;
}
.liquid-glass:hover,
.ov-close:hover,
.mstack-close:hover,
.mjump-close:hover,
.mob-menu-btn:hover,
.choice-strip-decline:hover,
.choice-strip-customize:hover,
.cookie-save-btn:hover,
.lb-close:hover{
  background:var(--glass-light-bg-hover) !important;
  border-color:var(--glass-light-border-hover) !important;
  color:var(--text-bright);
  -webkit-text-fill-color:var(--text-bright);
}
.liquid-glass:active,
.ov-close:active,
.mstack-close:active,
.mjump-close:active,
.mob-menu-btn:active,
.choice-strip-decline:active,
.choice-strip-customize:active,
.cookie-save-btn:active,
.lb-close:active{
  background:var(--glass-light-bg-active) !important;
  box-shadow:var(--glass-light-shadow-active) !important;
}

/* ── PRIMARY (luminous chrome-cream affirmative CTAs) ──
   Equilibrium / workshop CTAs (.ws-cta-btn / .ws-submit / .ws-book-link)
   intentionally NOT included — they keep their original page-specific
   styling so equilibrium and the workshop pages don't read as iOS chrome.
   .sdr-submit-btn (subscribe form submit) also reverted out — the
   sender.net form lives inside the subscribe overlay and reads better
   in its own original brutalist treatment than as a pill-cream chrome. */
.liquid-glass.is-primary,
.choice-strip-accept,
.cookie-accept-all-btn{
  background:var(--glass-primary-bg) !important;
  border:1px solid var(--glass-primary-border) !important;
  backdrop-filter:var(--glass-filter) !important;
  -webkit-backdrop-filter:var(--glass-filter) !important;
  color:var(--text-bright) !important;
  -webkit-text-fill-color:var(--text-bright) !important;
  box-shadow:var(--glass-primary-shadow) !important;
  border-radius:999px !important;
  -webkit-tap-highlight-color:transparent;
}
.liquid-glass.is-primary:hover,
.choice-strip-accept:hover,
.cookie-accept-all-btn:hover{
  background:var(--glass-primary-bg-hover) !important;
}
.liquid-glass.is-primary:active,
.choice-strip-accept:active,
.cookie-accept-all-btn:active{
  background:var(--glass-primary-bg-active) !important;
}

/* ── ON-DARK (light pill on photographic / dark backgrounds) ──
   `#opv-cta-btn` (the overscroll preview "go to" CTA, injected by
   subpage.js into #opv-card at the bottom of every series page)
   was previously in this group too. Pulled out so the membrane.js
   wiring can paint the dark frosted membrane material on it
   uncontested — same pattern as .priv-cookies-cta button and the
   other membrane'd buttons. */
.liquid-glass.is-on-dark,
.mcard-cta-arrow,
.mhint{
  background:var(--glass-dark-bg) !important;
  border:1px solid var(--glass-dark-border) !important;
  backdrop-filter:var(--glass-filter) !important;
  -webkit-backdrop-filter:var(--glass-filter) !important;
  color:#f0eeeb !important;
  -webkit-text-fill-color:#f0eeeb !important;
  box-shadow:var(--glass-dark-shadow) !important;
  -webkit-tap-highlight-color:transparent;
}
.liquid-glass.is-on-dark:hover{
  background:var(--glass-dark-bg-hover) !important;
  border-color:rgba(240,238,235,0.55) !important;
}
.liquid-glass.is-on-dark:active{
  background:var(--glass-dark-bg-active) !important;
}

/* ── SURFACE (large chrome panels share the saturate-blur + a top
   edge specular; each panel keeps its own bg/border). ──
   .lightbox is intentionally NOT included here: it has its own custom
   blur(48px) backdrop, and the surface's brightness(1.05) + heavy
   --glass-surface-shadow on top of the lightbox's 92%-opaque cream
   background made the whole overlay read as a solid white wall —
   image inside became invisible.

   `.page-header-sticky` and `.left-bar` were ALSO previously in this
   group — pulled out as part of the membrane integration:
     · sticky's compact state is rendered by the injected pill-surface
       (membrane.js); the parent must be transparent so the membrane
       reads cleanly. Non-compact bg/blur is handled below in the
       SUBPAGE PAGE-HEADER section, scoped via :not(.compact).
     · .left-bar's heavy 24px backdrop-filter was producing a huge
       painting-blurred-through-bar halo on desktop subpages. The bar
       has its own translucent bg + box-shadow that read as glass on
       their own, so the backdrop-filter was net-negative. */
.liquid-surface,
body[data-page="sub"] .doc-header,
.choice-strip,
.ov-bottom,
.ov-panel{
  backdrop-filter:var(--glass-filter) !important;
  -webkit-backdrop-filter:var(--glass-filter) !important;
  box-shadow:var(--glass-surface-shadow) !important;
}
/* `.bg-preview-mirror` was previously in the surface group above —
   pulled out because it's a 380px-wide fixed strip at left:0, and its
   24px backdrop-filter was sampling whatever the menu's preview-stage
   was rendering and blurring it across the full bar footprint. The
   user reported this as the persistent "blur behind the menu in
   collapsed state" on desktop subpages. The mirror's child
   .bg-preview-mirror-img provides the visual on its own — no
   backdrop-filter needed. */

/* ── Desktop menu rows — pronounced light-glass on hover. ──
   The .left-bar already has the saturate-blur via .liquid-surface,
   so we just paint the higher-tint fill + the edge-highlights here. */
@media(min-width:701px){
  .snav-item{ transition:background 0.18s ease, box-shadow 0.18s ease, color 0.2s; }
  .snav-item:hover,
  .snav-item.active{
    background:var(--glass-light-bg-active) !important;
    box-shadow:
      inset 0 1.5px 0 rgba(255,255,255,0.85),
      inset 0 4px 6px rgba(255,255,255,0.22),
      inset 0 -1px 0 rgba(20,18,16,0.05),
      inset 0 0 0 0.5px rgba(255,255,255,0.50) !important;
  }
  .bar-nav-link{ transition:background 0.2s, padding 0.25s, margin 0.25s, box-shadow 0.2s; }
  .bar-nav-link:hover{
    background:rgba(255,255,255,0.45);
    border-radius:6px;
    box-shadow:
      inset 0 1.2px 0 rgba(255,255,255,0.75),
      inset 0 3px 4px rgba(255,255,255,0.18);
    padding-left:8px;padding-right:8px;
    margin-left:-8px;margin-right:-8px;
  }
}

/* ── Per-element overrides on top of the shared variants ──
   These are PURELY about size/shape/typography; the glass recipe
   itself comes from the variant rules above. */
.slide-prev,.slide-next{
  border-radius:999px !important;
  padding:10px 22px !important;
  cursor:pointer !important;
  font-family:'Safiro',sans-serif !important;
  letter-spacing:0.14em !important;
}
/* Overscroll preview round buttons (subpage.js #opv-card area).
   The CTA is a pill, the nav arrows are round discs. The pill +
   disc shapes both inherit the .is-on-dark glass treatment above
   from the selector list, so here we just lock the radius. */
#opv-cta-btn{
  border-radius:999px !important;
  padding:14px 30px !important;
}

/* Workshop / equilibrium page CTAs were previously force-rounded here.
   Reverted — let those pages keep their original button geometry. */
@media(max-width:700px){
  .mjump-cell:hover,
  .mjump-cell:active{
    box-shadow:
      inset 0 0 0 1px rgba(255,255,255,0.35),
      inset 0 1px 0 rgba(255,255,255,0.55);
  }
}

/* ── OVERLAYS ─────────────── */
.overlay{position:fixed;inset:0;z-index:10000;pointer-events:none;display:none;}
.overlay.open{pointer-events:all;display:block;}
.ov-bd{position:absolute;inset:0;background:rgba(250,250,249,0.50);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);opacity:0;transition:opacity 0.35s;z-index:1;}
.overlay.open .ov-bd{opacity:1;}
.ov-panel{
  position:absolute;top:0;right:-500px;width:460px;height:100dvh;
  background:var(--bg-panel);
  backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);
  border-left:1px solid var(--border-mid);
  overflow-y:auto;
  transition:right 0.38s cubic-bezier(0.25,0.46,0.45,0.94);
  display:flex;flex-direction:column;
  box-shadow:-4px 0 24px rgba(0,0,0,0.04);
  z-index:2;
}
.overlay.open .ov-panel{right:0;}
.ov-top{
  display:flex;justify-content:space-between;align-items:center;
  padding:20px 28px;border-bottom:1px solid var(--border);flex-shrink:0;
}
.ov-label{font-size:var(--fs-small);letter-spacing:0.22em;color:var(--text-dim);line-height:1;display:flex;align-items:center;}
.ov-close{
  width:36px;height:36px;background:none;
  border:1px solid var(--border-mid);border-radius:50%;cursor:pointer;
  position:relative;transition:all 0.2s;flex-shrink:0;
}
.ov-close:hover{border-color:var(--acc-strong);background:var(--acc-muted);}
.ov-close::before,.ov-close::after{
  content:'';position:absolute;top:50%;left:50%;
  width:14px;height:1px;background:var(--text-mid);transform-origin:center;
}
.ov-close::before{transform:translate(-50%,-50%) rotate(45deg);}
.ov-close::after{transform:translate(-50%,-50%) rotate(-45deg);}
.ov-close:hover::before,.ov-close:hover::after{background:var(--text-bright);}
.ov-body{padding:28px;flex:1;}

.portrait{width:100%;aspect-ratio:4/3;object-fit:cover;margin-bottom:16px;border:1px solid var(--border);}
.portrait-credit{font-size:var(--fs-small);letter-spacing:0.12em;color:var(--text-dim);margin-bottom:22px;}
.about-p{font-size:var(--fs-body);line-height:1.9;color:var(--text-mid);margin-bottom:14px;}
/* About + privacy overlays: pin the action row to the bottom of the panel.
   `.ov-bottom` is a real sibling of `.ov-body` inside `.ov-panel`. The panel
   stops being its own scroll container — `.ov-body` scrolls internally
   instead. The bottom strip is a flex sibling that doesn't shrink, so it
   stays pinned at the panel bottom. Uses !important on the structural
   declarations because the base `.ov-panel{display:flex;...overflow-y:auto}`
   rule has equal specificity weight in the cascade and order-of-loading
   could otherwise win. */
#ov-about .ov-panel,
#ov-privacy .ov-panel{
  display:flex !important;
  flex-direction:column !important;
  overflow:hidden !important;
}
#ov-about .ov-body,
#ov-privacy .ov-body{
  flex:1 1 0 !important;
  min-height:0 !important;
  overflow-y:auto !important;
  -webkit-overflow-scrolling:touch;
}
#ov-about .ov-bottom,
#ov-privacy .ov-bottom{
  flex:0 0 auto !important;
}
.ov-bottom{
  background:var(--bg-panel-strong);
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  border-top:1px solid var(--border);
  z-index:2;
}
/* Desktop tweaks for the about overlay: smaller body text. The button row
   keeps the shared `.about-links` font-size (var(--fs-body) = 16px) — we
   no longer down-size the buttons themselves. */
@media(min-width:701px){
  #ov-about .about-p{font-size:14px;line-height:1.85;}
  #ov-about .about-links{padding:14px 28px;margin:0;border-top:none;}
}
/* About-overlay link row: portfolio | cv | contact | privacy on one line.
   Same font size, even spacing, vertical separators between links.
   Mirrors the original genesis.html layout — canonical for all pages. */
.about-links{
  display:flex;gap:0;flex-wrap:wrap;justify-content:center;
  border-top:1px solid var(--border);padding-top:20px;margin-top:24px;
}
.about-links a{
  font-size:var(--fs-body);letter-spacing:0.1em;color:var(--text-mid);
  text-decoration:none;padding:6px 14px;
  border-right:1px solid var(--border-mid);
  border-bottom:1px solid transparent;transition:all 0.2s;
}
.about-links a:last-child{border-right:none;}
.about-links a:hover{color:var(--text-bright);border-bottom-color:var(--acc-border);}

.sub-big{font-size:var(--fs-display);font-weight:600;color:var(--text-bright);letter-spacing:-0.02em;line-height:0.92;margin-bottom:22px;}
.sub-intro{font-size:var(--fs-body);line-height:1.85;color:var(--text-bright);margin-bottom:26px;letter-spacing:0.04em;}
.priv-section{margin-bottom:22px;}
.priv-section h3{font-size:var(--fs-body);font-weight:600;letter-spacing:0.12em;color:var(--text-bright);margin-bottom:10px;}
.priv-section p,.priv-section li{font-size:var(--fs-body);line-height:1.85;color:var(--text-bright);margin-bottom:6px;opacity:0.85;}
/* Privacy overlay: same font scale as cookie overlay (14px desktop / 15px
   mobile) so the two policies feel coherent. The default .priv-section sizes
   above are used by other consumers (e.g. inline policy excerpts) so we
   override only inside #ov-privacy. */
#ov-privacy .sub-intro{font-size:14px;line-height:1.8;letter-spacing:0.04em;}
#ov-privacy .priv-section h3{font-size:13px;letter-spacing:0.12em;}
#ov-privacy .priv-section p,
#ov-privacy .priv-section li{font-size:14px;line-height:1.8;}
#ov-privacy .priv-eff{font-size:12px;}
/* Cookie-settings CTA pinned to bottom of the privacy overlay. Lives as a
   sibling of .ov-body inside .ov-panel — same .ov-bottom flex-shrink:0 base
   styling as the about-overlay action row. */
.priv-cookies-cta{
  padding:14px 28px;
  display:flex;justify-content:center;
}
.priv-cookies-cta button{
  font-size:13px;letter-spacing:0.1em;color:var(--text-mid);
  text-decoration:none;padding:10px 22px;
  background:transparent;border:1px solid var(--border-mid);
  border-radius:999px;cursor:pointer;transition:all 0.2s;
}
.priv-cookies-cta button:hover{color:var(--text-bright);border-color:var(--acc-strong);background:var(--acc-muted);}
@media(max-width:700px){
  .priv-cookies-cta{
    padding:18px 28px calc(18px + env(safe-area-inset-bottom,0px));
  }
  .priv-cookies-cta button{font-size:14px;padding:10px 22px;}
}
/* DRIFT NOTE: tulpas had only ".priv-section ul{padding-left:16px;}";
   home has ul,ol{padding-left:18px;}. Home is canonical. */
.priv-section ul,.priv-section ol{padding-left:18px;}
.priv-section a{color:var(--text-bright);border-bottom:1px solid var(--border-mid);opacity:0.85;}
.priv-eff{font-size:var(--fs-small);letter-spacing:0.12em;color:var(--text-dim);margin-bottom:24px;}

.cookie-toggle{margin:16px 0;display:flex;align-items:center;}
.cookie-toggle input[type="checkbox"]{display:none;}
.cookie-toggle input[type="checkbox"]:disabled + label{opacity:0.5;cursor:not-allowed;}
.cookie-toggle label{cursor:pointer;display:flex;align-items:center;gap:12px;font-size:var(--fs-body);color:var(--text-mid);transition:color 0.2s;-webkit-tap-highlight-color:transparent;}
.cookie-toggle label:hover{color:var(--text-bright);}
/* Cookie toggle — strong on/off contrast.
   OFF: dim recessed track, dark/dim thumb (looks "off / unlit").
   ON:  luminous bright track + bright cream thumb pulled to the
        right edge ("lit / engaged").
   The visual difference between states is intentionally large so
   the user can read on/off at a glance. */
.toggle-switch{
  width:44px;height:24px;
  background:rgba(20,18,16,0.18);
  border:1px solid rgba(20,18,16,0.20);
  border-radius:12px;
  position:relative;
  box-shadow:
    inset 0 1px 2px rgba(20,18,16,0.10),
    inset 0 -1px 0 rgba(255,255,255,0.20);
  transition:background 0.3s, border-color 0.3s, box-shadow 0.3s;
}
.toggle-switch::after{
  content:'';position:absolute;top:2px;left:2px;
  width:18px;height:18px;
  background:rgba(40,38,36,0.55);
  border-radius:50%;
  transition:transform 0.3s, background 0.3s, box-shadow 0.3s;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.20),
    0 1px 2px rgba(20,18,16,0.18);
}
.cookie-toggle input[type="checkbox"]:checked + label .toggle-switch{
  background:var(--glass-primary-bg);
  border-color:var(--glass-primary-border);
  box-shadow:
    0 0 0 3px rgba(255,255,255,0.22),
    0 0 18px rgba(255,255,255,0.30),
    inset 0 1.4px 0 rgba(255,255,255,0.95),
    inset 0 -1px 0 rgba(20,18,16,0.06);
}
.cookie-toggle input[type="checkbox"]:checked + label .toggle-switch::after{
  transform:translateX(20px);
  background:var(--bg-solid);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.95),
    0 1px 3px rgba(20,18,16,0.18);
}
.cookie-save-section{display:flex;gap:12px;margin-top:32px;padding-top:24px;border-top:1px solid var(--border);}
.cookie-save-btn,.cookie-accept-all-btn{flex:1;padding:14px 20px;font-size:var(--fs-body);letter-spacing:0.08em;cursor:pointer;transition:all 0.3s;border:none;border-radius:999px;}
.cookie-save-btn{background:transparent;border:1px solid var(--border-mid);color:var(--text-bright);}
.cookie-save-btn:hover{background:var(--bg-panel);border-color:var(--text-mid);}
.cookie-accept-all-btn{background:var(--text-bright);border:1px solid var(--text-bright);color:var(--bg-solid);}
.cookie-accept-all-btn:hover{background:var(--text-mid);border-color:var(--text-mid);}
#ov-cookies .sub-intro{font-size:14px;line-height:1.8;letter-spacing:0.04em;}
#ov-cookies .priv-section h3{font-size:13px;letter-spacing:0.12em;}
#ov-cookies .priv-section p{font-size:14px;line-height:1.8;}
#ov-cookies .cookie-toggle label{font-size:14px;}
#ov-cookies .cookie-save-btn,#ov-cookies .cookie-accept-all-btn{font-size:14px;}

@media(max-width:700px){
  .ov-panel{width:100vw;right:-100vw;}
  .ov-top{padding:24px 28px;}
  .ov-label{font-size:18px;}
  .ov-close{width:44px;height:44px;}
  .ov-close::before,.ov-close::after{width:16px;}
  /* About-links mobile: liquid-glass pill matching the .mdock vocabulary,
     but FLOATING ABSOLUTELY over the scrolling about text — no reserved
     row, no fixed background strip. The text scrolls behind the pill so
     the user catches the bottom paragraphs through the frosted glass.
     Position:absolute pulls the pill out of the .ov-panel flex flow;
     the .ov-body's padding-bottom keeps the last paragraph from being
     permanently hidden under it. */
  /* Selector uses `.about-links.ov-bottom` (matching BOTH classes
     the element has) AND keeps !important on height/margin/padding
     to beat `#ov-about .ov-bottom { height:0 !important }` further
     down in the same @media block. Both rules carry !important, so
     specificity decides — mine is 0,1,2,0 (extra .ov-bottom class)
     vs theirs 0,1,1,0, mine wins. Without !important, my rule loses
     to theirs regardless of specificity (CSS cascade: !important
     beats specificity), and the pill collapses to 0 height. */
  #ov-about .about-links.ov-bottom{
    position:absolute;
    left:16px;right:16px;
    bottom:calc(14px + env(safe-area-inset-bottom,0px));
    z-index:5;
    margin:0 !important;
    padding:0 !important;
    display:flex;align-items:stretch;
    flex-wrap:nowrap;gap:0;
    border-top:none;
    height:48px !important;
    /* Visual treatment (bg / border / box-shadow / backdrop-filter)
       previously lived here but has moved entirely to the membrane
       pill-surface that membrane.js injects as a child — same pattern
       as the sticky compact pill. Stacking two glass layers (the
       container's own AND the membrane's) made the pill read as
       flattened. Only layout + shape stay. */
    border-radius:999px;
    overflow:hidden;
  }
  #ov-about .about-links a{
    flex:1;
    display:flex;align-items:center;justify-content:center;
    font-size:11px;letter-spacing:0.16em;text-transform:uppercase;
    color:var(--text-mid);text-decoration:none;
    padding:0 6px;
    -webkit-tap-highlight-color:transparent;
    white-space:nowrap;position:relative;
    border:none;border-bottom:none;
    transition:color 0.2s, background 0.2s;
  }
  /* Hairline dividers between cells (drawn on each link except the first). */
  #ov-about .about-links a + a::before{
    content:'';position:absolute;left:0;top:28%;bottom:28%;
    width:1px;background:var(--border-mid);
  }
  #ov-about .about-links a:active{
    color:var(--text-bright);
    background:rgba(255,255,255,0.18);
  }
  /* Collapse the .ov-bottom flex slot so the pill floats absolutely over
     .ov-body instead of taking layout space. */
  #ov-about .ov-bottom{
    flex:0 0 0 !important;
    height:0 !important;
    padding:0 !important;margin:0 !important;
    background:transparent;
    backdrop-filter:none;-webkit-backdrop-filter:none;
    border-top:none;
    overflow:visible;
  }
  /* And give the body content enough trailing padding so the last
     paragraph isn't permanently hidden under the floating pill. The
     text DOES pass behind the pill while scrolling — that's the point —
     but at scroll-end the user can read the last line clearly. */
  #ov-about .ov-body{
    padding-bottom:calc(82px + env(safe-area-inset-bottom,0px)) !important;
  }

  /* Mobile privacy overlay: the cookie-settings CTA floats freely at the
     bottom over the scrolling policy text — same pattern as about-links.
     No backdrop, no divider, no panel. The button is just a translucent
     pill that the policy text scrolls behind. Container collapses to 0,
     button is positioned absolutely. */
  #ov-privacy .priv-cookies-cta.ov-bottom{
    position:absolute;
    left:0;right:0;
    bottom:calc(14px + env(safe-area-inset-bottom,0px));
    z-index:5;
    flex:0 0 0 !important;
    height:auto !important;
    padding:0 !important;margin:0 !important;
    background:transparent !important;
    backdrop-filter:none !important;-webkit-backdrop-filter:none !important;
    border-top:none !important;
    box-shadow:none !important;
    display:flex;justify-content:center;
    pointer-events:none; /* let the policy text underneath scroll-tap through */
    overflow:visible;
  }
  /* Host gets the SAME liquid-glass-primary base as the other
     membrane'd buttons (.choice-strip-accept, .cookie-accept-all-btn):
     bright cream-white pill with backdrop-filter + drop shadow.
     The membrane material's translucent dark surface + iri shimmer
     + top-edge highlight then sits on top of that bright base —
     the same layered visual the user already approved on the GDPR
     and cookies accept buttons. Without this base, the membrane's
     42%-opaque surface against the cream panel was reading as a
     faint smudge ("fully transparent feel"). */
  #ov-privacy .priv-cookies-cta.ov-bottom button{
    pointer-events:auto;
    background:var(--glass-primary-bg) !important;
    border:1px solid var(--glass-primary-border) !important;
    backdrop-filter:var(--glass-filter) !important;
    -webkit-backdrop-filter:var(--glass-filter) !important;
    box-shadow:var(--glass-primary-shadow) !important;
    font-size:13px;letter-spacing:0.12em;
    padding:10px 22px;border-radius:999px;
    overflow:hidden; /* clip injected iri layers to the pill shape */
  }
  /* And padding-bottom on the privacy body so the last paragraph isn't
     permanently hidden under the floating button. */
  #ov-privacy .ov-body{
    padding-bottom:calc(82px + env(safe-area-inset-bottom,0px)) !important;
  }
  .about-links:not(.about-links-secondary):not(#ov-about .about-links) a{font-size:18px;letter-spacing:0.08em;padding:6px 14px;}
  .about-links-secondary a{font-size:12px;letter-spacing:0.06em;padding:4px 10px;}
  .about-p{font-size:var(--fs-body);line-height:1.85;}
  .portrait-credit{font-size:var(--fs-small);}
  .sub-big{font-size:var(--fs-display);line-height:0.95;}
  .sub-intro{font-size:var(--fs-body);line-height:1.75;}
  .priv-section h3{font-size:var(--fs-h2);}
  .priv-section p,.priv-section li{font-size:var(--fs-body);}
  .priv-eff{font-size:var(--fs-small);}
  #ov-cookies .sub-intro{font-size:15px!important;}
  #ov-cookies .priv-section h3{font-size:14px!important;}
  #ov-cookies .priv-section p{font-size:15px!important;}
  #ov-cookies .cookie-toggle label{font-size:15px!important;}
  #ov-cookies .cookie-save-btn,#ov-cookies .cookie-accept-all-btn{font-size:15px!important;}
  /* Privacy overlay mobile: same scale as cookie overlay (15px / 14px h3) */
  #ov-privacy .sub-intro{font-size:15px!important;}
  #ov-privacy .priv-section h3{font-size:14px!important;}
  #ov-privacy .priv-section p,
  #ov-privacy .priv-section li{font-size:15px!important;line-height:1.8!important;}
  #ov-privacy .priv-eff{font-size:13px!important;}
  .cookie-save-section{flex-direction:column;}
  .toggle-switch{width:52px;height:28px;}
  .toggle-switch::after{width:22px;height:22px;}
  .cookie-toggle input[type="checkbox"]:checked + label .toggle-switch::after{transform:translateX(24px);}
}

/* ── GDPR BANNER ────────────────────────────────────────────── */
.choice-strip {
  position:fixed; bottom:0; left:0; right:0;
  /* z-index 10000 (was 300) — needed to render ABOVE the
     `.page-transition` cover (z-index:9999) that's "active" during
     every fresh page load. With the old 300, the banner was being
     painted behind the page-transition's pixel-grid and looked
     invisible on desktop in private/fresh-load mode (where no
     consent is stored, so the banner actually tries to show). The
     pixel-grid then fades to opacity:0 so it doesn't visually
     conflict with banner content; it just needs to not be on top
     in the z-stack. Equal to .overlay (10000) — overlays come
     later in DOM order, so they still correctly cover the banner
     when an overlay is opened. */
  z-index:10000;
  background:var(--bg-panel-strong);
  backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);
  border-top:1px solid var(--border-mid);
  padding:16px 28px;
  /* `display:flex !important` defensive against the [hidden]
     attribute (UA stylesheet rule) and any other source of
     display:none. The banner was rendering at 0×0 in Safari
     private mode despite no traceable CSS rule setting
     display:none — the !important kills any such hiding. */
  display:flex !important;
  align-items:center; justify-content:space-between; gap:20px;
  transform:translateY(100%);
  transition:transform 0.45s cubic-bezier(0.25,0.46,0.45,0.94);
  box-shadow:0 -4px 24px rgba(0,0,0,0.03);
}
.choice-strip.show { transform:translateY(0); }
.choice-strip-text {
  font-size:var(--fs-small); letter-spacing:0.06em;
  color:var(--text-mid); line-height:1.7; flex:1;
}
.choice-strip-text a {
  color:var(--text-mid); border-bottom:1px solid var(--border-mid);
  text-decoration:none; transition:color 0.2s; cursor:pointer;
}
.choice-strip-text a:hover { color:var(--text-bright); }
.choice-strip-actions { display:flex; gap:10px; flex-shrink:0; }
/* Pill-shaped CTAs to align with the rest of the chrome (the round
   ov-close button, the rounded-pill view-toggle on the work pages, etc.)
   `border-radius:999px` gives a fully-rounded pill at any height. */
.choice-strip-decline {
  padding:9px 18px; font-size:var(--fs-small); letter-spacing:0.18em;
  background:none; border:1px solid var(--border-mid);
  color:var(--text-dim); cursor:pointer; transition:all 0.3s;
  border-radius:999px;
}
.choice-strip-decline:hover { border-color:var(--border-mid); color:var(--text-mid); }
.choice-strip-customize {
  padding:9px 20px; font-size:var(--fs-small); letter-spacing:0.18em;
  background:var(--bg-panel); border:1px solid var(--border-mid);
  color:var(--text-bright); cursor:pointer; transition:all 0.3s;
  border-radius:999px;
}
.choice-strip-customize:hover { background:var(--bg-solid); border-color:var(--text-mid); }
.choice-strip-accept {
  padding:9px 22px; font-size:var(--fs-small); letter-spacing:0.18em;
  background:var(--text-bright); border:1px solid var(--text-bright);
  color:var(--bg-solid); cursor:pointer; transition:all 0.3s;
  border-radius:999px;
}
.choice-strip-accept:hover { background:var(--text-mid); border-color:var(--text-mid); }
@media(max-width:700px){
  .choice-strip { flex-direction:column; align-items:flex-start; padding:14px 18px; }
  .choice-strip-actions { width:100%; flex-wrap:wrap; }
  .choice-strip-accept { flex:1 1 100%; order:1; text-align:center; }
  .choice-strip-customize { flex:1; order:2; text-align:center; }
  .choice-strip-decline { flex:1; order:3; text-align:center; }
}

/* ── HTML/BODY BASELINE ─────────────────────────────────────── */
/* CRITICAL: html background MUST be set so the browser shows the cream
   colour during the unpainted moment between page navigations (Safari
   shows its default white otherwise — that's the "white flash" when
   navigating from one page to another). The html bg is the very first
   thing painted by the browser, before any other CSS rule applies. */
html{overflow-x:hidden;background:var(--bg-solid);}
/* position:relative on body anchors the .page-footer (position:absolute
   bottom:0) to body's intrinsic bottom — short pages get footer at
   bottom of viewport (via min-height:100dvh), long pages get footer
   below all content (visible after scrolling). */
body{background:var(--bg-solid);color:var(--text-bright);min-height:100dvh;position:relative;}

/* ── INSTANT CONTENT-AREA COVER (SUBPAGES ONLY) ─────────────────
   body::before is opaque on first paint; html.page-ready removes it.
   Scoped to body[data-page="sub"] because home doesn't add the
   page-ready class (its inline pageTransition skips it) — applying
   the cover to home would leave it permanently covering the hero
   content. Subpages inherit page-ready via shared.js's pageTransition. */
body[data-page="sub"]::before{
  content:"";position:fixed;top:0;right:0;bottom:0;left:0;z-index:9998;
  background:var(--bg-solid);
  pointer-events:none;
}
@media(min-width:701px){
  body[data-page="sub"]::before{left:var(--sliver);}
  html.bar-preopen body[data-page="sub"]::before{left:var(--bar-w);}
  .left-bar:hover ~ body[data-page="sub"]::before,
  .left-bar.open ~ body[data-page="sub"]::before{left:var(--bar-w);}
}
@media(max-width:700px){
  body[data-page="sub"]::before{left:0;}
}
html.page-ready body[data-page="sub"]::before{display:none;}

/* ── PAGE TRANSITION (pixel-dissolve overlay) ─────────────────
   Built dynamically by shared.js's pageTransition. CSS hoisted from
   per-page inline so it's available immediately at first paint. */
.page-transition{
  position:fixed;inset:0;z-index:9999;pointer-events:none;
  display:grid;grid-template-columns:repeat(30,1fr);grid-template-rows:repeat(20,1fr);
  /* Default clip excludes the visible sliver. When bar opens, expands
     to clip --bar-w. .full overrides to no clip (fullscreen cover). */
  clip-path:polygon(var(--sliver) 0, 100% 0, 100% 100%, var(--sliver) 100%);
}
.left-bar.open ~ .page-transition,
.left-bar:hover ~ .page-transition{clip-path:polygon(var(--bar-w) 0, 100% 0, 100% 100%, var(--bar-w) 100%);}
@media(min-width:701px){html.bar-preopen .page-transition{clip-path:polygon(var(--bar-w) 0, 100% 0, 100% 100%, var(--bar-w) 100%);}}
.page-transition.full{clip-path:none;}
.page-transition.active{pointer-events:all;}
.pixel-block{
  background:var(--bg-solid);opacity:0;
  transition:opacity 0.4s ease;
}
.page-transition.out .pixel-block{opacity:1;}

/* ── LIST TRANSITION (section-switch pixel dissolve in left bar) ─ */
.list-transition{
  position:absolute;inset:0;z-index:10;pointer-events:none;
  display:grid;grid-template-columns:repeat(10,1fr);grid-template-rows:repeat(12,1fr);
}
.list-transition.active{pointer-events:all;}
.list-pixel{
  background:var(--bg-solid);
  opacity:0;
  transition:opacity 0.25s cubic-bezier(0.4, 0.0, 0.2, 1);
}
.list-transition.active .list-pixel{opacity:1;}
@media(max-width:700px){
  .page-transition{
    grid-template-columns:repeat(18,1fr);
    grid-template-rows:repeat(24,1fr);
    clip-path:none;
  }
  .list-transition{grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(15,1fr);}
}

/* ── BACKGROUND STAGE — chrome.js renders previewWrap_machines and
   previewWrap_rebel with class="bg-stage". They need fixed-position
   sizing here so the absolutely-positioned <video> children have
   a proper container. Hoisted from per-page (previously home-only)
   so the menu hover-preview videos render correctly on every page. */
.bg-stage{
  position:fixed;top:0;left:var(--sliver);right:0;bottom:0;
  z-index:0;overflow:hidden;
  transition:left 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
}
.bg-mirror{
  position:fixed;top:0;left:0;width:var(--sliver);bottom:0;
  z-index:0;overflow:hidden;
  transition:width 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
}
@media(min-width:701px){
  /* !important needed to beat the inline default `left:var(--sliver)` set
     by chrome.js on previewWrap_machines/rebel — inline styles otherwise
     win this cascade. */
  html:has(.left-bar.open) .bg-stage,
  html:has(.left-bar:hover) .bg-stage,
  html.bar-preopen .bg-stage{left:var(--bar-w)!important;}
  html:has(.left-bar.open) .bg-mirror,
  html:has(.left-bar:hover) .bg-mirror,
  html.bar-preopen .bg-mirror{width:var(--bar-w);}
}
.bg-mirror-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity 0.8s ease;pointer-events:none;}
.bg-mirror-img.on{opacity:1;}
@media(max-width:700px){
  .bg-stage{display:none;}
  .bg-mirror{display:none;}
}

/* ── BACKGROUND PREVIEW STAGE (subpage hover) ─────────────── */
.bg-preview{
  position:fixed;
  top:0;left:var(--bar-w);right:0;
  height:100dvh;
  z-index:7;
  pointer-events:none;
  overflow:hidden;
}
.bg-preview-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity 0.8s ease;}
.bg-preview-img.on{opacity:1;}
.bg-preview-mirror{
  position:fixed;
  top:0;left:0;
  width:var(--bar-w);
  height:100dvh;
  z-index:4;
  pointer-events:none;
  overflow:hidden;
}
.bg-preview-mirror-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity 0.8s ease;}
.bg-preview-mirror-img.on{opacity:1;}
.preview-label{
  position:fixed;bottom:60px;left:calc(var(--sliver) + 60px);right:60px;
  display:flex;flex-direction:column;gap:6px;
  pointer-events:none;opacity:0;
  transition:opacity 0.4s ease, left 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
  color:#f0eeeb;z-index:50;
}
.preview-label.on{opacity:1;}
body:has(.left-bar:hover) #previewLabel,
body:has(.left-bar.open) #previewLabel { left:calc(var(--bar-w) + 60px); }
@media(max-width:700px){.preview-label{display:none;}}
.preview-series{font-size:32px;font-weight:600;letter-spacing:-0.02em;line-height:1.15;white-space:normal;word-break:break-word;max-width:480px;}
.preview-desc{font-size:12px;letter-spacing:0.04em;line-height:1.6;max-width:600px;opacity:0.85;}
.preview-dots{display:flex;gap:8px;margin-top:10px;}
.preview-dot{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:0.3;transition:all 0.3s;}
.preview-dot.on{opacity:1;width:24px;border-radius:3px;}
@media(max-width:700px){
  .bg-preview{display:none;}
  .bg-preview-mirror{display:none;}
  /* The desktop sidebar must not render at all on mobile — its default
     transform leaves a sliver poking out at the left edge. Hoisted from
     per-page inline CSS so every subpage inherits it automatically. */
  .left-bar{display:none!important;}
  .bar-sliver{display:none;}
  #barTopDesktop{display:none;}
  .bar-top{padding-top:26px;}
}

/* ── LEFT-BAR ──────────────────────── */
/* Backdrop-filter restored: the menu IS supposed to be a frosted-
   glass surface, not just an opaque cream panel. The earlier "halo"
   complaint turned out to be from .bg-preview-mirror (a separate
   bar-width fixed strip that was also in the surface-group rule);
   that's been pulled out separately. .left-bar's own blur is part
   of the design and stays. */
.left-bar{
  position:fixed;top:0;left:0;z-index:100;
  width:var(--bar-w);height:100dvh;
  background:var(--bg-panel);
  backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);
  border-right:1px solid var(--border-mid);
  transform:translateX(calc(var(--sliver) - var(--bar-w)));
  transition:transform 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
  display:flex;overflow:hidden;
  box-shadow:4px 0 24px rgba(0,0,0,0.03);
}
.left-bar:hover,.left-bar.open{transform:translateX(0);}
@media(min-width:701px){html.bar-preopen .left-bar{transform:translateX(0);}}
@media(min-width:701px){
  html.bar-preopen .left-bar .sliver-menu-text{opacity:0;}
  html.bar-preopen .left-bar .sliver-collapse-wrap{opacity:1;}
  html.bar-preopen .left-bar .sliver-collapse-text{color:var(--acc);}
  html.bar-preopen .left-bar .sliver-dot{background:var(--acc);}
}
@media(min-width:701px){html.no-bar-anim .left-bar{transition:none!important;}}
.left-bar.collapsing{transform:translateX(calc(var(--sliver) - var(--bar-w)))!important;}

.bar-sliver{
  width:var(--sliver);flex-shrink:0;
  display:flex;flex-direction:column;align-items:center;
  border-right:1px solid var(--border-mid);cursor:pointer;padding:20px 0;
}
.sliver-menu-word{flex:1;display:flex;align-items:center;justify-content:center;}
.sliver-menu-text{
  font-size:13px;font-weight:600;letter-spacing:0.25em;
  color:var(--text-dim);
  writing-mode:vertical-rl;text-orientation:mixed;
  transform:rotate(180deg);
  white-space:nowrap;
  transition:opacity 0.3s,color 0.3s;
}
.left-bar:hover .sliver-menu-text,
.left-bar.open .sliver-menu-text{opacity:0;}
.sliver-collapse-wrap{
  display:flex;align-items:center;justify-content:center;
  padding-bottom:18px;opacity:0;transition:opacity 0.3s;
}
.left-bar:hover .sliver-collapse-wrap,
.left-bar.open .sliver-collapse-wrap{opacity:1;}
.sliver-collapse-text{
  font-size:11px;font-weight:600;letter-spacing:0.20em;color:var(--text-dim);
  writing-mode:vertical-rl;text-orientation:mixed;transform:rotate(180deg);
  white-space:nowrap;transition:color 0.3s;
}
.left-bar:hover .sliver-collapse-text,
.left-bar.open .sliver-collapse-text{color:var(--acc);}
.sliver-dots{display:flex;flex-direction:column;gap:5px;align-items:center;padding-bottom:20px;}
.sliver-dot{width:3px;height:3px;border-radius:50%;background:var(--text-dim);transition:background 0.4s;}
.left-bar:hover .sliver-dot,.left-bar.open .sliver-dot{background:var(--acc);}

.bar-content{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0;}
.bar-top{padding:26px 26px 20px;border-bottom:1px solid var(--border);flex-shrink:0;position:relative;overflow:hidden;}
.bar-top-pixels{
  position:absolute;inset:0;z-index:200;pointer-events:none;
  display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(4,1fr);
  isolation:isolate;
}
.bar-top-pixel{background:var(--bg-solid);opacity:0;transition:opacity 0.4s ease;}
.bar-top-pixels.out .bar-top-pixel{opacity:1;}
@media(max-width:700px){.bar-top-pixels{display:none;}}

.big-name{
  display:flex;align-items:flex-end;
  font-size:var(--fs-hero);font-weight:600;color:var(--text-bright);
  line-height:0.88;letter-spacing:-0.03em;text-decoration:none;
  margin-bottom:10px;cursor:pointer;transition:opacity 0.3s;
  -webkit-tap-highlight-color:transparent;
}
.big-name:hover{opacity:0.85;}
.tagline{font-size:var(--fs-small);letter-spacing:0.12em;color:var(--text-dim);line-height:1.7;}
@media(min-width:701px){.bar-top .tagline{display:none;}}

.sig-wrap{
  display:block;
  flex-shrink:0;
  height:calc(var(--fs-hero) * 2 * 1.05 + 4px);
  width:calc(var(--fs-hero) * 2 * 1.05 * 0.7);
  overflow:hidden;
  position:relative;
}
.sig-anim{
  display:block;
  height:calc(100% - 4px);
  width:auto;
  position:absolute;
  top:2px;
  left:50%;
  transform:translateX(-50%);
  opacity:0.9;
  transition:opacity 0.3s ease;
  pointer-events:none;
  -webkit-user-select:none;
  user-select:none;
}
.big-name:hover .sig-anim{opacity:1;}
.name-text{
  display:block;
  margin-left:-20px;
  position:relative;
  background:linear-gradient(to bottom,
    rgba(35,33,30,0.95) 0%,
    rgba(85,82,78,0.85) 45%,
    rgba(85,82,78,0.85) 55%,
    rgba(35,33,30,0.95) 100%
  );
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
}
.name-text-glow{
  position:absolute;
  inset:0;
  background:linear-gradient(to bottom,
    rgba(35,33,30,0.95) 0%,
    rgba(230,229,228,0.98) 45%,
    rgba(230,229,228,0.98) 55%,
    rgba(35,33,30,0.95) 100%
  );
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
  opacity:0;
}
@media(min-width:701px){
  .name-text-glow.glow-ready{
    animation:sig-glow 9s linear forwards;
  }
  @keyframes sig-glow{
    0%   { opacity:0; }
    7%   { opacity:0.31; }
    14%  { opacity:0.53; }
    21%  { opacity:0.58; }
    29%  { opacity:0.66; }
    36%  { opacity:0.78; }
    43%  { opacity:0.91; }
    50%  { opacity:1; }
    57%  { opacity:0.96; }
    64%  { opacity:0.85; }
    71%  { opacity:0.74; }
    79%  { opacity:0.60; }
    86%  { opacity:0.54; }
    93%  { opacity:0.55; }
    100% { opacity:0; }
  }
}
.name-deroo{display:inline-block;padding-left:0.5em;}

.bar-nav{padding:14px 22px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;flex-direction:column;gap:1px;}
.bar-nav-link{font-size:16px;letter-spacing:0.07em;color:var(--text-mid);text-decoration:none;padding:7px 0;border-bottom:1px solid transparent;transition:all 0.3s;display:block;}
.bar-nav-link:hover,.bar-nav-link.cur{color:var(--text-bright);border-bottom-color:var(--acc-border);}.bar-nav-link.cur{font-weight:550;}
.bar-series{flex:1;overflow-y:auto;position:relative;}
.bar-series::-webkit-scrollbar{width:2px;}
.bar-series::-webkit-scrollbar-thumb{background:var(--border-mid);}

.bar-series-hint{
  position:absolute;top:24px;left:0;right:0;
  text-align:center;font-size:22px;line-height:1;
  color:var(--text-dim);
  opacity:0;transition:opacity 0.6s ease;
  pointer-events:none;user-select:none;
}
.bar-series-hint.in{opacity:0.55;}
.bar-series-hint.out{opacity:0;}
@media(max-width:700px){.bar-series-hint{display:none;}}

.bar-col-header{display:grid;grid-template-columns:32px 1fr auto;padding:8px 22px;border-bottom:1px solid var(--border);background:rgba(var(--r),var(--g),var(--b),0.06);flex-shrink:0;gap:8px;}
.bar-col-header span{font-size:var(--fs-small);letter-spacing:0.18em;color:var(--text-dim);}
.snav-item{display:grid;grid-template-columns:32px 1fr auto;gap:8px;align-items:center;padding:15px 22px;font-size:15px;font-weight:500;color:var(--text-mid);text-decoration:none;border-bottom:1px solid var(--border);transition:all 0.2s;cursor:pointer;}
.snav-item:hover{color:var(--text-bright);background:var(--acc-muted);}
.snav-item.active{color:var(--text-bright);}
.snav-num{font-size:10px;letter-spacing:0.12em;color:var(--text-dim);text-align:left;}
.snav-name{font-size:14px;font-weight:500;color:inherit;line-height:1.25;overflow-wrap:break-word;}
.snav-item:hover .snav-name,.snav-item.active .snav-name{color:var(--text-bright);}
.snav-item.active .snav-name{font-weight:600;}
.snav-year{font-size:10px;letter-spacing:0.12em;color:var(--text-dim);text-align:right;}
.snav-explore{
  grid-column:1 / -1;
  max-height:0;overflow:hidden;
  transition:max-height 0.25s ease, padding-top 0.25s ease;
  padding-top:0;
}
.snav-item:hover .snav-explore{max-height:40px;padding-top:4px;}
.snav-explore-inner{
  display:inline-block;font-size:10px;letter-spacing:0.18em;
  color:var(--text-dim);position:relative;transition:color 0.2s;
}
.snav-explore-inner::after{
  content:'';position:absolute;bottom:-2px;left:0;
  width:100%;height:1px;background:var(--acc);
  transform-origin:left;animation:pulse-ul 3s ease-in-out infinite;
}
.snav-item:hover .snav-explore-inner{color:var(--text-mid);}
.snav-item:hover .snav-explore-inner::after{animation:pulse-ul 3s ease-in-out infinite;}

/* Bar-info: subscribe link sits centred horizontally and only takes the
   width of its own text (so the pulse-underline tracks the text width
   instead of stretching across the bar). The .info-links row of
   about | portfolio | cv | contact also centres as a group, so the two
   rows feel like a single grouped footer. */
.bar-info{padding:18px 22px;border-top:1px solid var(--border);flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:14px;margin-bottom:var(--gdpr-h,0px);transition:margin-bottom 0.45s cubic-bezier(0.25,0.46,0.45,0.94);}
.bar-info .sub-link{
  display:inline-block;
  width:auto;
  max-width:max-content;
  font-size:16px;
  letter-spacing:0.07em;
  color:var(--text-mid);
  padding:6px 0;
}
.bar-info .sub-link:hover{color:var(--text-bright);}
.bar-info .sub-link::after{ bottom:-2px; }
.info-links{display:flex;gap:0;flex-wrap:nowrap;align-items:center;justify-content:center;}
.info-link{font-size:16px;letter-spacing:0.08em;color:var(--text-dim);text-decoration:none;background:none;border:none;cursor:pointer;padding:5px 10px;border-right:1px solid var(--border-mid);transition:color 0.25s;white-space:nowrap;}
.info-link:last-child{border-right:none;padding-right:0;margin-right:0;}
.info-link:hover{color:var(--text-bright);}
.mobile-only-link{display:none;}
.desktop-only-link{}
.info-link:nth-last-child(2){border-right:none;}

/* Mobile menu trigger button — pill-shaped, plain "menu" text, matches
   the close-button vocabulary used on .ov-close / .mstack-close (round
   shape, subtle border, backdrop-blur background). Floats at top-LEFT
   so it doesn't collide with the .mstack-close X (which lands at top-
   right when the deck opens). Compact 36px-tall pill so it doesn't
   dominate the page header. */
/* The standalone .mob-top-bar was replaced by extending the page-header
   sticky to cover the top zone — this way the pill floats over the
   SAME blurred surface that the rest of the chrome uses, and the
   pill's own backdrop-filter actually has scrolling content to blur
   (the .mob-top-bar previously had nothing scrolling behind it). The
   .main margin-top override below resets the per-page 58px margin so
   the .page-header-sticky starts at viewport y=0 and covers the notch
   zone. .header-left's padding-top is bumped to clear the pill. */
.mob-top-bar{display:none!important;}
@media(max-width:700px){
  body[data-page="sub"] .main{margin-top:0!important;}
}

/* Mobile menu trigger — pill that travels between two corners.
   At rest (top of page) it sits in the top-LEFT corner above the
   page-header. Once the user scrolls past a small threshold the
   pill animates down to the bottom-LEFT corner of the viewport.
   Reverse on scroll back. The `top` value is rewritten by JS
   (subpage.js #mobBtn-tracker IIFE) and the CSS `transition: top`
   handles the smoothing. */
.mob-menu-btn{
  display:none;
  -webkit-appearance:none;
  appearance:none;
  position:fixed;
  top:calc(env(safe-area-inset-top, 0px) + 7px);
  left:14px;
  z-index:200;
  height:44px;
  padding:0 22px;
  /* "Liquid glass" — layered shadows simulate a piece of refractive
     glass:
       1. outer drop shadow — lifts the pill off the surface
       2. inset top highlight — strong specular catch on the top edge
       3. inset secondary highlight — softer light just below the rim
       4. inset bottom subtle line — implies thickness / underside
       5. inset full ring — refractive edge around the whole pill
     Combined with a saturated/brightened backdrop-filter the pill
     reads as a 3D shape with light passing through it. */
  background:rgba(255,255,255,0.30);
  border:1px solid rgba(255,255,255,0.55);
  border-radius:999px;
  cursor:pointer;
  font-family:'Safiro',sans-serif!important;
  font-size:16px;
  letter-spacing:0.14em;
  color:var(--text-mid);
  -webkit-text-fill-color:var(--text-mid);
  -webkit-tap-highlight-color:transparent;
  backdrop-filter:blur(24px) saturate(200%) brightness(1.05);
  -webkit-backdrop-filter:blur(24px) saturate(200%) brightness(1.05);
  box-shadow:
    0 8px 24px rgba(20,18,16,0.08),
    0 2px 4px rgba(20,18,16,0.04),
    inset 0 1.4px 0 rgba(255,255,255,0.85),
    inset 0 3px 4px rgba(255,255,255,0.35),
    inset 0 -1px 0 rgba(20,18,16,0.06),
    inset 0 0 0 0.5px rgba(255,255,255,0.40);
  transition:top 0.32s cubic-bezier(0.25,0.46,0.45,0.94), background 0.2s, border-color 0.2s, color 0.2s, box-shadow 0.2s;
  align-items:center;
  justify-content:center;
  white-space:nowrap;
  line-height:1;
}
.mob-menu-btn:hover{
  color:var(--text-bright);
  -webkit-text-fill-color:var(--text-bright);
  border-color:rgba(255,255,255,0.75);
  background:rgba(255,255,255,0.42);
}
.mob-menu-btn:active{
  background:rgba(255,255,255,0.55);
  box-shadow:
    0 2px 6px rgba(20,18,16,0.06),
    inset 0 1px 0 rgba(255,255,255,0.55),
    inset 0 -1px 0 rgba(20,18,16,0.10);
}
@media(max-width:700px){
  /* The dedicated mobile menu pill is removed — the sticky header
     itself now serves as the menu trigger on mobile (subpage.js
     wires the .page-header-sticky click → mstackShow on mobile).
     Per-page rules (e.g. equilibrium-level1.html) still try to
     `display:flex` this button, so we override with !important. */
  .mob-menu-btn{display:none!important;}
}

/* ── PAGE FOOTER ──────────────────────────────────────────
   Home only — anchored to bottom of body via position:absolute (body
   has position:relative + min-height:100dvh from the baseline above).
   Hidden on subpages and on mobile (.mdock replaces on mobile). */
.page-footer{
  position:absolute;bottom:0;left:var(--sliver);right:0;
  display:grid;grid-template-columns:1fr 1fr 1fr;padding:10px 32px;
  border-top:1px solid var(--border);
  background:rgba(250,250,249,0.08);
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  transition:left 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
  z-index:4;
}
body[data-page="sub"] .page-footer{display:none!important;}
.page-footer span{font-size:var(--fs-small);letter-spacing:0.14em;color:var(--text-dim);}
.page-footer span:nth-child(2){text-align:center;}
.page-footer span:nth-child(3){text-align:right;}
/* Bar open / hovered / preopen → shifts footer right by --bar-w */
.left-bar:hover ~ .page-footer,
.left-bar.open ~ .page-footer{left:var(--bar-w);}
@media(min-width:701px){html.bar-preopen .page-footer{left:var(--bar-w);}}
.desktop-cookie-link{}
@media(max-width:700px){
  .desktop-cookie-link{display:none;}
  /* On mobile the .mdock replaces .page-footer */
  .page-footer{display:none!important;}
}

/* ── LANDSCAPE LOCK ── */
.landscape-lock{display:none;position:fixed;inset:0;z-index:99999;background:var(--bg-solid);flex-direction:column;align-items:center;justify-content:center;gap:32px;padding:48px 32px;text-align:center;}
@media screen and (max-width:900px) and (orientation:landscape){.landscape-lock{display:flex!important;}}
html.video-fs-landscape .landscape-lock{display:none!important;}
.landscape-lock-line{width:40px;height:1px;background:var(--acc);animation:ll-line 2.8s ease-in-out infinite;}
.landscape-lock-label{font-size:10px;letter-spacing:0.28em;color:var(--text-dim);text-transform:uppercase!important;}
.landscape-lock-heading{font-size:var(--fs-h1,32px);font-weight:600;letter-spacing:-0.02em;line-height:0.92;color:var(--text-bright);}
.landscape-lock-sub{font-size:var(--fs-small,12px);letter-spacing:0.18em;color:var(--text-dim);line-height:1.8;}
@keyframes ll-line{0%,100%{transform:scaleX(0.15);opacity:0.2;}50%{transform:scaleX(1);opacity:0.8;}}

/* ════════════════════════════════════════════════════════════
   MOBILE CARD STACK + SLOT-MACHINE REEL (≤700px)
   Base markup is shared via chrome.js. Subpages opt into
   "overlay mode" by adding data-overlay="true" + .mstack-close.
   ════════════════════════════════════════════════════════════ */
.mstack{display:none;}
@media(max-width:700px){
  /* Mstack box. In page mode (homepage) it's the whole UI; in overlay
     mode (subpages) it's hidden until .show is added by the menu toggle. */
  .mstack{
    flex-direction:column;
    position:fixed;inset:0;z-index:30;
    background:var(--bg-solid);color:var(--text-bright);
    overflow:hidden;
  }
  /* Page mode (no data-overlay): auto-shows. */
  .mstack:not([data-overlay]){display:flex;}
  /* Overlay mode (subpages): hidden by default; .show toggles on. Z-index 200
     puts it above the .mob-menu-btn (200) by virtue of source order. */
  .mstack[data-overlay]{display:none;z-index:200;}
  .mstack[data-overlay].show{display:flex;animation:mstack-morph-in 0.40s cubic-bezier(0.25,0.46,0.45,0.94);}
  /* Morph reveal: clip-path animates from a rounded rectangle at the
     sticky-header pill's footprint (top: safe+14, sides:16, height:~46,
     border-radius:28) out to fullscreen. Reads as the pill "growing"
     into the cardstack overlay rather than fading on top of it. */
  @keyframes mstack-morph-in{
    0% {
      clip-path: inset(
        calc(env(safe-area-inset-top, 0px) + 14px)
        16px
        calc(100% - env(safe-area-inset-top, 0px) - 60px)
        16px
        round 28px
      );
      opacity: 0.85;
    }
    60% { opacity: 1; }
    100% {
      clip-path: inset(0 0 0 0 round 0);
      opacity: 1;
    }
  }

  /* ── SLOT-MACHINE REEL ─────────────────────────────────────────────────── */
  .mreel-wrap{
    position:relative;flex-shrink:0;
    /* 76px = pane (44) + 16px peek area on top + 16px peek area on bottom.
       Was 88px, which gave 22px peek areas — labels were so far from the
       pane that only descenders/ascenders showed through, making the
       above peek look more visible than the below. Tightening symmetrically
       brings both prev/next labels visibly closer to the loupe edge. */
    height:76px;
    /* Default (homepage menu) — no close button to align with, so the
       wrap sits flush against the safe-area inset and the loupe pane
       sits at safe+38 vertically. */
    margin-top:env(safe-area-inset-top,0);
    overflow:hidden;
  }
  /* Subpage menu only — pushed down 6px so the wrap's vertical centre
     (margin-top + 38) lines up with the .mstack-close button's centre
     (top:safe+22, height:44, centre at safe+44). The homepage has no
     close button so this offset would just lower the slot machine for
     no reason. */
  .mstack[data-overlay] .mreel-wrap{
    margin-top:calc(env(safe-area-inset-top,0) + 6px);
  }
  /* Loupe-style window frame — a glass lens that the scrolling
     labels are magnified through. The radial gradient gives the
     pane a brighter "lens center" that fades into a darker rim,
     mimicking a real magnifying glass. `backdrop-filter:saturate`
     boosts the colour of the label that lands inside without
     blurring it (label stays crisp). The frame sits directly on
     top of the active label (z-index 2 above the .mreel-item
     z-index 1), and the magnification of the .cur item
     (transform:scale + bigger font) reads as if the lens is
     actually enlarging it. */
  .mreel-frame{
    position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);
    /* Height + brightness match .mstack-close so the loupe pane and the
       overlay-mode close button read as the same component on the same
       horizontal axis. Same dim-warm tint, same neutral border, same
       6px blur — no radial gradient or brightness boost (which made the
       pane glow noticeably brighter than the surrounding chrome). */
    width:calc(100% - 32px);height:44px;
    pointer-events:none;
    border-radius:999px;
    background:rgba(40,38,36,0.10);
    border:1px solid var(--border-mid);
    backdrop-filter:blur(6px);
    -webkit-backdrop-filter:blur(6px);
    /* Behind the labels (z-index:1) so the active text shows ON TOP of
       the pane rather than disappearing behind it. The pane reads as the
       background of the active label rather than a lens covering it. */
    z-index:1;
  }
  /* Reel scroll area sits ABOVE the pane so labels are rendered on top. */
  .mreel{ z-index:2; }

  /* Overlay mode: the .mstack-close button sits at right:14px (44px wide),
     so the reel's frame would otherwise extend past the close button.
     Anchor the frame to left:16/right:64 (instead of the centred layout)
     so the pane stops cleanly before the button. */
  .mstack[data-overlay] .mreel-frame{
    left:16px;
    right:64px;
    width:auto;
    transform:translateY(-50%);
  }
  /* Also shift the scrollable reel so the active label centers WITHIN the
     offset frame instead of in the viewport. Without this, labels sit at
     viewport-center while the frame sits 24px to the left of viewport-
     center, making the active text look pushed to the right edge of the
     pane on subpages. Right inset of 48px = (close-button area 58px) − the
     frame's own right inset gap (10px), so label-center == frame-center. */
  .mstack[data-overlay] .mreel{
    right:48px;
  }
  /* Top + bottom fades — short (10px) so prev/next preview labels are
     clearly visible just outside the pane. Was 16-22px which masked
     most of the peek glyphs. The fade still smooths the wrap edge so
     the labels don't hard-clip. */
  .mreel-fade-top,.mreel-fade-bot{
    position:absolute;left:0;right:0;height:10px;
    z-index:3;pointer-events:none;
  }
  .mreel-fade-top{top:0;background:linear-gradient(to bottom,var(--bg-solid) 0%,rgba(250,250,249,0.0) 100%);}
  .mreel-fade-bot{bottom:0;background:linear-gradient(to top,var(--bg-solid) 0%,rgba(250,250,249,0.0) 100%);}

  /* Scrollable reel itself */
  .mreel{
    position:absolute;inset:0;
    overflow-y:auto;
    scroll-snap-type:y mandatory;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
  }
  .mreel::-webkit-scrollbar{display:none;}
  .mreel-item{
    /* Item shorter than the frame (32 vs 44) so the prev/next items
       sit closer to the active centre — their text glyphs land in the
       peek areas (just outside the pane on top + bottom). With items
       == frame, prev/next centres were 44px above/below the active
       centre, putting their glyphs entirely off-screen above/below
       the wrap. 32px brings them ~16px above/below the pane edge so
       previews are visible. */
    height:32px;
    display:flex;align-items:center;justify-content:center;
    scroll-snap-align:center;scroll-snap-stop:always;
    font-size:13px;letter-spacing:0.10em;
    color:var(--text-dim);
    transition:color 0.25s ease, transform 0.25s ease, font-size 0.25s ease, letter-spacing 0.25s ease, font-weight 0.25s ease;
    cursor:pointer;-webkit-tap-highlight-color:transparent;
    /* Suppress iOS long-press text-selection callout (Copy/Share popover)
       so our long-press → quick-jump grid handler runs cleanly. */
    user-select:none;-webkit-user-select:none;
    -webkit-touch-callout:none;
    flex-shrink:0;
  }
  /* Spacers so first/last items can be centered. Height = (mreel-wrap
     76px - mreel-item 32px) / 2 = 22px on each side. */
  .mreel-spacer{height:22px;flex-shrink:0;}
  /* Looking-glass enlargement: the label that lands inside the
     window-pane frame gets a transform-scale bump on top of its
     bigger font + tracking, so the text reads as if it's being
     magnified through a lens. The transition (declared on
     .mreel-item) animates the size + color jump as labels enter
     and leave the frame. */
  .mreel-item.cur{
    color:var(--text-bright);
    /* Active label sized to fit comfortably inside the 44px pane while
       leaving the prev/next labels visible just outside it. The +1px
       font + scale(1.10) + bold + bright color is enough to read as
       "selected"; bigger fonts overflowed the pane and pushed previews
       off the wrap entirely. */
    font-size:14px;letter-spacing:0.20em;
    font-weight:600;
    transform:scale(1.08);
    text-shadow:0 0 0.5px rgba(20,18,16,0.10);
  }
  /* Long-press feedback — label shrinks + dims while user holds, so they
     know the gesture is being detected. Transition matches LONG_PRESS_MS
     so it completes right as the grid opens. */
  .mreel-item.pressing{
    transform:scale(0.88);
    opacity:0.55;
    transition:transform 0.45s ease, opacity 0.45s ease;
  }
  /* Live position-in-section indicator next to active label, e.g. "(1/8)" */
  .mreel-count{
    font-size:0.65em;letter-spacing:0.04em;
    font-weight:400;
    opacity:0.55;margin-left:6px;
    font-variant-numeric:tabular-nums;
  }
  /* Slot-machine spin animation when section changes via tap */
  .mreel.spinning .mreel-item{
    transition:none;
    filter:blur(2px);opacity:0.7;
  }

  /* Live "1/8" position-in-section indicator, rendered INLINE next to the
     active reel label (replaces both the static (8) count and the right-edge
     mini-counter). Appears next to .mreel-item.cur only — see paintChrome().
     On intro the active label is 'works' but we hide this since the user
     isn't on a real work item yet. */

  /* Faint up/down chevrons next to the active reel label so the slot-machine
     reads as interactive. They sit just outside the active "frame" left/right. */
  .mreel-cue{
    /* Hidden by request — the slot reel pattern is self-explanatory and the
       arrow glyphs added visual noise on the left edge. */
    display:none;
    position:absolute;top:50%;
    transform:translateY(-50%);
    font-size:11px;line-height:1;
    color:var(--text-mid);
    opacity:0.55;pointer-events:none;
    z-index:4;
    transition:opacity 0.25s ease;
  }
  .mreel-cue.left{ left:14px; }
  .mreel-cue-stack{ display:flex;flex-direction:column;align-items:center;gap:2px; }

  /* Right-edge dot indicators: show position WITHIN the current section
     so the user has a map of how far through they are. Hidden on the
     intro card. */
  .mdots{
    position:absolute;right:6px;top:50%;
    transform:translateY(-50%);
    display:flex;flex-direction:column;align-items:center;gap:6px;
    z-index:7;pointer-events:none;
    transition:opacity 0.25s ease;
  }
  .mdots.hide{ opacity:0; }
  .mdot{
    width:5px;height:5px;border-radius:50%;
    background:rgba(250,250,249,0.30);
    transition:background 0.2s ease, transform 0.2s ease;
  }
  .mdot.on{
    background:rgba(250,250,249,0.95);
    transform:scale(1.25);
  }

  /* ── CARD STACK ────────────────────────────────────────────────────────── */
  /* No overflow:hidden + higher z-index than the reel so a swiping card
     visually passes OVER the slot-machine reel instead of being clipped by it. */
  .mcards{
    position:relative;flex:1;
    perspective:1200px;
    z-index:5;
  }
  .mreel-wrap{ z-index:1; }
  .mcard{
    position:absolute;left:16px;right:16px;
    /* Bottom value tuned so the user sees ACTIVE + 2 peeks fully above
       the dock pill, and the 3rd peek (pos 3) is ENTIRELY behind the
       pill — its 28px strip darkens through the frosted glass which
       the glow animation pulses against. Math:
         dock pill is (46 + 40 + 2-border + 14-bottom-margin) =
         102 + safe-bot from the bottom of the mstack.
         pos N peek bottoms sit at (X - 38, X - 70, X - 98) from bottom.
         For pos 2 fully above dock: X - 70 ≥ 102 + safe → X ≥ 172 + safe
         For pos 3 entirely behind dock: X - 70 ≤ 102 + safe → X ≤ 172 + safe
       Satisfied exactly at X = 172 + safe-bot — pos 2's bottom flush
       against the pill's top, pos 3's whole 28px peek strip behind the
       pill. */
    top:8px;bottom:calc(172px + env(safe-area-inset-bottom, 0px));
    border-radius:18px;
    overflow:hidden;
    /* Background matches page bg so the edges blend in during slide-in,
       lazy image load, or any moment the .mcard-media hasn't fully
       painted. .mcard-media itself stays dark — that's the art stage.
       No border — the artwork inside + drop shadow define the card edge. */
    background:var(--bg-solid);
    border:none;
    /* Lighter, warmer shadow — softer brown so it doesn't read heavy on the
     cream page bg. Two layers: a wide soft halo + a tight close shadow for
     edge definition. */
    box-shadow:0 18px 42px -20px rgba(70,55,42,0.18),
               0 3px 10px -4px rgba(70,55,42,0.10);
    will-change:transform,opacity;
    transition:transform 0.55s cubic-bezier(0.22,1,0.36,1),
               opacity 0.45s cubic-bezier(0.22,1,0.36,1);
    transform:translateY(120%);opacity:0;
    cursor:pointer;-webkit-tap-highlight-color:transparent;
  }
  /* Peek positions — each card behind the active one is offset & scaled down */
  .mcard[data-pos="0"]{transform:translateY(0) scale(1);opacity:1;z-index:5;}
  .mcard[data-pos="1"]{transform:translateY(38px) scale(0.94);opacity:0.92;z-index:4;}
  .mcard[data-pos="2"]{transform:translateY(70px) scale(0.88);opacity:0.70;z-index:3;}
  .mcard[data-pos="3"]{transform:translateY(98px) scale(0.82);opacity:0.45;z-index:2;}
  .mcard[data-pos="-1"]{transform:translateY(-110%) scale(0.96);opacity:0;z-index:1;}
  .mcard[data-pos="hidden"]{transform:translateY(120%) scale(0.8);opacity:0;z-index:0;pointer-events:none;}
  /* While dragging, disable transition for 1:1 finger tracking */
  .mcards.dragging .mcard{transition:none;}

  /* Card visual content (media area stays dark — it's the art stage) */
  .mcard-media{position:absolute;inset:0;background:#1a1a1a;}
  .mcard-media img,.mcard-media video{
    position:absolute;inset:0;width:100%;height:100%;
    object-fit:cover;display:block;
    /* Slightly favor upper portion of the frame on portrait crops so
       horizontally-composed works don't lose their center of interest.
       Per-card override possible via inline `--art-pos`. */
    object-position:var(--art-pos, center 38%);
  }
  /* The 'we-are-machines-made-for-dreaming' preview video has burned-in
     letterbox bars in the source file (the encoded frame includes a black
     border around the actual content), and object-fit:cover can't crop
     them off because they're part of the pixel data. Overscan the video
     element by ~10% so the bars push past the .mcard-media / .mjump-thumb
     edge — the rest of the frame still composes well at this scale because
     the underlying content is shot wide. Targeted by URL so other preview
     videos (which encode cleanly) keep their natural framing. */
  .mcard[data-url*="we-are-machines"] .mcard-media video,
  .mjump-cell[data-match-url*="we-are-machines"] .mjump-thumb video{
    transform:scale(1.10);
    transform-origin:center center;
  }
  .mcard-scrim{
    position:absolute;inset:0;
    background:linear-gradient(to bottom,
      rgba(0,0,0,0.42) 0%,
      rgba(0,0,0,0.05) 35%,
      rgba(0,0,0,0.15) 60%,
      rgba(0,0,0,0.85) 100%);
    pointer-events:none;
  }
  .mcard-label{
    position:absolute;left:22px;right:22px;top:22px;
    color:#f0eeeb;
    pointer-events:none;
  }
  /* Top meta row: card number on the LEFT, year on the RIGHT (flex
     space-between). Splitting them with the full card width between
     prevents '01 · 2024' from reading as a date like '01/2024'. */
  .mcard-num{
    display:flex;align-items:center;justify-content:space-between;
    font-size:12px;letter-spacing:0.22em;text-transform:uppercase;
    color:rgba(240,238,235,0.65);margin-bottom:8px;
  }
  .mcard-num .num{ display:inline-block; }
  .mcard-num .year{ display:inline-block; opacity:0.85; }
  .mcard-title{
    font-size:28px;font-weight:600;letter-spacing:-0.02em;line-height:1.05;
    color:#f0eeeb;margin-bottom:8px;text-transform:none;
  }
  .mcard-meta{
    font-size:13px;letter-spacing:0.10em;
    color:rgba(240,238,235,0.7);
  }
  .mcard-cta{
    position:absolute;left:22px;right:22px;bottom:22px;
    display:flex;align-items:center;justify-content:space-between;gap:14px;
    color:#f0eeeb;pointer-events:none;
  }
  .mcard-cta-text{
    font-size:13px;letter-spacing:0.20em;text-transform:uppercase;
    color:rgba(240,238,235,0.95);
  }
  .mcard-cta-arrow{
    width:44px;height:44px;border-radius:50%;
    border:1px solid rgba(240,238,235,0.45);
    display:flex;align-items:center;justify-content:center;
    font-size:18px;color:#f0eeeb;
    background:rgba(240,238,235,0.10);
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  }

  /* ── INTRO CARD (Plan Cult video + actual animated name/tagline) ───────── */
  /* Placeholder: the video is now deferred so the card can paint first.
     Without a poster, the .mcard-media area would be solid #1a1a1a — which
     would flash black before the video arrives. This subtle vertical gradient
     (matching the mood of the Plan Cult video) gives the card visual presence
     immediately, so deferring the video doesn't read as "broken" on slow
     networks or Low Power Mode. The video, once loaded, paints over this. */
  .mcard.intro .mcard-media{
    background:radial-gradient(ellipse at 50% 30%,
      #3b4452 0%,
      #232932 45%,
      #15181d 100%);
  }
  /* The intro card relies on a single .mcard-intro-blur element to mask the
     hardcoded subtitles AND tint the text-readable zone. The generic dark
     scrim is suppressed here so we don't stack multiple darkening / blurring
     layers over the Plan Cult video. */
  .mcard.intro .mcard-scrim{ display:none; }
  /* SINGLE bottom blur strip — does subtitle masking AND text-zone tinting
     with one element + one gradient mask. Was previously paired with the
     multi-stop .mcard-scrim which produced overlapping dark layers; now this
     is the only effect over the Plan Cult video.
     Real DOM element (.mcard-intro-blur) — iOS Safari is unreliable with
     backdrop-filter on ::before/::after inside transformed parents.
     translateZ(0) forces its own compositing layer. */
  .mcard.intro .mcard-intro-blur{
    position:absolute;left:0;right:0;bottom:0;
    height:42%;                             /* covers subtitle + name/CTA zone */
    pointer-events:none;
    z-index:2;
    /* 20px blur (>16px) smears the burned-in subtitles illegible. Subtle
       built-in dark tint replaces the deleted scrim so the name/tagline/CTA
       still have contrast against the video — no second element needed. */
    backdrop-filter:blur(20px) saturate(0.85);
    -webkit-backdrop-filter:blur(20px) saturate(0.85);
    background:linear-gradient(to bottom,
      rgba(0,0,0,0) 0%,
      rgba(0,0,0,0.22) 55%,
      rgba(0,0,0,0.55) 100%);
    /* One gradient mask: blur + tint fade in smoothly from the top of the
       strip and reach full strength before the subtitle row. */
    -webkit-mask-image:linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.85) 30%, black 55%);
    mask-image:linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.85) 30%, black 55%);
    -webkit-transform:translateZ(0);
    transform:translateZ(0);
  }
  /* Force the intro card to be its own stacking/compositing context so the
     backdrop-filter inside it is computed against the video below it. */
  .mcard.intro{ isolation:isolate; }
  /* The intro card hosts the existing #nameBtn (moved in via JS). The
     .tagline ("multidisciplinary visual artist & curator") is now hidden —
     see the .tagline rule below — so the host block only contains the
     animated S + 'eppe / de roo'. With the tagline gone there is room to
     drop the whole block lower on the card; bottom was 96px (which left the
     tagline ~24px above the .mcard-cta), now 72px so the bottom of the
     name lines up roughly where the tagline's bottom used to sit. The S
     and the 'eppe / de roo' text keep their own internal spacing/sizes
     (the .big-name + .sig-wrap rules below are unchanged), and left:22px
     keeps them aligned with the .mcard-cta and other card-edge content. */
  .mcard.intro .mcard-name-host{
    position:absolute;left:22px;right:22px;bottom:72px;
    pointer-events:auto;color:rgba(250,250,249,0.96);
    z-index:3;                              /* above the bottom blur strip */
    /* The hosted #nameBtn is an <a href="#"> — without these iOS Safari shows
       its native link-preview popup on long-press, which lifts/animates the
       visible S+name and competes with our jump-grid handler. */
    -webkit-touch-callout:none;
    -webkit-user-select:none;user-select:none;
  }
  .mcard.intro .mcard-name-host *{
    -webkit-touch-callout:none;
    -webkit-user-select:none;user-select:none;
  }
  .mcard.intro .mcard-name-host .big-name{
    /* Same size as old mobile menu: --fs-hero is 52px on ≤700px */
    font-size:52px;font-weight:600;line-height:0.88;letter-spacing:-0.03em;
    color:rgba(250,250,249,0.96);
    display:flex;align-items:flex-end;
    margin-bottom:4px;
    text-decoration:none;cursor:pointer;
  }
  /* Original old-menu ratios: ~113h × ~76w (taller than wide, aspect 0.7).
     The wrap is intentionally narrower than the webp's natural aspect — the
     base .sig-anim rule (line ~550) renders the image at full height, width
     auto, position absolute, and `overflow:hidden` here crops the sides. */
  .mcard.intro .mcard-name-host .sig-wrap{
    display:block;
    height:calc(52px * 2 * 1.05 + 4px);
    width:calc(52px * 2 * 1.05 * 0.7);
    flex-shrink:0;overflow:hidden;
    position:relative;                      /* needed: base .sig-anim is position:absolute */
  }
  /* Only override opacity — let the base .sig-anim rule (height:100%-4px;
     width:auto; position:absolute; centered) do the actual sizing. */
  .mcard.intro .mcard-name-host .sig-anim{
    opacity:0.93;
  }
  .mcard.intro .mcard-name-host .name-text{
    display:block;margin-left:-20px;        /* original old-menu offset */
    background:none!important;
    -webkit-text-fill-color:rgba(250,250,249,0.96)!important;
  }
  .mcard.intro .mcard-name-host .name-text-glow{display:none!important;}
  .mcard.intro .mcard-name-host .name-deroo{display:inline-block;padding-left:0.5em;}
  /* Tagline ("multidisciplinary visual artist & curator") removed from the
     mobile menu intro card on user request. Hidden via CSS rather than
     deleted from the DOM so the introHostStash markup + the existing JS
     that .appendChild()s it stay valid (move is a no-op visually). */
  .mcard.intro .mcard-name-host .tagline{
    display:none;
  }
  /* Make sure the CTA stays above the bottom blur strip */
  .mcard.intro .mcard-cta{ z-index:3; }
  /* Branded current-page card on a subpage: the .mcard-intro-blur strip is
     a backdrop-blur designed to mask the burned-in subtitles on the Plan
     Cult video. There are no subtitles on a static page thumbnail, and the
     blur was visibly distorting the bottom of the tulpas image — disable
     it for the branded variant. The .mcard-name-host has its own contrast
     against the image (text is rgba(250,250,249,0.96), the .sig-anim is a
     bright-white webp), so legibility is fine without the strip. */
  .mcard.intro[data-branded="1"] .mcard-intro-blur{
    display:none;
  }

  /* ── DOCK at very bottom (two-row liquid-glass pill) ─────────────────────
     Subscribe gets the full row 1 (full text + glow pulse) so the long
     "subscribe to transmissions" string never has to fight about/contact
     for space. About + contact share row 2, split by a 1px hairline.
     Whole pill floats over the cards with breathing room around it. */
  /* A faint dark CARD-shaped rectangle behind the dock pill, sized like
     a peek card (slightly inset from the pill width, ~28px tall — about
     half the subscribe row). Positioned so its top edge aligns with the
     pill's top edge: through the pill's frosted glass it darkens just
     the SUBSCRIBE row of the pill, not the about/contact row below.
     Reads like the lowest card in the deck is tucked behind the upper
     part of the pill. */
  .mstack::before{
    content:'';
    position:absolute;
    /* 16px inset from the pill's own edges, so it looks narrower than
       the pill (like a card behind a slightly-larger pill). */
    left:32px;right:32px;
    /* Bottom edge is 74px from .mstack bottom — pill bottom is at 14 +
       safe, pill top at 14 + safe + 88 = 102 + safe. Rect height 28,
       bottom at safe+74 → top at safe+102 = exactly pill top edge. */
    bottom:calc(env(safe-area-inset-bottom,0) + 74px);
    height:28px;
    background:rgba(30,24,18,0.55);
    border-radius:12px;
    z-index:9;         /* below .mdock (z:10) — pill is on top, dark below */
    pointer-events:none;
  }
  .mdock{
    position:absolute;
    left:16px;right:16px;
    bottom:calc(env(safe-area-inset-bottom,0) + 14px);
    padding:0;
    display:flex;flex-direction:column;align-items:stretch;
    z-index:10;pointer-events:auto;
    background:var(--glass-light-bg);
    border:1px solid var(--glass-light-border);
    box-shadow:var(--glass-light-shadow);
    backdrop-filter:var(--glass-filter);
    -webkit-backdrop-filter:var(--glass-filter);
    border-radius:22px;
    overflow:hidden;
  }
  /* Floating pill no longer needs the old flush-bottom gradient strip. */
  .mdock::before{display:none;}
  /* Subscribe — row 1, full width. Glow pulse animation draws attention. */
  .mdock .sub-link{
    flex:0 0 auto;
    height:46px;
    background:none;border:none;
    display:flex;align-items:center;justify-content:center;
    font-size:12px;letter-spacing:0.20em;text-transform:uppercase;
    font-weight:600;color:var(--text-bright);
    padding:0 16px;cursor:pointer;
    -webkit-tap-highlight-color:transparent;
    white-space:nowrap;position:relative;
    animation:dock-sub-glow 2.6s ease-in-out infinite;
    transition:background 0.2s;
  }
  /* Hairline below the subscribe row, inset slightly so it doesn't visually
     hit the rounded pill corners. */
  .mdock .sub-link::after{
    content:'';position:absolute;left:18px;right:18px;
    bottom:0;height:1px;background:var(--border-mid);
  }
  .mdock .sub-link:active{background:rgba(255,255,255,0.18);}
  /* Soft white-glow pulse — animates text-shadow only so it doesn't
     reflow or repaint the pill geometry. */
  @keyframes dock-sub-glow{
    0%,100%{
      text-shadow:0 0 0 rgba(255,255,255,0);
      color:var(--text-bright);
    }
    50%{
      text-shadow:0 0 14px rgba(255,255,255,0.65),
                  0 0 6px rgba(255,255,255,0.5);
      color:#fff;
    }
  }
  /* About + contact — row 2, two equal cells split by a centered hairline. */
  .mdock-row{
    flex:0 0 auto;
    display:flex;height:40px;align-items:stretch;
  }
  .mdock-row .footer-link{
    flex:1;
    background:none;border:none;
    display:flex;align-items:center;justify-content:center;
    font-size:11px;letter-spacing:0.18em;text-transform:uppercase;
    color:var(--text-mid);text-decoration:none;
    padding:0 8px;cursor:pointer;
    -webkit-tap-highlight-color:transparent;
    white-space:nowrap;position:relative;
    transition:color 0.2s, background 0.2s;
  }
  .mdock-row .footer-link + .footer-link::before{
    content:'';position:absolute;left:0;top:25%;bottom:25%;
    width:1px;background:var(--border-mid);
  }
  .mdock-row .footer-link:active{
    color:var(--text-bright);
    background:rgba(255,255,255,0.18);
  }

  /* ── First-visit hint chip (animated swipe prompt) ───────────────────────
     Sits in the upper portion of the card area, just below the slot reel,
     so it reads as "this is how to interact with the cards" rather than
     floating near the dock. */
  .mhint{
    position:absolute;left:50%;
    top:calc(110px + env(safe-area-inset-top,0));
    bottom:auto;
    transform:translateX(-50%);
    display:flex;align-items:center;gap:8px;
    padding:9px 16px;border-radius:20px;
    background:rgba(40,38,36,0.78);
    border:1px solid rgba(40,38,36,0.18);
    color:rgba(250,250,249,0.92);
    font-size:11px;letter-spacing:0.20em;text-transform:uppercase;
    white-space:nowrap;pointer-events:none;
    z-index:11;
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
    opacity:0;transition:opacity 0.4s ease;
  }
  .mhint.show{opacity:1;}
  .mhint-arrow{font-size:13px;animation:mhint-bob 1.5s ease-in-out infinite;}
  @keyframes mhint-bob{0%,100%{transform:translateY(0);}50%{transform:translateY(-3px);}}
  /* Jump-grid variant of the hint chip — pinned to the bottom of the overlay
     (above the home indicator on iOS) so it floats over the grid without
     blocking the section header at the top. z-index high enough to sit above
     the cells but below the morphing ghost. */
  .mjump-hint{
    top:auto;
    bottom:calc(20px + env(safe-area-inset-bottom,0));
    z-index:42;
  }

  /* ── Quick-jump grid (long-press a reel label to open) ───────────────────
     Full-screen modal that overlays the entire .mstack. Shows thumbnail
     grid for the chosen section so the user can scan all items at once
     instead of swiping through. Tap a thumb → close + jump to that card. */
  .mjump-overlay{
    position:fixed;inset:0;z-index:40;
    background:rgba(250,250,249,0.96);
    backdrop-filter:blur(20px) saturate(0.9);
    -webkit-backdrop-filter:blur(20px) saturate(0.9);
    display:flex;flex-direction:column;
    opacity:0;pointer-events:none;
    transition:opacity 0.25s ease;
  }
  .mjump-overlay.show{opacity:1;pointer-events:auto;}
  /* Sticky header: stays anchored above the scrollable grid with its OWN
     opaque background + bottom shadow, so cards scrolling underneath are
     fully obscured (no see-through) and the head reads as a clear separator
     above the thumbnails. z-index:2 keeps it above grid content during the
     morph animation. */
  .mjump-head{
    /* Floats absolutely over the top of the scrollable grid so cells
       can scroll UNDER the title pill + close button. Each pill carries
       its own backdrop-filter so cells passing behind get blurred,
       matching the iOS-style chrome elsewhere on the site. The
       .mjump-grid below adds top-padding equal to the head's height so
       cards never start clipped under the head. */
    position:absolute;
    top:0;left:0;right:0;
    z-index:2;
    display:flex;align-items:center;
    gap:8px;
    padding:14px 14px 12px;
    padding-top:calc(14px + env(safe-area-inset-top,0));
    background:transparent;
  }
  .mjump-head::before{
    /* Invisible left spacer mirroring the close button's footprint so the
       title (flex:1, centered) ends up geometrically centered in the row.
       44px = .mjump-close width. */
    content:"";
    width:44px;height:44px;
    flex-shrink:0;
    visibility:hidden;
  }
  .mjump-head .mjump-title{
    /* iOS-style sticky-header pill: matches .mjump-close height (44px)
       and shares the liquid-glass surface treatment so the head reads
       as a balanced pair of pills. flex:0 1 auto + margin:auto means
       the pill auto-sizes to its text content and centers itself in
       the row between the left spacer and the close button. */
    flex:0 1 auto;
    margin:auto;
    display:inline-flex;
    align-items:center;
    height:44px;
    padding:0 22px;
    border-radius:999px;
    background:var(--glass-light-bg);
    border:1px solid var(--glass-light-border);
    box-shadow:var(--glass-light-shadow);
    backdrop-filter:var(--glass-filter);
    -webkit-backdrop-filter:var(--glass-filter);
    /* Tappable affordance — handler in cardstack.js closes the
       grid and jumps the cardstack to the section the pill is
       currently showing. */
    cursor:pointer;
    -webkit-tap-highlight-color:transparent;
    transition:opacity 0.18s ease, background 0.2s, border-color 0.2s;
  }
  .mjump-head .mjump-title:active{ opacity:0.6; }
    /* Smooth content swap as the title text changes between sections
       on scroll — feels less jumpy when the pill width adjusts. */
    transition:padding 0.25s ease;
  }
  .mjump-head .mjump-close{
    /* No more position:absolute — flex aligns it for us. flex-shrink:0
       keeps the 44×44 size even on narrow screens. */
    flex-shrink:0;
  }
  .mjump-title{
    font-size:13px;letter-spacing:0.20em;text-transform:uppercase;
    color:var(--text-mid);font-weight:600;
    white-space:nowrap;
  }
  /* Mirror .ov-close exactly — same size, same border, same 1px-stroke X
     drawn via ::before/::after — so every close button on the site reads
     as the same component. The ✕ character in the markup gets hidden by
     font-size:0 / color:transparent (the aria-label on the <button>
     keeps it accessible). */
  .mjump-close{
    width:44px;height:44px;background:none;
    border:1px solid var(--border-mid);border-radius:50%;
    cursor:pointer;position:relative;flex-shrink:0;
    font-size:0;color:transparent;
    -webkit-tap-highlight-color:transparent;
    transition:all 0.2s;
  }
  .mjump-close::before,.mjump-close::after{
    content:'';position:absolute;top:50%;left:50%;
    width:16px;height:1px;background:var(--text-mid);transform-origin:center;
  }
  .mjump-close::before{transform:translate(-50%,-50%) rotate(45deg);}
  .mjump-close::after{transform:translate(-50%,-50%) rotate(-45deg);}
  .mjump-close:active{border-color:var(--text-mid);}
  .mjump-close:active::before,.mjump-close:active::after{background:var(--text-bright);}
  /* Outer container is now flex-column so section headers and per-section
     grids stack naturally; each section grid is a 2-col grid below its
     header. The whole thing scrolls as one continuous view. */
  .mjump-grid{
    flex:1;overflow-y:auto;
    /* Top padding clears the floating head (44px pill + 14px top padding
       + safe-area inset + 12px bottom padding ≈ 70px). scroll-padding-top
       matches so programmatic scrollTo lands the section header below
       the floating head instead of pinned underneath it. */
    padding:16px 16px 24px;
    padding-top:calc(env(safe-area-inset-top,0) + 80px);
    padding-bottom:calc(24px + env(safe-area-inset-bottom,0));
    scroll-padding-top:calc(env(safe-area-inset-top,0) + 80px);
    display:flex;flex-direction:column;gap:18px;
    -webkit-overflow-scrolling:touch;
  }
  .mjump-section-head{
    display:flex;align-items:baseline;gap:8px;
    margin-top:6px;
    padding:0 4px;
    border-bottom:1px solid var(--border);
    padding-bottom:8px;
    /* Tappable affordance — handler in cardstack.js closes the
       jump grid and jumps the cardstack to the first card of
       this section. */
    cursor:pointer;
    -webkit-tap-highlight-color:transparent;
    transition:opacity 0.18s ease;
  }
  .mjump-section-head:active{ opacity:0.55; }
  .mjump-section-head:first-child{ margin-top:0; }
  .mjump-section-name{
    font-size:14px;letter-spacing:0.18em;text-transform:uppercase;
    color:var(--text-bright);font-weight:600;
  }
  .mjump-section-count{
    font-size:11px;letter-spacing:0.10em;
    color:var(--text-dim);font-variant-numeric:tabular-nums;
  }
  .mjump-section-grid{
    display:grid;grid-template-columns:repeat(2,1fr);gap:14px;
  }
  .mjump-cell{
    display:flex;flex-direction:column;
    cursor:pointer;-webkit-tap-highlight-color:transparent;
    transition:transform 0.15s ease, opacity 0.15s ease;
  }
  .mjump-cell:active{transform:scale(0.97);}
  /* Long-press feedback — same vocabulary as the reel labels: cell shrinks
     + dims while held so the user knows the gesture is registering. */
  .mjump-cell.pressing{
    transform:scale(0.92);
    opacity:0.7;
    transition:transform 0.5s ease, opacity 0.5s ease;
  }
  .mjump-thumb{
    width:100%;aspect-ratio:1/1;
    border-radius:14px;overflow:hidden;
    background:#1a1a1a;
    position:relative;
    /* Bright liquid-glass rim — light edge catches at the top of the
       thumb, soft white refractive ring all the way around. Bumped
       border opacity from 0.16 → 0.55 and the inset ring brightness
       so the rim reads as light, not dark. */
    border:1px solid rgba(255,255,255,0.55);
    box-shadow:
      0 8px 24px rgba(0,0,0,0.18),
      0 2px 4px rgba(0,0,0,0.08),
      inset 0 1.5px 0 rgba(255,255,255,0.85),
      inset 0 3px 4px rgba(255,255,255,0.30),
      inset 0 -1px 0 rgba(255,255,255,0.10),
      inset 0 0 0 0.5px rgba(255,255,255,0.35);
    transition:transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
  }
  .mjump-cell:hover .mjump-thumb,
  .mjump-cell:active .mjump-thumb{
    border-color:rgba(255,255,255,0.85);
    box-shadow:
      0 6px 16px rgba(0,0,0,0.16),
      inset 0 1.8px 0 rgba(255,255,255,0.95),
      inset 0 4px 6px rgba(255,255,255,0.40),
      inset 0 -1px 0 rgba(255,255,255,0.18),
      inset 0 0 0 0.5px rgba(255,255,255,0.55);
  }
  .mjump-thumb img,.mjump-thumb video{
    position:absolute;inset:0;width:100%;height:100%;
    object-fit:cover;display:block;
    object-position:var(--art-pos, center 38%);
  }
  .mjump-num{
    position:absolute;top:8px;left:10px;z-index:2;
    font-size:10px;letter-spacing:0.16em;color:rgba(240,238,235,0.9);
    text-shadow:0 1px 4px rgba(0,0,0,0.45);
  }
  .mjump-text{
    padding:8px 4px 0;
    display:flex;flex-direction:column;gap:2px;
  }
  .mjump-tt{
    font-size:13px;line-height:1.25;letter-spacing:-0.01em;
    color:var(--text-bright);font-weight:600;
  }
  .mjump-yr{
    font-size:10px;letter-spacing:0.18em;color:var(--text-dim);
    text-transform:uppercase;
  }
  /* Overlay-mode close button. Only rendered when the mstack is in overlay
     mode (subpages). Mirrors .mjump-close visually so the close vocabulary is
     consistent across the menu and the jump grid. */
  .mstack-close{
    display:none;
    position:absolute;
    /* Vertically center with the slot-machine active line. The reel container
       (.mreel-wrap) is 88px tall, anchored at margin-top:safe-area-inset-top.
       Its visual center sits at safe + 44; a 44px-tall button centers there
       when its top = safe + 22 — close ↔ active reel frame share the same
       horizontal axis. */
    top:calc(env(safe-area-inset-top,0) + 22px);
    right:14px;
    z-index:60;
    width:44px;height:44px;background:rgba(40,38,36,0.10);
    border:1px solid var(--border-mid);border-radius:50%;
    cursor:pointer;
    font-size:0;color:transparent;
    -webkit-tap-highlight-color:transparent;
    backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
    transition:background 0.2s,border-color 0.2s;
  }
  .mstack-close::before,.mstack-close::after{
    content:'';position:absolute;top:50%;left:50%;
    width:16px;height:1px;background:var(--text-mid);transform-origin:center;
  }
  .mstack-close::before{transform:translate(-50%,-50%) rotate(45deg);}
  .mstack-close::after{transform:translate(-50%,-50%) rotate(-45deg);}
  .mstack-close:active{background:rgba(40,38,36,0.18);border-color:var(--text-mid);}
  .mstack-close:active::before,.mstack-close:active::after{background:var(--text-bright);}
  .mstack[data-overlay] .mstack-close{display:block;}


  /* When the jump grid is open, hide the overlay-mode close button on the
     menu so we don't end up with two stacked X's (the jump grid has its own
     close in its sticky header). Was using :has() — lands on Safari 15.4+
     so older devices showed both buttons (visibly misaligned: .mstack-close
     sits at safe+22px, .mjump-close is centered in the .mjump-head row at
     safe+~36px — they read as a stacked pair). Now driven by a JS-toggled
     class (.mjump-open) added in openJumpGrid / removed in closeJumpGrid:
     guaranteed to work in every browser.
     Was using `display:none` — but the close button has backdrop-filter:
     blur(6px), and toggling display causes iOS Safari to throw away its
     backdrop composite and rebuild it on re-show, which appears as a
     visible flash of the X (~1 frame of unstyled appearance) every time
     the user closes the jump grid back to the deck. Using opacity +
     visibility instead keeps the element in the render tree continuously,
     so the backdrop-filter stays composited and there's no re-paint
     hitch. pointer-events:none ensures the invisible button can't catch
     stray touches that the .mjump-close header button is meant to handle. */
  .mstack[data-overlay].mjump-open .mstack-close{
    opacity:0;
    visibility:hidden;
    pointer-events:none;
  }
}

@media(max-width:340px){
  /* Narrow screens (iPhone SE / mini): trim pill margins + shrink labels. */
  .mdock{ left:12px; right:12px; }
  .mdock .sub-link{ height:42px; font-size:11px; letter-spacing:0.16em; }
  .mdock-row{ height:36px; }
  .mdock-row .footer-link{ font-size:10px; letter-spacing:0.14em; padding:0 6px; }
  .mreel-item{ font-size:12px; letter-spacing:0.08em; }
  .mreel-item.cur{ font-size:13px; letter-spacing:0.14em; }
  .mreel-cue{ font-size:10px; }
  .mreel-cue.left{ left:10px; }
  .mcard{ left:12px; right:12px; bottom:calc(168px + env(safe-area-inset-bottom, 0px)); }
  .mcard-cta{ left:16px; right:16px; bottom:18px; }
  .mcard-cta-arrow{ width:38px; height:38px; font-size:16px; }
  .mhint{ font-size:10px; padding:8px 12px; top:calc(100px + env(safe-area-inset-top,0)); }
}


/* ── SENDER.NET SUBSCRIPTION FORM (lives inside #ov-subscribe) ── */
.sdr-form-field{margin-bottom:10px;}
.sdr-form-field input{
  background:rgba(var(--r),var(--g),var(--b),0.08);
  color:var(--text-bright);
  border:1px solid var(--border-mid);
  border-radius:0;
  font-size:16px;
  padding:16px 18px;
  letter-spacing:0.06em;
  width:100%;
  box-sizing:border-box;
}
.sdr-form-field input::placeholder{color:var(--text-dim);}
.sdr-form-field input:focus{border-color:var(--acc-strong);outline:none;}
.sdr-privacy-note{
  font-size:13px;color:var(--text-bright);
  line-height:1.8;margin-bottom:20px;opacity:0.85;
}
.sdr-privacy-note a{
  color:var(--text-bright);
  border-bottom:1px solid var(--border-mid);text-decoration:none;
  opacity:0.85;
}
.sdr-what-label{
  display:block;font-size:13px;letter-spacing:0.08em;
  color:var(--text-bright);font-weight:400;
  margin:24px 0 10px 0;opacity:0.7;
}
.sdr-what-desc{
  font-size:14px;
  color:var(--text-bright);
  opacity:0.85;line-height:1.8;display:block;
  margin-bottom:20px;
}
.sdr-groups{margin-bottom:20px;margin-top:4px;}
.sdr-group-row{margin-bottom:14px;display:flex;align-items:center;}
.sdr-group-row label{display:flex;align-items:center;gap:12px;cursor:pointer;margin:0;}
.sdr-group-row input[type="checkbox"]{
  width:18px;height:18px;margin:0;
  flex-shrink:0;cursor:pointer;
  appearance:none;-webkit-appearance:none;
  background:rgba(var(--r),var(--g),var(--b),0.08);
  border:1px solid var(--border-mid);
  border-radius:0;
  position:relative;
  transition:all 0.2s;
}
.sdr-group-row input[type="checkbox"]:checked{
  background:var(--acc-muted);
  border-color:var(--acc-strong);
}
.sdr-group-row input[type="checkbox"]:checked::after{
  content:'';
  position:absolute;
  left:50%;top:50%;
  transform:translate(-50%,-50%);
  width:8px;height:8px;
  background:var(--acc);
}
.sdr-group-row .sdr-label{
  font-size:15px;color:var(--text-bright);
  font-weight:400;font-style:normal;
  letter-spacing:0.06em;margin:0;opacity:0.9;
  line-height:1.5;
}
.sdr-submit-btn{
  background:transparent;
  border:1px solid var(--border-mid);
  border-radius:0;
  color:var(--text-bright);
  font-size:16px;font-weight:500;
  letter-spacing:0.18em;
  padding:18px 20px;
  width:100%;
  transition:all 0.35s;
  margin-top:6px;
  opacity:0.9;
  cursor:pointer;
}
.sdr-submit-btn:hover{
  background:var(--acc-muted);
  border-color:var(--acc-strong);
  color:var(--text-bright);
  opacity:1;
}
.sdr-submit-btn:disabled{opacity:0.5;cursor:not-allowed;}
.sdr-success{display:none;}
.sdr-success h4{
  font-size:18px;font-weight:500;
  color:var(--text-bright);
  letter-spacing:0.06em;
  margin-bottom:8px;
}
.sdr-success p{
  font-size:14px;
  color:var(--text-mid);
}
@media(max-width:700px){
  /* iOS Safari auto-zooms <input> when font-size < 16px and never zooms back out
     after submit — keep at 16px on mobile to prevent the zoom entirely. */
  .sdr-form-field input{font-size:16px;padding:18px 20px;}
  .sdr-privacy-note{font-size:15px;line-height:1.7;}
  .sdr-what-label{font-size:15px;letter-spacing:0.08em;}
  .sdr-what-desc{font-size:15px;line-height:1.7;}
  .sdr-group-row{margin-bottom:16px;}
  .sdr-group-row input[type="checkbox"]{width:22px;height:22px;}
  .sdr-group-row input[type="checkbox"]:checked::after{width:10px;height:10px;}
  .sdr-group-row .sdr-label{font-size:15px;line-height:1.5;}
  .sdr-submit-btn{font-size:15px;font-weight:500;padding:20px 22px;opacity:1;}
}
/* ─────────────────────────────────────────────────────────────────
   SUBPAGE .main CONTAINER — pushes content right of the bar-sliver
   so the page-header (left column = .header-left holding .series-title)
   isn't hidden under chrome.js's left-bar sliver. Originally inline in
   each subpage; lost from 5 of them during mega-extraction, which made
   the title appear "missing" on tulpas / ascetics / compositions /
   genesis / machines. Hoisted scoped to subpages so any new subpage
   inherits automatically.
   ───────────────────────────────────────────────────────────────── */
body[data-page="sub"] .main{
  padding-left:var(--sliver);
  transition:padding-left 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
  position:relative;
  z-index:1;
}
body[data-page="sub"] .left-bar.open ~ .main{padding-left:var(--bar-w);}
@media(min-width:701px){
  html.bar-preopen body[data-page="sub"] .main{padding-left:var(--bar-w);}
}
@media(max-width:700px){
  html:has(body[data-page="sub"]),body[data-page="sub"]{height:100%;overflow:hidden;}
  body[data-page="sub"] .main{
    padding-left:0;
    padding-top:0;
    padding-bottom:calc(env(safe-area-inset-bottom, 20px) + 100px);
    height:100dvh;
    overflow-y:auto;
    overflow-x:hidden;
    margin-top:58px;
    transition:none;
  }
}


/* ─────────────────────────────────────────────────────────────────
   SUBPAGE PAGE-HEADER (shared across all subpages)
   Hoisted from inline styles in 11 subpages.
   ───────────────────────────────────────────────────────────────── */
body[data-page="sub"] .header-right-mobile{display:none;}
body[data-page="sub"] .page-header-sticky{
  position:sticky;top:0;z-index:50;
  padding:0;
  will-change:transform;
  /* Suppress iOS Safari's default rectangular gray tap-highlight
     overlay — it draws a hard rectangle over the rounded compact
     pill and over the .header-left/.header-right bg blur, looking
     like a paint glitch. The pill itself is .compact-tappable for
     opening the mobile menu, which is why a touch action triggers
     the highlight in the first place. */
  -webkit-tap-highlight-color:transparent;
}
/* Full-bar (non-compact) treatment — bg + blur + bottom seam.
   Scoped to :not(.compact) so the compact state is naturally
   transparent (the injected .pill-surface from membrane.js takes
   over the visual). No !important needed; no override needed in
   membrane.css. */
body[data-page="sub"] .page-header-sticky:not(.compact){
  background:var(--bg-panel);
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  border-bottom:1px solid var(--border-mid);
}
body[data-page="sub"] .page-header{display:grid;grid-template-columns:1fr 1fr;}
body[data-page="sub"] .header-left{padding:28px 32px 22px;border-right:1px solid var(--border-mid);transition:padding 0.35s ease;}
body[data-page="sub"] .series-title{
  font-size:var(--fs-h1);font-weight:600;color:var(--text-bright);
  letter-spacing:-0.02em;line-height:0.88;margin-bottom:10px;
  background:none;border:none;cursor:default;padding:0;
  text-align:left;display:block;width:100%;
  /* `color` added to the transition list so the dark→cream switch
     when the sticky becomes .compact animates smoothly alongside
     the font-size / line-height / margin shrinks. */
  transition:font-size 0.35s ease, line-height 0.35s ease, margin-bottom 0.35s ease, color 0.35s ease;
}
@media(max-width:700px){
  body[data-page="sub"] .series-title{cursor:pointer;}
  body[data-page="sub"] .series-title:hover{color:var(--text-bright);opacity:0.8;}
}
body[data-page="sub"] .series-meta{font-size:var(--fs-small);letter-spacing:0.12em;color:var(--text-dim);line-height:1.8;transition:font-size 0.35s ease, color 0.35s ease;}
body[data-page="sub"] .header-right{padding:28px 32px;transition:padding 0.35s ease;}
body[data-page="sub"] .series-desc{font-size:var(--fs-body);line-height:1.9;color:var(--text-mid);transition:font-size 0.35s ease, line-height 0.35s ease, color 0.35s ease;}
/* COMPACT HEADER on scroll (desktop) */
@media(min-width:701px){
  body[data-page="sub"] .page-header-sticky.compact .header-left{padding:10px 32px 8px;}
  body[data-page="sub"] .page-header-sticky.compact .series-title{font-size:26px;line-height:1;margin-bottom:3px;}
  body[data-page="sub"] .page-header-sticky.compact .series-meta{font-size:10px;}
  body[data-page="sub"] .page-header-sticky.compact .header-right{padding:10px 32px;}
  body[data-page="sub"] .page-header-sticky.compact .series-desc{font-size:13px;line-height:1.55;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
}
/* COMPACT TEXT COLOR — switch to cream-white over the dark membrane
   surface (rgba(20,15,10,0.42)) when the sticky becomes a pill.
   Same value the source spec uses for .pill-text plus the
   .pill-title text-shadow. The transition is on the base rules
   above (color 0.35s ease) so the dark→cream switch animates
   smoothly along with font/padding shrinks. Applies to BOTH desktop
   and mobile compact states (no @media wrap). */
body[data-page="sub"] .page-header-sticky.compact .series-title,
body[data-page="sub"] .page-header-sticky.compact .series-meta,
body[data-page="sub"] .page-header-sticky.compact .series-desc{
  color:rgba(245,238,225,0.95);
  -webkit-text-fill-color:rgba(245,238,225,0.95);
  text-shadow:0 1px 0 rgba(0,0,0,0.20);
}
/* COMPACT HEADER on scroll (mobile) — header stays at the TOP of
   the viewport at all times; on scroll past 60px it MORPHS from a
   full-width sticky bar into a floating pill in place. No bottom-
   fly any more — the user wanted the header to stay anchored to
   the top.

   The morph is animated via transitions on the few properties that
   can be interpolated (margin, border-radius, padding, background,
   box-shadow, border-color). Width "shrink" is achieved through
   margin transitions (margin:0 → margin:0 14px) which animate.
   We keep position:sticky throughout so the layout is identical
   in both states; .compact only changes the visual treatment. */
@media(max-width:700px){
  body[data-page="sub"] .page-header-sticky{
    transition:
      margin 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
      top 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
      transform 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
      border-radius 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
      background 0.30s ease,
      box-shadow 0.30s ease,
      border-color 0.30s ease;
    will-change:margin, top, transform, border-radius;
  }
  /* Compact pill — pinned at the top of the viewport.
     IMPORTANT: with position:sticky the stuck position is anchored by
     `top:` (set to 0 on the base rule), NOT by margin-top. Pushing the
     pill down with margin-top alone has no effect once it's stuck —
     it only affects in-flow position. So we override `top:` to
     safe-area + 22px instead, which actually shifts the stuck
     position. Side margins still come from the margin shorthand. */
  /* Compact pill — pinned at the top of the viewport. The visual
     treatment (bg / border / shadow / clip / blur / filter) is ALL
     provided by the .pill-surface injected by membrane.js, which is
     the SINGLE clipping layer (matches the source spec where .pill
     alone has overflow:hidden + border-radius + filter:url; .pill-wrap
     parent is just positioning). NO overflow:hidden here — adding
     a second clipping layer at the parent broke Safari's compositor
     handling of filter:url and produced a rectangular halo around
     the rounded pill. border-radius stays so layout/spacing reads
     correctly; the surface inherits it. */
  body[data-page="sub"] .page-header-sticky.compact{
    top:calc(env(safe-area-inset-top, 0px) + 14px) !important;
    margin:0 16px 0 !important;
    border-radius:28px;
    z-index:50;
  }
  /* Bottom-docked variant — toggled by the drag handler in subpage.js.
     Position stays `sticky` (same as the top-dock state) so the only
     thing changing between the two states is transform — fully
     interpolatable, so the slide between top and bottom animates
     cleanly via the transition rule above. The translateY value is
     calculated so the pill's bottom edge ends up at viewport-height
     minus the bottom safe-area inset minus 14px breathing room.
       100dvh   = full viewport
       100%     = the pill's own height
       safe-top + safe-bot = system insets
       28px     = top inset (14) + bottom inset (14)
     The previous `position:fixed` swap made the slide instantaneous
     because `position` is not animatable — that was the glitch. */
  body[data-page="sub"] .page-header-sticky.compact.dock-bottom{
    transform:translateY(calc(
      100dvh
      - 100%
      - env(safe-area-inset-top, 0px)
      - env(safe-area-inset-bottom, 0px)
      - 28px
    ));
  }
  body[data-page="sub"] .page-header-sticky.compact.is-dragging{
    transition:none !important;
    cursor:grabbing;
  }
  body[data-page="sub"] .page-header-sticky.compact .header-left{
    padding:10px 20px 8px !important;
    border-bottom:none !important;
    background:transparent !important;
    transition:padding 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
  }
  body[data-page="sub"] .page-header-sticky.compact .series-title{
    font-size:22px !important; line-height:1 !important; margin-bottom:2px !important;
  }
  body[data-page="sub"] .page-header-sticky .header-left,
  body[data-page="sub"] .page-header-sticky .series-title,
  body[data-page="sub"] .page-header-sticky .series-meta{
    transition:font-size 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
               padding 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
               margin 0.42s cubic-bezier(0.25,0.46,0.45,0.94),
               line-height 0.42s cubic-bezier(0.25,0.46,0.45,0.94);
  }
  body[data-page="sub"] .page-header-sticky.compact .series-meta{font-size:11px !important;}
  body[data-page="sub"] .page-header-sticky.compact .header-right-mobile{
    display:none !important;
  }
}

/* MOBILE responsive page-header */
@media(max-width:700px){
  /* Same backdrop blur on the sticky header as desktop. The previous
     transparent-+-solid-bg-on-children setup looked flat compared to
     the desktop's frosted-glass treatment. We blur on the sticky
     wrapper itself and let .header-left / .header-right-mobile remain
     translucent so content scrolling behind them gets the same blur
     pass as on desktop. The header-left's top padding is bumped to
     clear the floating menu pill (which lives at top:safe+7 height:44),
     so the pill sits inside the header chrome — and because the header
     itself is what's drawing the blur, the area immediately around AND
     under the pill is the same frosted surface (consistent with the
     blur the pill's own backdrop-filter applies). */
  body[data-page="sub"] .page-header-sticky{
    position:sticky;top:0;z-index:45;
    padding:0;
  }
  /* Mobile non-compact full-bar — same scoping as desktop: visual
     treatment only when NOT compact, so the membrane owns the
     compact pill cleanly. */
  body[data-page="sub"] .page-header-sticky:not(.compact){
    background:var(--bg-panel);
    backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
    border-bottom:1px solid var(--border-mid);
  }
  body[data-page="sub"] .page-header{display:block;}
  body[data-page="sub"] .header-left{
    background:transparent;
    backdrop-filter:none;-webkit-backdrop-filter:none;
    border-bottom:1px solid var(--border);
    border-right:none;
    /* Tight padding-top now that the floating menu pill is gone — only
       enough to clear the safe-area / status-bar zone with a little
       breath. Title starts ~14px below the notch. */
    padding:calc(env(safe-area-inset-top, 0px) + 14px) 20px 16px;
  }
  body[data-page="sub"] .header-right-desktop{display:none;}
  body[data-page="sub"] .header-right-mobile{
    display:block;position:relative;
    padding:20px 20px 24px;
    background:transparent;
  }
  body[data-page="sub"] .series-desc{font-size:18px;line-height:1.75;}
  body[data-page="sub"] .series-meta{font-size:16px;line-height:1.9;}
  body[data-page="sub"] .series-title{font-size:38px;}
}

/* ─────────────────────────────────────────────────────────────────
   SUBPAGE VIEW-TOGGLE / DOC-HEADER (shared)
   Hoisted from inline CSS in 8 subpages.
   ───────────────────────────────────────────────────────────────── */
body[data-page="sub"] .doc-header{
  display:flex;align-items:center;justify-content:space-between;
  padding:14px 32px;border-bottom:1px solid var(--border-mid);
}
body[data-page="sub"] .doc-label{font-size:var(--fs-small);letter-spacing:0.20em;color:var(--text-dim);}
body[data-page="sub"] .view-toggle{display:flex;align-items:center;gap:10px;}
body[data-page="sub"] .view-toggle-label{font-size:10px;letter-spacing:0.18em;color:var(--text-dim);transition:color 0.25s;user-select:none;}
body[data-page="sub"] .view-toggle-label.active{color:var(--text-bright);}
body[data-page="sub"] .view-toggle-track{
  width:44px;height:22px;
  border:1px solid var(--border-mid);
  border-radius:11px;
  position:relative;cursor:pointer;flex-shrink:0;
  background:transparent;
  transition:border-color 0.25s, background 0.25s;
}
body[data-page="sub"] .view-toggle-track:hover{
  border-color:var(--text-mid);
}
body[data-page="sub"] .view-toggle-thumb{
  position:absolute;top:2px;left:2px;width:16px;height:16px;
  border-radius:50%;
  background:var(--text-mid);
  transition:transform 0.3s cubic-bezier(0.25,0.46,0.45,0.94);
}
body[data-page="sub"] .view-toggle-track.book .view-toggle-thumb{transform:translateX(22px);}

/* ─────────────────────────────────────────────────────────────────
   SUBPAGE MASONRY GRID (Pinterest-style mosaic)
   ───────────────────────────────────────────────────────────────── */
body[data-page="sub"] .masonry{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  grid-auto-rows:8px;
  gap:2px;
  padding:2px;
  opacity:1;transition:opacity 0.2s;
}
body[data-page="sub"] .masonry.hidden{opacity:0;pointer-events:none;height:0;overflow:hidden;}
body[data-page="sub"] .mitem{position:relative;cursor:pointer;overflow:hidden;opacity:0;transform:translateY(14px);transition:opacity 0.55s ease,transform 0.55s ease;}
body[data-page="sub"] .mitem.visible{opacity:1;transform:translateY(0);}
body[data-page="sub"] .mimg{width:100%;height:100%;object-fit:cover;display:block;transition:transform 0.5s ease;border:1px solid var(--border);}
body[data-page="sub"] .mitem:hover .mimg{transform:scale(1.025);}
body[data-page="sub"] .moverlay{position:absolute;bottom:0;left:0;right:0;padding:22px 16px 14px;background:linear-gradient(to top,rgba(95,90,85,0.88) 0%,transparent 100%);opacity:0;transition:opacity 0.3s;pointer-events:none;}
body[data-page="sub"] .mitem:hover .moverlay{opacity:1;}
body[data-page="sub"] .moverlay-title{display:block;font-size:16px;font-weight:500;color:rgba(250,250,249,0.85);}
body[data-page="sub"] .moverlay-info{display:block;font-size:11px;letter-spacing:0.08em;color:rgba(250,250,249,0.65);margin-top:3px;}

/* ─────────────────────────────────────────────────────────────────
   SUBPAGE BOOK MODE (book-spread / book-text / book-image)
   .tulpas-book is the legacy class name retained for compatibility.
   ───────────────────────────────────────────────────────────────── */
body[data-page="sub"] .tulpas-book,
body[data-page="sub"] .book-mode{opacity:0;pointer-events:none;height:0;overflow:hidden;transition:opacity 0.2s;}
body[data-page="sub"] .tulpas-book.visible,
body[data-page="sub"] .book-mode.visible{opacity:1;pointer-events:all;height:auto;overflow:visible;}
body[data-page="sub"] .book-spread{display:grid;grid-template-columns:1fr 1fr;border-bottom:1px solid var(--border-mid);}
body[data-page="sub"] .book-spread:last-child{border-bottom:none;}
body[data-page="sub"] .book-image{overflow:hidden;position:relative;}
body[data-page="sub"] .book-image img{display:block;width:100%;height:100%;object-fit:cover;min-height:420px;transition:transform 0.7s cubic-bezier(0.25,0.46,0.45,0.94);cursor:zoom-in;}
body[data-page="sub"] .book-image:hover img{transform:scale(1.03);}
body[data-page="sub"] .book-text{
  padding:64px 52px;display:flex;flex-direction:column;justify-content:center;
  border-left:1px solid var(--border-mid);
}
body[data-page="sub"] .book-spread:nth-child(even) .book-image{order:1;}
body[data-page="sub"] .book-spread:nth-child(even) .book-text{border-left:none;border-right:1px solid var(--border-mid);order:0;}
/* Explicit class-based ordering (entrotopia/apocalypse). Compound class
   selector beats :nth-child pseudo-class specificity, so these win when
   .text-left / .img-left is present and override the even/odd fallback. */
body[data-page="sub"] .book-spread.text-left .book-image{order:1;}
body[data-page="sub"] .book-spread.text-left .book-text{order:0;border-left:1px solid var(--border-mid);border-right:none;}
body[data-page="sub"] .book-spread.img-left .book-image{order:0;}
body[data-page="sub"] .book-spread.img-left .book-text{order:1;border-left:none;border-right:1px solid var(--border-mid);}

/* FULL-BLEED spread (used by exhibition pages — entrotopia, apocalypse).
   Single-column layout: the image fills the full width, and the .book-text
   floats over the bottom of the image as a darkening gradient overlay with
   white-tinted text. Hoisted from per-page so the cascade order is reliable
   regardless of which @import / inline order future pages use. */
body[data-page="sub"] .book-spread.full-bleed{
  grid-template-columns:1fr;
  position:relative;
  overflow:hidden;
}
body[data-page="sub"] .book-spread.full-bleed .book-image{order:0;}
body[data-page="sub"] .book-spread.full-bleed .book-image img{min-height:560px;}
body[data-page="sub"] .book-spread.full-bleed .book-text{
  position:absolute;bottom:0;left:0;right:0;
  padding:48px 52px;
  background:linear-gradient(transparent,rgba(20,18,16,0.68));
  border:none;
  display:block;
}
body[data-page="sub"] .book-spread.full-bleed .book-text-num{color:rgba(250,250,249,0.40);}
body[data-page="sub"] .book-spread.full-bleed .book-text-title{color:rgba(250,250,249,0.95);}
body[data-page="sub"] .book-spread.full-bleed .book-text-artist{color:rgba(250,250,249,0.55);}
body[data-page="sub"] .book-spread.full-bleed .book-text-desc{color:rgba(250,250,249,0.72);}
/* Mobile: keep the full-bleed overlay clean. The generic mobile rule
   `.book-text{padding:28px 20px;border-top:1px solid;...}` would otherwise
   draw a horizontal divider line through the middle of the image and crop
   the gradient padding too tightly. Bigger title size here as well — the
   exhibition main titles ("welcome to the age of chaos…", "sit back, relax
   & enjoy the apocalypse") need real presence over a full-page photo. */
@media(max-width:700px){
  body[data-page="sub"] .book-spread.full-bleed .book-text{
    padding:60px 24px 30px!important;
    border:none!important;
    background:linear-gradient(transparent 0%,rgba(20,18,16,0.55) 35%,rgba(20,18,16,0.85) 100%)!important;
  }
  body[data-page="sub"] .book-spread.full-bleed .book-text-num{
    margin-bottom:14px;
  }
  body[data-page="sub"] .book-spread.full-bleed .book-text-title{
    font-size:30px;line-height:1.05;margin-bottom:12px;
  }
  body[data-page="sub"] .book-spread.full-bleed .book-text-artist{
    font-size:11px;margin-bottom:14px;
  }
  body[data-page="sub"] .book-spread.full-bleed .book-text-desc{
    font-size:14px;line-height:1.65;
  }
  body[data-page="sub"] .book-spread.full-bleed .book-image img{
    min-height:78dvh;
  }
}
/* Desktop: bump the full-bleed title — the previous 22px got lost over a
   large photographic background. The non-full-bleed spreads keep the
   shared 22px treatment via .book-text-title above. */
@media(min-width:701px){
  body[data-page="sub"] .book-spread.full-bleed .book-text-title{
    font-size:42px;line-height:1.0;letter-spacing:-0.02em;margin-bottom:14px;
  }
}
body[data-page="sub"] .book-text-num{font-size:10px;letter-spacing:0.22em;color:var(--text-dim);margin-bottom:20px;display:block;}
body[data-page="sub"] .book-text-title{font-size:22px;font-weight:600;letter-spacing:-0.02em;color:var(--text-bright);line-height:1.1;margin-bottom:14px;}
body[data-page="sub"] .book-text-meta{font-size:12px;letter-spacing:0.12em;color:var(--text-dim);line-height:1.9;}
/* book-text-artist / book-text-desc — used by exhibition pages
   (entrotopia, apocalypse) for artist credit and description text inside
   book spreads. Styles match the originals. */
body[data-page="sub"] .book-text-artist{font-size:11px;letter-spacing:0.18em;color:var(--text-dim);margin-bottom:18px;display:block;}
body[data-page="sub"] .book-text-desc{font-size:var(--fs-body);line-height:1.9;color:var(--text-mid);}

/* ─────────────────────────────────────────────────────────────────
   SUBPAGE LIGHTBOX (tulpas convention — .lightbox / #lightbox)
   ───────────────────────────────────────────────────────────────── */
body[data-page="sub"] .lightbox{position:fixed;inset:0;z-index:10000;background:rgba(250,250,249,0.92);backdrop-filter:blur(48px);-webkit-backdrop-filter:blur(48px);opacity:0;pointer-events:none;transition:opacity 0.4s ease;display:grid;grid-template-rows:1fr auto;}
body[data-page="sub"] .lightbox.open{opacity:1;pointer-events:all;}
body[data-page="sub"] .lb-close{position:absolute;top:24px;right:24px;z-index:10;width:36px;height:36px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;}
body[data-page="sub"] .lb-close::before,
body[data-page="sub"] .lb-close::after{background:var(--text-mid);}
body[data-page="sub"] .lb-close:hover::before,
body[data-page="sub"] .lb-close:hover::after{background:var(--text-bright);}
body[data-page="sub"] .lb-close::before,
body[data-page="sub"] .lb-close::after{content:'';position:absolute;top:50%;left:50%;width:20px;height:1.5px;background:var(--text-bright);transform-origin:center;transition:background 0.2s;}
body[data-page="sub"] .lb-close::before{transform:translate(-50%,-50%) rotate(45deg);}
body[data-page="sub"] .lb-close::after{transform:translate(-50%,-50%) rotate(-45deg);}
body[data-page="sub"] .lb-stage{display:flex;align-items:center;justify-content:center;padding:64px 80px 24px;overflow:hidden;}
body[data-page="sub"] .lb-img{max-width:100%;max-height:100%;object-fit:contain;transition:opacity 0.28s ease;display:block;box-shadow:0 8px 40px rgba(0,0,0,0.08);border:1px solid var(--border);}
body[data-page="sub"] .lb-img.fade{opacity:0;}
body[data-page="sub"] .lb-info{padding:16px 32px 24px;border-top:1px solid var(--border);display:grid;grid-template-columns:auto 1fr auto;align-items:center;flex-shrink:0;gap:24px;}
body[data-page="sub"] .lb-prev,
body[data-page="sub"] .lb-next{background:none;border:none;cursor:pointer;font-size:16px;letter-spacing:0.20em;color:var(--text-mid);transition:color 0.2s;padding:4px 0;white-space:nowrap;opacity:0.55;}
body[data-page="sub"] .lb-prev:hover,
body[data-page="sub"] .lb-next:hover{color:var(--text-bright);opacity:1;}
body[data-page="sub"] .lb-info-center{text-align:center;}
body[data-page="sub"] .lb-title{font-size:20px;font-weight:500;color:var(--text-bright);}
body[data-page="sub"] .lb-meta{font-size:12px;letter-spacing:0.1em;color:var(--text-dim);margin-top:5px;}
body[data-page="sub"] .lb-counter{font-size:11px;letter-spacing:0.18em;color:var(--text-dim);white-space:nowrap;margin-top:4px;}

/* MOBILE responsive: book / masonry / lightbox / view-toggle */
@media(max-width:700px){
  body[data-page="sub"] .masonry{
    grid-template-columns:1fr;
    grid-auto-rows:auto;
    padding:0 0 calc(env(safe-area-inset-bottom, 60px) + 20px + var(--gdpr-h)) 0;
    gap:1px;
  }
  body[data-page="sub"] .mitem{grid-row:unset!important;}
  body[data-page="sub"] .mimg{height:auto;object-fit:contain;}
  body[data-page="sub"] .doc-header{padding:12px 20px;}
  body[data-page="sub"] .book-spread{grid-template-columns:1fr;}
  body[data-page="sub"] .book-text{padding:28px 20px;border-left:none!important;border-right:none!important;border-top:1px solid var(--border-mid);}
  body[data-page="sub"] .book-spread:nth-child(even) .book-image{order:0;}
  body[data-page="sub"] .book-spread:nth-child(even) .book-text{order:1;}
  body[data-page="sub"] .book-image img{min-height:260px;}
  body[data-page="sub"] .tulpas-book,
  body[data-page="sub"] .book-mode{padding-bottom:calc(env(safe-area-inset-bottom, 60px) + 20px);}
  body[data-page="sub"] .lb-stage{padding:40px 24px 16px;}
  body[data-page="sub"] .lb-close{top:16px;right:16px;opacity:0.5;}
  body[data-page="sub"] .lb-info{padding:12px 16px 20px;gap:16px;}
  body[data-page="sub"] .lb-prev,
  body[data-page="sub"] .lb-next{font-size:14px;padding:6px 0;}
  body[data-page="sub"] .lb-title{font-size:17px;}
  body[data-page="sub"] .lb-meta{font-size:11px;}
  body[data-page="sub"] .lb-counter{margin-top:4px;}
}
