/*
 * weir.css — the descent.
 *
 * THE ONE RULE THIS FILE EXISTS TO KEEP. There is no opaque fill anywhere in
 * this document. One photograph is fixed behind the whole page and every
 * section is a translucent pane over it, so the ground moves under the
 * content as you scroll and no band is ever a flat rectangle of colour.
 *
 * That is not a flourish, it is the correction. The build this replaces put a
 * photograph in its hero and then filled the remaining 6,760 pixels of a
 * 7,536-pixel page — ninety per cent of it — with two solid colours. Measured
 * against the reference it was rebranding, which has ZERO opaque blocks over
 * 7,917 pixels, that is the whole of the difference, and it is why the page
 * read as flat no matter how carefully the two colours were chosen.
 *
 * EVERY COLOUR BELOW IS SOLVED, NOT PICKED. `tools/palette.mjs` samples the
 * photograph, composites each pane over the darkest and lightest parts of it
 * IN GAMMA SPACE as a browser does, and solves the ink to clear 4.5:1 against
 * the WORST end of that range rather than against an average that exists
 * nowhere on the page. The numbers in the comments are that tool's output.
 */

/* ---- the solved palette ------------------------------------------- */
:root {
/* TOKENS:BEGIN */
  /* GENERATED by tools/build.mjs from data/palette.json — do not edit.
     Solved against the hero photograph itself: each pane composited over the
     darkest and lightest parts of the frame, ink cleared against the worse
     end. Scrim 21%. */
  --ink: #0A1619;              /* worst 5.48:1 on pane-1 */
  --ink-2: #0F1618;
  --ink-deep: #DDF3F3;
  --ink-deep-2: #E1F1F1;

  --flow: #011723;
  --sill: #280D01;
  --flow-chip: #0B5675;   /* solved against the card only */
  --sill-chip: #8D320A;
  --flow-lg: #064159;       /* large text only: AA is 3.0:1 there */
  --sill-lg: #682805;
  --flow-deep: #BBF3FD;
  --sill-deep: #FDE6CA;
  --sill-deep-lg: #FCB159;

  --pane-1: rgba(247, 251, 252, 0.44);
  --pane-2: rgba(188, 218, 230, 0.5);
  --pane-3: rgba(168, 203, 220, 0.52);
  --pane-deep: rgba(6, 32, 46, 0.7);
  --card: rgba(253, 254, 254, 0.72);
  --card-deep: rgba(18, 56, 74, 0.7);
  --glass: rgba(255, 255, 255, 0.52);
  --glass-thin: rgba(255, 255, 255, 0.3);
  --scrim: rgba(220, 234, 241, 0.21);
/* TOKENS:END */
  --line: rgba(41, 69, 76, 0.16);
  --line-2: rgba(41, 69, 76, 0.09);
  --line-deep: rgba(168, 208, 208, 0.20);

  --blur: saturate(150%) blur(14px);
  --blur-lg: saturate(160%) blur(26px);

  --shadow-sm: 0 1px 2px rgba(8, 41, 58, .06), 0 3px 14px rgba(8, 41, 58, .07);
  --shadow-md: 0 10px 34px rgba(8, 41, 58, .13);
  --shadow-lg: 0 30px 80px rgba(8, 41, 58, .22);

  --r: 20px;
  --r-sm: 13px;
  --r-lg: 30px;
  --maxw: 1140px;

  --display: "Instrument Serif", Georgia, "Times New Roman", serif;
  --sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

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

html { scroll-behavior: smooth; }
/* The nav is sticky, so an anchor that lands flush with the top lands UNDER
   it. */
:target, [id] { scroll-margin-top: 6rem; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  color: var(--ink);
  /* The fallback under the photograph, and the only flat colour in the file.
     It is visible for the instant before the image decodes and nowhere
     else. */
  background: #9FBFCC;
  -webkit-font-smoothing: antialiased;
}

/**
 * The photograph, fixed behind everything.
 *
 * `position: fixed` on a child rather than `background-attachment: fixed` on
 * the body: iOS Safari does not honour the attachment property, and the
 * failure mode is not a fallback but a photograph that jumps a whole viewport
 * on every scroll frame.
 */
.field {
  position: fixed;
  inset: 0;
  z-index: -2;
  background: url("../img/hero.jpg") center 42% / cover no-repeat;
  /* A SMALL BLUR ON THE GROUND, NOT ON THE PANES. Blurring a full-width pane
     with `backdrop-filter` destroys the photograph and leaves a smooth wash —
     that mistake is documented above. Blurring the PHOTOGRAPH itself by a
     couple of pixels does the opposite: the macro texture and the colour
     survive (which is what the flatness statistic measures) while the fine
     detail stops competing with body copy set on top of it. Scaled up 1.02 so
     the blur has material to pull from at the edges instead of fading out. */
  filter: blur(2.5px);
  transform: scale(1.02);
}
/**
 * The scrim, and its strength is solved rather than chosen.
 *
 * A photograph cannot be a page ground on its own: this one runs from
 * luminance 0.003 to 0.75, a range of 250:1, and no single ink is readable
 * across it. `tools/palette.mjs` searches upward from zero for the smallest
 * wash under which every pane still carries its ink, and writes the answer
 * into --scrim. Too little and the page is unreadable; too much and there is
 * no photograph left — a sibling set one by eye "for legibility", veiled its
 * hero, and moved the measured contrast by three hundredths of one per cent.
 *
 * The vignette on top of it is doing a different job: holding the corners
 * down so the nav and the footer have something to sit against.
 */
.field::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(6, 32, 46, .32), rgba(6, 32, 46, 0) 58%),
    radial-gradient(120% 70% at 50% 100%, rgba(6, 32, 46, .36), rgba(6, 32, 46, 0) 58%),
    var(--scrim);
}

/* ---- type ---------------------------------------------------------- */
h1, h2, h3 { font-family: var(--display); font-weight: 400; letter-spacing: -0.012em; margin: 0; }
h1 { font-size: clamp(3.4rem, 9vw, 6.6rem); line-height: 0.94; }
h2 { font-size: clamp(2.1rem, 4.4vw, 3.5rem); line-height: 1.04; }
h3 { font-size: 1.32rem; line-height: 1.25; }
p { margin: 0 0 1.05em; }
a { color: var(--flow); text-decoration-thickness: 1px; text-underline-offset: 3px; }
strong { font-weight: 600; }
code, .mono { font-family: var(--mono); font-size: 0.88em; font-variant-ligatures: none; }

/* A PILL, NOT BARE TEXT. An eyebrow set directly on a pane has to clear AA
   against the deepest one, and that forces the warm accent to near-black. Give
   it a surface of its own and the colour is solved against the card instead,
   where the gamut has room. */
.eyebrow {
  display: inline-block;
  font: 500 0.72rem/1 var(--mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sill-chip);
  background: rgba(255, 253, 249, .70);
  border: 1px solid rgba(255, 255, 255, .5);
  padding: .42rem .7rem .38rem;
  border-radius: 7px;
  margin: 0 0 1.1rem;
}
.band-deep .eyebrow { color: var(--sill-deep); background: rgba(9, 40, 56, .55); border-color: var(--line-deep); }
.lede { font-size: 1.18rem; color: var(--ink-2); max-width: 62ch; }

/* ---- layout -------------------------------------------------------- */
.wrap { width: min(var(--maxw), calc(100% - 3rem)); margin-inline: auto; }
.band { padding: clamp(4rem, 9vw, 7.5rem) 0; position: relative; }
.band-1 { background: var(--pane-1); }
.band-2 { background: var(--pane-2); }
.band-3 { background: var(--pane-3); }
.band-deep { background: var(--pane-deep); color: var(--ink-deep); }
.band-deep h1, .band-deep h2, .band-deep h3 { color: #E6F4F4; }
.band-deep .lede, .band-deep p { color: var(--ink-deep); }
.band-deep a { color: var(--flow-deep); }
/**
 * BLUR THE SMALL THINGS, NEVER THE FULL-WIDTH ONES.
 *
 * `backdrop-filter: blur(14px)` on a section is the fastest way to undo
 * everything the alpha was chosen for. The pane can be a perfectly measured
 * 0.44 and the page still arrives flat, because a 14px blur across a
 * full-width band destroys the photograph's detail and leaves a smooth wash —
 * the statistics of the frame look fine (spread 0.71, chroma 0.17) and the
 * texture is gone. That is exactly how this build was flat the second time,
 * after the alphas were already right.
 *
 * On a card or a pill it is doing the opposite job: those are small, they sit
 * ON the photograph rather than replacing it, and frosting the few hundred
 * pixels behind them is what makes them read as glass rather than as paper.
 */
.nav-inner, .card, .hero-card, .chip { backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur); }

.hair { height: 1px; background: var(--line); border: 0; margin: 0; }

/* ---- nav ----------------------------------------------------------- */
.nav { position: sticky; top: 0; z-index: 50; padding: 0.85rem 0; }
.nav-inner {
  display: flex; align-items: center; gap: 1.4rem;
  padding: 0.5rem 0.6rem 0.5rem 1.1rem;
  /* The CARD surface, not the thin glass. At 0.52 a heading scrolling under
     the pill reads straight through it and both are illegible; a nav is a
     small element, so it is held to the card bar (0.85) rather than the
     section bar (0.60), and it is the one element on the page that has to
     stay readable over content it has never seen. */
  background: var(--card);
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: 999px;
  box-shadow: var(--shadow-sm);
}
.brand { display: inline-flex; align-items: center; gap: .6rem; font-family: var(--display); font-size: 1.42rem; color: var(--ink); text-decoration: none; }
.brand svg { display: block; }
.nav-links { display: flex; gap: 1.25rem; margin-left: .6rem; flex: 1; }
.nav-links a { color: var(--ink-2); text-decoration: none; font-size: .93rem; }
.nav-links a:hover, .nav-links a[aria-current] { color: var(--flow); text-decoration: underline; }
@media (max-width: 860px) { .nav-links { display: none; } }

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .68rem 1.25rem; border-radius: 999px;
  font: 500 .95rem/1 var(--sans); text-decoration: none; cursor: pointer;
  border: 1px solid transparent; transition: transform .15s ease, box-shadow .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: linear-gradient(#0E5D7E, #073F57); color: #F2FAFC; box-shadow: var(--shadow-sm); }
.btn-primary:hover { box-shadow: var(--shadow-md); }
.btn-ghost { background: var(--glass); color: var(--ink); border-color: rgba(255, 255, 255, .6); }

/* The X link in the nav. A square target at the size Apple and WCAG both ask
   for (44px), with the glyph itself smaller inside it — an icon sized to its
   artwork is a tap target that misses on a phone. */
.icon-link {
  display: inline-grid; place-items: center;
  width: 40px; height: 40px; border-radius: 999px;
  color: var(--ink); text-decoration: none;
  background: var(--glass-thin); border: 1px solid rgba(255, 255, 255, .45);
  transition: background .15s ease;
}
.icon-link:hover { background: var(--glass); }
.icon-link svg { display: block; }

/* ---- hero ---------------------------------------------------------- */
.hero { padding: clamp(2.5rem, 7vw, 5rem) 0 clamp(3rem, 7vw, 6rem); }
.hero-card {
  background: var(--glass);
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  padding: clamp(2.2rem, 5vw, 4rem) clamp(1.5rem, 5vw, 4rem) clamp(2rem, 4vw, 3rem);
  text-align: center;
}
.hero-card h1 { margin: .5rem 0 1.2rem; }
.hero-card .lede { margin-inline: auto; text-align: center; font-size: clamp(1.02rem, 2vw, 1.2rem); }
.hero-actions { display: flex; gap: .8rem; justify-content: center; flex-wrap: wrap; margin: 1.9rem 0 1.4rem; }
.chips { display: flex; gap: .55rem; justify-content: center; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .42rem .85rem; border-radius: 999px;
  background: var(--glass-thin); border: 1px solid rgba(255, 255, 255, .5);
  font-size: .84rem; color: var(--ink);
}
.chip .mono { color: var(--ink-2); }

/* ---- cards --------------------------------------------------------- */
.grid { display: grid; gap: 1.1rem; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(275px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.card {
  background: var(--card);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 1.5rem 1.5rem 1.3rem;
  box-shadow: var(--shadow-sm);
}
.card h3 { margin-bottom: .55rem; }
.card p:last-child { margin-bottom: 0; }
.card-icon {
  width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
  background: rgba(244, 251, 255, .8); color: var(--flow-chip); margin-bottom: 1rem;
}
.band-deep .card { background: var(--card-deep); border-color: var(--line-deep); color: var(--ink-deep); }
.band-deep .card-icon { background: rgba(64, 218, 242, .14); color: var(--flow-deep); }

/* ---- numbers ------------------------------------------------------- */
.stat { text-align: center; }
.stat .n { font-family: var(--display); font-size: clamp(2.6rem, 6vw, 4rem); line-height: 1; display: block; }
/* THE LARGE-TEXT ACCENT, and it is not a shortcut. A stat number renders at
   40px+, where WCAG's bound is 3.0:1, not 4.5:1 — solving a 42px figure to the
   body-text target is a rule applied where it does not apply, and it costs the
   page most of its colour. The small accents next to them stay at 4.5. */
.stat .n.warm { color: var(--sill-lg); }
.stat .n.cool { color: var(--flow-lg); }
.band-deep .stat .n.warm { color: var(--sill-deep-lg); }
.band-deep .stat .n.cool { color: var(--flow-deep); }
.stat .c { font-size: .88rem; color: var(--ink-2); display: block; margin-top: .6rem; }
.band-deep .stat .c { color: var(--ink-deep-2); }

table { width: 100%; border-collapse: collapse; font-size: .92rem; }
th, td { text-align: left; padding: .62rem .7rem; border-bottom: 1px solid var(--line-2); }
th { font: 500 .74rem/1.3 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2); }
td.num, th.num { text-align: right; font-family: var(--mono); font-size: .86rem; }
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.tag { display: inline-block; padding: .16rem .5rem; border-radius: 6px; font: 500 .74rem/1.5 var(--mono); }
/* A TINT THAT DARKENS ITS OWN GROUND COSTS THE TEXT ON IT. These were tinted
   with the accent colour at 12% — which drops the local background below the
   pane the accent was solved against, and "ok" landed at 3.97:1. A near-white
   wash raises it instead, so the chip is lighter than the pane and the ink
   keeps every point of the ratio it was solved for. */
.tag-bad { background: rgba(255, 250, 244, .62); color: var(--sill-chip); }
.tag-good { background: rgba(244, 251, 255, .62); color: var(--flow-chip); }
.tag-mute { background: rgba(250, 252, 252, .58); color: var(--ink-2); }
.band-deep .tag-bad { background: rgba(120, 53, 8, .30); color: var(--sill-deep); }
.band-deep .tag-good { background: rgba(9, 78, 106, .34); color: var(--flow-deep); }
.band-deep .tag-mute { background: rgba(200, 220, 226, .16); color: var(--ink-deep-2); }

/* ---- prose --------------------------------------------------------- */
.prose { max-width: 68ch; }
.prose h2 { margin: 2.6rem 0 1rem; }
.prose h3 { margin: 2rem 0 .6rem; }
.prose ul { padding-left: 1.15rem; }
.prose li { margin-bottom: .5rem; }
pre {
  background: rgba(8, 41, 58, .90); color: #DDEEF0; padding: 1.1rem 1.2rem;
  border-radius: var(--r-sm); overflow-x: auto; font-family: var(--mono); font-size: .82rem; line-height: 1.6;
}
.band-deep pre { background: rgba(3, 22, 32, .82); }

/* ---- footer -------------------------------------------------------- */
.foot { background: var(--pane-deep); color: var(--ink-deep); padding: 3.2rem 0 2.4rem; backdrop-filter: var(--blur); }
.foot a { color: var(--flow-deep); }
.foot-grid { display: grid; gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.foot h4 { font: 500 .74rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-deep-2); margin: 0 0 .9rem; }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: .45rem; font-size: .92rem; }
/* `.small` was defined next to the footer and given the footer's LIGHT ink,
   then used on light panes all over the app — pale grey on pale glass, 1.1:1.
   The default is the light-pane ink; the deep surfaces override it, which is
   the direction that cannot go wrong by being reused somewhere new. */
.small { font-size: .84rem; color: var(--ink-2); }
.foot .small, .band-deep .small { color: var(--ink-deep-2); }

/* ---- app ----------------------------------------------------------- */
.field-row { display: flex; gap: .6rem; align-items: stretch; }
input[type="text"], input[type="number"] {
  flex: 1; min-width: 0; padding: .72rem .9rem; font: 400 1rem var(--mono);
  color: var(--ink); background: rgba(255, 255, 255, .78);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
input:focus-visible, .btn:focus-visible, a:focus-visible { outline: 2px solid var(--flow); outline-offset: 2px; }
.kv { display: flex; justify-content: space-between; gap: 1rem; padding: .5rem 0; border-bottom: 1px solid var(--line-2); }
.kv:last-child { border-bottom: 0; }
.kv .k { color: var(--ink-2); font-size: .92rem; }
.kv .v { font-family: var(--mono); font-size: .9rem; text-align: right; }
/* A 42-character address in a mono face is wider than a 390px phone, and a
   flex row will not break it — so the row pushed the document sideways by
   73px and only the narrow-width audit noticed. Wrap the value, and stack the
   pair once the two halves stop fitting beside each other. */
.kv .v { overflow-wrap: anywhere; min-width: 0; }
@media (max-width: 620px) {
  .kv { flex-direction: column; align-items: flex-start; gap: .15rem; }
  .kv .v { text-align: left; }
}

/* ---- reveal -------------------------------------------------------- */
/**
 * SCOPED TO `.js`, WHICH AN INLINE SCRIPT IN THE HEAD SETS.
 *
 * Written unscoped, `.rise { opacity: 0 }` hides thirteen elements from every
 * reader without JavaScript — permanently, with no fallback and no error. The
 * live audit caught it as "13 elements at opacity 0" and the first reading of
 * that was that the observer had not fired. It had. The rule was simply
 * unconditional.
 */
.js .rise { opacity: 0; transform: translateY(14px); transition: opacity .6s ease, transform .6s ease; }
.js .rise.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .js .rise { opacity: 1; transform: none; transition: none; } }
