/* The blog, on the same footing as the landing page.
 *
 * It imports nothing from landing.css deliberately: that file is tuned for one
 * long marketing page with full-bleed figures, and a post wants a narrower
 * measure and quieter furniture. What the two share is the palette and the
 * faces, restated here so a change to one page cannot silently restyle the
 * other.
 */
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/fonts/plex-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/fonts/plex-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bebas Neue';
  src: url('/fonts/bebas-neue.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --bg: #08090b; --bg2: #0e1014; --ink: #e9ecf1; --muted: #8b93a1;
  --faint: #5b626e; --edge: #1c2027; --accent: #ff7a45;
  --ok: #4ade80; --warn: #fbbf24; --bad: #f2857c;
  /* One step up from --bg2: enough to register as a hover, not enough to
     look like a different component. */
  --raised: #161a20;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) {
    --bg: #ffffff; --bg2: #f6f7f9; --ink: #14171c; --muted: #5b626e;
    --faint: #8b93a1; --edge: #e3e6ea; --bad: #b3261e; --ok: #1c7c4a;
    --raised: #eef0f3;
    /* The dark-mode orange and yellow fail on white; these are the app's light values. */
    --accent: #c94410; --warn: #9a5a00;
  }
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 400 17px/1.7 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 42rem; margin: 0 auto; padding: 3rem 1.5rem 5rem; }

header.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 3.5rem; }
.mark {
  font: 400 1.6rem/1 'Bebas Neue', ui-sans-serif, sans-serif;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent);
  text-decoration: none;
}
.mark span { color: var(--ink); }
.top nav { display: flex; gap: 1.25rem; font-size: 0.9rem; }
.top nav a { color: var(--muted); text-decoration: none; }
.top nav a:hover { color: var(--ink); }

h1 {
  font: 400 clamp(2.1rem, 5.5vw, 3rem)/1.05 'Bebas Neue', ui-sans-serif, sans-serif;
  letter-spacing: 0.02em; margin: 0; text-wrap: balance;
}
.dateline {
  margin: 0.9rem 0 0; font-size: 0.8rem; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.standfirst { margin: 1.5rem 0 2rem; font-size: 1.15rem; color: var(--muted); }

h2 {
  font: 400 1.5rem/1.15 'Bebas Neue', ui-sans-serif, sans-serif;
  letter-spacing: 0.05em; margin: 3rem 0 0.75rem; text-wrap: balance;
}
h3 { font-size: 1.05rem; font-weight: 600; margin: 2rem 0 0.5rem; }
p { margin: 0 0 1.1rem; }
a { color: var(--accent); text-underline-offset: 2px; }
strong { font-weight: 600; }
ul, ol { margin: 0 0 1.1rem; padding-left: 1.3rem; }
li { margin-bottom: 0.5rem; }
code {
  font: 400 0.85em/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--bg2); border: 1px solid var(--edge);
  border-radius: 4px; padding: 0.1em 0.35em;
}
blockquote {
  margin: 1.5rem 0; padding: 0.1rem 0 0.1rem 1.15rem;
  border-left: 3px solid var(--accent); color: var(--muted);
}

/* The legal pages (/legal/*), rendered by the server from internal/legal. They
 * are the only documents here with tables - the cookie inventory - and a rule
 * between the header block and section 1. A table wider than a phone scrolls
 * inside its own box rather than widening the page. */
hr { border: 0; border-top: 1px solid var(--edge); margin: 2rem 0; }
.legal .meta { color: var(--muted); font-size: 0.9rem; }
.legal table {
  display: block; overflow-x: auto; border-collapse: collapse;
  margin: 1rem 0 1.5rem; font-size: 0.9rem;
}
.legal th, .legal td {
  text-align: left; vertical-align: top; padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--edge);
}
.legal th { color: var(--muted); font-weight: 600; white-space: nowrap; }
.legal td:first-child { white-space: nowrap; }
.legal-links { display: flex; flex-wrap: wrap; gap: 1rem; margin: 0 0 2rem; font-size: 0.9rem; }
.legal-links a[aria-current="page"] { color: var(--ink); text-decoration: none; font-weight: 600; }

/* Figures: a screenshot is the argument here, so it gets a frame and a caption
 * that says what to look at rather than what it is. */
figure { margin: 2rem 0; }
figure img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--edge); border-radius: 8px;
  background: var(--bg2);
}
figcaption { margin-top: 0.7rem; font-size: 0.85rem; color: var(--muted); }
figcaption b { color: var(--ink); font-weight: 600; }
figcaption em { font-style: italic; }
/* Provenance. Every screenshot here is generated data, and the caption has to
 * say so on its own line rather than trailing off the end of a sentence. */
figcaption .src { display: block; margin-top: 0.3rem; color: var(--faint); font-size: 0.8rem; }

/* A screenshot narrower than the column is shown at its own size. Stretching a
 * 384px capture across 620px makes the product look blurry, which is the
 * opposite of what a screenshot is for. */
figure.narrow img { width: auto; max-width: 100%; margin-inline: auto; }

/* Dense product screenshots break out past the text measure where there is room.
 * A 42rem column is right for prose and far too narrow for a full application
 * window: squeezed to 620px, the labels stop being legible and the figure
 * stops being evidence. The measure of the prose is unchanged. */
@media (min-width: 64rem) {
  figure.wide { width: 56rem; margin-inline: calc((39rem - 56rem) / 2); }
  /* The image breaks out; its caption does not. A 56rem line of 0.85rem text is
   * an unreadable measure, and the caption is prose. */
  figure.wide figcaption { max-width: 39rem; margin-inline: auto; }
}

.pull {
  margin: 2rem 0; padding: 1.1rem 1.25rem;
  border: 1px solid var(--edge); background: var(--bg2); border-radius: 8px;
}
.pull p:last-child { margin-bottom: 0; }
.pull h3 { margin-top: 0; }

/* The state chips, matching what the product renders. */
.chip {
  display: inline-block; font: 400 0.68rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.1em; text-transform: uppercase;
  border: 1px solid currentColor; border-radius: 3px; padding: 0.2em 0.4em;
  vertical-align: middle;
}
.chip-pass { color: var(--ok); }
.chip-fail { color: var(--bad); }
.chip-unv { color: var(--warn); }

/* The index: one card per post.
 *
 * It was a list separated by hairlines, which is honest and reads as an
 * afterthought — a page with one post on it looked like a page that had failed
 * to load. A card gives the post an edge, a ground and somewhere for a hover
 * state to happen, and stacks without redesign as posts accumulate. */
.index { list-style: none; padding: 0; margin: 2.5rem 0 0; display: grid; gap: 1rem; }
.index li { margin: 0; }
.index a {
  display: block; overflow: hidden;
  color: var(--ink); text-decoration: none;
  background: var(--bg2); border: 1px solid var(--edge); border-radius: 10px;
  /* Color only. The card used to lift on hover, which moves the text you are
     reading out from under your eyes and, on a list, makes the whole page
     twitch as the pointer crosses it. A hover state has to say "this is live"
     without relocating the thing being pointed at. */
  transition: border-color 150ms ease, background-color 150ms ease;
}
.index a:hover { border-color: var(--accent); background: var(--raised); }
.index a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .index a { transition: none; } }

/* The card's image. Fixed aspect so a row of cards keeps one baseline
   whatever each image happens to be, and a hairline under it so the picture
   ends somewhere definite rather than bleeding into the text.
   Every source has to survive being cropped to 16:7: a drawn one is authored
   at that ratio so nothing is taken away, and a screenshot needs slack, since
   framed flush its top edge slices a line of text in half and the card opens
   on a broken sentence. */
.index figure { margin: 0; border-bottom: 1px solid var(--edge); }
.index img { display: block; width: 100%; aspect-ratio: 16 / 7; object-fit: cover; object-position: center; }
.index .body { padding: 1.5rem; }
/* Date first and small: it is the card's eyebrow, not a footnote. */
.index .dateline { margin: 0 0 0.5rem; color: var(--accent); }
.index .body > *:first-child { margin-top: 0; }
.index h2 { margin: 0 0 0.5rem; }
.index a:hover h2 { color: var(--accent); }
.index p { color: var(--muted); margin: 0; font-size: 0.95rem; }
/* The affordance. A card with no verb on it is a card people do not press. */
.index .more {
  display: inline-block; margin-top: 0.9rem;
  font-size: 0.85rem; color: var(--accent);
}

footer {
  margin-top: 4rem; padding-top: 1.5rem; border-top: 1px solid var(--edge);
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: center;
  color: var(--muted); font-size: 0.85rem;
}
footer nav { margin-left: auto; display: flex; gap: 1rem; }
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--ink); }

/* The dragonfly behind the 404.
 *
 * Same venation grammar as the landing page's butterfly — both come out of
 * cmd/wing, which draws a wing as a graph of veins and the cells between them
 * because that is the same structure the product draws for an access path.
 *
 * A DIFFERENT specimen, though, and deliberately. Reusing the landing wing
 * here would quietly say "you are on the landing page"; drawing some other
 * kind of mark would say the error pages belong to a different product. A
 * dragonfly is the same graph under different numbers — a long taut costa, a
 * narrow apex, twice the crossveins — so it reads as a sibling rather than a
 * copy or a stranger.
 *
 * Scoped to body.err so a blog post, which shares this stylesheet, never
 * grows one. Fixed to the bottom-right corner it is flying out of, in the
 * region the copy never reaches, and masked to nothing well before it gets
 * near a line of text. */
body.err { position: relative; overflow-x: hidden; }
body.err::before {
  content: ""; position: fixed; right: 0; bottom: 0;
  width: min(54rem, 84vw); aspect-ratio: 1100 / 980;
  background: url('/wing-dragonfly.svg') bottom right / contain no-repeat;
  /* Low enough to pass under a paragraph without competing with it. The
     measure is centered at 42rem, so on a wide window the wing unavoidably
     reaches under the copy — the landing page's wing does the same, and the
     answer there is the same as here: keep it faint and put the words on top,
     rather than shrink it until it reads as a smudge. */
  opacity: 0.42;
  -webkit-mask-image: radial-gradient(104% 100% at 95% 93%, #000 20%, transparent 72%);
  mask-image: radial-gradient(104% 100% at 95% 93%, #000 20%, transparent 72%);
  pointer-events: none;
  z-index: 0;
}
/* The words sit over it, always. */
body.err .wrap { position: relative; z-index: 1; }
/* Orange on white carries less than orange on near-black, so light needs more
 * of it to register at all — the same correction landing.css makes. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) body.err::before { opacity: 0.62; }
}

/* ---- The 404, rendered as a finding -------------------------------------
 *
 * Same shape the console draws for a real one: an eyebrow, a control key, a
 * card per section, and a strip of metadata underneath. The joke only works
 * if it is accurate, so these follow the product's own proportions rather
 * than being a pastiche of them. Scoped under body.err with the wing. */
body.err .f-eyebrow {
  margin: 0 0 0.35rem;
  font: 400 0.72rem/1 'Bebas Neue', ui-sans-serif, sans-serif;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--accent);
}
body.err h1 { font-size: clamp(1.9rem, 4.5vw, 2.4rem); line-height: 1.15; }
body.err .f-key {
  margin: 0.6rem 0 2rem; color: var(--muted);
  font: 400 0.8rem/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
body.err .f-key code { background: none; border: 0; padding: 0; color: var(--muted); }

body.err .f-card {
  border: 1px solid var(--edge); border-radius: 10px;
  background: var(--bg2); margin: 0 0 1rem; overflow: hidden;
}
body.err .f-card h2 {
  margin: 0; padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--edge);
  font: 400 0.95rem/1 'Bebas Neue', ui-sans-serif, sans-serif;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink);
}
body.err .f-body { padding: 1.25rem; }
body.err .f-body p { margin: 0 0 0.9rem; }
body.err .f-body p:last-child { margin-bottom: 0; }

/* The offending path, presented the way the console presents the entity a
 * finding was raised against. */
body.err .f-entity {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem;
  margin-top: 1.1rem; padding: 0.75rem 0.9rem;
  border: 1px solid var(--edge); border-radius: 8px; background: var(--bg);
}
body.err .f-entity code {
  min-width: 0; flex: 1; overflow-wrap: anywhere;
  background: none; border: 0; padding: 0; color: var(--ink);
  font-size: 0.85rem;
}

body.err .f-meta {
  display: flex; flex-wrap: wrap; gap: 2rem;
  margin: 1.75rem 0 0; padding-top: 1.25rem; border-top: 1px solid var(--edge);
}
body.err .f-meta div { display: flex; flex-direction: column; gap: 0.3rem; }
body.err .f-meta dt {
  font: 400 0.62rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint);
}
body.err .f-meta dd { margin: 0; font-size: 0.9rem; color: var(--muted); }
body.err .f-meta dd.open { color: var(--accent); }

/* The places the deadpan is allowed to break.
 *
 * The finding conceit was reading as a REAL finding, which is a joke that has
 * stopped being one — somebody could reasonably wonder whether the site is
 * broken in a way we are tracking. These are the tells: a tag saying the
 * control is mapped to nothing, and an aside admitting the bit. Two of them,
 * placed where the eye already stops, because a joke explained everywhere is
 * not funnier, it is just louder. */
body.err .f-wink {
  display: inline-block; margin-left: 0.6rem;
  font: 400 0.62rem/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint);
  border: 1px dashed var(--edge); border-radius: 3px; padding: 0.35em 0.5em;
  vertical-align: 1px;
}
body.err .f-aside {
  margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px dashed var(--edge);
  color: var(--faint); font-size: 0.9rem; font-style: italic;
}
