/* Chip Dust. Measurements from BlogChip-layout.vectorcraft at 1pt = 1.5px.
   Colours are VectorCraft's own screen rendering of the CMYK values (vectorcraft-cli convert to SVG). */

@font-face { font-family: "Author"; src: url("fonts/Author-Variable.ttf") format("truetype"); font-weight: 200 700; font-display: swap; }
@font-face { font-family: "Author"; src: url("fonts/Author-VariableItalic.ttf") format("truetype"); font-weight: 200 700; font-style: italic; font-display: swap; }
@font-face { font-family: "Chillax"; src: url("fonts/Chillax-Bold.otf") format("opentype"); font-weight: 700; font-display: swap; }

@view-transition { navigation: auto; }

:root {
  --backdrop: #a3a9aa;
  --panel: #bbbfbf;
  --chip: #5f7675;
  --spark: #53b8c5;                 /* the lit pixel in the chip; the site's only accent */
  --ink: #2a2b2c;
  --muted: #333;
  --chip-ink: #2a2b2c;              /* chip outline stays dark in both themes */
  --series-2: #1a6f7a;              /* the spark, darkened to read as a chart line on the panel */
  --critical: #b3261e;              /* critical vulnerabilities, and nothing else */

  --measure: 40.75rem;              /* 434pt column */
  --gutter: clamp(1rem, 5vw, 2rem);
  --rule: .25rem;                   /* 2.83pt header rule */
  --radius: clamp(1rem, 4vw, 1.55rem); /* 16.7pt panel corners */
  --pad: clamp(1.25rem, 5vw, 3rem);

  color-scheme: light;
}

/* Lights off, on request only: the chip goes dark and its pixel glows. */
:root[data-theme="dark"] {
  --backdrop: #191b1c;
  --panel: #232627;
  --ink: #d4d9d9;
  --muted: #9ba3a3;
  --series-2: var(--spark);
  --critical: #ff6b5e;
  color-scheme: dark;
}
:root[data-theme="dark"] .chip .px-a { filter: drop-shadow(0 0 .6px var(--spark)) drop-shadow(0 0 1.5px var(--spark)); }

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

html {
  background: var(--backdrop);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  hanging-punctuation: first last;
}

body {
  margin: 0;
  min-height: 100dvh;
  color: var(--ink);
  font: 400 1.125rem/1.6 "Author", system-ui, sans-serif;
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--spark); color: #000; }

:focus-visible { outline: 3px solid var(--spark); outline-offset: 3px; border-radius: 2px; }

/* One centred column for everything. */
header.site, main {
  width: min(var(--measure), 100% - 2 * var(--gutter));
  margin-inline: auto;
}

/* Header: chip, wordmark, menu, thick rule.
   Sticky with a negative top, so the top padding scrolls away and the rest stays. */
header.site {
  position: sticky;
  top: -1.7rem;
  z-index: 2;
  background: var(--backdrop);
  display: flex;
  align-items: end;
  justify-content: space-between;
  padding-block: 2.6rem .55rem;
  border-bottom: var(--rule) solid var(--ink);
}

.brand {
  display: flex;
  align-items: end;
  gap: .45rem;
  color: var(--ink);
  text-decoration: none;
  font: 700 1.125rem/1 "Chillax", sans-serif;
  letter-spacing: .005em;
}

.chip { width: 1.33rem; height: 1.33rem; flex: none; }
.chip .edge, .chip .px-b { fill: var(--chip-ink); }
.chip .die { fill: var(--chip); }
.chip .px-a { fill: var(--spark); }
/* Hover: the two pixels trade places, like a bit flipping. */
.brand:hover .px-a { animation: flip-a .5s steps(1) 1; }
.brand:hover .px-b { animation: flip-b .5s steps(1) 1; }
@keyframes flip-a { 50% { fill: var(--chip-ink); } }
@keyframes flip-b { 50% { fill: var(--spark); } }

/* On a post, a thin laser runs along the centre of the black rule as you read,
   inset from both ends so it always sits wrapped in black. */
@supports (animation-timeline: scroll()) {
  body:has(article) header.site::after {
    content: "";
    position: absolute;
    left: .4rem;
    right: .4rem;
    bottom: calc((var(--rule) + 1px) / -2);
    height: 1px;
    background: var(--spark);
    transform-origin: left;
    animation: read linear both;
    animation-timeline: scroll(root);
  }
  @keyframes read { from { scale: 0 1; } to { scale: 1 1; } }
}

/* Theme switch: a square, half lit. Light mode lights the left in ink; dark lights the right in spark. */
.theme-toggle {
  margin: 0 1rem 0 auto;
  padding: .25rem 0 .2rem;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: 0;
}
.theme-toggle svg { width: 12px; height: 12px; }
.theme-toggle .frame { fill: none; stroke: var(--ink); stroke-width: 2; }
.theme-toggle .lit { fill: var(--ink); transition: x .25s, fill .25s; }
.theme-toggle[aria-pressed="true"] .lit { x: 6px; fill: var(--spark); filter: drop-shadow(0 0 1.5px var(--spark)); }

/* Thick, thin, thick: 2.83pt / 1.42pt / 2.83pt bars packed tight. */
.menu-toggle {
  display: grid;
  gap: 1px;
  width: 1.5rem;
  padding: .25rem 0 .2rem;
  margin: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.menu-toggle span { height: 4px; background: var(--ink); transition: translate .2s, rotate .2s, opacity .2s; }
.menu-toggle span:nth-child(2) { height: 2px; }
/* Hover energises the thin line, like a trace. */
.menu-toggle:hover span:nth-child(2) { background: var(--spark); }
/* Lines fold into a cross while the menu is open. */
.menu-toggle:has(+ .menu:popover-open) span:nth-child(1) { translate: 0 4px; rotate: 45deg; }
.menu-toggle:has(+ .menu:popover-open) span:nth-child(2) { opacity: 0; }
.menu-toggle:has(+ .menu:popover-open) span:nth-child(3) { translate: 0 -4px; rotate: -45deg; }

/* The menu lives in the top layer, pinned to the column's right edge.
   It's a small chip: a dark edge, thick at the top for its silkscreen, around a dusty die. */
.menu {
  inset: auto;
  top: 5.25rem;
  right: max(var(--gutter), (100% - var(--measure)) / 2);
  margin: 0;
  min-width: 11rem;
  padding: .75rem 1.25rem;
  border: .45rem solid var(--chip-ink);
  border-top-width: 1.75rem;
  border-radius: 1rem;
  background: url("dust.svg"), var(--panel);
  background-clip: padding-box;
  color: var(--ink);
  box-shadow: 0 .75rem 2rem rgb(0 0 0 / .12);
  font-weight: 600;
  overflow: visible;
}
.menu::before {
  content: "Index";
  position: absolute;
  top: -1.3rem;
  left: .8rem;
  font-size: .7rem;
  line-height: 1;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #bbbfbf;
}
/* Links light a pixel beside them, like the section headings. */
.menu a { position: relative; display: block; padding-block: .25rem; color: var(--ink); text-decoration: none; }
.menu a::before {
  content: "";
  position: absolute;
  left: -.8rem;
  top: 50%;
  width: .35rem;
  height: .35rem;
  translate: 0 -50%;
  background: var(--spark);
  opacity: 0;
  transition: opacity .15s;
}
.menu a:hover::before { opacity: 1; }
.menu:popover-open { animation: drop .18s ease-out; }
@keyframes drop { from { opacity: 0; translate: 0 -.5rem; } }
/* Where supported, hang the menu off the button itself. */
@supports (anchor-name: --a) {
  .menu-toggle { anchor-name: --menu-toggle; }
  .menu { position-anchor: --menu-toggle; top: calc(anchor(bottom) + 1rem); right: anchor(right); }
}

/* Dateline: "Oct 29 2026", year in bold. */
.date { margin: 0; color: var(--muted); font-size: 1.125rem; line-height: 1.2; }
.date b { font-weight: 700; }

/* Post */
article { padding-block: 3rem 4rem; }
h1, h2, h3 { text-wrap: balance; }
article h1 {
  margin: .35rem 0 1.4rem;
  font-weight: 700;
  font-size: clamp(2rem, 1.5rem + 2.4vw, 2.4rem);
  line-height: 1.08;
  letter-spacing: -.01em;
}

.panel {
  padding: var(--pad);
  border-radius: var(--radius);
  background: url("dust.svg"), var(--panel);
}
.panel > :first-child { margin-top: 0; }
.panel > :last-child { margin-bottom: 0; }
.panel p, .panel li { text-wrap: pretty; }
.panel h2 { position: relative; margin: 2.25em 0 .5em; font-size: 1.4rem; line-height: 1.2; }
/* One lit pixel hangs in the margin beside each section heading. */
.panel h2::before {
  content: "";
  position: absolute;
  left: calc(-.5 * var(--pad) - .16em);
  top: .42em;
  width: .32em;
  height: .32em;
  background: var(--spark);
}
.panel h3 { margin: 1.75em 0 .4em; font-size: 1.15rem; }
.panel a { color: var(--ink); text-decoration-color: var(--chip); text-decoration-thickness: 2px; text-underline-offset: .2em; }
.panel a:hover { text-decoration-color: var(--ink); }
.panel blockquote { margin: 1.5em 0; padding-left: 1.25rem; border-left: var(--rule) solid var(--spark); font-style: italic; }
.panel hr { margin: 2.5em auto; width: 3rem; border: 0; border-top: var(--rule) solid var(--ink); }
.panel code { font: .9em/1.4 ui-monospace, monospace; background: rgb(255 255 255 / .35); padding: .1em .3em; border-radius: .3em; }
.panel pre { overflow-x: auto; padding: 1rem 1.25rem; border-radius: 1rem; background: rgb(255 255 255 / .35); }
.panel pre code { background: none; padding: 0; }
.panel img { max-width: 100%; height: auto; border-radius: 1rem; }

/* Branch: an optional detail page, wired to the main line by a trace from the heading-pixel column. */
.branch-wrap { position: relative; margin: 2em 0; padding-left: 1.5rem; }
.branch-wrap::before {
  content: "";
  position: absolute;
  left: calc(-.5 * var(--pad));
  width: calc(.5 * var(--pad) + 1.5rem);
  top: 50%;
  height: 2px;
  translate: 0 -50%;
  background: var(--ink);
  transition: background-color .2s;
}
.branch-wrap::after {
  content: "";
  position: absolute;
  left: calc(-.5 * var(--pad) - 3.5px);
  top: 50%;
  width: 7px;
  height: 7px;
  translate: 0 -50%;
  background: var(--spark);
}
.branch-wrap:has(.branch:hover)::before { background: var(--spark); }
.panel .branch {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  gap: .1rem;
  max-width: 100%;
  padding: .7rem 2.75rem .75rem 1rem;
  border: 2px solid var(--ink);
  border-radius: .75rem;
  color: var(--ink);
  text-decoration: none;
}
.panel .branch::after { content: "→"; position: absolute; right: 1rem; top: 50%; translate: 0 -50%; font-weight: 700; transition: translate .2s; }
.panel .branch:hover::after { translate: .3rem -50%; }
.branch-title { font-weight: 700; line-height: 1.25; }

/* Branch page: the dateline names the parent, and a link at the foot returns to the card. */
.branch-page .date a, .return a { color: var(--ink); text-decoration-color: var(--chip); text-decoration-thickness: 2px; text-underline-offset: .2em; }
.return { margin: 1.5rem 0 0; font-weight: 600; }
.return a::before { content: "← "; }

/* Bent spoon: an inline icon in the text colour, so it follows the theme. */
.spoon {
  display: inline-block;
  height: 1.3em;
  aspect-ratio: 57.7 / 84.2;
  margin-left: .2em;
  vertical-align: -.25em;
  background: currentColor;
  mask: url("spoon.svg") center / contain no-repeat;
}

/* End mark: a tiny chip closes every post. */
.end {
  display: inline-block;
  width: .75em;
  height: .75em;
  margin-left: .4em;
  vertical-align: -.05em;
  background: url("chip.svg") center / contain no-repeat;
}

/* Wide tables scroll inside the panel instead of widening the page. */
.table-wrap { overflow-x: auto; margin: 1.5em 0; }
table { border-collapse: collapse; width: 100%; font-size: .95rem; font-variant-numeric: tabular-nums; }
th, td { padding: .5rem .75rem; text-align: left; vertical-align: top; border-bottom: 1px solid var(--backdrop); }
th { font-weight: 700; border-bottom: 2px solid var(--ink); }

/* Static line chart, drawn by build.py from a note's table. The second line takes the accent;
   the light theme needs a darker step of it to stand off the panel. */
.chart { margin: 1.75em 0; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; font-variant-numeric: tabular-nums; }
.chart .tick, .chart .end-value { font-size: 12px; fill: var(--muted); }
.chart .end-value { fill: var(--ink); font-weight: 600; }
.chart .grid { stroke: var(--ink); stroke-opacity: .12; }
.chart .base { stroke: var(--ink); stroke-width: 1.5; }
.chart .line { fill: none; stroke-width: 2.5; stroke-linejoin: round; }
.chart .s0 { stroke: var(--ink); fill: var(--ink); }
.chart .s1 { stroke: var(--series-2); fill: var(--series-2); }
.chart .line.s0, .chart .line.s1 { fill: none; }
.chart .hit { fill: transparent; }
.chart .hit:hover { fill: var(--ink); fill-opacity: .06; }
.chart-keys { display: flex; flex-wrap: wrap; gap: .25rem 1.25rem; margin-bottom: .5rem; font-size: .85rem; font-weight: 600; }
.chart-keys .key::before { content: ""; display: inline-block; width: 1rem; height: 3px; margin-right: .4rem; vertical-align: .25em; background: var(--ink); }
.chart-keys .key.s1::before { background: var(--series-2); }
/* The SVG scales with the column, so its text needs a boost on a phone. */
@media (max-width: 30rem) { .chart .tick, .chart .end-value { font-size: 16px; } }
.chart-caption { margin: .5rem 0 0; font-size: .75rem; line-height: 1.4; color: var(--muted); }

/* Index: each entry is the logo blown up. A dark rounded edge holds a square-shouldered die;
   the date is silkscreened on the edge, and the logo's two pixels sit in the die's corner. */
.posts { list-style: none; margin: 0; padding-block: 3rem 4rem; padding-inline: 0; display: grid; gap: 2rem; }
.chipcard {
  --edge: .55rem;
  display: block;
  padding: 0 var(--edge) var(--edge);
  border-radius: var(--radius);
  background: var(--chip-ink);
  color: var(--ink);
  text-decoration: none;
}
.silk {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: .35rem 1rem;
  margin: 0;
  padding: .7rem calc(var(--pad) - var(--edge)) .6rem;
  font-size: .8rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #bbbfbf;                   /* the edge is dark in both themes, so the print stays light */
}
.silk b { font-weight: 700; color: var(--spark); }
.die {
  position: relative;
  padding: calc(var(--pad) - var(--edge)) calc(var(--pad) - var(--edge)) calc(var(--pad) + .5rem);
  border-radius: .2rem .2rem calc(var(--radius) - var(--edge)) calc(var(--radius) - var(--edge));
  background: url("dust.svg"), var(--panel);
}
.die h2 {
  margin: 0 0 .6rem;
  font-size: clamp(1.6rem, 1.3rem + 1.5vw, 2rem);
  line-height: 1.1;
  letter-spacing: -.01em;
}
.die p { margin: 0; color: var(--muted); text-wrap: pretty; }
.chipcard:hover h2 { text-decoration: underline; text-decoration-color: var(--chip); text-decoration-thickness: 3px; text-underline-offset: .15em; }
.pins { position: absolute; right: calc(var(--pad) - var(--edge)); bottom: 1rem; display: flex; }
.pins i { width: .6rem; height: .6rem; background: var(--chip-ink); }
.pins i:first-child { background: var(--spark); }
/* Hover flips the bit, as on the brand. */
.chipcard:hover .pins i:first-child { animation: flip-a .5s steps(1) 1; }
.chipcard:hover .pins i:last-child { animation: flip-b .5s steps(1) 1; }
:root[data-theme="dark"] .pins i:first-child { filter: drop-shadow(0 0 2px var(--spark)); }

/* Scroll-driven chart (see scrolly.js). It pins under the header while its section scrolls past,
   and stays hidden without JavaScript. */
.scrolly-chart { display: none; }
.scrolly.ready .scrolly-chart {
  display: block;
  position: sticky;
  top: 3.1rem;
  z-index: 1;
  margin: 0 calc(-1 * var(--pad)) 1.5rem;
  padding: 1rem var(--pad) .75rem;
  background: url("dust.svg"), var(--panel);
  /* Covers the gap up to the header, whose height varies a little. */
  box-shadow: 0 -1rem 0 var(--panel);
}
/* Text fades as it slides under the chart. */
.scrolly.ready .scrolly-chart::after {
  content: "";
  position: absolute;
  inset: 100% 0 auto;
  height: 1.5rem;
  background: linear-gradient(var(--panel), transparent);
  pointer-events: none;
}
.scrolly-chart svg { display: block; overflow: visible; font-variant-numeric: tabular-nums; }
.scrolly-chart .axis text, .scrolly-chart .tick { font-size: 12px; fill: var(--muted); }
.scrolly-chart .axis line { stroke: var(--muted); }
.scrolly-chart .axis .base { stroke: var(--ink); stroke-width: 1.5; }
.scrolly-chart .grid { stroke: var(--ink); stroke-opacity: .12; }
/* Lines build up scene by scene on one fixed scale. A line draws itself in the first time it
   shows; once drawn (see scrolly.js) it only fades, so scrolling back never replays it. */
.scrolly-chart .series { opacity: 0; transition: opacity .4s; }
.scrolly-chart .series.on { opacity: 1; }
.scrolly-chart .line, .scrolly-chart .partial { fill: none; stroke: var(--chip); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.scrolly-chart .critical :is(.line, .partial) { stroke: var(--critical); }
.scrolly-chart .line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.scrolly-chart .series.on .line { stroke-dashoffset: 0; transition: stroke-dashoffset 1.6s cubic-bezier(.4, 0, .2, 1); }
.scrolly-chart .series.drawn .line { stroke-dashoffset: 0; transition: none; }
.scrolly-chart .partial { stroke-dasharray: 3 4; stroke-linecap: butt; }
.scrolly-chart .partial, .scrolly-chart .now { opacity: 0; }
.scrolly-chart .series.on :is(.partial, .now) { opacity: 1; transition: opacity .3s 1.5s; }
.scrolly-chart .series.drawn :is(.partial, .now) { opacity: 1; transition: none; }
.scrolly-chart .now { fill: var(--ink); }
.scrolly-chart .critical .now { fill: var(--critical); }
/* The key holds a place for every line from the start, so nothing shifts when one arrives. */
.scrolly-keys { display: flex; gap: 1.25rem; margin-bottom: .4rem; font-size: .8rem; font-weight: 600; }
.scrolly-keys .key { opacity: 0; transition: opacity .4s; }
.scrolly-keys .key.on { opacity: 1; }
.scrolly-keys .key::before { content: ""; display: inline-block; width: 1rem; height: 3px; margin-right: .4rem; vertical-align: .25em; background: var(--chip); }
.scrolly-keys .key.critical::before { background: var(--critical); }
.scrolly-chart figcaption { margin-top: .6rem; min-height: 2.8em; font-size: .95rem; line-height: 1.4; transition: opacity .2s; }
.scrolly-chart figcaption.swap { opacity: 0; }
.scrolly-chart .source { margin: .3rem 0 0; font-size: .75rem; line-height: 1.4; color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* Chip dust stirred by hovering a link (see dust.js). */
.speck {
  position: fixed;
  z-index: 10;
  width: 2px;
  height: 2px;
  margin: -1px 0 0 -1px;
  background: var(--spark);
  box-shadow: 0 0 6px 1px color-mix(in srgb, var(--spark) 45%, transparent);
  pointer-events: none;
}
