/* purabioma — SHARED FLOW LAYER (ships to production; not lab chrome).
 *
 * WHY THIS FILE EXISTS
 *   The pages carried ~30 inline style="margin-top:36px" declarations. Inline
 *   spacing wins over every stylesheet, so a theme swap could re-colour and
 *   re-type the page but never re-space it — direction D's airy 96px rhythm
 *   and direction C's tight block rhythm were both stuck with the same
 *   hardcoded 36px. These utilities replace them.
 *
 * HOW IT STAYS THEME-AGNOSTIC
 *   Every value below resolves a --sp-* token, and all four themes declare
 *   that scale with their OWN values (see any themes/<id>/theme.css). One
 *   class, four rhythms: swapping the theme now re-spaces the markup too.
 *
 * SCOPE: flow (space between siblings) and two pieces of functional chrome
 * that every page shares. Component styling stays in the theme files. */

/* ---------- flow: space above an element, on the theme's scale ---------- */
.flow-xs  { margin-block-start: var(--sp-xs); }
.flow-sm  { margin-block-start: var(--sp-sm); }
.flow-md  { margin-block-start: var(--sp-md); }
.flow-lg  { margin-block-start: var(--sp-lg); }
.flow-xl  { margin-block-start: var(--sp-xl); }
.flow-xxl { margin-block-start: var(--sp-xxl); }
/* the odd one out BY DESIGN: kills ALL margins, not just the start edge.
   Its call sites are flush labels, last-children and card stacks that need
   the theme's default end margin gone too. Do NOT put it on a heading whose
   following content needs breathing room — the heading's own 0.5em below
   is part of the type system, and this class deletes it. */
.flow-none { margin: 0; }

/* horizontal group with theme-scaled gaps — replaces inline flex declarations
   (gap survives direct-manipulation edits; source whitespace does not) */
.cluster { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
.cluster-md { display: flex; flex-wrap: wrap; gap: var(--sp-md); }
/* vertical stack, same idea */
.stack-md { display: grid; gap: var(--sp-md); }

/* middot separator: visually equal space on BOTH sides. Two corrections are
   needed, because this sits inside tracked type:
   1. letter-spacing:0 on the dot itself — otherwise the tracking adds a
      trailing space after the dot, widening its right side only;
   2. the glyph BEFORE the dot also carries a trailing tracking space, which
      lives outside this element — so the start padding is reduced by exactly
      one tracking unit. Each direction declares its own --track (the tracking
      of the type the separator sits in); the fallback is 0 for untracked text.
   Result: the gap before a dot equals the gap after it, whether the dot
   precedes a word or an icon. */
.dot-sep {
  letter-spacing: 0;
  padding-inline: calc(0.5em - var(--track, 0em)) 0.5em;
}

/* re-anchors a block inside a centred section. Directions B and D centre
   .hero as their page-title treatment; a data card (definition list, chip
   row, photo frame) cannot inherit that without reading half-centred. This
   keeps the title centred where the direction centres it and the record
   anchored, WITHOUT a second .section — direction C paints its colour blocks
   on .section > .wrap and cycles them by :nth-of-type, so splitting a page
   into two sections would split one block into two tinted blocks. */
.align-start { text-align: start; }
/* B and D centre paragraph BOXES inside .hero (see their .hero p rule); an
   anchored block must opt back out or its copy would sit indented from the
   card beside it. Selector is (0,2,1) — above the theme rule's (0,1,1),
   which loads after this file. */
.hero .align-start p { margin-inline: 0; }

/* centres a measure-limited paragraph inside a centred block */
.measure-center { margin-inline: auto; }
.text-center { text-align: center; }
/* one step down from the inherited size — em, so it scales with whichever
   theme's body size is in force rather than pinning a rem value */
.text-sm { font-size: 0.9em; }
.text-muted { color: var(--text-3); }
.text-accent { color: var(--madder-text); }
.level-row { align-items: baseline; }
/* a button that owns its row (mobile-width CTAs) */
.btn-block { width: 100%; justify-content: center; }

/* ---------- functional chrome shared by every page ---------- */
/* live region for prototype announcements: pinned above the fold's bottom
   edge, never intercepting pointer events, clear of iOS home indicators */
.announce-global {
  position: fixed; z-index: 60; pointer-events: none;
  inset-block-end: max(var(--sp-md), env(safe-area-inset-bottom));
  inset-inline: max(var(--sp-md), env(safe-area-inset-left)) max(var(--sp-md), env(safe-area-inset-right));
  color: var(--madder-text);
}

/* ---------- teaser stage: the fold is a hard guarantee ----------
   Every coming-soon version uses .stage as its full-viewport shell, and the
   colophon line — copyright, flag, and the Nightfall/Daybreak switch — is its
   last grid row. That line must NEVER sit below the fold in a browser window.
   Owning it here rather than per page means every existing version and every
   future one inherits the guarantee; a page may still choose its own grid
   proportions (v03 weights the rows 1.15fr / auto / 1fr to leave the lower
   third to the steam), but not its own sizing or spacing scale.

   Two mechanisms, and they are both load-bearing:

   1. 100svh, NOT 100dvh. svh is the SMALLEST viewport height — the state with
      the browser's UI fully expanded. dvh tracks whatever the viewport is at
      this instant, so on mobile a dvh-sized shell is exactly as tall as the
      screen with the URL bar retracted, and the footer drops underneath it the
      moment the bar comes back. svh is the only unit that is correct in every
      chrome state. (lvh, the largest, is the one that guarantees a scrollbar.)

   2. The vertical rhythm is fluid in AVAILABLE height. Each step is
      clamp(floor, fraction-of---vh-avail, ceiling): in a normal window the
      ceilings apply and the composition is exactly as designed; as the window
      shortens, the GAPS give way before the type does, so the content block
      shrinks to fit instead of pushing the colophon out of view. Type is
      never scaled by this — an unreadable line above the fold would be a
      worse failure than a scroll.
      The measure is --vh-avail, not raw vh, because a media query cannot see
      a custom property: keying the scale off the same value the shell is
      sized with is what makes the compression chrome-aware instead of merely
      viewport-aware.

   The base-scale floors bind between ~213px (xl) and ~357px (tight) of
   available height; in practice the short-window tiers below take over long
   before that. Under ~400px (a phone in landscape, or heavy browser zoom)
   the page scrolls, which is correct behaviour rather than a defect: at that
   point the only way to keep the colophon visible would be to shrink type
   past legibility. Phones in portrait are comfortably above the floors and
   stay single-screen.

   SAFE AREA: padding floors at --flow-tight and grows to clear notches and the
   home indicator; env() resolves to 0 elsewhere, so max() is a no-op there.

   --lab-chrome: in the design lab the switcher strip sits above the stage and
   is part of the page, so a bare 100svh shell would push the colophon down by
   exactly the strip's height. lab.js measures the strip and publishes it as
   --lab-chrome; in the deploy build the strip does not exist, the variable is
   never set, and the fallback 0px makes this an ordinary 100svh. */
.stage {
  /* the height this shell actually gets: viewport minus any lab chrome */
  --vh-avail: calc(100svh - var(--lab-chrome, 0px));

  /* the guarantee below is border-box arithmetic: min-block-size and padding
     on the same box. Owned here, not delegated to the themes' global reset —
     the one file claiming a hard guarantee must not depend on a fifth file. */
  box-sizing: border-box;

  --flow-tight: clamp(10px, calc(var(--vh-avail) * 0.028), 24px);
  --flow: clamp(12px, calc(var(--vh-avail) * 0.038), 32px);
  --flow-loose: clamp(14px, calc(var(--vh-avail) * 0.048), 40px);
  --flow-section: clamp(14px, calc(var(--vh-avail) * 0.056), 48px);
  --flow-xl: clamp(16px, calc(var(--vh-avail) * 0.075), 64px);

  min-block-size: var(--vh-avail);
  padding: var(--flow-tight);
  padding-inline: max(var(--flow-tight), env(safe-area-inset-left)) max(var(--flow-tight), env(safe-area-inset-right));
  padding-block-end: max(var(--flow-tight), env(safe-area-inset-bottom));
  text-align: center;
}

/* SHORT WINDOWS — two tiers. The @media conditions are viewport-height
   thresholds (a media query cannot see --lab-chrome), but every value INSIDE
   resolves against --vh-avail, same as the base scale — the compression stays
   chrome-aware and the ramp stays in the base scale's order
   (tight < flow < loose ≤ section < xl; loose and section meet at the floors).
   Tier 1 (≤640px) tightens the gap ramp below what the continuous --vh-avail
   fractions give, and lets DECORATION yield before type: the steam glyph and
   the wordmark scale down, the headline and body do not. The wordmark cap is
   min()-combined with the page's own --wordmark-size, so a tier can only
   ever SHRINK a mark the page sized smaller — never enlarge it. (The steam
   caps are plain: no page declares its own steam size to protect.)
   Tier 2 (≤560px) is phone-landscape / preview-pane territory — no desktop
   window is this short. Here the gaps go to fixed minimums and the headline
   is capped as well: it is the single place type yields, and it buys the ~30px
   that keeps v02 (which carries both a glyph and a large mark) above the fold.
   Both selectors are .stage-scoped so they outrank the pages' own
   single-class rules without !important. */
@media (max-height: 640px) {
  .stage {
    --flow-tight: clamp(10px, calc(var(--vh-avail) * 0.022), 16px);
    --flow: clamp(12px, calc(var(--vh-avail) * 0.028), 22px);
    --flow-loose: clamp(14px, calc(var(--vh-avail) * 0.034), 26px);
    --flow-section: clamp(14px, calc(var(--vh-avail) * 0.036), 28px);
    --flow-xl: clamp(16px, calc(var(--vh-avail) * 0.044), 34px);
  }
  .stage .steam {
    block-size: clamp(30px, calc(var(--vh-avail) * 0.06), 64px);
    inline-size: auto;
  }
  .stage .logotype {
    font-size: min(var(--wordmark-size, clamp(28px, 6vw, 58px)),
                   clamp(20px, calc(var(--vh-avail) * 0.044), 44px));
  }
}
@media (max-height: 560px) {
  .stage {
    --flow-tight: 9px;
    --flow: 11px;
    --flow-loose: 13px;
    --flow-section: 13px;
    --flow-xl: 15px;
  }
  .stage .steam { block-size: 26px; }
  .stage .logotype {
    font-size: min(var(--wordmark-size, clamp(28px, 6vw, 58px)), 20px);
  }
  .stage .teaser h1 { font-size: clamp(22px, calc(var(--vh-avail) * 0.054), 34px); }
}

/* scene switch: sits in the footer beside the language control on every page.
   Colour comes from the footer it lands in (currentColor), so it reads
   correctly on direction C's pine block and direction D's cream alike. */
.scene-toggle {
  display: inline-flex; align-items: center; gap: var(--sp-xs);
  font: inherit; color: inherit; background: none; cursor: pointer;
  border: 1px solid color-mix(in srgb, currentColor 32%, transparent);
  border-radius: 999px; padding: var(--sp-xs) var(--sp-md); min-block-size: 32px;
  opacity: 0.75; transition: opacity 0.2s ease, border-color 0.2s ease;
}
.scene-toggle:hover, .scene-toggle:focus-visible {
  opacity: 1; border-color: color-mix(in srgb, currentColor 60%, transparent);
}
.scene-toggle svg { inline-size: 13px; block-size: 13px; display: block; }
/* one button, two glyphs: the visible one is the scene this click DELIVERS —
   sun + "Daybreak" while the page is dark, moon + "Nightfall" while it is
   light. DESTINATION-LABELLED BY OWNER RULING (2026-08-13, final): the
   control is an action button whose label and glyph name where it takes
   you; scene.js writes the matching "Switch to …" aria-label and no state
   attribute (state on a destination-named control contradicts its name). */
html[data-scene="day"] .scene-toggle .i-day,
html[data-scene="night"] .scene-toggle .i-night { display: none; }
/* no data-scene means scene.js never ran (JS off) — a control that cannot
   switch must not render at all, and without this rule it would show BOTH
   glyphs on a dead button */
html:not([data-scene]) .scene-toggle { display: none; }
/* (direction B re-squares this control in its own theme.css — component
   styling stays out of this file) */

/* ---------- production behaviour shared by every direction ----------
   These three things ship. They lived in lab.css once, but lab.css is
   stripped from every deploy — anything a PRODUCTION page needs must live
   here, or the behaviour silently dies the moment the lab chrome goes. */

/* modern-height animation opt-in: enables 0 → auto height transitions
   (every theme's FAQ <details> slide). Spec-required opt-in; engines
   without it keep the instant toggle. */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
}

/* theme gates (markup superset): the pages carry the UNION of all
   direction-specific markup; each gate hides an element for every direction
   EXCEPT its owner. A production build pins one direction, but the union
   markup still renders — so the gates must ship with it. */
html:not([data-theme="c"]) .ticker-section { display: none !important; }   /* C: herb marquee   */
html:not([data-theme="d"]) .wordmark .emblem { display: none !important; } /* D: gold emblem    */
html:not([data-theme="d"]) .tile-btn { display: none !important; }         /* D: Discover plate */
