/* ════════════════════════════════════════════════════════════════════════
   adityaraju.com — single non-scrolling stage divided into ZONES (v5.0.0).

   HOW THIS FILE IS ORGANISED
   ──────────────────────────
   1. SHARED FOUNDATION   reset, stage, fonts, theme, header/footer/center bars,
                          the bits every section reuses.
   2. LAYOUT / ZONES      WHERE a section sits. Position only, section-agnostic.
                          Driven by the --cell-* CSS variables js/main.js writes
                          on each <section> from controls/order.html's zone lines.
   3. SECTION A / B / C / D  WHAT each section IS (its glyph, ambient scene,
                          sub-nav). Each block is self-contained and scoped to
                          [data-section="x"]; you can read, edit, or lift one out
                          without touching the others. Engine-built sections
                          (any new id in a zone line) use the generic fallback
                          styling at the bottom of this file.

   TO EDIT (the two knobs you'll reach for most) — both live in controls/:
       • rename a section / change its link → controls/a.html, b.html, c.html, …
       • rearrange the page                → controls/order.html (zone lines)

   The structural ids a / b / c / d never change — where they sit comes
   entirely from the zone lines; label, link, symbol, scene are all swappable.
   ════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════
   1 · SHARED FOUNDATION
   ══════════════════════════════════════════ */

  *        { margin:0; padding:0; box-sizing:border-box; -webkit-user-select:none; user-select:none; }
  html,body{ height:100%; }
  body{
    position:relative; width:100%;
    min-height:100vh; height:100vh; height:100dvh;   /* dvh keeps it stable on mobile */
    overflow:hidden; overscroll-behavior:none;        /* single non-scrolling stage   */
    background:#ffffff; color:#2b2b2b;
    font-family:"Cormorant Garamond", Georgia, serif; -webkit-font-smoothing:antialiased;
    padding-top:env(safe-area-inset-top,0);
    padding-bottom:env(safe-area-inset-bottom,0);
  }
  @media (prefers-reduced-motion: no-preference){
    body{ transition:opacity 0.7s ease; }
  }
  body.loading{ opacity:0; }

  /* dark theme — only the neutrals flip; each section's vivid accents are left alone */
  :root[data-theme="dark"] body{ background:#1a1916; color:#e9e4da; }

  /* ── header bar (top) ─────────────────────────────────────────────────
     Sits fixed at the very top. The JS sets --bar-height to the bar's actual
     pixel height when a message is present, 0px when not — everything below
     offsets by that variable so nothing overlaps. */
  :root{ --bar-height: 0px; }

  #header-bar{
    position:fixed; top:0; left:0; right:0; z-index:100;
    /* The bar sits at the very top and its dark background fills the notch
       strip, so the notch area takes the header colour; the top padding
       pushes the text clear of the notch. Browser: env() does it. Installed
       app: --safe-abs does (override just below). */
    padding:calc(env(safe-area-inset-top,0px) + 0.55em) 1.4em 0.55em;
    background:#2b2724; color:#e9e4da;
    font-size:0.78rem; letter-spacing:0.12em; text-align:center;
    line-height:1.5; white-space:pre-line;
    display:none;   /* shown by JS only when message is non-empty */
  }
  :root[data-theme="dark"] #header-bar{ background:#111010; color:#c9c4b8; }
  /* Installed app: --safe-abs (reliable, iOS-floored) fills the notch strip
     with the header background and clears the text below it. */
  :root[data-standalone] #header-bar{ padding-top:calc(var(--safe-abs) + 0.55em); }

  /* ── footer bar (bottom) ─────────────────────────────────────────────────
     Same mechanism as the header bar, mirrored to the bottom edge.
     The JS sets --footer-bar-height when controls/footer.html has a message. */
  :root{ --footer-bar-height: 0px; }

  /* ── stage geometry ──────────────────────────────────────────────────
     The visible stage is the viewport minus the header bar, the footer
     bar, AND the site-title band (--site-title-height, set by the JS to
     the wordmark's reserved height when a "site title:" is present, 0px
     otherwise — v5.6.0). The title band reserves at the TOP by default;
     with "site title position: bottom" (v5.7.0) the JS sets
     data-title-pos="bottom" on the root, moving the band to the bottom
     (--title-top-band → 0). --stage-top is where the stage begins;
     --stage-h is its height (the full title band is subtracted either
     way). Every cell/ambient/divider position below is expressed in
     these two, so the zones always split only the space the title
     doesn't occupy. */
  :root{
    --site-title-height: 0px;
    --title-top-band: var(--site-title-height);
    /* Notch / safe-area handling for an INSTALLED app, in two parts:
       --safe-abs = the raw top inset (0 in a browser — the chrome handles
         the notch; the real env() inset in a standalone app; floored at
         59px on iOS, which often reports it as 0 under the notch/Dynamic
         Island — see the data-standalone stamp in js/main.js). Used by
         whichever top element FILLS the notch strip so it takes that
         element's colour (header bar → header dark; title band → its
         background), keeping the strip theme-aware for free.
       --safe-top = the GEOMETRY reserve, non-zero only when NOTHING fills
         the notch (header off): then the stage + title band start below a
         blank strip showing the page background. When the header is on it
         absorbs the notch into its own height (--bar-height), so --safe-top
         stays 0 and nothing double-counts. */
    --safe-abs: 0px;
    --safe-top: 0px;
    --stage-top: calc(var(--safe-top) + var(--bar-height) + var(--title-top-band));
    --stage-h:   calc(100dvh - var(--safe-top) - var(--bar-height) - var(--site-title-height) - var(--footer-bar-height));
  }
  :root[data-standalone]{ --safe-abs: env(safe-area-inset-top,0px); }
  :root[data-standalone="ios"]{ --safe-abs: max(env(safe-area-inset-top,0px), 59px); }
  :root[data-standalone][data-header="off"]{ --safe-top: var(--safe-abs); }
  :root[data-title-pos="bottom"]{ --title-top-band: 0px; }

  #footer-bar{
    position:fixed; bottom:0; left:0; right:0; z-index:100;
    /* the background fills the home-indicator inset, but the inset is split
       EVENLY between the top and bottom padding so the text sits at the
       optical centre of the visible bar (equal gap to the bar's top edge and
       to the screen edge). Total padding is unchanged, so the measured
       --footer-bar-height — and all the stage geometry off it — is identical. */
    padding:calc(env(safe-area-inset-bottom,0px) / 2 + 0.55em) 1.4em calc(env(safe-area-inset-bottom,0px) / 2 + 0.55em);
    background:#2b2724; color:#e9e4da;
    font-size:0.78rem; letter-spacing:0.12em; text-align:center;
    line-height:1.5; white-space:pre-line;
    display:none;   /* shown by JS only when message is non-empty */
  }
  :root[data-theme="dark"] #footer-bar{ background:#111010; color:#c9c4b8; }

  /* Push any in-flow content clear of the safe area / bars. --safe-top
     reserves the notch in an installed app; env() covers the browser. */
  body{
    padding-top:max(env(safe-area-inset-top,0px), var(--safe-top), var(--bar-height));
    padding-bottom:max(env(safe-area-inset-bottom,0px), var(--footer-bar-height));
  }

  /* ── side bars (floating, frosted tall-oval slivers) ─────────────────────
     A tall ellipse anchored at a side edge of the viewport, vertically
     centred, spanning ~90% of the viewport height. Most of the oval sits
     off-screen — JS sets its width/height/left (or right) inline so only a
     slender curved sliver (~70px at the widest, mid-height point) protrudes
     onto the page, like the limb of a large body edging into view. The
     message sits flex-centred at the oval's vertical middle. A `side:`
     directive in controls/sides.html picks left, right, or both edges;
     for `both`, JS clones #side-bar into a runtime #side-bar-right
     element (not present in index.html), so every rule here targets both
     ids. Doesn't reserve layout space and doesn't affect
     --bar-height/proximity math. No hover/solid state, and
     pointer-events:none so clicks/hover always reach the section
     underneath. `background: none` in the control file strips the wash,
     blur and border (see the shared data-bg rule below the centre bar). */
  #side-bar, #side-bar-right{
    position:fixed; top:50%; left:0; z-index:90;
    transform:translateY(-50%);
    border-radius:50%;
    pointer-events:none;
    display:flex; align-items:center; justify-content:flex-end;
    box-sizing:border-box;
    background:rgba(43,39,36,0.10);
    -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
    border:0.5px solid rgba(233,228,218,0.18);
    color:#2b2724;
    text-shadow:0 1px 3px rgba(255,255,255,0.5);
    font-size:0.78rem; letter-spacing:0.1em; text-align:center;
    line-height:1.5;
    display:none;   /* shown by JS only when message is non-empty */
  }
  :root[data-theme="dark"] #side-bar,
  :root[data-theme="dark"] #side-bar-right{ background:rgba(17,16,16,0.35); border-color:rgba(201,196,184,0.22); color:#c9c4b8; text-shadow:none; }

  /* ── center bar (floating, frosted pill at the middle of the stage) ──────
     The real centre of the page — controls/center.html. Sized by its own
     content, centred on the STAGE (viewport minus the header bar, the
     site-title band and the footer bar) using the same --stage-top /
     --stage-h helpers the section cells use, so it sits on exactly the
     centre the symbols and the centre dot share and re-centres by itself
     when a bar is switched on or off. Reserves no layout space and is
     pointer-events:none, so it never blocks the section behind it. */
  #center-bar{
    position:fixed;
    top:calc(var(--stage-top) + var(--stage-h) * 0.5);
    left:50%; z-index:90;
    transform:translate(-50%, -50%);
    max-width:min(60vw, 34rem);
    padding:0.75rem 1.5rem;
    border-radius:999px;
    pointer-events:none;
    align-items:center; justify-content:center;
    box-sizing:border-box;
    background:rgba(43,39,36,0.10);
    -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
    border:0.5px solid rgba(233,228,218,0.18);
    color:#2b2724;
    text-shadow:0 1px 3px rgba(255,255,255,0.5);
    font-size:0.78rem; letter-spacing:0.1em; text-align:center;
    line-height:1.5;
    display:none;   /* shown by JS only when message is non-empty */
  }
  :root[data-theme="dark"] #center-bar{ background:rgba(17,16,16,0.35); border-color:rgba(201,196,184,0.22); color:#c9c4b8; text-shadow:none; }

  /* `background: none` in sides.html / center.html — text only. The
     text-shadow stays: it's what keeps pale text legible once the wash
     behind it is gone. */
  #side-bar[data-bg="none"], #side-bar-right[data-bg="none"], #center-bar[data-bg="none"]{
    background:none;
    border-color:transparent;
    -webkit-backdrop-filter:none; backdrop-filter:none;
  }

  /* ── canvas (the page as three parts, one piece centred in each) ──────
     The stage is thought of as three EQUAL parts stacked top to bottom;
     each piece sits at the centre of its own part — 1/6, 1/2, 5/6 of the
     stage, i.e. the middle of each third, NOT the lines between them.
     The middle part shares the centre-of-the-stage anchor with
     #center-bar (top uses the stage helpers so it stays centred as bars
     toggle). All three float over whatever section is behind them and
     reserve no layout space. --cp-max-w is set inline by the JS, per
     piece, from that part's `max width:` line (default below). */
  .canvas-piece{
    position:fixed;
    left:50%; z-index:92;
    transform:translate(-50%, -50%);
    margin:0;
    width:min(var(--cp-max-w, 420px), 86vw);
    max-width:86vw;
    display:none;               /* shown by JS only when the part has content */
    flex-direction:column; align-items:center;
    pointer-events:none;        /* a link inside re-enables itself below */
    text-align:center;
  }
  #canvas-top   { top:calc(var(--stage-top) + var(--stage-h) * 0.16667); }
  #canvas-middle{ top:calc(var(--stage-top) + var(--stage-h) * 0.5); }
  #canvas-bottom{ top:calc(var(--stage-top) + var(--stage-h) * 0.83333); }

  .canvas-piece a{ pointer-events:auto; display:block; width:100%; }
  .canvas-piece img{
    display:block; width:100%; height:auto;
    border-radius:10px;
    box-shadow:0 10px 34px rgba(0,0,0,0.28);
    border:0.5px solid rgba(43,39,36,0.14);
  }
  .canvas-piece figcaption{
    margin-top:0.7rem;
    font-size:0.82rem; letter-spacing:0.08em; line-height:1.5;
    color:#2b2724; text-shadow:0 1px 3px rgba(255,255,255,0.5);
    white-space:pre-line;
  }
  /* words with no picture above them aren't a caption — they're a line of
     their own, so they stand a little larger and lose the gap that would
     otherwise separate them from an image that isn't there. The JS sets
     data-no-image on a part that loaded no picture. */
  .canvas-piece[data-no-image] figcaption{
    margin-top:0;
    font-size:0.95rem; letter-spacing:0.06em;
  }
  :root[data-theme="dark"] .canvas-piece img{
    border-color:rgba(201,196,184,0.18);
    box-shadow:0 10px 34px rgba(0,0,0,0.55);
  }
  :root[data-theme="dark"] .canvas-piece figcaption{ color:#c9c4b8; text-shadow:none; }
  :root[data-canvas="off"] .canvas-piece{ display:none !important; }

  /* ---- canvas in TWO PARTS (controls/canvas.html `layout:`) ----
     The stage becomes a BIG band of 3/4 and a SMALL band of 1/4, each
     piece centred in its own band: the big one at 3/8 of the stage when
     it's on top and 5/8 when it's below, the small one at 7/8 and 1/8.
     The band that isn't in use is hidden, so words parked under the
     other prefix can't drift over the picture. */
  :root[data-canvas-layout="two-parts"] #canvas-middle{
    top:calc(var(--stage-top) + var(--stage-h) * 0.375);
  }
  :root[data-canvas-layout="two-parts"] #canvas-bottom{
    top:calc(var(--stage-top) + var(--stage-h) * 0.875);
  }
  :root[data-canvas-layout="two-parts"] #canvas-top{ display:none !important; }

  :root[data-canvas-layout="two-parts-small-top"] #canvas-middle{
    top:calc(var(--stage-top) + var(--stage-h) * 0.625);
  }
  :root[data-canvas-layout="two-parts-small-top"] #canvas-top{
    top:calc(var(--stage-top) + var(--stage-h) * 0.125);
  }
  :root[data-canvas-layout="two-parts-small-top"] #canvas-bottom{ display:none !important; }

  /* The big picture is sized by HEIGHT, not width: it grows until it
     nearly fills its 3/4 band (94% of it — the rest is breathing room
     above and below) and is held to 88vw across, so on a wide window
     it's height-bound with empty space at the sides, like a film still.
     There is deliberately NO pixel default here: the three-part 420px
     ceiling, or any other fixed width, would stop the picture long
     before its band does and the mode would lose its point. `max
     width:` still works when one is written, as a ceiling.
     width/height stay auto so the browser scales the picture down to
     fit inside that box with its proportions intact. The figure keeps a
     DEFINITE width (the picture is centred in it, and so is any
     caption) — a shrink-to-fit figure and a percentage max-width on the
     picture inside it are circular, and the browser resolves that
     circle by making the picture far smaller than its band. */
  :root[data-canvas-layout^="two-parts"] #canvas-middle{
    width:min(var(--cp-max-w, 88vw), 88vw);
    max-width:88vw;
  }
  :root[data-canvas-layout^="two-parts"] #canvas-middle img{
    width:auto; height:auto;
    max-width:100%;
    max-height:calc(var(--stage-h) * 0.75 * 0.94);
  }
  /* Words standing alone in a two-part canvas take the film-title
     treatment of the picture above them: caps, very wide tracking, in
     the slate blue sampled from the key art (#385878 at its
     core — lifted to #6f96bd on the dark page so it reads, kept deep on
     the light one). Deliberately NOT Cormorant: the lettering being
     echoed is a sans, and the site loads only the one webfont — this
     borrows the system sans instead of paying for a second round trip.
     Tracking adds its space AFTER the last letter, so text-indent puts
     the same space back at the front and the word stays optically
     centred. Scoped to words-alone pieces in two-part mode; captions
     under a picture, and all of three-part mode, are untouched. */
  :root[data-canvas-layout^="two-parts"] .canvas-piece[data-no-image] figcaption{
    font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
    font-weight:700;
    /* fluid: a word tracked out this far gets wide fast, so the size
       eases down on a narrow screen rather than running past the edge */
    font-size:clamp(1.5rem, 4vw, 3rem);
    text-transform:uppercase;
    letter-spacing:0.42em;
    text-indent:0.42em;
    color:#3d5f82;
  }
  :root[data-theme="dark"][data-canvas-layout^="two-parts"]
    .canvas-piece[data-no-image] figcaption{ color:#6f96bd; }

  /* ---- the shine — a metallic light across a words-alone piece ----
     Switched on per part with `shine:` in controls/canvas.html; blank or
     `off` and none of this applies, so the words stay the flat slate
     blue set above. The letters stop carrying their own colour and start
     carrying a GRADIENT instead — base, a dark shoulder, the highlight,
     the shoulder again, base — clipped to the glyphs. Sliding that
     gradient sideways is the whole effect. Safari needs
     -webkit-text-fill-color as well as `color`: without it the text is
     painted over its own clip and nothing shows at all.
     Three states, in the order they hand off to each other:
       [data-shine-state="idle"]  a faint pass every 9s, soft highlight
       .shine-sweep               the bright pass, on hover or on tap
       [data-shine-state="tilt"]  no animation whatsoever — the light
                                  PARKS where the device is being held
                                  (--shine-x, written by the JS), and the
                                  idle loop is dropped, because a phone in
                                  a hand is already moving. */
  .canvas-piece figcaption[data-shine]{
    --shine-base:#3d5f82; --shine-edge:#2c4761;
    --shine-hi:#b9d3ea;   --shine-soft:#7f9db9;
    background-image:linear-gradient(105deg,
      var(--shine-base) 0%,  var(--shine-base) 40%,
      var(--shine-edge) 46%, var(--shine-hi)   50%, var(--shine-edge) 54%,
      var(--shine-base) 60%, var(--shine-base) 100%);
    background-size:300% 100%;
    background-position:var(--shine-x, 100%) 0;
    background-repeat:no-repeat;
    -webkit-background-clip:text; background-clip:text;
    color:transparent; -webkit-text-fill-color:transparent;
    /* ONLY the word takes the pointer. The figure itself stays
       pointer-events:none, so the picture and the rest of the canvas go
       on passing the mouse through to the section behind them. */
    pointer-events:auto;
  }
  :root[data-theme="dark"] .canvas-piece figcaption[data-shine]{
    --shine-base:#6f96bd; --shine-edge:#4d6f94;
    --shine-hi:#eaf3fb;   --shine-soft:#a9c6e0;
  }

  /* The light enters at the left edge and leaves at the right. With a
     background three times the width of the box, 100% shows its right
     third and 0% its left third — both pure base — and the highlight at
     the gradient's middle crosses the word on the way between them. */
  @keyframes canvasShine{
    from{ background-position:100% 0; }
    to  { background-position:0% 0; }
  }
  .canvas-piece figcaption[data-shine][data-shine-state="idle"]{
    background-image:linear-gradient(105deg,
      var(--shine-base) 0%,  var(--shine-base) 43%,
      var(--shine-soft) 50%,
      var(--shine-base) 57%, var(--shine-base) 100%);
    animation:canvasShine 9s ease-in-out infinite;
  }
  /* The sweep interrupts whatever the word was doing. It restates the
     bright gradient because the idle rule above matches just as strongly
     and would otherwise keep its soft one; !important on the animation
     is what lets a single pass cut across an infinite loop. */
  .canvas-piece figcaption[data-shine].shine-sweep{
    background-image:linear-gradient(105deg,
      var(--shine-base) 0%,  var(--shine-base) 40%,
      var(--shine-edge) 46%, var(--shine-hi)   50%, var(--shine-edge) 54%,
      var(--shine-base) 60%, var(--shine-base) 100%);
    animation:canvasShine 1.1s ease-in-out !important;
  }
  /* Motion off, surface intact: no timer, no sweep, but tilt still moves
     the light — that isn't an animation, it's where the thing is held. */
  @media (prefers-reduced-motion:reduce){
    .canvas-piece figcaption[data-shine],
    .canvas-piece figcaption[data-shine][data-shine-state="idle"],
    .canvas-piece figcaption[data-shine].shine-sweep{
      animation:none !important;
    }
  }

  /* on a short screen the height cap would leave a stamp — let the
     picture be width-driven there instead */
  @media (max-width:640px){
    :root[data-canvas-layout^="two-parts"] #canvas-middle{
      width:min(var(--cp-max-w, 94vw), 94vw); max-width:94vw;
    }
    :root[data-canvas-layout^="two-parts"] #canvas-middle img{
      max-height:calc(var(--stage-h) * 0.75);
    }
  }

  /* ---- site title — visible wordmark in its own reserved band ----
     Text comes from the "site title:" line of controls/title.html (set
     by js/main.js into #site-title + its data-title copy; blank line =
     the element keeps its `hidden` attribute). Fixed inside its own
     reserved band (--site-title-height, published by the JS), so the
     three zones split only the space below it — the title no longer eats
     into zone 1 (v5.6.0). "site title position: bottom" (v5.7.0) flips
     it to the bottom band, just above the footer. Grey at rest, washes
     rainbow on hover — the same ::after mechanism as the section
     labels. */
  #site-title{
    position:fixed; top:calc(var(--safe-top) + var(--bar-height) + 14px); left:50%;
    transform:translateX(-50%); z-index:6;
    font-size:1.15rem; font-weight:400; letter-spacing:0.18em;
    color:#b3aea4; white-space:nowrap;
  }
  :root[data-title-pos="bottom"] #site-title{
    top:auto; bottom:calc(var(--footer-bar-height) + 14px);
  }
  /* Installed app, title at top: tighten the pad below the notch from 14px
     to 6px so the wordmark hugs the notch. --safe-top now carries the REAL
     measured inset (js/main.js), so there's no overshoot to sit below. */
  :root[data-standalone][data-title-pos="top"] #site-title{
    top:calc(var(--safe-top) + var(--bar-height) + 6px);
  }
  :root[data-theme="dark"] #site-title{ color:#cac5bb; }
  #site-title::after{
    content:attr(data-title); position:absolute; left:0; top:0;
    background:linear-gradient(90deg,#ff5d73,#ff9f45,#ffd54a,#5ad67d,#43c6e8,#b66cff,#ff5d73);
    background-size:200% 100%;
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
    opacity:0; transition:opacity .55s ease;
  }
  #site-title:hover::after{ opacity:1; }
  @media (prefers-reduced-motion: no-preference){
    #site-title::after{ animation:rainbowFlow 8s linear infinite; }
  }

  /* ---- site-title band background (v5.7.0) ----
     Fills the title's reserved band with a colour ("site title
     background:" in controls/title.html), so the title can contrast
     against the zones. The JS sets --title-band-bg (and, for a
     "light | dark" pair, --title-band-bg-dark) and un-hides #title-band;
     it fills the band's full width and reserved height, top- or
     bottom-anchored to match the title position. Sits behind the title
     text and the seam border, never intercepts the mouse. */
  #title-band{
    position:fixed; left:0; right:0; z-index:0; pointer-events:none;
    top:calc(var(--safe-top) + var(--bar-height)); height:var(--site-title-height);
    background:var(--title-band-bg, transparent);
  }
  :root[data-title-pos="bottom"] #title-band{
    top:auto; bottom:var(--footer-bar-height);
  }
  /* Installed app, header off, title at top: the title band is the top
     element, so extend it up to fill the notch strip — the notch area takes
     the band's background colour (theme-aware). Its bottom edge stays at
     the stage seam (--safe-top + --site-title-height), so geometry is
     unchanged; only the fill grows upward. If the band has no background
     it fills with transparent and the page background shows, as before. */
  :root[data-standalone][data-header="off"][data-title-pos="top"] #title-band{
    top:0; height:calc(var(--safe-top) + var(--site-title-height));
  }
  :root[data-theme="dark"] #title-band{
    background:var(--title-band-bg-dark, var(--title-band-bg, transparent));
  }
  #title-band[hidden]{ display:none; }

  /* shared reveal keyframes, reused by every section's label */
  @keyframes rainbowFlow { to{ background-position:-200% 0; } }

  /* ---- the symbol unit: glyph + label (+ optional sub-nav), stacked & centred ---- */
  .symbol{
    position:absolute; left:50%; z-index:3;
    display:flex; flex-direction:column; align-items:center; gap:var(--symbol-gap,9px);
    color:#b3aea4; transition:color .4s ease;
  }
  .link{
    position:relative;
    display:grid; justify-items:center; row-gap:var(--glyph-gap,7px);
    text-decoration:none; color:inherit;
  }
  /* grid, not flex (v5.12.0): the subtitle and the guide note share one
     cell so both always start at the same top edge, however tall either
     one is — with the old bottom-anchored .guide-note, a multi-line
     subtitle pushed the guide pill down away from the label (visible on
     any section with a 2+ line subtitle). */
  .link > .glyph{ grid-row:1; }
  .link > .label{ grid-row:2; }
  .link > .subtitle,
  .link > .guide-note{ grid-row:3; grid-column:1; }
  .link:focus-visible{ outline:2px solid #c98b8b; outline-offset:6px; border-radius:3px; }

  /* ---- the label: grey at rest, washes rainbow on approach (shared mechanism) ----
     Text comes from data-label (set by the script), so renaming a section needs no
     CSS edit. The ::after layer is the rainbow copy that fades in over the grey. */
  .label{
    position:relative; display:inline-block;
    font-size:1.05rem; font-weight:400; letter-spacing:0.2em; text-transform:uppercase;
    color:#b3aea4;
  }
  .label::after{
    content:attr(data-label); position:absolute; left:0; top:0;
    background:linear-gradient(90deg,#ff5d73,#ff9f45,#ffd54a,#5ad67d,#43c6e8,#b66cff,#ff5d73);
    background-size:200% 100%;
    -webkit-background-clip:text; background-clip:text;
    -webkit-text-fill-color:transparent; color:transparent;
    opacity:0; transition:opacity .55s ease;
  }
  .symbol:hover .label::after,
  .symbol:focus-visible .label::after,
  .section.near .label::after{ opacity:1; }
  @media (prefers-reduced-motion: no-preference){
    .label::after{ animation:rainbowFlow 8s linear infinite; }
  }

  /* ---- the subtitle: optional line under the label ----
     Text comes from data-subtitle (set by the script). Empty subtitle = element
     is hidden (via the `hidden` attribute, set in JS) so it never reserves space.
     By default always visible; sections with [data-subtitle-hover="true"] only
     reveal it on hover/proximity, fading in the same way the label rainbow does. */
  .subtitle{
    /* no margin-top: the .link grid's row-gap spaces it from the label,
       and the guide note shares its cell so both must start flush */
    display:block; text-align:center;
    font-size:0.72rem; font-weight:400; letter-spacing:0.12em; text-transform:uppercase;
    color:#b3aea4; opacity:1; transition:opacity .55s ease;
    /* honour explicit line breaks (each `subtitle:` line in the control
       file becomes its own centred row) while still wrapping long rows */
    white-space:pre-line; line-height:1.8;
  }
  :root[data-theme="dark"] .subtitle{ color:#cac5bb; }
  [data-subtitle-hover="true"] .subtitle{ opacity:0; }
  [data-subtitle-hover="true"] .symbol:hover .subtitle,
  [data-subtitle-hover="true"] .symbol:focus-visible .subtitle,
  [data-subtitle-hover="true"].section.near .subtitle{ opacity:1; }

  /* ---- dev guide note: shares the below-label slot with the subtitle ----
     Development-mode only. At rest the guide (an amber pill, text from each
     section's "guide:" line) occupies the slot, overlaid on the subtitle's
     reserved space; on hover/proximity it fades out and the subtitle fades
     in — the guide↔subtitle swap. Written into a .guide-note by js/main.js,
     inserted right after .label and before .subtitle so the sibling swap
     `.guide-note ~ .subtitle` below works. */
  .guide-note{
    /* placed by the .link grid above (same cell as .subtitle) — no
       absolute anchor, so it always starts at the subtitle's top edge
       regardless of how many lines the subtitle has (v5.12.0) */
    text-align:center; white-space:nowrap; pointer-events:none;
    opacity:0; transition:opacity .55s ease;
  }
  .guide-note .guide-pill,
  .zone-guide .guide-pill{
    display:inline-block;
    font-size:0.6rem; font-weight:400; letter-spacing:0.14em; text-transform:uppercase;
    color:#b48a5a; border:0.5px solid rgba(201,138,75,0.55);
    border-radius:4px; padding:2px 8px;
  }
  :root[data-theme="dark"] .guide-note .guide-pill,
  :root[data-theme="dark"] .zone-guide .guide-pill{
    color:#c9a87c; border-color:rgba(201,168,124,0.5);
  }
  /* the guide↔subtitle swap: the LEAVING element snaps away instantly
     (duration 0), only the ARRIVING one fades in, slightly delayed
     (v5.12.2). The two share one slot; any approach that fades both
     lets an interrupted/flickering hover state catch them co-visible
     and mixed (the v5.12.1 delay-sequencing still had that hole).
     With the leaving side snapping to 0 in the same frame the state
     flips, co-visibility is structurally impossible, not just
     well-timed. */
  body[data-site-status="development"] .guide-note{
    opacity:1; transition:opacity .35s ease .12s;
  }
  body[data-site-status="development"] .symbol:hover .guide-note,
  body[data-site-status="development"] .symbol:focus-visible .guide-note,
  body[data-site-status="development"] .section.near .guide-note{
    opacity:0; transition:opacity 0s linear 0s;
  }
  /* while a guide is present in dev, the subtitle yields the resting slot to
     it (even always-on subtitles) and returns on hover/proximity */
  body[data-site-status="development"] .guide-note ~ .subtitle{
    opacity:0; transition:opacity 0s linear 0s;
  }
  body[data-site-status="development"] .symbol:hover .guide-note ~ .subtitle,
  body[data-site-status="development"] .symbol:focus-visible .guide-note ~ .subtitle,
  body[data-site-status="development"] .section.near .guide-note ~ .subtitle{
    opacity:1; transition:opacity .35s ease .12s;
  }

  /* ---- sub-nav (small link pill) — shared by any section, not just one ----
     Absolutely placed just below the symbol so it never shifts the glyph+label
     off the section's centre line. Each section gets its own <nav class="subnav">
     populated from the "nav links:" block of its own control file
     (controls/a.html, b.html, c.html). */
  .subnav{
    position:absolute; top:100%; left:50%; transform:translateX(-50%);
    margin-top:14px; text-align:center; font-size:0.78rem; letter-spacing:0.05em;
    white-space:nowrap;
    opacity:0; transition:opacity .5s ease; pointer-events:auto;
    padding:0.5em 1.1em; border-radius:999px; background-color:rgba(255,255,255,0.78);
  }
  .symbol:hover .subnav,
  .section.near .subnav{ opacity:1; }
  .subnav a{ color:#6b6358; text-decoration:none; transition:color .4s ease; }
  .subnav a:hover{ color:#b3676b; }
  .subnav a:focus-visible{ outline:2px solid #b3676b; outline-offset:4px; border-radius:2px; }
  .subnav .plain{ color:#6b6358; }
  .subnav .sep{ color:#c9c4b8; margin:0 0.55rem; }
  :root[data-theme="dark"] .subnav{ background-color:rgba(26,25,22,0.78); }
  :root[data-theme="dark"] .subnav a{ color:#cabfae; }
  :root[data-theme="dark"] .subnav .plain{ color:#cabfae; }
  :root[data-theme="dark"] .subnav .sep{ color:#565049; }

  /* ---- narrow/mobile: long subtitles and nav pills no longer run past the
     screen edge and vanish. Subtitle wraps onto multiple centred lines.
     The nav pill becomes a capped-height, vertically scrollable list (dot
     separators dropped, items stacked, divider between rows). Height is
     capped at a fraction of the *visible* stage (same --bar-height /
     --footer-bar-height aware formula used for symbol centering) so the
     list can never run off the top/bottom of the viewport either — it
     scrolls internally instead. Bottom-slot sections open the list upward
     since there's more headroom above the glyph than below it there. ---- */
  @media (max-width:640px){
    .subtitle{ max-width:80vw; white-space:pre-line; }

    .subnav{
      max-width:78vw;
      max-height:calc(var(--stage-h) * 0.28);
      overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
      white-space:normal;
      display:flex; flex-direction:column; align-items:stretch; gap:0;
      border-radius:16px;
      scrollbar-width:none;
      -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 10px,#000 calc(100% - 10px),transparent 100%);
              mask-image:linear-gradient(to bottom,transparent 0,#000 10px,#000 calc(100% - 10px),transparent 100%);
    }
    .subnav::-webkit-scrollbar{ display:none; }
    .subnav .sep{ display:none; }
    .subnav a,
    .subnav .plain{
      display:block; padding:0.45em 0.3em; text-align:center;
      border-bottom:1px solid rgba(0,0,0,0.08);
    }
    .subnav a:last-child,
    .subnav .plain:last-child{ border-bottom:none; }
    :root[data-theme="dark"] .subnav a,
    :root[data-theme="dark"] .subnav .plain{ border-bottom-color:rgba(255,255,255,0.09); }

    [data-edge="bottom"] .subnav{ top:auto; bottom:100%; margin-top:0; margin-bottom:14px; }
  }

  /* ── the bones: WHAT'S ON switches (v5.17.0) ──────────────────────────
     controls/home.html's "WHAT'S ON" block. js/main.js's loadHome() sets
     one data attribute per switch on the ROOT element, and everything
     below reacts — no loader is rewritten, nothing races: whenever the
     attribute lands, the CSS takes effect.

     The bars publish their measured heights as inline custom properties
     on :root, so zeroing a height here MUST use !important — a stylesheet
     !important beats a non-important inline style, which is exactly the
     override we want. That way the stage geometry, symbol centring and
     the proximity math (which all read these same vars) recompute as if
     the bar were never there, instead of leaving a gap behind. */

  /* zones off — the whole stage empties: sections, zone washes, zone
     labels/guides/flags, and every seam mark. The zone structure itself
     is untouched; this only stops it being drawn. */
  :root[data-zones="hidden"] .section,
  :root[data-zones="hidden"] .zone-bg,
  :root[data-zones="hidden"] .zone-label,
  :root[data-zones="hidden"] .divider:not(#title-border){ display:none !important; }

  /* header bar off — hidden AND its reserved band released */
  :root[data-header="off"]{ --bar-height:0px !important; }
  :root[data-header="off"] #header-bar{ display:none !important; }

  /* footer bar off — same, plus the status strip that folds into it */
  :root[data-footer="off"]{ --footer-bar-height:0px !important; }
  :root[data-footer="off"] #footer-bar{ display:none !important; }

  /* site title off — wordmark, its band, and its seam line; band released */
  :root[data-title="off"]{ --site-title-height:0px !important; }
  :root[data-title="off"] #site-title,
  :root[data-title="off"] #title-band,
  :root[data-title="off"] #title-border{ display:none !important; }

  /* the floating chrome — none of these reserve layout space, so they
     only need hiding */
  :root[data-role-tag="off"]    #role-tag{ display:none !important; }
  :root[data-corner-tags="off"] .corner-tag{ display:none !important; }
  :root[data-side-bars="off"]   #side-bar,
  :root[data-side-bars="off"]   #side-bar-right{ display:none !important; }
  :root[data-center-bar="off"]  #center-bar{ display:none !important; }
  :root[data-source-mark="off"] #source-mark{ display:none !important; }

  /* status badge off — the badge and the panel anchored to it. The
     pill/bar legend is deliberately NOT hidden here: in `bar` style it
     reserves its own height inside --footer-bar-height, so hiding it
     from CSS alone would leave the gap behind. Switch the legend off in
     controls/status.html instead. (With the inline styles the dots need
     the badge as their trigger, so they go quiet on their own.) */
  :root[data-status-badge="off"] #site-status-badge,
  :root[data-status-badge="off"] #status-panel{ display:none !important; }

  /* ── the bones: the centre dot (v5.17.0) ──────────────────────────────
     controls/home.html's "center dot:" line. The value is the style, and
     it arrives as data-center-dot on the root; loadHome() creates the
     element only when the dot is on, so `off` leaves nothing behind.

     It centres on the STAGE — the space the bars leave — using the same
     --stage-top/--stage-h formula the section symbols centre on, so it
     sits exactly where a symbol would and re-centres by itself whenever
     a bar is switched off. Never intercepts the mouse. */
  #center-dot{
    position:fixed; z-index:60; pointer-events:none;
    left:50%;
    top:calc(var(--stage-top) + var(--stage-h) * 0.5);
    transform:translate(-50%,-50%);
    border-radius:50%;
    /* default = amber: the clay every glyph warms to on hover, and the
       one style that reads as deliberate on an otherwise empty page */
    width:9px; height:9px; background:#c98a4b; border:none;
  }
  :root[data-center-dot="grey"] #center-dot{
    width:7px; height:7px; background:#b7b7b7;
  }
  :root[data-center-dot="outline"] #center-dot{
    width:11px; height:11px; background:none; border:1.4px solid #b7b7b7;
  }
  :root[data-center-dot="bindu"] #center-dot{
    width:4px; height:4px; background:#b7b7b7;
  }
  /* the greys lift in dark mode exactly as the glyphs do; amber is the
     same clay in both themes */
  :root[data-theme="dark"][data-center-dot="grey"] #center-dot,
  :root[data-theme="dark"][data-center-dot="bindu"] #center-dot{ background:#c8c0b2; }
  :root[data-theme="dark"][data-center-dot="outline"] #center-dot{ border-color:#c8c0b2; }

/* ══════════════════════════════════════════
   2 · LAYOUT / ZONES   (position only — no section identity here)
   v5.0.0: the page is horizontal ZONES stacked top to bottom, each
   seating any number of sections side by side (controls/order.html).
   js/main.js computes every section's cell as four fractions of the
   visible stage and writes them as CSS variables on the <section>:
       --cell-t  top edge      --cell-h  height     (of the stage)
       --cell-l  left edge     --cell-w  width      (of the viewport)
   The rules below turn those into positions: the symbol centres in
   its cell, the ambient fills it. The visible stage is the viewport
   minus the header AND footer bars (--bar-height /
   --footer-bar-height; both 0 when hidden). On ≤640px screens the
   engine recomputes the cells as one vertical stack — same
   variables, so no media overrides are needed here.
   ══════════════════════════════════════════ */

  .section{ display:contents; }   /* a section is its parts; the parts inherit its cell vars */

  /* — symbol anchor: centred in its cell of the *visible* stage — */
  .symbol{
    top:calc(var(--stage-top) + var(--stage-h) * (var(--cell-t, 0) + var(--cell-h, 1) * 0.5));
    left:calc((var(--cell-l, 0) + var(--cell-w, 1) * 0.5) * 100vw);
    transform:translate(-50%,-50%);
  }

  /* — ambient: fills the section's cell exactly. Inner-edge feathering
       masks and --anchor-y (the glyph's rough vertical line, for scenes
       that aim effects at the glyph) are set inline by the engine,
       since which edges are "inner" depends on the cell. */
  .ambient{
    position:fixed; overflow:hidden;
    z-index:1; pointer-events:none; opacity:0; transition:opacity 1.6s ease;
    top:calc(var(--stage-top) + var(--stage-h) * var(--cell-t, 0));
    height:calc(var(--stage-h) * var(--cell-h, 1));
    left:calc(var(--cell-l, 0) * 100vw);
    width:calc(var(--cell-w, 1) * 100vw);
  }
  .ambient.on    { opacity:1; transition:opacity .8s ease; }
  .ambient.fading{ opacity:0; transition:opacity 1.6s ease; }

  /* default glyph box for engine-built sections (a–d's bespoke blocks
     below override where they differ) */
  .glyph{ width:60px; height:60px; }

  /* — a section not seated in any zone simply doesn't exist on screen — */
  .section[data-unseated] .symbol,
  .section[data-unseated] .ambient{ display:none !important; }
  .section[data-unseated]{ pointer-events:none; }

  /* an auto-loaded drop-in background (set by js/main.js). Default: fill the band.
     To TILE a texture instead of filling, change cover→auto and no-repeat→repeat. */
  .ambient.has-image{
    background-size:cover; background-position:center; background-repeat:no-repeat;
  }

  /* ── hidden sections ─────────────────────────────────────────────────
     [data-hidden] is set by the engine when "hidden: yes" appears in that
     section's control file (controls/a.html, b.html, c.html). The section
     still occupies its third of the stage — the space is reserved — but
     everything inside is invisible. Proximity hover is also suppressed so
     it can't trigger ambient scenes. */
  .section[data-hidden] .symbol,
  .section[data-hidden] .ambient{ visibility:hidden !important; opacity:0 !important; }
  .section[data-hidden]{ pointer-events:none; }

  /* ── section border glyphs (dividers) ─────────────────────────────────
     Small optional glyphs sitting exactly on a seam — the horizontal
     seams between zones and the vertical seams between sections inside
     a zone. The elements are created at runtime by js/main.js from
     controls/dividers.html (one per configured seam, tagged with its
     control key as data-key); the engine writes each one's position as
     the same stage fractions the section cells use:
         --div-x  across the viewport   --div-y  down the visible stage */
  .divider{
    position:absolute; z-index:4;
    left:calc(var(--div-x, 0.5) * 100vw);
    top:calc(var(--stage-top) + var(--stage-h) * var(--div-y, 0.5));
    transform:translate(-50%,-50%);
    display:flex; align-items:center; justify-content:center;
    width:26px; height:26px;
  }
  .divider[hidden]{ display:none; }

  .divider-glyph{ width:18px; height:18px; color:#b7b7b7; transition:color .8s ease, filter .8s ease; }
  :root[data-theme="dark"] .divider-glyph{ color:#c8c0b2; }
  .divider:hover .divider-glyph{ color:#c98a4b; filter:drop-shadow(0 0 3px rgba(201,138,75,.3)); }

  /* ── line dividers ────────────────────────────────────────────────────
     A full seam LINE instead of a point glyph (types line | line-dashed |
     line-dotted | line-soft in controls/dividers.html). The same .divider
     element, stretched across the seam: a horizontal seam (between zones)
     spans the viewport width; a vertical seam (between side-by-side
     sections) spans that zone's height. --div-len is the length as a
     fraction (of 100vw for horizontal, of the visible stage for vertical),
     written by the engine. Purely decorative — never intercepts the mouse. */
  .divider[data-line]{
    width:auto; height:auto; display:block; pointer-events:none; z-index:1;
    --divider-line: rgba(140,135,126,0.5);
  }
  :root[data-theme="dark"] .divider[data-line]{ --divider-line: rgba(200,192,178,0.42); }
  .divider[data-line][data-axis="h"]{
    width:calc(var(--div-len, 1) * 100vw); height:1px;
  }
  .divider[data-line][data-axis="v"]{
    width:1px;
    height:calc(var(--stage-h) * var(--div-len, 1));
  }
  /* solid / soft use a background fill; soft fades out at both ends */
  .divider[data-line-style="line"]{ background:var(--divider-line); }
  .divider[data-line-style="line-soft"][data-axis="h"]{
    background:linear-gradient(90deg, transparent, var(--divider-line) 18%, var(--divider-line) 82%, transparent);
  }
  .divider[data-line-style="line-soft"][data-axis="v"]{
    background:linear-gradient(180deg, transparent, var(--divider-line) 18%, var(--divider-line) 82%, transparent);
  }
  /* dashed / dotted drawn as a centred border so the pattern renders on the
     thin axis (the element itself carries no background) */
  .divider[data-line-style="line-dashed"][data-axis="h"],
  .divider[data-line-style="line-dotted"][data-axis="h"]{ height:0; border-top-width:1px; border-top-style:dashed; border-color:var(--divider-line); }
  .divider[data-line-style="line-dashed"][data-axis="v"],
  .divider[data-line-style="line-dotted"][data-axis="v"]{ width:0; border-left-width:1px; border-left-style:dashed; border-color:var(--divider-line); }
  .divider[data-line-style="line-dotted"][data-axis="h"]{ border-top-style:dotted; border-top-width:1.5px; }
  .divider[data-line-style="line-dotted"][data-axis="v"]{ border-left-style:dotted; border-left-width:1.5px; }

  /* ── zone backgrounds (v5.16.0) ───────────────────────────────────────
     A base wash behind a whole horizontal zone, from controls/zones.html
     ("zone N background:"). One fixed element per zone that has one,
     spanning the zone's rectangle via the same --stage-top/--stage-h
     formula the labels and cells use (--zbg-y = the zone's top, --zbg-h
     its height, as fractions of the visible stage). Sits at z-index 0 —
     BELOW the section ambients (z-index 1), which fade in over it on
     approach ("zone behind, section on top"). Theme-aware: --zbg paints
     in light, --zbg-dark in dark (falling back to --zbg). Each var is a
     colour token or url(...). Image zones get their inner seams softened
     by an inline mask the engine builds. Never intercepts the mouse. */
  .zone-bg{
    position:fixed; left:0; right:0; z-index:0; pointer-events:none;
    top:calc(var(--stage-top) + var(--stage-h) * var(--zbg-y, 0));
    height:calc(var(--stage-h) * var(--zbg-h, 1));
    background:var(--zbg, transparent) center/cover no-repeat;
  }
  :root[data-theme="dark"] .zone-bg{
    background:var(--zbg-dark, var(--zbg, transparent)) center/cover no-repeat;
  }
  .zone-bg[hidden]{ display:none; }

  /* Readability over a coloured zone: a section's faint grey label/
     subtitle can wash out on a zone background. The engine tags every
     section seated in a zone that HAS a background with data-zone-bg;
     in LIGHT mode we darken its label/subtitle so they contrast. Dark
     mode keeps the light default (which reads against the dark zone
     colours). Purely additive — no background, no tag, no change; the
     rainbow-wash ::after on hover is unaffected. */
  :root:not([data-theme="dark"]) .section[data-zone-bg] .label,
  :root:not([data-theme="dark"]) .section[data-zone-bg] .subtitle{
    color:#4a453e;
  }

  /* ── zone labels ──────────────────────────────────────────────────────
     A faint name for each horizontal zone, from controls/zones.html.
     One element per named zone, pinned to that zone's top-left corner —
     positioned with the same --stage-top/--stage-h stage formula the
     section cells and dividers use (--zl-y is the zone's top as a
     fraction of the visible stage). Floats over the zone, reserves no
     layout space, never intercepts the mouse. */
  .zone-label{
    position:fixed; z-index:95; left:16px;
    top:calc(var(--stage-top) + var(--stage-h) * var(--zl-y, 0) + 12px);
    display:flex; flex-direction:column; align-items:flex-start; gap:6px;
    font-size:0.6rem; font-weight:400; letter-spacing:0.22em; text-transform:uppercase;
    color:#a49e92; opacity:0.8; pointer-events:none; white-space:nowrap;
  }
  :root[data-theme="dark"] .zone-label{ color:#8c8578; }
  .zone-label[hidden]{ display:none; }
  .zone-label .zl-row{ display:inline-flex; align-items:center; gap:7px; }

  /* zone flag (v5.9.0) — the zone's readiness dot, beside its label.
     Same 7px dot + colours as the sections' inline .status-dot, read
     from the "zone N:" lines of controls/status.html. Follows the
     inline legend styles: always on for "inline", hidden until the
     site badge is hovered for "inline on hover" (the shared
     data-flags gate on <body>). */
  .zone-flag{ width:7px; height:7px; border-radius:50%; flex:none; }
  body[data-flags="hover"] .zone-flag{ opacity:0; transition:opacity .45s ease; }
  body[data-flags="hover"][data-flags-on] .zone-flag{ opacity:1; }
  .zone-flag[data-status="green"] { background:#7ea86b; }
  .zone-flag[data-status="orange"]{ background:#d99a3c; }
  .zone-flag[data-status="red"]   { background:#c0524f; }
  :root[data-theme="dark"] .zone-flag[data-status="green"] { background:#8fc27a; }
  :root[data-theme="dark"] .zone-flag[data-status="orange"]{ background:#e3ab52; }
  :root[data-theme="dark"] .zone-flag[data-status="red"]   { background:#d16e6b; }

  /* zone guide (v5.9.0) — a dev note for the zone, tucked under its
     label as the same amber pill the section guide notes use (the
     .guide-pill styling is shared). Development-only, same gate as
     the section guides; never shows in staging/production. */
  .zone-guide{ display:none; }
  body[data-site-status="development"] .zone-guide{ display:block; }

  /* ── status legend ───────────────────────────────────────────────────
     One shared home for the per-section readiness dots — dot + label
     per section, populated by js/main.js from each section's "status:"
     line, styled per controls/status-legend.html:
       pill = floating pill centred just above the footer bar, same
              look as #site-status-badge; reserves no layout space
       bar  = full-width strip at the very bottom edge, BELOW the
              footer bar; reserves its own height (js publishes it as
              --status-bar-height and folds it into --footer-bar-height
              so the stage shrinks and the footer bar lifts up)
     Hidden entirely when style is blank/unrecognised or no section
     has a status. */
  :root{ --status-bar-height: 0px; }

  #status-legend{
    display:none; align-items:center; pointer-events:none; z-index:105;
    flex-wrap:wrap; justify-content:center; row-gap:0.45em;
    font-size:0.66rem; font-weight:400; letter-spacing:0.1em; text-transform:uppercase;
  }
  #status-legend .sl-item{ display:flex; align-items:center; gap:6px; }
  /* full-width flex break — splits the legend into rows (crown layout:
     the crown section's flag above, the bottom-row sections below) */
  #status-legend .sl-break{ flex-basis:100%; height:0; }
  #status-legend .sl-dot{ width:7px; height:7px; border-radius:50%; flex:none; }
  #status-legend .sl-dot[data-status="green"] { background:#7ea86b; }
  #status-legend .sl-dot[data-status="orange"]{ background:#d99a3c; }
  #status-legend .sl-dot[data-status="red"]   { background:#c0524f; }
  :root[data-theme="dark"] #status-legend .sl-dot[data-status="green"] { background:#8fc27a; }
  :root[data-theme="dark"] #status-legend .sl-dot[data-status="orange"]{ background:#e3ab52; }
  :root[data-theme="dark"] #status-legend .sl-dot[data-status="red"]   { background:#d16e6b; }

  #status-legend[data-style="pill"]{
    position:fixed; left:50%; transform:translateX(-50%);
    bottom:calc(var(--footer-bar-height) + 14px);
    column-gap:1.1em; padding:0.35em 1.1em; border-radius:999px;
    background:rgba(43,39,36,0.65); color:#e9e4da;
  }
  :root[data-theme="dark"] #status-legend[data-style="pill"]{ background:rgba(17,16,16,0.7); color:#c9c4b8; }

  #status-legend[data-style="bar"]{
    position:fixed; left:0; right:0; bottom:0; z-index:101;
    justify-content:center; column-gap:1.6em;
    padding:0.45em 1.4em calc(env(safe-area-inset-bottom,0px) + 0.45em);
    background:#1c1a18; color:#c9c4b8;
    border-top:0.5px solid rgba(233,228,218,0.12);
  }
  :root[data-theme="dark"] #status-legend[data-style="bar"]{ background:#0d0c0c; }

  /* bar mode: footer bar lifts above the strip; the strip (not the
     footer) now absorbs the home-indicator safe-area inset */
  body[data-status-legend="bar"] #footer-bar{
    /* the strip below absorbs the inset, so the footer resets BOTH paddings
       to the plain value (the default splits the inset across the two) */
    bottom:var(--status-bar-height); padding-top:0.55em; padding-bottom:0.55em;
  }

  /* ── focus dot ────────────────────────────────────────────────────────
     A single pointer for "what I'm working on" — controls/status.html's
     "focus:" line names at most one section; the engine sets data-focus
     on that section's .symbol only. Floats above the symbol.
     Development-only gate (guide notes now live below the label, in the
     subtitle slot — see .guide-note above). */
  body[data-site-status="development"] .symbol[data-focus]::after{
    content:''; position:absolute; bottom:100%; left:50%; transform:translateX(-50%);
    margin-bottom:14px; width:7px; height:7px; border-radius:50%;
    background:#c98a4b; pointer-events:none;
    animation:focusPulse 2.2s ease-in-out infinite;
  }
  @keyframes focusPulse{
    0%, 100%{ box-shadow:0 0 0 0 rgba(201,138,75,0.45); }
    50%     { box-shadow:0 0 0 5px rgba(201,138,75,0); }
  }
  @media (prefers-reduced-motion: reduce){
    body[data-site-status="development"] .symbol[data-focus]::after{ animation:none; }
  }

  /* ── site-wide status badge ──────────────────────────────────────────
     Fixed to the bottom-right corner of the viewport, just above the
     footer bar (rides up with --footer-bar-height), independent of
     section order — a small dot + "<status> mode" label read from
     controls/status.html by js/main.js. Hidden until a recognised
     status is set. Hoverable (pointer-events:auto) since v5.1.0: in
     legend style "badge" it's the anchor the status panel unfolds
     from. */
  #site-status-badge{
    position:fixed; bottom:calc(var(--footer-bar-height) + 12px); right:12px; z-index:110;
    display:none; align-items:center; gap:6px;
    padding:0.3em 0.7em; border-radius:999px;
    background:rgba(43,39,36,0.65); color:#e9e4da;
    font-size:0.66rem; font-weight:400; letter-spacing:0.1em; text-transform:uppercase;
    pointer-events:auto; cursor:default;
  }
  #site-status-badge::before{
    content:''; width:7px; height:7px; border-radius:50%; flex:none;
  }
  #site-status-badge[data-status="development"]::before{ background:#c0524f; }
  #site-status-badge[data-status="staging"]::before     { background:#d99a3c; }
  #site-status-badge[data-status="production"]::before  { background:#7ea86b; }
  :root[data-theme="dark"] #site-status-badge{ background:rgba(17,16,16,0.7); color:#c9c4b8; }

  /* ── corner tags (v5.10.0; true corners + dotless since v5.11.0;
     bordered + inset since v5.11.2; always win their corner since
     v5.11.3; stackable since v5.13.0) ────────────────────────────────
     Small frosted tags in the viewport corners, from
     controls/corners.html. A corner's line can REPEAT — each repeat
     renders its own pill chip, and the chips sit in a horizontal row
     flowing away from the corner (left corners run left→right, right
     corners right→left), 6px apart. The .corner-tag element is just
     the invisible positioned row; each .corner-chip carries the pill
     chrome (status-badge look, no dot, plus a faint border/shadow so
     it reads against the bar's near-identical background tone).
     Vertically CENTRED in its bar's strip (v5.12.0): the row's centre
     sits at half the bar's height (translateY pulls it back by half
     its own height); the max(…, 12px) keeps it on-screen when that
     bar is hidden (height 0). 10px off the side edge — ALL FOUR
     corners identically, always. The status badge and role tag are
     the ones that yield when a corner tag shares their corner (see
     their own rules below), never the other way around. Hidden until
     filled (js/main.js flips display to flex). Independent of the
     standalone #role-tag below (they share only their visual
     chrome). */
  .corner-tag{
    position:fixed; z-index:110;
    display:none; align-items:center; gap:6px;
    pointer-events:none;
  }
  .corner-tag[data-corner="top-right"],
  .corner-tag[data-corner="bottom-right"]{ flex-direction:row-reverse; }
  .corner-chip{
    display:flex; align-items:center; white-space:nowrap;
    padding:0.3em 0.7em; border-radius:999px;
    background:rgba(43,39,36,0.78); color:#e9e4da;
    border:1px solid rgba(255,255,255,0.16);
    box-shadow:0 1px 5px rgba(0,0,0,0.28);
    font-size:0.66rem; font-weight:400; letter-spacing:0.1em; text-transform:uppercase;
  }
  :root[data-theme="dark"] .corner-chip{
    background:rgba(17,16,16,0.82); color:#c9c4b8;
    border-color:rgba(255,255,255,0.12);
  }
  .corner-tag[data-corner="top-left"]    { top:max(calc(var(--bar-height) / 2), 12px); left:10px; transform:translateY(-50%); }
  .corner-tag[data-corner="top-right"]   { top:max(calc(var(--bar-height) / 2), 12px); right:10px; transform:translateY(-50%); }
  .corner-tag[data-corner="bottom-left"] { bottom:max(calc(var(--footer-bar-height) / 2), 12px); left:10px; transform:translateY(50%); }
  .corner-tag[data-corner="bottom-right"]{ bottom:max(calc(var(--footer-bar-height) / 2), 12px); right:10px; transform:translateY(50%); }

  /* ── role tag (v5.8.0; independent again since v5.10.1; moved to
     mirror the status badge, dotless, since v5.11.0; yields to the
     bottom-left corner tag since v5.11.3) ─────────────────────────────
     Its own small frosted tag, from the "role:" line of
     controls/title.html — same chrome as the corner tags/status badge,
     bottom-left, the exact mirror of the "<status> mode" badge
     (bottom-right). Hidden until filled (js/main.js flips display to
     flex). */
  #role-tag{
    position:fixed; z-index:110;
    bottom:calc(var(--footer-bar-height) + 12px); left:12px;
    display:none; align-items:center;
    padding:0.3em 0.7em; border-radius:999px;
    background:rgba(43,39,36,0.65); color:#e9e4da;
    font-size:0.66rem; font-weight:400; letter-spacing:0.1em; text-transform:uppercase;
    pointer-events:none;
  }
  :root[data-theme="dark"] #role-tag{ background:rgba(17,16,16,0.7); color:#c9c4b8; }
  /* stack clear of the bottom-left corner tag whenever one is shown —
     the corner tag always wins the corner, the role tag moves up just
     enough to clear it (never more): max() picks the normal resting
     position whenever the footer bar is already tall enough to clear
     the corner tag on its own (the common case with any real footer
     message), and only falls back to the fixed 40px floor when the
     footer bar is short or absent and would otherwise overlap it. */
  body[data-corner-bl] #role-tag{
    bottom:max(calc(var(--footer-bar-height) + 12px), 40px);
  }

  /* ── site status badge stacking (bottom-right corner tag wins) ───────
     Mirrors the role-tag rule above: the corner tag always sits flush
     in its corner, the status badge is the one that moves up just
     enough to clear it whenever a bottom-right corner tag is shown. */
  body[data-corner-br] #site-status-badge{
    bottom:max(calc(var(--footer-bar-height) + 12px), 40px);
  }

  /* ── source mark (v5.11.0) ────────────────────────────────────────────
     The site's foundation stone — a single quiet word from the
     dedicated controls/source.html, floating subtly just above the
     footer bar at the bottom of the stage. z-index:0, the same lowest
     tier as #title-band, so every bar/tag/legend sits above it. Not a
     UI control like the others: no chrome, no pill, just a bare faint
     line of text. Hidden until filled (js/main.js flips display to
     block). */
  #source-mark{
    position:fixed; z-index:0; left:0; right:0;
    bottom:calc(var(--footer-bar-height) + 8px);
    display:none; text-align:center; pointer-events:none;
    font-size:0.64rem; font-weight:400; letter-spacing:0.28em; text-transform:uppercase;
    color:#b3aea4; opacity:0.4;
  }
  :root[data-theme="dark"] #source-mark{ color:#cac5bb; }

  /* ── inline status dots (legend style: inline) ───────────────────────
     Each section's readiness flag as a 7px dot, centred on the TOP-LEFT
     corner of that section's glyph (created by js/main.js's
     renderInlineDots, appended to .symbol so it positions against the
     symbol box — 50% is the glyph centre, less its ~30px half-width).
     Rides with the symbol everywhere — zones, stacking, hidden. */
  .status-dot{
    position:absolute; top:0; left:calc(50% - 30px);
    transform:translate(-50%,-50%);
    width:7px; height:7px; border-radius:50%;
    pointer-events:none; z-index:5;
  }
  /* "inline on hover": dots exist but stay invisible until the site
     badge is hovered/tapped — js/main.js sets data-flags="hover" on
     <body> for that style and toggles data-flags-on from the badge */
  body[data-flags="hover"] .status-dot{ opacity:0; transition:opacity .45s ease; }
  body[data-flags="hover"][data-flags-on] .status-dot{ opacity:1; }
  .status-dot[data-status="green"] { background:#7ea86b; }
  .status-dot[data-status="orange"]{ background:#d99a3c; }
  .status-dot[data-status="red"]   { background:#c0524f; }
  :root[data-theme="dark"] .status-dot[data-status="green"] { background:#8fc27a; }
  :root[data-theme="dark"] .status-dot[data-status="orange"]{ background:#e3ab52; }
  :root[data-theme="dark"] .status-dot[data-status="red"]   { background:#d16e6b; }

  /* ── status panel (legend style: badge) ──────────────────────────────
     The flags, hidden until the badge is hovered/tapped — then this
     panel (created at runtime by js/main.js) unfolds upward from the
     badge as a mini-map of the page: one outlined .sp-row per zone,
     seats left to right. Reuses the legend's .sl-item/.sl-dot pieces,
     plus data-status="none" — the hollow "no flag yet" ring for seated
     sections without a status line. */
  #status-panel{
    position:fixed; right:12px;
    bottom:calc(var(--footer-bar-height) + 42px);
    z-index:110;
    display:flex; flex-direction:column; gap:6px;
    min-width:170px; max-width:min(340px, calc(100vw - 24px));
    padding:0.65em 0.75em; border-radius:12px;
    background:rgba(43,39,36,0.85); color:#e9e4da;
    -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
    font-size:0.66rem; font-weight:400; letter-spacing:0.1em; text-transform:uppercase;
    opacity:0; transform:translateY(6px); pointer-events:none;
    transition:opacity .35s ease, transform .35s ease;
  }
  #status-panel.on{ opacity:1; transform:translateY(0); pointer-events:auto; }
  #status-panel[hidden]{ display:none; }
  #status-panel .sp-row{
    display:flex; justify-content:center; align-items:center;
    flex-wrap:wrap; column-gap:1.1em; row-gap:0.35em;
    border:0.5px solid rgba(233,228,218,0.18); border-radius:7px;
    padding:0.35em 0.7em;
  }
  #status-panel .sl-item{ display:flex; align-items:center; gap:6px; }
  #status-panel .sl-dot{ width:7px; height:7px; border-radius:50%; flex:none; }
  #status-panel .sl-dot[data-status="green"] { background:#7ea86b; }
  #status-panel .sl-dot[data-status="orange"]{ background:#d99a3c; }
  #status-panel .sl-dot[data-status="red"]   { background:#c0524f; }
  #status-panel .sl-dot[data-status="none"]  { background:transparent; border:1.2px solid #8a857b; box-sizing:border-box; }
  :root[data-theme="dark"] #status-panel{ background:rgba(17,16,16,0.85); color:#c9c4b8; border:0.5px solid rgba(201,196,184,0.16); }
  @media (prefers-reduced-motion: reduce){
    #status-panel{ transition:none; transform:none; }
  }

/* ══════════════════════════════════════════════════════════════════════
   3A · SECTION A  — currently "PRESERVATION"
        glyph: heart outline (grey → amber, gentle glow on hover)   ambient: (none — placeholder ready)
   ══════════════════════════════════════════════════════════════════════ */

  [data-section="a"] .link{ --glyph-gap:11px; }
  /* — glyph: heart outline (static shape, grey → amber glow on hover) — */
  [data-section="a"] .glyph{ width:60px; height:60px; }
  .glyph .heart-shape{ color:#b7b7b7; transition:color .8s ease, filter .8s ease; }
  :root[data-theme="dark"] .glyph .heart-shape{ color:#c8c0b2; }
  .symbol:hover .glyph .heart-shape,
  .symbol:focus-visible .glyph .heart-shape,
  :root .near .glyph .heart-shape{
    color:#c98a4b; animation:heartPulse 3.8s ease-in-out infinite alternate; filter:drop-shadow(0 0 3px rgba(201,138,75,.3));
  }
  @keyframes heartPulse{ from{ filter:drop-shadow(0 0 2px rgba(201,138,75,.25)); } to{ filter:drop-shadow(0 0 6px rgba(201,138,75,.5)); } }
  @media (prefers-reduced-motion: reduce){
    .symbol:hover .glyph .heart-shape,
    .symbol:focus-visible .glyph .heart-shape,
    :root .near .glyph .heart-shape{ animation:none; }
  }

/* ══════════════════════════════════════════════════════════════════════
   3B · SECTION B  — currently "BODY"
        glyph: earth — globe outline with real coastlines (earth-africa
        via controls/b.html; three sibling views in library/symbols/), static at
        rest, grey → amber glow on hover (replaces zen stones — their
        rules stay below for the library file)   ambient: none (drop an
        `background:` line in controls/b.html to give it one — image
        from library/backgrounds/)   sub-nav: small pill of links that fades in on approach.
   ══════════════════════════════════════════════════════════════════════ */

  [data-section="b"] .link{ --glyph-gap:11px; }
  [data-section="b"] .glyph{ width:60px; height:60px; transition:filter .5s ease; }
  .glyph .zen-stones{ color:#b7b7b7; transition:color .8s ease, filter .8s ease; }
  :root[data-theme="dark"] .glyph .zen-stones{ color:#c8c0b2; }
  .symbol:hover .glyph .zen-stones,
  .symbol:focus-visible .glyph .zen-stones,
  :root .near .glyph .zen-stones{
    color:#c98a4b; animation:stonesPulse 3.8s ease-in-out infinite alternate; filter:drop-shadow(0 0 3px rgba(201,138,75,.3));
  }
  @keyframes stonesPulse{ from{ filter:drop-shadow(0 0 2px rgba(201,138,75,.25)); } to{ filter:drop-shadow(0 0 6px rgba(201,138,75,.5)); } }
  @media (prefers-reduced-motion: reduce){
    .symbol:hover .glyph .zen-stones,
    .symbol:focus-visible .glyph .zen-stones,
    :root .near .glyph .zen-stones{ animation:none; }
  }

  /* earth — shared by all four library/symbols/earth-*.svg views (africa,
     americas, asia, india); which one shows is controls/b.html's
     symbol: field. Same grey → amber pulse pattern as the stones. */
  .glyph .earth{ color:#b7b7b7; transition:color .8s ease, filter .8s ease; }
  :root[data-theme="dark"] .glyph .earth{ color:#c8c0b2; }
  .symbol:hover .glyph .earth,
  .symbol:focus-visible .glyph .earth,
  :root .near .glyph .earth{
    color:#c98a4b; animation:earthPulse 3.8s ease-in-out infinite alternate; filter:drop-shadow(0 0 3px rgba(201,138,75,.3));
  }
  @keyframes earthPulse{ from{ filter:drop-shadow(0 0 2px rgba(201,138,75,.25)); } to{ filter:drop-shadow(0 0 6px rgba(201,138,75,.5)); } }
  @media (prefers-reduced-motion: reduce){
    .symbol:hover .glyph .earth,
    .symbol:focus-visible .glyph .earth,
    :root .near .glyph .earth{ animation:none; }
  }

/* ══════════════════════════════════════════════════════════════════════
   3C · SECTION C  — currently "IMAGINE"
        glyph: macbook (open laptop outline, grey → amber glow on hover)
        ambient: none, and kept off below (the retired dream-sky scene
        lives in archive/dream-sky.html)
        To re-skin this section, edit only what's below + its markup block in <body>.
   ══════════════════════════════════════════════════════════════════════ */

  /* — glyph: macbook (grey open-laptop outline at rest; on hover swaps to a
       warm amber with a soft pulsing glow — same swap pattern as A's heart and
       B's stones, no shape change) — */
  [data-section="c"] .link{ --glyph-gap:12px; }
  [data-section="c"] .glyph{ width:56px; height:56px; }
  .glyph .macbook{ color:#b3aea4; transition:color .6s ease, filter .6s ease; }
  :root[data-theme="dark"] .glyph .macbook{ color:#cac5bb; }
  .symbol:hover .glyph .macbook,
  .symbol:focus-visible .glyph .macbook,
  :root .near .glyph .macbook{
    color:#c98a4b; filter:drop-shadow(0 0 3px rgba(201,138,75,.3));
  }
  @media (prefers-reduced-motion: no-preference){
    .symbol:hover .glyph .macbook,
    .symbol:focus-visible .glyph .macbook,
    :root .near .glyph .macbook{ animation:macbookPulse 3.8s ease-in-out infinite alternate; }
  }
  @keyframes macbookPulse{ from{ filter:drop-shadow(0 0 2px rgba(201,138,75,.25)); } to{ filter:drop-shadow(0 0 6px rgba(201,138,75,.5)); } }
  /* C's ambient is kept off entirely — this also blocks drop-in backgrounds
     (a `background:` line in controls/c.html) for C, which is the long-standing behavior.
     Remove this rule to let C show a drop-in background like A and B. */
  [data-section="c"] .ambient{ display:none !important; }
  /* C's label: no delay needed (ambient off) */
  [data-section="c"] .label::after{ transition-delay:0s; }
  @media (prefers-reduced-motion: reduce){
    [data-section="c"] .label::after{ transition-delay:0s !important; }
  }

/* ══════════════════════════════════════════════════════════════════════
   3D · SECTION D  — currently "WORLD" (crown layout only)
        glyph: earth — same globe as B's old symbol (earth-africa via
        controls/d.html; the shared .earth rules live in 3B above, so
        colour/hover behavior comes from there)   ambient: none (drop an
        `background:` line in controls/d.html to give it one — image
        from library/backgrounds/)
        WHERE it sits (and when it exists at all) is the CROWN + SECTION D
        block in part 2 — this block is only WHAT it looks like.
   ══════════════════════════════════════════════════════════════════════ */

  [data-section="d"] .link{ --glyph-gap:11px; }
  /* 50px box (not 60): the earth fills far more of its viewBox than the
     ring glyphs, so a smaller box matches their ~41px visible ink. */
  [data-section="d"] .glyph{ width:50px; height:50px; }

/* ══════════════════════════════════════════════════════════════════════
   LIBRARY-ONLY GLYPHS — not wired to a section yet; behavior travels
   with the class wherever a symbol: field points (see library/symbols/ README
   notes in CLAUDE.md).
   ══════════════════════════════════════════════════════════════════════ */

  /* house — shared by all eight library/symbols/house*.svg variants (base house
     plus heart/sun/check/plus/star/smiley/tao marks above the door).
     Same grey → amber pulse pattern as the heart, earth, and macbook. */
  .glyph .house{ color:#b7b7b7; transition:color .8s ease, filter .8s ease; }
  :root[data-theme="dark"] .glyph .house{ color:#c8c0b2; }
  .symbol:hover .glyph .house,
  .symbol:focus-visible .glyph .house,
  :root .near .glyph .house{
    color:#c98a4b; animation:housePulse 3.8s ease-in-out infinite alternate; filter:drop-shadow(0 0 3px rgba(201,138,75,.3));
  }
  @keyframes housePulse{ from{ filter:drop-shadow(0 0 2px rgba(201,138,75,.25)); } to{ filter:drop-shadow(0 0 6px rgba(201,138,75,.5)); } }
  @media (prefers-reduced-motion: reduce){
    .symbol:hover .glyph .house,
    .symbol:focus-visible .glyph .house,
    :root .near .glyph .house{ animation:none; }
  }

  /* ── generic glyph fallback (v4.18.0) ────────────────────────────────
     Any symbol in library/symbols/ works by name alone: glyphs whose
     markup has no bespoke class block above get the standard grey →
     amber pulse automatically. Targets the svg's DIRECT children via
     :where() so these rules carry (0,1,0) specificity and every bespoke
     block (.heart-shape/.zen-stones/.earth/.macbook/.house) always wins
     unchanged. New symbols need no CSS at all unless they want their
     own behavior. */
  .glyph > :where(g, path, circle, rect, ellipse){
    color:#b7b7b7; transition:color .8s ease, filter .8s ease;
  }
  :root[data-theme="dark"] .glyph > :where(g, path, circle, rect, ellipse){ color:#c8c0b2; }
  .symbol:hover .glyph > :where(g, path, circle, rect, ellipse),
  .symbol:focus-visible .glyph > :where(g, path, circle, rect, ellipse),
  :root .near .glyph > :where(g, path, circle, rect, ellipse){
    color:#c98a4b; animation:glyphPulse 3.8s ease-in-out infinite alternate; filter:drop-shadow(0 0 3px rgba(201,138,75,.3));
  }
  @keyframes glyphPulse{ from{ filter:drop-shadow(0 0 2px rgba(201,138,75,.25)); } to{ filter:drop-shadow(0 0 6px rgba(201,138,75,.5)); } }
  @media (prefers-reduced-motion: reduce){
    .symbol:hover .glyph > :where(g, path, circle, rect, ellipse),
    .symbol:focus-visible .glyph > :where(g, path, circle, rect, ellipse),
    :root .near .glyph > :where(g, path, circle, rect, ellipse){ animation:none; }
  }
