/* hotato.css — the one design layer.
 *
 * Every page carries its own inline <style> with a full token block, and those
 * blocks drifted: 32 tokens held more than two different values across the
 * site, and the same heading role rendered at 32.8px, 48px and 50.4px on three
 * pages. This file is linked LAST in every <head>, so its tokens win, and one
 * edit here moves the whole site.
 *
 * Rules and rationale: DESIGN-STANDARD.md.
 *
 * Palette: neutral surfaces, ember as the one brand accent, and semantic colour
 * that means something (green passed, red failed, yellow open, blue
 * acknowledged, purple superseded). Nothing is coloured for mood.
 */

/* ─── Dark: the default ─────────────────────────────────────────────────── */
:root,
:root[data-theme="dark"] {
  --ground: #0d1117;
  --surface: #161b22;
  --surface-2: #1c2128;
  --surface-3: #21262d;
  --card: #161b22;
  --codebg: #010409;
  --hero-glow-rgb: 22, 27, 34;

  --ink: #e6edf3;
  --ink-2: #c9d1d9;
  --muted: #9aa4af;   /* 5.0:1 on --surface; #8b949e fell to 4.1 there */
  --faint: #98a1ac;   /* the smallest ids sit on --surface-2; 5.0:1 there */

  --line: rgba(240, 246, 252, .12);
  --line-2: rgba(240, 246, 252, .06);
  --line-strong: #484f58;

  --ember: #ff5a1f;
  --ember-bright: #ff7a3c;
  --ember-ink: #ff8a5c;
  --ember-glow: #ff6a2a;

  --pass: #3fb950;
  --pass-ink: #56d364;
  --crimson: #f85149;
  --fail: var(--crimson);
  --warn: #d29922;
  --info: #4493f8;
  --fail-stop: #ab7df8;

  --tl-caller: #d29922;
  --tl-agent: #4493f8;
  --tl-pass: #3fb950;
  --tl-fail: var(--crimson);

  --glow: rgba(255, 90, 31, .22);
  --sel: rgba(255, 90, 31, .28);
  --shadow: rgba(1, 4, 9, .55);

  /* Surfaces that stay dark in both themes: terminal output is written for a
     dark ground, so a light code background would strand its own colours. */
  --ground-dark: #010409;
  --surface-dark: #0d1117;
  --surface-2-dark: #161b22;
  --card-dark: #0d1117;
  --ink-on-dark: #e6edf3;
  --ink-2-on-dark: #c9d1d9;
  --muted-on-dark: #9aa4af;
  --faint-on-dark: #98a1ac;
  --line-on-dark: rgba(240, 246, 252, .14);
  --line-on-dark-2: rgba(240, 246, 252, .07);

  --grad-cta: linear-gradient(135deg, #e2470f, #ff6a2a 58%, #c2380a);
  --grad-mesh-dark: radial-gradient(92% 80% at 12% -10%, rgba(255, 90, 31, .07), transparent 55%);
}

/* ─── Light ─────────────────────────────────────────────────────────────── */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ground: #ffffff;
    --surface: #f6f8fa;
    --surface-2: #eaeef2;
    --surface-3: #dfe3e8;
    --card: #ffffff;
    --hero-glow-rgb: 246, 248, 250;

    --ink: #1f2328;
    --ink-2: #39424c;
    --muted: #59636e;
    --faint: #6e7781;

    --line: rgba(31, 35, 40, .15);
    --line-2: rgba(31, 35, 40, .07);
    --line-strong: #afb8c1;

    --ember: #c93c04;
    --ember-bright: #e2470f;
    --ember-ink: #a83203;

    --pass: #1a7f37;
    --pass-ink: #116329;
    --crimson: #cf222e;
    --warn: #9a6700;
    --info: #0969da;
    --fail-stop: #8250df;

    --tl-caller: #9a6700;
    --tl-agent: #0969da;
    --tl-pass: #1a7f37;

    --glow: rgba(255, 90, 31, .12);
    --sel: rgba(226, 71, 15, .18);
    --shadow: rgba(31, 35, 40, .10);

    --grad-mesh-dark: radial-gradient(92% 80% at 12% -10%, rgba(255, 90, 31, .05), transparent 55%);
  }
}

:root[data-theme="light"] {
  --ground: #ffffff;
  --surface: #f6f8fa;
  --surface-2: #eaeef2;
  --surface-3: #dfe3e8;
  --card: #ffffff;
  --hero-glow-rgb: 246, 248, 250;

  --ink: #1f2328;
  --ink-2: #39424c;
  --muted: #59636e;
  --faint: #6e7781;

  --line: rgba(31, 35, 40, .15);
  --line-2: rgba(31, 35, 40, .07);
  --line-strong: #afb8c1;

  --ember: #c93c04;
  --ember-bright: #e2470f;
  --ember-ink: #a83203;

  --pass: #1a7f37;
  --pass-ink: #116329;
  --crimson: #cf222e;
  --warn: #9a6700;
  --info: #0969da;
  --fail-stop: #8250df;

  --tl-caller: #9a6700;
  --tl-agent: #0969da;
  --tl-pass: #1a7f37;

  --glow: rgba(255, 90, 31, .12);
  --sel: rgba(226, 71, 15, .18);
  --shadow: rgba(31, 35, 40, .10);

  --grad-mesh-dark: radial-gradient(92% 80% at 12% -10%, rgba(255, 90, 31, .05), transparent 55%);
}

/* ─── Type: one fixed scale, no fluid body type ─────────────────────────── */
:root,
:root[data-theme="dark"],
:root[data-theme="light"] {
  --fs-display: 2.5rem;   /* 40px, the home hero only */
  --fs-h1: 2rem;          /* 32px, page title */
  --fs-title: 1.5rem;     /* 24px, section heading */
  --fs-subhead: 1.25rem;  /* 20px, subsection */
  --fs-h3: var(--fs-subhead);
  --fs-story: 1.25rem;
  --fs-subtitle: 1.0625rem;
  --fs-body: 1rem;
  --fs-small: .875rem;
  --fs-label: .75rem;
  --fs-card-label: .75rem;
  --fs-mono-term: .8125rem;

  /* Space: the old bands ran to 10.5rem, which is most of why a page took
     five screens to say three things. */
  --band-y: clamp(8rem, 13vw, 14rem);
  --head-gap: clamp(3rem, 5.5vw, 4.5rem);
  --stack-gap: clamp(4rem, 7vw, 6rem);
  --gap: clamp(1.1rem, 2.1vw, 1.6rem);
  --split-gap: clamp(1.9rem, 3.8vw, 3rem);
  --sp-section: clamp(2.5rem, 5vw, 3.75rem);
  --sp-block: 1.4rem;
  --sp-flow: .9rem;
  --cap-gap: 1.1rem;

  --radius-card: 6px;
  --radius-cmd: 6px;
  --measure: 68ch;
  --measure-wide: 72ch;
}

/* Normalisation. Selectors are prefixed with `html body` so this layer beats a
   page's own rule at equal intent without reaching for !important. Every entry
   here exists because a page shipped that role at a different size. */
html body h1 { font-size: var(--fs-h1); line-height: 1.25; letter-spacing: -.018em; }
html body h2 { font-size: var(--fs-title); line-height: 1.3; letter-spacing: -.014em; }
html body h3 { font-size: var(--fs-h3); line-height: 1.35; letter-spacing: -.01em; }
html body h4 { font-size: 1rem; line-height: 1.4; }

html body .hero h1 { font-size: var(--fs-display); line-height: 1.1; }
/* The hero's line spans were nowrap, which is why the h1 could not carry the
   canonical tagline the README, PyPI and llms.txt all use: the third line
   overflowed its 488px column. Let them wrap and the sentence fits. */
html body .hero h1 .hl { white-space: normal; }
html body .pagehero h1 { font-size: var(--fs-h1); line-height: 1.2; }
html body .content h1 { font-size: var(--fs-h1); line-height: 1.25; }
html body .content h2 { font-size: var(--fs-title); line-height: 1.3; }
html body .content h3 { font-size: var(--fs-h3); line-height: 1.35; }
html body .footcta h2 { font-size: var(--fs-title); letter-spacing: -.014em; }
html body .fixstory h3,
html body .card h3,
html body .dcard h3,
html body .qa h3 { font-size: 1rem; line-height: 1.4; }
html body .hero-lede { font-size: var(--fs-subtitle); }

html body { font-size: var(--fs-body); }

/* ─── Components ────────────────────────────────────────────────────────── */
/* Status reads as a badge, and the colour carries the state. */
html body .chip,
html body .badge {
  font-size: var(--fs-label);
  font-weight: 500;
  border: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: 2em;
  padding: 2px 8px;
}

html body .chip.open,      html body .badge.open      { color: var(--warn); }
html body .chip.acknowledged, html body .badge.acknowledged { color: var(--info); }
html body .chip.fixed,     html body .badge.fixed     { color: var(--pass); }
html body .chip.disputed,  html body .badge.disputed  { color: var(--crimson); }
html body .chip.merged,    html body .badge.merged,
html body .chip.withdrawn, html body .badge.withdrawn { color: var(--fail-stop); }

/* Flat cards: the old glow-and-shadow treatment fought the content. */
html body .card,
html body .dcard,
html body .qa {
  box-shadow: none;
  border-radius: var(--radius-card);
}

/* Tables read as data, not as decoration. */
html body table { font-size: var(--fs-small); border-collapse: collapse; }
html body th {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  font-weight: 600;
  text-align: left;
}
html body td, html body th { border-bottom: 1px solid var(--line-2); }

/* ─── Section rhythm ───────────────────────────────────────────────────── */
/* Bands collapse one side of their padding when they stack, so two sections
   could meet on 79px of air and read as one long page. A hairline gives the
   boundary a place to be, and the padding above it is now generous enough that
   a section head has room to breathe. */
html body .band + .band,
html body section + section.band,
html body .atlaslive + .band { border-top: 1px solid var(--line-2); }
html body .band + .band { padding-top: var(--band-y); }
html body .footcta { border-top: 1px solid var(--line-2); }

/* Every other band sits on --surface, so the page reads as a stack of plates
   instead of one unbroken colour. --surface is one step off --ground in both
   themes (#161b22 on #0d1117 dark, #f6f8fa on #ffffff light), which is enough
   to separate a section without putting text on a new contrast problem: --ink,
   --ink-2 and --muted are all specified against both tones. */
html body .band:nth-of-type(even) { background: var(--surface); }

/* A card on a tinted band needs to step the other way, or it disappears into
   the plate it sits on. */
html body .band:nth-of-type(even) .card,
html body .band:nth-of-type(even) .dcard,
html body .band:nth-of-type(even) .qa { background: var(--ground); }

/* The closing call to action closes the page, so it gets the deeper ground and
   keeps its own hairline. */
html body .footcta { background: var(--surface); }
html body .footcta .cmd,
html body .footcta .cmdline { background: var(--ground); }

/* Nothing readable ships below 12px. These classes were rendering label text at
   9.3px to 11.5px, which is under the floor in the standard's type scale. */
html body .foot-col-label,
html body .st,
html body .tn,
html body .ck,
html body .rpill,
html body .ir-sev,
html body .ir-id,
html body .wtitle,
html body caption,
html body dt,
html body th.mono { font-size: var(--fs-label) !important; }  /* a floor, so a page rule with a
   deeper selector (.dim .tn) cannot drop a label back to 9px */

/* The skip link hardcodes near-black on ember. Ember is bright in dark mode and
   deep in light mode, so the text flips with the theme to keep 4.5:1. */
html body .skip { color: #0d1117; }
:root[data-theme="light"] html body .skip,
:root[data-theme="light"] .skip { color: #ffffff; }
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .skip { color: #ffffff; } }

/* ─── The mascot, standard 4b ───────────────────────────────────────────── */
/* One stylesheet places the decoration so every page gets the same treatment
   and no page has to hand-place a potato. Both pieces are decorative: they are
   background images, so nothing is announced and nothing is focusable. */

/* The peek is cropped at its sill, so it belongs ON a boundary -- but the
   closing band already carries the page's own mascot, and 4b allows one per
   band. Per-page peeks are placed in the pages themselves; this layer only
   carries the watermark, which is the piece that has to follow the theme. */

/* The ghost is a silhouette with no face, which is the only thing that reads as
   texture rather than a sticker at this size. One per band, behind the content,
   low enough that body text keeps its contrast. */
html body .footcta::after {
  content: "";
  position: absolute;
  /* Kept inside the band: at -2% this pushed the document 29px wider than the
     viewport at 1440 and 8px at 390, and a pseudo-element does not show up in
     a querySelectorAll overflow hunt, so it read as a mystery. */
  right: 0;
  bottom: -6%;
  width: clamp(180px, 26vw, 420px);
  aspect-ratio: 1;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' width='40' height='40' aria-hidden='true' focusable='false'> watermark scale a face reads as a sticker, a shape reads as texture. --> <path d='M8 24c-1.5-8 4-14 13-14.5 10-.5 12 6 12 12 0 7-5.5 12-13.5 12C12 33.5 9 30 8 24Z' fill='currentColor'/> </svg>") center / contain no-repeat;
  opacity: .045;
  pointer-events: none;
  z-index: 0;
}
html body .footcta > * { position: relative; z-index: 1; }

/* 404 and the other genuinely empty states get the face, because there is
   nothing else on the page to look at. */
html body .pagehero.is-empty::after,
html body .emptystate::after {
  content: "";
  display: block;
  width: 40px;
  height: 40px;
  margin: var(--stack-gap) 0 0;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' width='40' height='40' aria-hidden='true' focusable='false'> watermark scale a face reads as a sticker, a shape reads as texture. --> <path d='M8 24c-1.5-8 4-14 13-14.5 10-.5 12 6 12 12 0 7-5.5 12-13.5 12C12 33.5 9 30 8 24Z' fill='currentColor'/> </svg>") center / contain no-repeat;
  opacity: .12;
}

@media (prefers-reduced-motion: reduce) {
  html body .footcta::before,
  html body .footcta::after { transition: none; animation: none; }
}

/* currentColor does not resolve inside a background image, so the silhouette
   needs its ink stated per theme rather than inherited. */
:root[data-theme="dark"] html body .footcta::after,
:root[data-theme="dark"] .footcta::after { background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' width='40' height='40' aria-hidden='true' focusable='false'> watermark scale a face reads as a sticker, a shape reads as texture. --> <path d='M8 24c-1.5-8 4-14 13-14.5 10-.5 12 6 12 12 0 7-5.5 12-13.5 12C12 33.5 9 30 8 24Z' fill='%2523e6edf3'/> </svg>"); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .footcta::after { background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' width='40' height='40' aria-hidden='true' focusable='false'> watermark scale a face reads as a sticker, a shape reads as texture. --> <path d='M8 24c-1.5-8 4-14 13-14.5 10-.5 12 6 12 12 0 7-5.5 12-13.5 12C12 33.5 9 30 8 24Z' fill='%2523e6edf3'/> </svg>"); } }
:root[data-theme="light"] html body .footcta::after,
:root[data-theme="light"] .footcta::after { background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' width='40' height='40' aria-hidden='true' focusable='false'> watermark scale a face reads as a sticker, a shape reads as texture. --> <path d='M8 24c-1.5-8 4-14 13-14.5 10-.5 12 6 12 12 0 7-5.5 12-13.5 12C12 33.5 9 30 8 24Z' fill='%25231f2328'/> </svg>"); }
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .footcta::after { background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' width='40' height='40' aria-hidden='true' focusable='false'> watermark scale a face reads as a sticker, a shape reads as texture. --> <path d='M8 24c-1.5-8 4-14 13-14.5 10-.5 12 6 12 12 0 7-5.5 12-13.5 12C12 33.5 9 30 8 24Z' fill='%25231f2328'/> </svg>"); } }

/* Nav links shipped at --muted, which is a caption tone: 3.43:1 at 14.4px on
   the light nav, under the 4.5 floor. They are primary navigation, so they get
   body ink. */
html body .nav-links a { color: var(--ink-2); }
html body .nav-links a:hover,
html body .nav-links a[aria-current] { color: var(--ink); }

/* ─── Platform tag ──────────────────────────────────────────────────────── */
/* The atlas tags every platform a failure was reported on, and the homepage
   strip shows the same rows, so the component belongs to the site rather than
   to the generator. The hue rides on the element as --pf-h and comes from the
   feed's own stack_hues map, so a platform is the same colour in both places.
   One lightness per theme keeps fourteen tags in a row reading as a palette;
   measured worst case 6.38:1 dark and 5.80:1 light, both over the 4.5 floor.
   The flat declarations before each oklch() are what a browser without oklch()
   keeps, so the tag degrades to the grey it replaced. */
:root, :root[data-theme="dark"] {
  --pf-l: 80%; --pf-c: .12; --pf-fill: .16; --pf-edge: .34;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { --pf-l: 42%; --pf-c: .12; --pf-fill: .12; --pf-edge: .30; }
}
:root[data-theme="light"] { --pf-l: 42%; --pf-c: .12; --pf-fill: .12; --pf-edge: .30; }

html body .tag {
  display: inline-flex; align-items: center;
  font-size: var(--fs-label); font-weight: 500; line-height: 1.5;
  padding: 1px 7px; border-radius: 2em;
  border: 1px solid var(--line); white-space: nowrap;
}
html body .tag.pf {
  color: var(--muted); background: var(--surface-2);
  color: oklch(var(--pf-l) var(--pf-c) var(--pf-h));
  background: oklch(var(--pf-l) var(--pf-c) var(--pf-h) / var(--pf-fill));
  border-color: oklch(var(--pf-l) var(--pf-c) var(--pf-h) / var(--pf-edge));
}
/* The strip's row is one link, so the tags inside it wrap as a group and never
   push the row wider than its column. */
html body .al-pf { display: inline-flex; flex-wrap: wrap; gap: 4px; align-items: center; }

/* ─── Section rhythm, continued: the closing band ───────────────────────── */
/* Every band pads --band-y (187px at 1440). The closing call to action padded
   88px, less than half, so the page ended on a visibly tighter beat than every
   section above it. It is a band; it keeps the band's rhythm. */
html body .footcta { padding-block: var(--band-y); }

/* ─── Footer ────────────────────────────────────────────────────────────── */
/* A footer is a grid: a brand column that says what this is, then columns of
   stacked links under quiet headings, then a rule and a bottom bar. It was
   briefly laid out as wrapped rows of links with inline headings -- a density
   workaround borrowed from the atlas -- which reads as a sitemap dump rather
   than a footer. This is the ordinary shape, which is the right one. */
html body footer.site {
  padding-block: clamp(3rem, 5vw, 4.5rem) clamp(1.5rem, 2.5vw, 2.25rem);
  border-top: 1px solid var(--line);
  background: var(--surface);
}
html body .foot-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
  padding-block: 0;
}
html body .foot-brandcol {
  display: block; max-width: 30ch;
}
html body .foot-brandcol .brand { margin-bottom: .85rem; }
html body .foot-brandcol p {
  font-size: var(--fs-small); line-height: 1.6;
  color: var(--muted); margin: 0; max-width: none;
}
html body .foot-col { display: block; }
html body .foot-col-label {
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .07em;
  font-weight: 600; color: var(--ink); margin: 0 0 1rem;
}
html body .foot-col ul {
  display: grid; gap: .6rem; margin: 0; padding: 0; list-style: none;
}
html body .foot-col li { line-height: 1.4; }
html body .foot-col a {
  font-size: var(--fs-small); color: var(--muted); text-decoration: none;
}
html body .foot-col a:hover { color: var(--ink); text-decoration: underline; }

/* the bottom bar: one quiet line, set off by a rule */
html body .foot-in {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  padding-top: 1.35rem !important;
  border-top: 1px solid var(--line-2);
}
html body .foot-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .9rem;
  font-size: var(--fs-small); color: var(--muted);
}
html body .foot-meta a { color: var(--muted); }
html body .foot-meta a:hover { color: var(--ink); }
html body .foot-meta .sep { color: var(--line-strong); }

@media (max-width: 860px) {
  html body .foot-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  html body .foot-brandcol { grid-column: 1 / -1; max-width: 44ch; }
}
@media (max-width: 520px) {
  html body .foot-cols { grid-template-columns: minmax(0, 1fr); }
}

/* Content should not run into the footer. The last band on a page ended on its
   own padding and then the footer's border began, so the two read as one block.
   A page ends with room. */
html body main > :last-child,
html body main + footer,
html body footer.site { margin-top: 0; }
html body main { padding-bottom: clamp(2rem, 4vw, 3.5rem); }
html body .band:last-of-type { padding-bottom: calc(var(--band-y) + 1rem); }
