/* ==========================================================================
   Auspicious Music — concept design system
   Phase 1 artefact. Every value here is intended to migrate verbatim into
   src/styles/tokens.css when the Astro implementation starts.
   ========================================================================== */

@layer reset, tokens, base, layout, components, motion, touch;

/* ---------------------------------------------------------------- tokens */
@layer tokens {
  :root {
    /* ─── Palette, revision 3 — "Ink Stage / Paper Study" ────────────
     * Revision 2 was one paper neutral with two accent axes. Correct as
     * a system, flat as a page: canvas and surface sat 4% apart, the
     * string field whispered at 13% ink, and nothing on the site cast
     * light or shadow. Revision 3 keeps the two-axis discipline and adds
     * the third thing a stage needs: darkness.
     *
     * The model is five distinguishable elevations:
     *
     *   stage-deep → stage → stage-raised     the ink-black theatre where
     *                                         sound happens: hero, lab,
     *                                         tool consoles, error pages,
     *                                         footer
     *   tint                                  a warm workbench plane for
     *                                         tools and annotations
     *   canvas → surface → surface-bright     the ivory paper where the
     *                                         editorial work is read
     *
     * The accent split survives unchanged in meaning:
     *   signal (cool blue)  → action and navigation only
     *   copper (warm metal) → material, archive, the strings themselves
     * No element may use both. Every text/background pair below clears
     * WCAG AA; the arithmetic lives in the design notes.
     */

    /* Paper — the editorial world */
    --canvas: #f1eee6;          /* soft ivory · ink 15.7:1 */
    --surface: #f9f7f2;         /* lifted band */
    --surface-bright: #ffffff;  /* brightest step, used sparingly */
    --tint: #e5dfd1;            /* warm workbench plane · ink 13.7:1 */
    --ink: #14151c;             /* ink with the stage's blue cast */
    --ink-soft: #34353f;
    --muted: #5d5e69;           /* 5.5:1 on canvas · 4.8:1 on tint */
    --line: rgba(20, 21, 28, 0.14);
    --line-strong: rgba(20, 21, 28, 0.26);

    /* Stage — the ink-black theatre */
    --stage: #0d0f16;           /* near-black with a navy/ink cast */
    --stage-deep: #090a10;      /* vignette floor */
    --stage-raised: #161925;    /* raised plane: cards, consoles */
    --stage-high: #1e2231;      /* second lift: inputs, hovers */
    --stage-text: #f3f0e9;      /* ivory · 16.8:1 on stage */
    --stage-muted: #9fa1af;     /* 7.5:1 on stage · 6.2:1 on high */
    --stage-line: rgba(243, 240, 233, 0.15);
    --stage-line-soft: rgba(243, 240, 233, 0.07);

    /* Action axis — cool signal blue, links and CTAs only */
    --accent: #2b4fd7;          /* 5.7:1 on canvas · 4.9:1 on tint */
    --accent-hover: #2340b8;
    --accent-on: #ffffff;       /* 6.6:1 on accent */
    --accent-on-dark: #9db4ff;  /* 9.5:1 on stage */

    /* Material axis — copper, the colour of a wound string */
    --ember: #9c4513;           /* text weight · 5.5:1 canvas · 4.8:1 tint */
    --ember-bright: #c4681f;    /* fills, markers, the string's glow */
    --ember-on-dark: #e0995c;   /* 8.1:1 on stage */

    /* Legacy aliases — every page written against revision 2 keeps
       working; the values now point into the stage model. */
    --dark: var(--stage);
    --dark-surface: var(--stage-raised);
    --dark-text: var(--stage-text);
    --dark-muted: var(--stage-muted);
    --dark-line: var(--stage-line);
    --dark-line-soft: var(--stage-line-soft);

    /* Status — legacy + download surfaces only, never decorative */
    --status-ok: #1a7f45;
    --status-off: #5d5e69;
    --status-ext: #2f5fbf;

    /* Light — how a plane says which way is up. The stage is lit from
       high centre like a rehearsal room; paper takes no gradient at all,
       it is matte by definition. Grain is a hand-authored SVG turbulence
       tile at ~4% so large dark planes read as material, not as #000. */
    --light-stage:
      radial-gradient(120% 90% at 50% -20%,
        rgba(243, 240, 233, 0.085), transparent 62%),
      radial-gradient(80% 60% at 85% 110%,
        rgba(43, 79, 215, 0.05), transparent 70%);
    --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");

    /* Type families */
    --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

    /* Fluid type scale — 390px floor, 1440px ceiling */
    --t-display: clamp(2.625rem, 1.2rem + 6.2vw, 6.5rem);   /* 42 → 104 */
    --t-h1:      clamp(2.25rem, 1.32rem + 3.1vw, 4.75rem);  /* 36 → 76  */
    --t-h2:      clamp(2rem, 0.9rem + 3.5vw, 4rem);         /* 32 → 64  */
    --t-h3:      clamp(1.375rem, 1.1rem + 0.9vw, 2rem);     /* 22 → 32  */
    --t-h4:      clamp(1.125rem, 1.02rem + 0.35vw, 1.375rem);
    --t-body:    clamp(1.125rem, 1.05rem + 0.25vw, 1.25rem);/* 18 → 20  */
    --t-ui:      0.9375rem;                                  /* 15 */
    --t-ui-sm:   0.875rem;                                   /* 14 */
    --t-meta:    0.8125rem;                                  /* 13 */

    --lh-display: 0.98;
    --lh-heading: 1.08;
    --lh-body: 1.65;
    --lh-ui: 1.45;

    --tr-display: -0.028em;
    --tr-heading: -0.018em;
    --tr-meta: 0.02em;
    --tr-caps: 0.075em;

    /* Geometry */
    --header-h: 68px;            /* sticky header, subtracted from full-height scenes */
    --container: 82.5rem;        /* 1320px */
    --measure: min(74ch, 46rem); /* ~736px reading column */
    --gutter: clamp(1.25rem, 4vw, 3rem);

    --r-sm: 12px;
    --r-md: 16px;
    --r-lg: 20px;
    --r-media: 28px;
    --r-control: 12px;           /* inputs, selects, search fields */

    /* How far a paper section pulls itself over a stage plane */
    --sheet-overlap: 3.5rem;

    /* Vertical rhythm — deliberately three distinct steps so sections
       never share one identical spacing signature. */
    --band-tight: clamp(3rem, 6vw, 5rem);
    --band-base: clamp(4.5rem, 9vw, 8rem);
    --band-wide: clamp(6rem, 12vw, 11rem);

    /* Depth — three levels plus the paper edge. Shadows are cast by the
       ink of this palette, never by grey, so they stay warm on paper and
       read as stage darkness on the stage. */
    --shadow-lift: 0 12px 28px -18px rgba(13, 15, 22, 0.28);
    --shadow-media: 0 24px 60px -28px rgba(13, 15, 22, 0.34);
    --shadow-float: 0 32px 80px -32px rgba(13, 15, 22, 0.45);
    /* The sheet edge: cast upward by a paper plane overlapping the stage */
    --shadow-sheet: 0 -24px 60px -30px rgba(9, 10, 16, 0.55);

    /* Motion — matches the 450–900ms window in the brief */
    --dur-fast: 180ms;
    --dur-base: 450ms;
    --dur-slow: 720ms;
    --dur-hero: 1100ms;
    --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  }
}

/* ----------------------------------------------------------------- reset */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }

  html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
  }
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
  }

  body {
    min-height: 100svh;
    /* Guards the full-bleed media in the hero and story bands from ever
       producing a horizontal scrollbar. `clip` creates no scroll container. */
    overflow-x: clip;
  }

  img, svg, video { display: block; max-width: 100%; }
  img, video { height: auto; }

  button, input, select, textarea { font: inherit; color: inherit; }
  button { background: none; border: 0; cursor: pointer; }

  ul, ol { list-style: none; padding: 0; }

  a { color: inherit; }

  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 2px;
  }
  .band-dark :focus-visible { outline-color: var(--accent-on-dark); }
}

/* ------------------------------------------------------------------ base */
@layer base {
  body {
    background: var(--canvas);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: var(--lh-body);
    font-synthesis-weight: none;
    font-optical-sizing: auto; /* Inter's opsz axis carries the display cut */
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  h1, h2, h3, h4 {
    font-weight: 615;
    line-height: var(--lh-heading);
    letter-spacing: var(--tr-heading);
    text-wrap: balance;
  }

  h1 { font-size: var(--t-h1); }
  h2 { font-size: var(--t-h2); }
  h3 { font-size: var(--t-h3); }
  h4 { font-size: var(--t-h4); font-weight: 550; letter-spacing: -0.008em; }

  p { text-wrap: pretty; }

  strong { font-weight: 600; }

  /* Skip link — visible the moment it receives focus */
  .skip {
    position: absolute;
    left: var(--gutter);
    top: 0;
    z-index: 100;
    transform: translateY(-120%);
    background: var(--ink);
    color: var(--surface);
    padding: 0.75rem 1.25rem;
    border-radius: 0 0 var(--r-sm) var(--r-sm);
    font-size: var(--t-ui-sm);
    font-weight: 550;
    transition: transform var(--dur-fast) var(--ease);
  }
  .skip:focus-visible { transform: translateY(0); }
}

/* ---------------------------------------------------------------- layout */
@layer layout {
  .shell {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  .measure { max-width: var(--measure); }

  .band { padding-block: var(--band-base); }
  .band--tight { padding-block: var(--band-tight); }
  .band--wide { padding-block: var(--band-wide); }

  /* The stage. Every dark plane on the site is this one material: ink
     black with a navy cast, lit from high centre, with a breath of grain
     so it reads as a room rather than as a hex value. Positioned so the
     grain pseudo-element can sit behind the content. */
  .band-dark {
    position: relative;
    background-color: var(--stage);
    background-image: var(--light-stage);
    color: var(--dark-text);
  }
  .band-dark::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--grain);
    background-size: 160px 160px;
    opacity: 0.04;
    pointer-events: none;
  }
  .band-dark > * { position: relative; }
  .band-dark h1, .band-dark h2, .band-dark h3 { color: var(--dark-text); }

  .band-surface { background: var(--surface); }

  /* The workbench — the warm tinted plane between paper and stage.
     Tools live here; so do annotations that should feel material. */
  .band-tint { background: var(--tint); }

  /* The paper edge. A light section that follows a stage section pulls
     itself up over the dark plane like a sheet laid on the stage floor —
     the one overlap the design allows itself, and the reason the next
     section is already visible inside the hero viewport. */
  .sheet-lift {
    position: relative;
    z-index: 2;
    margin-top: calc(-1 * var(--sheet-overlap, 3.5rem));
    border-radius: var(--r-media) var(--r-media) 0 0;
    background: var(--canvas);
    box-shadow: var(--shadow-sheet);
  }

  /* ---- cards & app frames ----
     The reference language: content sits in lifted panels with real depth
     instead of hairline rows. Cards are paper lifted off the canvas;
     app frames are pieces of the stage — small working instruments. */
  .card {
    background: var(--surface-bright);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lift);
    transition: transform var(--dur-base) var(--ease),
                box-shadow var(--dur-base) var(--ease);
  }
  a.card { display: block; text-decoration: none; color: inherit; }
  @media (hover: hover) and (prefers-reduced-motion: no-preference) {
    a.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-media); }
  }

  .app-frame {
    position: relative;
    background:
      linear-gradient(178deg, var(--stage-raised), var(--stage) 62%);
    color: var(--stage-text);
    border: 1px solid var(--stage-line-soft);
    border-radius: var(--r-lg);
    box-shadow:
      inset 0 1px 0 rgba(243, 240, 233, 0.07),
      var(--shadow-float);
    overflow: clip;
  }
  .app-frame__bar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--stage-line-soft);
  }
  .app-frame__dots { display: inline-flex; gap: 5px; }
  .app-frame__dots i {
    inline-size: 8px;
    block-size: 8px;
    border-radius: 50%;
    background: var(--stage-line);
  }
  .app-frame__title {
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--stage-muted);
  }

  /* Honest stat tiles — every number on them is counted, not claimed. */
  .stat-tile {
    display: grid;
    gap: 0.125rem;
    padding: 0.875rem 1.125rem;
    border: 1px solid var(--stage-line-soft);
    border-radius: var(--r-md);
    background: color-mix(in srgb, var(--stage-raised) 72%, transparent);
    backdrop-filter: blur(4px);
  }
  .stat-tile strong {
    font-size: clamp(1.375rem, 1.15rem + 0.8vw, 1.75rem);
    font-weight: 640;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--stage-text);
  }
  .stat-tile span {
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--stage-muted);
    white-space: nowrap;
  }

  /* Soft ambient light on paper bands — the pale counterpart of the stage
     light, so bright sections stop reading as flat fills. */
  .band--glow {
    background-image:
      radial-gradient(56% 42% at 8% 0%, rgba(43, 79, 215, 0.055), transparent 70%),
      radial-gradient(50% 40% at 96% 100%, rgba(196, 104, 31, 0.06), transparent 70%);
  }

  /* Staff motif — five hairlines with graded spacing, the string field
     reduced to a mark. Decorates section heads on paper planes; never
     carries information. */
  .staff {
    position: relative;
    block-size: 22px;
    inline-size: 100%;
    background:
      linear-gradient(var(--line-strong), var(--line-strong)) 0 0 / 100% 1px,
      linear-gradient(var(--line), var(--line)) 0 6px / 100% 1px,
      linear-gradient(var(--line), var(--line)) 0 11px / 100% 1px,
      linear-gradient(var(--line), var(--line)) 0 15px / 100% 1px,
      linear-gradient(var(--line), var(--line)) 0 18px / 100% 1px;
    background-repeat: no-repeat;
  }
  .staff::after {
    content: "";
    position: absolute;
    top: -3px;
    left: var(--staff-node-x, 18%);
    inline-size: 7px;
    block-size: 7px;
    border-radius: 50%;
    background: var(--ember-bright);
  }
  /* The staff draws itself as it scrolls into view, the node travelling
     to its seat — scroll-driven, no JS; browsers without view() timelines
     simply see the finished mark. */
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {
      .staff {
        animation: staff-draw both;
        animation-timeline: view();
        animation-range: entry 10% entry 90%;
      }
      .staff::after {
        animation: staff-node both;
        animation-timeline: view();
        animation-range: entry 10% entry 90%;
      }
    }
  }

  /* Hairline that separates bands without drawing a box around them */
  .rule { border-top: 1px solid var(--line); }
  .band-dark .rule { border-top-color: var(--dark-line); }
}

/* ------------------------------------------------------------ components */
@layer components {

  /* ---- brand ---- */
  .brand {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
    text-decoration: none;
    font-weight: 600;
    font-size: 1.0625rem;
    letter-spacing: -0.017em;
    color: var(--ink);
    white-space: nowrap;
  }
  .brand svg { width: 22px; height: 22px; flex: none; }
  .band-dark .brand { color: var(--dark-text); }

  /* ---- site header ---- */
  .site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    background: color-mix(in srgb, var(--canvas) 82%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid transparent;
    transition: border-color var(--dur-base) var(--ease),
                background-color var(--dur-base) var(--ease);
  }
  /* GSAP toggles .is-scrolled past ~64px — approved motion family 7 */
  .site-header.is-scrolled {
    border-bottom-color: var(--line);
    background: color-mix(in srgb, var(--canvas) 94%, transparent);
  }

  /* Pages that open on the stage mark the header [data-over-dark]. The
     header goes transparent and the stage scene extends underneath it
     (the scene pulls itself up by the header's height), so the light and
     the string field own the full first viewport. Once the scene has
     scrolled past, the header resolves into the normal paper surface. */
  .site-header[data-over-dark]:not(.is-scrolled) {
    background: transparent;
    backdrop-filter: none;
    color: var(--dark-text);
  }
  .site-header[data-over-dark]:not(.is-scrolled) .brand { color: var(--dark-text); }
  .site-header[data-over-dark]:not(.is-scrolled) .nav a { color: color-mix(in srgb, var(--dark-text) 76%, transparent); }
  .site-header[data-over-dark]:not(.is-scrolled) .nav a:hover,
  .site-header[data-over-dark]:not(.is-scrolled) .nav a[aria-current="page"] { color: var(--dark-text); }
  .site-header[data-over-dark]:not(.is-scrolled) .icon-btn { color: color-mix(in srgb, var(--dark-text) 76%, transparent); }
  .site-header[data-over-dark]:not(.is-scrolled) .icon-btn:hover {
    color: var(--dark-text);
    background: var(--dark-line-soft);
  }
  .site-header[data-over-dark]:not(.is-scrolled) :focus-visible { outline-color: var(--accent-on-dark); }

  .site-header__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    min-height: var(--header-h);
  }

  .nav { display: none; }
  .nav ul { display: flex; align-items: center; gap: clamp(1rem, 1.9vw, 1.875rem); }
  .nav a {
    text-decoration: none;
    font-size: var(--t-ui-sm);
    font-weight: 500;
    letter-spacing: -0.005em;
    color: var(--ink-soft);
    padding-block: 0.5rem;
    position: relative;
    transition: color var(--dur-fast) var(--ease);
  }
  .nav a::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0.125rem;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--dur-fast) var(--ease);
  }
  .nav a:hover { color: var(--ink); }
  .nav a:hover::after { transform: scaleX(1); }
  .nav a[aria-current="page"] { color: var(--ink); font-weight: 550; }
  .nav a[aria-current="page"]::after { transform: scaleX(1); }

  /* ---- dropdown panels ----
     Each main topic opens a piece of the stage below the header: the same
     ink material as the hero and the footer, floating above the paper.
     Pure CSS — hover for the pointer, focus-within for the keyboard; the
     top-level link itself stays an ordinary link to the hub. */
  .nav-group { position: relative; }
  /* The hover underline stops before the caret instead of striking it. */
  .nav-group > a::after { right: 1.05rem; }
  .nav-caret {
    inline-size: 9px;
    block-size: 6px;
    margin-left: 0.375rem;
    vertical-align: 0.125rem;
    opacity: 0.6;
    transition: transform var(--dur-fast) var(--ease);
  }
  .nav-group:hover > a .nav-caret,
  .nav-group:focus-within > a .nav-caret { transform: rotate(180deg); }
  .nav-drop {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    padding-top: 0.75rem;             /* hover bridge between link and panel */
    opacity: 0;
    visibility: hidden;
    translate: 0 6px;
    transition: opacity var(--dur-fast) var(--ease),
                translate var(--dur-fast) var(--ease),
                visibility var(--dur-fast);
    z-index: 70;
  }
  .nav-group:hover .nav-drop,
  .nav-group:focus-within .nav-drop {
    opacity: 1;
    visibility: visible;
    translate: 0 0;
  }
  .nav-drop ul {
    display: grid;
    gap: 0.125rem;
    min-inline-size: 19rem;
    padding: 0.625rem;
    /* Raised two steps above the stage so the panel keeps a visible edge
       even when it opens over a dark hero — same ink family, clearly its
       own surface. */
    background: linear-gradient(178deg, var(--stage-high), var(--stage-raised) 70%);
    border: 1px solid var(--stage-line);
    border-radius: var(--r-md);
    box-shadow:
      inset 0 1px 0 rgba(243, 240, 233, 0.08),
      0 0 0 1px rgba(9, 10, 16, 0.65),
      var(--shadow-float);
  }
  .nav-drop a:hover { background: color-mix(in srgb, var(--stage-text) 12%, transparent); }
  .nav-drop a {
    display: grid;
    gap: 0.125rem;
    padding: 0.625rem 0.75rem;
    border-radius: var(--r-sm);
    color: var(--stage-text);
    font-weight: 500;
    transition: background-color var(--dur-fast) var(--ease);
  }
  .nav-drop a::after { content: none; }   /* no underline animation inside the panel */
  .nav-drop a:hover { background: var(--stage-high); color: var(--stage-text); }
  .nav-drop__label { font-size: var(--t-ui-sm); letter-spacing: -0.005em; }
  .nav-drop__note {
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--stage-muted);
  }
  .nav-drop :focus-visible { outline-color: var(--accent-on-dark); }

  .header-tools { display: flex; align-items: center; gap: 0.25rem; }

  /* The one commercial action the site leads to, carried in the header
     like the reference layouts carry theirs. Hidden while the burger
     handles narrow widths — the sheet lists it first instead. */
  .header-cta { display: none; }
  @media (min-width: 1240px) {
    .header-cta {
      display: inline-flex;
      min-block-size: 40px;
      padding-inline: 1.125rem;
      margin-right: 0.5rem;
      font-size: var(--t-ui-sm);
    }
  }

  .icon-btn {
    display: inline-grid;
    place-items: center;
    inline-size: 44px;
    block-size: 44px;
    border-radius: var(--r-sm);
    color: var(--ink-soft);
    transition: background-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease);
  }
  .icon-btn:hover { background: var(--line); color: var(--ink); }
  .icon-btn svg { width: 19px; height: 19px; }

  .menu-btn { display: inline-grid; }

  /* 1060 rather than 1100: seven destinations plus wordmark and two controls
     measure ~930px, so tablet landscape still gets the full navigation. */
  @media (min-width: 1060px) {
    .nav { display: block; }
    .menu-btn { display: none; }
  }

  /* ---- mobile sheet ----
     The menu is the curtain: the stage material drops over the page.
     Ivory navigation at display size, lit from above like the hero. */
  .sheet {
    position: fixed;
    inset: 0;
    z-index: 60;
    background-color: var(--stage);
    background-image: var(--light-stage);
    color: var(--stage-text);
    display: grid;
    grid-template-rows: auto 1fr auto;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur-base) var(--ease), visibility var(--dur-base);
  }
  .sheet[data-open="true"],
  .sheet:target { opacity: 1; visibility: visible; }
  .sheet .brand { color: var(--stage-text); }
  .sheet .icon-btn { color: var(--stage-muted); }
  .sheet .icon-btn:hover { color: var(--stage-text); background: var(--stage-line-soft); }
  .sheet :focus-visible { outline-color: var(--accent-on-dark); }
  .sheet__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 68px;
  }
  .sheet nav ul {
    display: grid;
    gap: 0.25rem;
    padding-block: 1.5rem;
  }
  .sheet nav a {
    display: block;
    text-decoration: none;
    font-size: clamp(1.75rem, 7vw, 2.5rem);
    font-weight: 615;
    letter-spacing: var(--tr-heading);
    padding-block: 0.5rem;
    color: var(--stage-text);
  }
  .sheet nav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 6px; text-decoration-thickness: 2px; }

  /* Grouped curtain: each topic is a fold that opens onto its pages. */
  .sheet__nav { overflow-y: auto; min-block-size: 0; }
  .sheet-group summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    font-size: clamp(1.5rem, 6vw, 2.25rem);
    font-weight: 615;
    letter-spacing: var(--tr-heading);
    padding-block: 0.5rem;
    color: var(--stage-text);
  }
  .sheet-group summary::-webkit-details-marker { display: none; }
  .sheet-group summary .nav-caret {
    inline-size: 14px;
    block-size: 9px;
    opacity: 0.55;
    transition: transform var(--dur-fast) var(--ease);
  }
  .sheet-group[open] summary .nav-caret { transform: rotate(180deg); }
  .sheet-group ul {
    padding: 0.25rem 0 1rem 1rem;
    gap: 0.125rem;
    border-left: 1px solid var(--stage-line);
    margin-left: 0.125rem;
  }
  .sheet-group ul a {
    font-size: 1.0625rem;
    font-weight: 500;
    padding-block: 0.4375rem;
    color: color-mix(in srgb, var(--stage-text) 82%, transparent);
  }
  .sheet-group ul a:hover { color: var(--stage-text); }
  .sheet-link { display: block; font-size: clamp(1.5rem, 6vw, 2.25rem); font-weight: 615; }
  .sheet__foot {
    padding-block: 1.75rem;
    border-top: 1px solid var(--stage-line);
    font-size: var(--t-ui-sm);
    color: var(--stage-muted);
  }

  /* ---- actions ---- */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 48px;
    padding-inline: 1.5rem;
    border-radius: var(--r-sm);
    font-size: var(--t-ui);
    font-weight: 550;
    letter-spacing: -0.006em;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease);
  }
  .btn:active { transform: translateY(1px); }

  .btn--primary {
    background: var(--accent);
    color: var(--accent-on);
    box-shadow: 0 8px 20px -10px rgba(43, 79, 215, 0.55);
  }
  .btn--primary:hover { background: var(--accent-hover); }

  .btn--quiet {
    border: 1px solid var(--line-strong);
    color: var(--ink);
  }
  .btn--quiet:hover { border-color: var(--ink); background: var(--surface); }

  .band-dark .btn--quiet { border-color: var(--dark-line); color: var(--dark-text); }
  .band-dark .btn--quiet:hover {
    border-color: var(--dark-text);
    background: var(--dark-surface);
  }

  /* Text link with an underline that draws in — used instead of buttons
     wherever the action is editorial rather than transactional. */
  .link {
    display: inline-flex;
    align-items: center;
    gap: 0.4375rem;
    text-decoration: none;
    font-size: var(--t-ui);
    font-weight: 550;
    color: var(--ink);
    padding-block: 0.25rem;
    border-bottom: 1px solid var(--line-strong);
    transition: border-color var(--dur-fast) var(--ease),
                gap var(--dur-fast) var(--ease);
  }
  .link:hover { border-bottom-color: var(--ink); gap: 0.6875rem; }
  .link svg { width: 14px; height: 14px; flex: none; }
  .band-dark .link { color: var(--dark-text); border-bottom-color: var(--dark-line); }
  .band-dark .link:hover { border-bottom-color: var(--dark-text); }

  /* ---- page heads ----
     The shared opening zone of every non-home page. Base values live here
     so a page that declares no head styles of its own (search) still
     opens correctly; page CSS may refine on top. */
  .pg-head { padding-block: clamp(2rem, 4vw, 3rem) clamp(2.5rem, 5vw, 3.5rem); }
  .pg-head h1 { max-width: 18ch; margin-top: 1.25rem; }
  .pg-head .lede { margin-top: 1.5rem; max-width: 58ch; }
  .pg-head--dark {
    position: relative;
    background-color: var(--stage);
    background-image: var(--light-stage);
    color: var(--stage-text);
    padding-block: clamp(2.5rem, 5vw, 4rem);
  }
  .pg-head--dark .crumbs { color: var(--stage-muted); }
  .pg-head--dark .crumbs a:hover { color: var(--stage-text); }
  .pg-head--dark .lede { color: var(--stage-muted); }

  /* ---- editorial primitives ---- */
  .kicker {
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--muted);
    font-weight: 500;
  }
  .band-dark .kicker { color: var(--dark-muted); }

  .section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    justify-content: space-between;
    gap: 1.5rem 2rem;
    margin-bottom: clamp(2rem, 4vw, 3.5rem);
  }
  .section-head h2 { max-width: 22ch; }
  .section-head p { color: var(--muted); max-width: 44ch; font-size: 1.0625rem; }
  .band-dark .section-head p { color: var(--dark-muted); }

  .lede {
    font-size: clamp(1.25rem, 1.05rem + 0.7vw, 1.625rem);
    line-height: 1.45;
    letter-spacing: -0.011em;
    color: var(--ink-soft);
    text-wrap: pretty;
  }
  .band-dark .lede { color: var(--dark-muted); }

  /* Technical metadata row — mono, never decorative */
  .meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--muted);
    line-height: 1.5;
  }
  .meta > * + *::before {
    content: "";
    display: inline-block;
    inline-size: 3px;
    block-size: 3px;
    border-radius: 50%;
    background: currentColor;
    vertical-align: 0.28em;
    margin-inline-end: 0.75rem;
    opacity: 0.55;
  }
  .band-dark .meta { color: var(--dark-muted); }

  /* ---- media ---- */
  .media {
    position: relative;
    overflow: hidden;
    border-radius: var(--r-media);
    background: color-mix(in srgb, var(--ink) 6%, var(--canvas));
  }
  .media img { inline-size: 100%; block-size: 100%; object-fit: cover; }

  /* Honest placeholder. Used wherever a real screenshot or photograph has
     not been captured yet — never a stock image, never an invented UI. */
  .ph {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: end;
    gap: 0.375rem;
    padding: 1.5rem;
    border-radius: var(--r-media);
    color: var(--muted);
    background-color: color-mix(in srgb, var(--ink) 5%, var(--canvas));
    background-image:
      linear-gradient(to right, var(--line) 1px, transparent 1px),
      linear-gradient(to bottom, var(--line) 1px, transparent 1px);
    background-size: 28px 28px;
    background-position: -1px -1px;
    border: 1px solid var(--line);
  }
  .ph__label {
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    line-height: 1.5;
  }
  .ph__label strong { color: var(--ink-soft); font-weight: 550; }
  .band-dark .ph {
    color: var(--dark-muted);
    background-color: var(--dark-surface);
    border-color: var(--dark-line);
    background-image:
      linear-gradient(to right, var(--dark-line-soft) 1px, transparent 1px),
      linear-gradient(to bottom, var(--dark-line-soft) 1px, transparent 1px);
  }
  .band-dark .ph__label strong { color: var(--dark-text); }

  /* ---- status marker (legacy + download only) ---- */
  .status {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--ink-soft);
    font-weight: 500;
  }
  .status::before {
    content: "";
    inline-size: 7px;
    block-size: 7px;
    border-radius: 50%;
    background: var(--status-off);
    flex: none;
    translate: 0 -1px;
  }
  .status[data-state="available"]::before { background: var(--status-ok); }
  .status[data-state="external"]::before { background: var(--status-ext); }
  .status[data-state="alternative"]::before {
    background: transparent;
    box-shadow: inset 0 0 0 2px var(--status-ext);
  }
  .status[data-state="unavailable"]::before {
    background: transparent;
    box-shadow: inset 0 0 0 2px var(--status-off);
  }
  .band-dark .status { color: var(--dark-text); }

  /* ---- editorial index row ----
     The scalable alternative to a card wall: a semantic list whose rows
     stay legible from 5 entries to 500. */
  .index { display: grid; }
  .index li { border-top: 1px solid var(--line); }
  .index li:last-child { border-bottom: 1px solid var(--line); }
  .index a {
    display: grid;
    gap: 0.375rem 2rem;
    padding-block: clamp(1.125rem, 2vw, 1.625rem);
    text-decoration: none;
    align-items: baseline;
    transition: background-color var(--dur-fast) var(--ease);
  }
  .index a:hover { background: var(--surface); }
  /* On a band that is already the lifted surface, the hover steps up to
     the next elevation instead of disappearing into its own background. */
  .band-surface .index a:hover { background: var(--surface-bright); }
  .index h3 {
    font-size: clamp(1.1875rem, 1.05rem + 0.55vw, 1.5rem);
    letter-spacing: -0.014em;
    text-wrap: pretty;
  }
  .index p { color: var(--muted); font-size: 1rem; line-height: 1.55; max-width: 62ch; }
  .index__date {
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }
  .index__topic {
    font-size: var(--t-meta);
    color: var(--muted);
    letter-spacing: 0.01em;
  }

  @media (min-width: 860px) {
    .index a {
      grid-template-columns: 8.5rem minmax(0, 1fr) 9rem;
      align-items: start;
    }
    .index a > .index__date { padding-top: 0.3rem; }
    .index__tail { text-align: right; }
  }

  /* ══ tool surfaces ══════════════════════════════════════════════════
     Shared by every calculator. A tool page is a working instrument, so
     the control sits at the top at a size you cannot miss, results are
     monospace and tabular, and every number is one click from the
     clipboard. Nothing is stored, nothing is sent anywhere.          */

  .tool-head { padding-block: clamp(2rem, 4vw, 3rem) clamp(1.5rem, 3vw, 2.25rem); }
  .tool-head h1 { max-width: 20ch; margin-top: 1.25rem; font-size: var(--t-h2); }
  .tool-head .lede { margin-top: 1.25rem; max-width: 56ch; }

  /* The control strip — a piece of the stage set down on the paper.
     Same material as the hero and the footer (ink, top light, grain),
     floating above the page rather than printed onto it, so the
     instrument reads as hardware and the prose below stays prose. */
  .console {
    position: relative;
    background-color: var(--stage);
    background-image: var(--light-stage);
    color: var(--dark-text);
    border: 1px solid var(--stage-line-soft);
    border-radius: var(--r-lg);
    padding: clamp(1.5rem, 3vw, 2.25rem);
    display: grid;
    gap: clamp(1.25rem, 2.5vw, 1.75rem);
    box-shadow: var(--shadow-float);
    overflow: clip;
  }
  .console::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--grain);
    background-size: 160px 160px;
    opacity: 0.04;
    pointer-events: none;
  }
  .console > * { position: relative; }
  .console :focus-visible { outline-color: var(--accent-on-dark); }
  .console__row {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1.25rem 2rem;
  }
  .field { display: grid; gap: 0.5rem; }
  .field > label {
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--dark-muted);
  }
  .field input[type="number"], .field input[type="text"], .field select {
    inline-size: 100%;
    min-inline-size: 0;
    min-block-size: 52px;
    padding: 0 0.875rem;
    border-radius: var(--r-sm);
    border: 1px solid var(--dark-line);
    background: var(--dark-surface);
    color: var(--dark-text);
    font-family: var(--font-mono);
    font-size: 1.375rem;
    font-variant-numeric: tabular-nums;
    transition: border-color var(--dur-fast) var(--ease);
  }
  .field input:hover, .field select:hover { border-color: var(--dark-muted); }
  .field input:focus-visible, .field select:focus-visible { border-color: var(--accent-on-dark); }
  .field--bpm input { inline-size: 8.5rem; }
  .field--sm input, .field--sm select { font-size: 1.0625rem; min-block-size: 44px; }

  /* Stepper buttons keep the primary control usable without a keyboard */
  .stepper { display: flex; align-items: center; gap: 0.5rem; }
  .stepper button {
    inline-size: 44px; block-size: 44px; flex: none;
    border-radius: var(--r-sm);
    border: 1px solid var(--dark-line);
    color: var(--dark-text);
    display: grid; place-items: center;
    font-size: 1.25rem; line-height: 1;
    transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
  }
  .stepper button:hover { background: var(--dark-surface); border-color: var(--dark-muted); }

  .console__note {
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--dark-muted); line-height: 1.6;
  }

  /* Results table — tabular numerals, copy on click */
  .tool-table-wrap { overflow-x: auto; }
  .tool-table {
    inline-size: 100%;
    border-collapse: collapse;
    font-variant-numeric: tabular-nums;
  }
  .tool-table caption {
    text-align: left;
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--muted);
    padding-block: 0 0.875rem;
  }
  .tool-table th, .tool-table td {
    text-align: right;
    padding: 0.6875rem 0.875rem;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
  }
  .tool-table th:first-child, .tool-table td:first-child {
    text-align: left; padding-left: 0;
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--muted);
  }
  .tool-table td:last-child, .tool-table th:last-child { padding-right: 0; }
  .tool-table thead th {
    font-family: var(--font-mono); font-size: var(--t-meta);
    font-weight: 500; letter-spacing: var(--tr-meta); color: var(--muted);
    border-bottom-color: var(--line-strong);
  }
  .tool-table tbody tr:hover { background: var(--surface); }
  .tool-table td button {
    font-family: var(--font-mono); font-size: 0.9375rem;
    color: var(--ink); padding: 0.25rem 0.375rem; margin: -0.25rem -0.375rem;
    border-radius: 5px;
    transition: background-color var(--dur-fast) var(--ease);
  }
  .tool-table td button:hover { background: var(--line); }
  .tool-table td button[data-copied="true"] { background: var(--ember-bright); color: var(--surface-bright); }
  .tool-table tr[data-highlight="true"] td { background: color-mix(in srgb, var(--ember-bright) 9%, transparent); }

  .copy-status {
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--muted);
    min-block-size: 1.5em; display: block; margin-top: 0.875rem;
  }

  /* Read-out cards for single-value tools */
  .readouts { display: grid; gap: 1px; background: var(--line); border-block: 1px solid var(--line); }
  @media (min-width: 700px) { .readouts { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .readout { background: var(--canvas); padding: 1.25rem 0; }
  @media (min-width: 700px) { .readout { padding-inline: 1.25rem; } .readout:first-child { padding-left: 0; } }
  .readout dt {
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--muted); margin-bottom: 0.5rem;
  }
  .readout dd {
    font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.018em; font-weight: 550; line-height: 1.1;
  }
  .readout dd small { font-size: 0.5em; font-weight: 400; color: var(--muted); margin-left: 0.25em; }

  /* ══ external media — two-click embed ═══════════════════════════════
     Nothing is requested from a third party until the reader asks for it.
     The frame stays empty, no cookie is set and no IP is transmitted until
     the button is pressed — which is exactly what the Datenschutzerklärung
     on this site promises, and the only way to embed video from a foreign
     platform without an up-front consent banner.

     On activation the frame loads from youtube-nocookie.com, which does
     not set tracking cookies before playback starts. */

  .embed {
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface);
    overflow: hidden;
  }
  .embed__frame {
    position: relative;
    aspect-ratio: 16 / 9;
    background-color: color-mix(in srgb, var(--ink) 5%, var(--canvas));
    background-image:
      linear-gradient(to right, var(--line) 1px, transparent 1px),
      linear-gradient(to bottom, var(--line) 1px, transparent 1px);
    background-size: 28px 28px;
    display: grid;
    place-items: center;
    padding: clamp(1.25rem, 3vw, 2rem);
  }
  .embed__frame iframe {
    position: absolute; inset: 0;
    inline-size: 100%; block-size: 100%;
    border: 0;
  }
  .embed__prompt { display: grid; gap: 0.875rem; justify-items: center; text-align: center; max-width: 42ch; }
  .embed__prompt p {
    font-family: var(--font-mono); font-size: var(--t-meta);
    letter-spacing: var(--tr-meta); color: var(--muted); line-height: 1.6;
  }
  .embed__note {
    padding: 0.875rem clamp(1rem, 2.5vw, 1.25rem);
    border-top: 1px solid var(--line);
    font-size: var(--t-meta);
    font-family: var(--font-mono);
    letter-spacing: var(--tr-meta);
    color: var(--muted);
    line-height: 1.6;
  }
  .embed__note a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

  /* ---- footer ----
     The site ends where it began: on the stage. Lit from below the
     horizon rather than from above — the house lights going down. */
  .site-footer {
    position: relative;
    background-color: var(--stage);
    background-image:
      radial-gradient(140% 100% at 50% 130%,
        rgba(243, 240, 233, 0.07), transparent 60%);
    color: var(--stage-text);
    border-top: 1px solid var(--stage-line-soft);
    padding-block: clamp(3.5rem, 7vw, 5.5rem) 2rem;
  }
  .site-footer::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--grain);
    background-size: 160px 160px;
    opacity: 0.04;
    pointer-events: none;
  }
  .site-footer > * { position: relative; }
  .site-footer .brand { color: var(--stage-text); }
  .footer-grid {
    display: grid;
    gap: 2.5rem 2rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  }
  .footer-grid h2 {
    font-size: var(--t-meta);
    font-family: var(--font-mono);
    letter-spacing: var(--tr-meta);
    color: var(--stage-muted);
    font-weight: 500;
    margin-bottom: 1rem;
  }
  .footer-grid ul { display: grid; gap: 0.625rem; }
  .footer-grid a {
    text-decoration: none;
    font-size: var(--t-ui-sm);
    color: color-mix(in srgb, var(--stage-text) 80%, transparent);
    transition: color var(--dur-fast) var(--ease);
  }
  .footer-grid a:hover { color: var(--stage-text); text-decoration: underline; text-underline-offset: 3px; }
  .site-footer :focus-visible { outline-color: var(--accent-on-dark); }

  .footer-base {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem 2rem;
    justify-content: space-between;
    align-items: center;
    margin-top: clamp(2.5rem, 5vw, 4rem);
    padding-top: 1.75rem;
    border-top: 1px solid var(--stage-line);
    font-size: var(--t-meta);
    color: var(--stage-muted);
  }

  /* ---- relaunch notice ---- */
  .relaunch {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.75rem 1.5rem;
    justify-content: space-between;
    padding-block: clamp(2rem, 4vw, 3rem);
    border-top: 1px solid var(--line);
  }
  .relaunch p {
    font-size: 1rem;
    color: var(--ink-soft);
    max-width: 58ch;
    line-height: 1.6;
  }
  .relaunch a { font-size: var(--t-ui-sm); }

  /* ---- breadcrumbs ---- */
  .crumbs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    font-size: var(--t-meta);
    color: var(--muted);
  }
  .crumbs a { text-decoration: none; }
  .crumbs a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
  .crumbs li + li::before { content: "/"; margin-inline-end: 0.5rem; opacity: 0.5; }
  .crumbs ol { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }

  /* ---- the spectral rail ----
     One thin line threading hero → portals → start-here. The node is the
     only accent mark besides the primary CTA in the first two viewports.
     GSAP drives --node-y on scroll; the static position below is the
     no-JS fallback and is intentionally already meaningful. */
  .rail {
    display: none;
    position: absolute;
    inset-block: 0;
    inline-size: 1px;
    background: linear-gradient(
      to bottom,
      transparent,
      var(--line-strong) 8%,
      var(--line-strong) 88%,
      transparent
    );
  }
  /* The travelling node is the material axis, not the action axis — it
     marks position in the page, it is not something you can click. */
  .rail::after {
    content: "";
    position: absolute;
    inset-inline: -3px;
    block-size: 7px;
    border-radius: 50%;
    background: var(--ember-bright);
    top: var(--node-y, 22%);
    transition: top var(--dur-slow) var(--ease-out);
  }

  /* ---- archive flag ----
     The material axis in its most frequent form. Any label that says a
     thing is historical uses this and nothing else. */
  .flag {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
    color: var(--ember);
    padding-left: 0.875rem;
    border-left: 2px solid var(--ember-bright);
    line-height: 1.5;
  }
  .band-dark .flag {
    color: var(--ember-on-dark);
    border-left-color: var(--ember-on-dark);
  }
  @media (min-width: 1240px) {
    .rail { display: block; }
  }
}

/* ---------------------------------------------------------- forced colors */
/* Windows High Contrast removes every background colour and image. The
   palette's job transfers to the system palette; what must survive is
   shape: controls keep a border (transparent borders become visible in
   forced-colors mode), and the decorative canvas field simply disappears
   with its background. */
@media (forced-colors: active) {
  .btn,
  .icon-btn,
  .field input,
  .field select,
  .search input,
  .search-row input,
  .stepper button,
  .tap button { border: 1px solid transparent; }
  .btn--primary { border-width: 2px; }
}

/* ------------------------------------------------------------ stage light */
/* On devices with a real pointer, the stage answers it: a soft pool of
   light follows the cursor across every dark plane. The coordinates come
   from motion.js as custom properties; without JS nothing renders. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .hero::after,
  .band-dark::after,
  .site-footer::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    background: radial-gradient(
      460px circle at var(--px, 50%) var(--py, -30%),
      rgba(243, 240, 233, 0.055),
      transparent 65%
    );
    opacity: 0;
    transition: opacity 700ms var(--ease);
  }
  .hero:hover::after,
  .band-dark:hover::after,
  .site-footer:hover::after { opacity: 1; }
}

@keyframes staff-draw {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}
@keyframes staff-node {
  from { left: 0%; opacity: 0; }
  40% { opacity: 1; }
  to { left: var(--staff-node-x, 18%); opacity: 1; }
}

/* ---------------------------------------------------------------- motion */
@layer motion {
  /* Reveal targets.
     The hidden state is opt-in per element (.will-reveal), applied by JS only
     to elements that start below the fold — never to anything already on
     screen, and never as a blanket rule. An element that JS never reaches
     stays visible, which is what keeps the page readable for crawlers, for
     print, and if the script fails outright. */
  [data-reveal].will-reveal {
    opacity: 0;
    transform: translateY(16px);
  }
  [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition: opacity var(--dur-slow) var(--ease-out),
                transform var(--dur-slow) var(--ease-out);
  }

  /* Printing and forced-visibility contexts always get the end state. */
  @media print {
    [data-reveal].will-reveal { opacity: 1 !important; transform: none !important; }
  }

  @media (prefers-reduced-motion: reduce) {
    [data-reveal].will-reveal,
    [data-reveal].is-in {
      opacity: 1;
      transform: none;
      transition: none;
    }
    .rail::after { transition: none; }
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
    }
  }
}

/* ───────────────────────────────────────────────────────────────── touch */
/* Last layer, so everything here overrides the desktop defaults above.
   Each block fixes a specific way a desktop-first stylesheet misbehaves on
   a phone — none of it is cosmetic. */
@layer touch {

  /* ---- 1 · iOS zoom on focus ---------------------------------------
     Mobile Safari zooms the whole viewport when a focused field measures
     under 16px, then leaves the page scrolled sideways. The site's UI
     size is 15px, so every text entry is lifted on coarse pointers only —
     the visual change is one pixel, the viewport stops jumping. */
     Buttons are deliberately excluded: they never trigger the zoom, and
     lifting them would break the monospace hint line in the masthead. */
  @media (pointer: coarse) {
    input, select, textarea { font-size: max(1rem, 1em); }
  }

  /* ---- 2 · display cutouts and the home indicator -------------------
     Portrait is handled by the browser; landscape on a notched phone is
     not. The gutter becomes a floor rather than a fixed value, and the
     two surfaces that sit against the bottom edge clear the indicator. */
  .shell {
    padding-left: max(var(--gutter), env(safe-area-inset-left, 0px));
    padding-right: max(var(--gutter), env(safe-area-inset-right, 0px));
  }
  .site-header { padding-top: env(safe-area-inset-top, 0px); }
  .sheet__foot { padding-bottom: max(1.75rem, env(safe-area-inset-bottom, 0px)); }
  .site-footer { padding-bottom: max(2rem, env(safe-area-inset-bottom, 0px)); }

  /* ---- 3 · hover does not exist on touch ----------------------------
     A tap fires :hover and leaves it applied until the next tap lands
     elsewhere, so rows stay highlighted after you have moved on. Row
     tints are dropped on hover-less pointers and re-attached to :active,
     which gives real press feedback instead of a stuck state. */
  @media (hover: none) {
    .index a:hover,
    .portal a:hover,
    .topics a:hover,
    .numbered a:hover,
    .tool-row a:hover,
    .tool-card a:hover,
    .screens a:hover,
    .listing a:hover,
    .start__lead a:hover h3,
    .tool-table tbody tr:hover,
    .tool-table td button:hover { background: transparent; text-decoration: none; }

    .index a:active,
    .portal a:active,
    .topics a:active,
    .numbered a:active,
    .tool-row a:active,
    .tool-card a:active,
    .screens a:active,
    .listing a:active { background: var(--surface); }

    .tool-table td button:active { background: var(--line); }
    .link:active, .related a:active, .footer-grid a:active { opacity: 0.6; }
  }

  /* ---- 4 · target size ----------------------------------------------
     WCAG 2.5.8 sets the floor at 24×24; these are lifted to the 44×44 of
     2.5.5 because they sit in dense stacks where the spacing exception
     does not apply. Inline links inside prose keep the inline exemption. */
  @media (pointer: coarse) {
    .footer-grid ul { gap: 0.125rem; }
    .footer-grid a { display: block; padding-block: 0.6875rem; }
    .crumbs a { display: inline-block; padding-block: 0.4375rem; }
    .crumbs ol { gap: 0.25rem 0.5rem; }
    .link { padding-block: 0.625rem; }
    .filters button { min-height: 44px; }
    .related a, .listing a, .paths a { min-block-size: 44px; }
    .sources a, .prose a { padding-block: 0.125rem; }
  }

  /* ---- 5 · sideways scrolling that has to stay ----------------------
     The settings extract and the tool tables are meant to scroll rather
     than reflow — a monospace configuration block that wraps is unreadable.
     Momentum on, and the gesture kept inside the element so it never
     turns into a back-navigation swipe. */
  .tool-table-wrap, .settings, .console {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  /* A table that scrolls without saying so reads as a table with missing
     columns. The caption already carries the copy hint; on narrow screens
     it also states the gesture. */
  @media (max-width: 700px) {
    .tool-table caption::after { content: " · scroll sideways for more"; }
  }

  /* ---- 6 · text selection while dragging the string -----------------
     Dragging across the masthead would otherwise start a text selection
     over the headline. The heading is not selectable during interaction;
     everything else on the page still is. */
  @media (pointer: coarse) {
    .hero__title { -webkit-user-select: none; user-select: none; }
  }

  /* ---- 7 · the last 30px --------------------------------------------
     Verified against 360, 390 and 430px. Below 400px the display scale
     and the two hero actions need slightly tighter handling than the
     fluid ramp alone provides. */
  @media (max-width: 400px) {
    .hero__actions { inline-size: 100%; }
    .hero__actions .btn { flex: 1 1 100%; }
    .btn { padding-inline: 1.125rem; }
    .meta { gap: 0.375rem 0.75rem; }
    .meta > * + *::before { margin-inline-end: 0.5rem; }
  }
}
