/* ==========================================================================
   ORRERY — tokens, reset, primitives, shell.

   The design is an instrument in a case at night: a midnight-blue ground,
   plates that sit visibly above it, hairline engraved rules, tabular figures,
   and small radii. Everything luminous on the page is one of two colours,
   because the Planckian locus only has two (see js/brand.js).

   The three sibling rebrands of the same reference went dark-oceanic, light
   warm-paper and warm near-black. This one is the only blue that is actually
   blue: --sky is L 0.189 C 0.025 against the oceanic sibling's L 0.127
   C 0.014, which is to say a midnight rather than an abyss, and its panels
   are plates rather than floating cards.

   Accent tokens are overwritten at boot from js/brand.js. The values here are
   the fallback for the frames before the module runs, and this block is the
   only place in the codebase those hexes may appear — validate.mjs enforces
   it.
   ========================================================================== */

:root {
  /* --- Ground. Four steps. --plate-3 is the lightest surface text ever sits
     on, so it is the worst case for every ink and every accent. Hue 269.8 is
     Rigel's: the dark between the marks carries the same hue as the hottest
     light on them. */
  --sky:          #0F131F;
  --plate:        #1A1E2A;
  --plate-2:      #232833;
  --plate-3:      #2E323D;

  /* --- Ink. Every step clears WCAG AA (4.5:1) against ALL FOUR grounds.
     Measured, not chosen — `node tools/contrast.mjs` reads these very
     declarations and exits non-zero on a failure. --faint is real text (table
     headers, field labels, figure captions), not decoration, which is exactly
     the step a sibling site got wrong at 2.6:1. It clears by 0.06; there is
     no room for a fifth step. */
  --ink:          #F4F6FD;
  --ink-2:        #C8CBD3;
  --ink-3:        #AAADB6;
  --faint:        #979AA4;

  /* Handed to the canvas as literal hex — a canvas cannot read a variable. */
  --sky-rgbhex:   #0F131F;

  /* --- The star scale (js/brand.js). Overwritten at boot.
     `-fill` is one lightness step up, for surfaces and graphics, which do not
     have to be legible as text. */
  --rigel:        #8398D6;
  --vega:         #8A98C6;
  --sun:          #A7968C;
  --arcturus:     #C1905A;
  --antares:      #CF8B35;

  --rigel-fill:   #A0B7FB;
  --vega-fill:    #A7B7EA;
  --sun-fill:     #C8B4AA;
  --arcturus-fill:#E4AF74;
  --antares-fill: #F3A94F;

  /* Two functional accents, because the physics gives two. */
  --hot:          var(--rigel);
  --hot-fill:     var(--rigel-fill);
  --cool:         var(--antares);
  --cool-fill:    var(--antares-fill);

  /* --- Rules. An engraved plate is mostly hairlines. Three weights, used
     consistently: --line for plate edges, --line-2 for table rows, --line-3
     for anything the reader is meant to notice. */
  --line:         color-mix(in srgb, var(--ink) 9%, transparent);
  --line-2:       color-mix(in srgb, var(--ink) 15%, transparent);
  --line-3:       color-mix(in srgb, var(--ink) 27%, transparent);

  /* The 1px top highlight that lifts a plate off the sky at this ground
     luminance — shadow does almost nothing here. One token, because it was
     written out as a decimal rgba() in three files and the brand check could
     not see any of them. */
  --edge:         color-mix(in srgb, var(--ink) 4.5%, transparent);

  /* Small radii on purpose. A milled plate has a chamfer, not a pill. The
     sibling that went oceanic uses 28/20/14/10; this one tops out at 10 and
     uses 4 nearly everywhere. */
  --r-lg:         10px;
  --r:            6px;
  --r-sm:         4px;

  /* Depth on a ground this dark comes from a 1px top highlight on plates, not
     from shadow. These are for the few things that genuinely float. */
  --shadow:       0 20px 44px -30px rgba(0, 0, 0, .95);
  --shadow-lg:    0 44px 96px -52px rgba(0, 0, 0, 1);

  --wrap:         1180px;
  --gutter:       clamp(20px, 5vw, 44px);
  --nav-h:        64px;

  /* Fraunces is the engraved display face, Manrope the instrument text face,
     DM Mono the numerics. None of the three appears on any sibling site — the
     type is part of not being the same template. */
  --display: 'Fraunces', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --sans:    'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif;
  --mono:    'DM Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ==========================================================================
   Reset
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 20px);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

body {
  margin: 0;
  background: var(--sky);
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: 16.5px;
  line-height: 1.62;
  font-weight: 400;
  letter-spacing: -0.003em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-variation-settings: 'SOFT' 0, 'WONK' 0;
  color: var(--ink);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.018em;
  margin: 0 0 .5em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.6rem, 6.4vw, 4.4rem); font-variation-settings: 'opsz' 120, 'SOFT' 0, 'WONK' 0; }
h2 { font-size: clamp(1.85rem, 3.5vw, 2.7rem); font-variation-settings: 'opsz' 60, 'SOFT' 0, 'WONK' 0; }
h3 { font-size: 1.16rem; font-weight: 600; letter-spacing: -0.008em; line-height: 1.28; }
h4 { font-size: 1rem; font-weight: 600; }
p  { margin: 0 0 1.05em; }
p:last-child { margin-bottom: 0; }

a { color: var(--hot); text-decoration: none; }
a:hover { color: var(--hot-fill); }

:focus-visible {
  outline: 2px solid var(--hot-fill);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

::selection { background: color-mix(in srgb, var(--hot-fill) 26%, transparent); color: var(--ink); }

/* Every numeral on this site is tabular. A table of parameters whose columns
   do not line up is a table nobody reads down. */
.num, .mono, table, .stat-v, time, code, kbd {
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   Layout primitives
   ========================================================================== */

.wrap { width: min(var(--wrap), 100% - var(--gutter) * 2); margin-inline: auto; }

.section { padding: clamp(64px, 9vw, 118px) 0; position: relative; }
.section.alt { background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--plate) 55%, transparent) 12% 88%, transparent); }
.section > .wrap { min-width: 0; }

.eyebrow {
  font-family: var(--mono);
  font-size: .69rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 1.1rem;
  display: flex;
  align-items: center;
  gap: .68rem;
  min-width: 0;
}
/* The instrument's tick. A flat arc, not a bar — the site's one repeated
   ornament, and it is elliptical for the same reason everything else is. */
.eyebrow::before {
  content: '';
  flex: none;
  width: 26px; height: 7px;
  border: 1px solid var(--line-3);
  border-bottom: 0;
  border-radius: 26px 26px 0 0 / 14px 14px 0 0;
  transform: translateY(2px);
}

.lede {
  font-size: clamp(1.02rem, 1.5vw, 1.17rem);
  color: var(--ink-3);
  max-width: 62ch;
  line-height: 1.66;
}

.head { max-width: 74ch; margin-bottom: clamp(34px, 4.4vw, 56px); }
.head.center { margin-inline: auto; text-align: center; }
.head.center .eyebrow { justify-content: center; }

.grid { display: grid; gap: clamp(14px, 1.7vw, 20px); }
.g-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 940px) { .g-3, .g-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .g-2, .g-3, .g-4 { grid-template-columns: minmax(0, 1fr); } }

/* Grid and flex items default to min-width:auto, whose content-based minimum
   is the widest unbreakable descendant. One nowrap label three levels down
   widened its track and scrolled a whole sibling site sideways at 500px.

   `.params` was left off this list and did exactly that: on /almanac the
   parameter tables' 534px min-content set the grid track, so the `.scroll-x`
   wrapper was stretched to its content and never scrolled — the whole page
   panned instead. At 320px that is a 234px overflow, a WCAG 1.4.10 Reflow
   failure on the page the entire site points at. ANY NEW GRID NEEDS THE SAME
   CAP, and tools/validate.mjs now measures every page at 390px rather than
   trusting that someone remembered. */
.grid > *, .plate, .rail, .cell, .bento > *,
.params > *, .problems > *, .stages > *, .needs > *, .phases > * { min-width: 0; }

/* ==========================================================================
   Plates — the one surface primitive
   ========================================================================== */

.plate {
  background: var(--plate);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: clamp(20px, 2.4vw, 30px);
  position: relative;
  /* The engraved highlight. This, not shadow, is what lifts a plate off the
     sky at this ground luminance. */
  box-shadow: inset 0 1px 0 var(--edge);
}
.plate.tight { padding: clamp(16px, 1.9vw, 22px); }
.plate.deep  { background: var(--plate-2); }

.plate-title {
  font-family: var(--mono);
  font-size: .69rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 .85rem;
}

/* ==========================================================================
   Buttons, pills, links
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .68rem 1.15rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: var(--plate-2);
  color: var(--ink);
  font-size: .92rem;
  font-weight: 600;
  letter-spacing: -0.004em;
  cursor: pointer;
  transition: background .16s, border-color .16s, color .16s, transform .16s;
  text-align: left;
}
.btn:hover { background: var(--plate-3); border-color: var(--line-3); color: var(--ink); }
.btn:active { transform: translateY(1px); }

.btn.primary {
  background: var(--hot-fill);
  border-color: var(--hot-fill);
  color: #0B0F19;
}
.btn.primary:hover { filter: brightness(1.08); color: #0B0F19; }

.btn.ghost { background: transparent; }
.btn.ghost:hover { background: var(--plate); }

.btn[aria-disabled='true'], .btn:disabled {
  opacity: .55;
  cursor: not-allowed;
  pointer-events: none;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .26rem .6rem;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--plate);
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.pill .dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--cool-fill);
  flex: none;
}

.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-weight: 600;
  font-size: .92rem;
  color: var(--hot);
}
.arrow-link svg { transition: transform .18s; flex: none; }
.arrow-link:hover svg { transform: translateX(3px); }

/* ==========================================================================
   Tables — the datasheet primitive
   ========================================================================== */

.tbl { width: 100%; border-collapse: collapse; font-size: .9rem; }
.tbl th, .tbl td {
  text-align: left;
  padding: .62rem .7rem;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.tbl thead th {
  font-family: var(--mono);
  font-size: .69rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 400;
  border-bottom-color: var(--line-2);
  white-space: nowrap;
}
.tbl td.n, .tbl th.n { text-align: right; font-family: var(--mono); white-space: nowrap; }
.tbl tbody tr:last-child td { border-bottom: 0; }
.tbl td strong { color: var(--ink); font-weight: 600; }

.scroll-x { overflow-x: auto; }

/* An unset parameter says so. The whole Almanac argument is that a table with
   a hole in it is worse than no table, so the hole gets a token. */
.unset {
  font-family: var(--mono);
  font-size: .78em;
  letter-spacing: .06em;
  color: var(--faint);
  border: 1px dashed var(--line-2);
  border-radius: 3px;
  padding: 0 .35em;
  white-space: nowrap;
}

/* ==========================================================================
   Shell — header
   ========================================================================== */

 /* Clipped rather than pushed to left:-9999px. The off-screen trick reports
    as horizontal overflow — the project's own shot.mjs flagged scrollWidth
    1537 against innerWidth 1360 and named this element — which then masks a
    real overflow the next time one appears. */
.skip {
  position: absolute; top: 0; left: 0; z-index: 200;
  width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
  background: var(--plate-3); color: var(--ink);
  padding: .7rem 1rem; border-radius: 0 0 var(--r-sm) 0;
}
.skip:focus {
  width: auto; height: auto; overflow: visible; clip-path: none;
}

.header {
  position: sticky; top: 0; z-index: 90;
  height: var(--nav-h);
  display: flex; align-items: center;
  background: color-mix(in srgb, var(--sky) 82%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.header .wrap { display: flex; align-items: center; gap: 1.1rem; min-width: 0; }

.brand {
  display: inline-flex; align-items: center; gap: .58rem;
  color: var(--ink); flex: none;
}
.brand svg { width: 26px; height: 26px; flex: none; }
.brand b {
  font-family: var(--display);
  font-variation-settings: 'opsz' 24, 'SOFT' 0, 'WONK' 0;
  font-weight: 600;
  font-size: 1.06rem;
  letter-spacing: .07em;
}

.nav { display: flex; align-items: center; gap: .15rem; min-width: 0; }
.nav a {
  padding: .42rem .68rem;
  border-radius: var(--r-sm);
  color: var(--ink-3);
  font-size: .9rem;
  font-weight: 500;
  white-space: nowrap;
}
.nav a:hover { color: var(--ink); background: var(--plate); }
.nav a[aria-current='page'] { color: var(--ink); }
.nav a[aria-current='page']::after {
  content: ''; display: block; height: 1px; margin-top: 3px;
  background: var(--hot);
}

.header-end { margin-left: auto; display: flex; align-items: center; gap: .5rem; flex: none; }

.icon-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  color: var(--ink-3);
}
.icon-link:hover { color: var(--ink); border-color: var(--line-3); }
.icon-link svg { width: 15px; height: 15px; }

.burger {
  display: none;
  width: 38px; height: 34px;
  align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--r-sm); color: var(--ink-2); cursor: pointer;
}
.burger span { display: block; width: 15px; height: 1px; background: currentColor; position: relative; }
.burger span::before, .burger span::after {
  content: ''; position: absolute; left: 0; width: 15px; height: 1px; background: currentColor;
}
.burger span::before { top: -5px; }
.burger span::after  { top:  5px; }

@media (max-width: 1000px) {
  /* Both rules are scoped to .js. The burger cannot open anything without
     JavaScript — the sheet's only opener is a click handler — so a build that
     hides the nav and shows the burger unconditionally offers a phone reader a
     visible control that does nothing and no route to any inner page. Scoped,
     a no-JS reader keeps the real nav; it just scrolls. */
  .js .nav { display: none; }
  .js .burger { display: inline-flex; }

  .nav {
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    flex: 1 1 auto;
    margin-right: .4rem;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { padding: .42rem .5rem; font-size: .86rem; }
}

/* An author `display` beats the UA stylesheet's [hidden]. Without this
   explicit rule the sheet is permanently open below 1000px — invisible on
   desktop, total on a phone. */
.mobile-sheet {
  position: fixed; inset: var(--nav-h) 0 0; z-index: 89;
  display: flex; flex-direction: column; gap: .2rem;
  padding: 1.1rem var(--gutter) 2rem;
  background: var(--sky);
  border-top: 1px solid var(--line);
  overflow-y: auto;
}
.mobile-sheet[hidden] { display: none; }
.mobile-sheet a {
  padding: .82rem .2rem;
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 1.02rem;
  font-weight: 500;
}
.mobile-sheet a:hover { color: var(--ink); }
.mobile-sheet .btn { margin-top: 1rem; justify-content: center; }

/* ==========================================================================
   Shell — footer
   ========================================================================== */

.footer {
  border-top: 1px solid var(--line);
  padding: clamp(44px, 6vw, 72px) 0 30px;
  background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--plate) 50%, transparent));
}
.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 44px);
}
@media (max-width: 860px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .footer-grid { grid-template-columns: minmax(0, 1fr); } }

.footer-brand p { color: var(--faint); font-size: .88rem; max-width: 38ch; margin-top: .9rem; }
.footer h3 {
  font-family: var(--mono);
  font-size: .69rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--faint); margin: 0 0 .85rem; font-weight: 400;
}
.footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.footer ul a { color: var(--ink-3); font-size: .89rem; display: inline-block; }
.footer ul a:hover { color: var(--ink); }

/* On a phone these are the only navigation below the fold and a 19px line box
   is not a target. Padding rather than a min-height so the columns keep their
   rhythm on desktop, where a pointer does not need it. */
@media (max-width: 860px) {
  .footer ul { gap: 0; }
  .footer ul a { padding: .55rem 0; }
  .footer ul li { border-bottom: 1px solid var(--line); }
  .footer ul li:last-child { border-bottom: 0; }
}

/* The star strip. Five swatches in temperature order, hottest first. Two
   pairs of them are the same colour, and the caption says why — the strip is
   the evidence for the palette's central claim, so it is not decoration and
   it does not get tidied. */
.star-strip { display: flex; gap: 0; margin-top: 1.4rem; border-radius: var(--r-sm); overflow: hidden; }
.star-strip i { flex: 1 1 0; height: 24px; display: block; }
/* Uppercase mono at --faint is the hardest text on the site to read, so its
   floor is 11.2px (.68rem) rather than the 10.2px the first build used. There
   is no WCAG minimum for size; there is a limit to what a 4.56:1 label can
   afford to be. */
.star-legend {
  display: flex; flex-wrap: wrap; gap: .4rem 1.1rem;
  margin-top: .6rem;
  font-family: var(--mono); font-size: .69rem; letter-spacing: .04em;
  color: var(--faint);
}
.star-legend span { white-space: nowrap; }

.footer-end {
  margin-top: clamp(34px, 4vw, 52px);
  padding-top: 22px;
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: .8rem 1.4rem;
  align-items: center;
  font-size: .8rem; color: var(--faint);
}
.footer-end .socials { display: flex; gap: .45rem; margin-left: auto; }

.demo-note {
  margin-top: 1.1rem;
  padding: .8rem .95rem;
  border: 1px solid var(--line);
  border-left: 2px solid var(--cool);
  border-radius: var(--r-sm);
  background: var(--plate);
  color: var(--ink-3);
  font-size: .82rem;
  line-height: 1.55;
}

/* ==========================================================================
   Reveal on scroll.

   Scoped to `.js [data-reveal]`, and the `js` class is set by an inline script
   emitted by tools/head.mjs BEFORE the stylesheet links, so the class is on
   <html> by the time this rule is parsed. Written unscoped, this rule hides the
   whole page from anyone without JavaScript — which is what it did on a sibling
   site, for 37 elements.

   The ordering was wrong once and the comment was still right, which is the
   dangerous combination: the script was emitted after all four <link>s. Nothing
   broke, because the class only has to be set before the first PAINT and not
   before the CSS is parsed — but the written record described a guarantee the
   build did not provide, and an audit caught the prose rather than the bug.
   ========================================================================== */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .62s cubic-bezier(.22,.7,.3,1), transform .62s cubic-bezier(.22,.7,.3,1);
  transition-delay: var(--d, 0ms);
}
.js [data-reveal].in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
}
