:root {
  --font-sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Obsidian light theme: Foundations/Colors (subset) */
  --color-base-00: #ffffff;
  --color-base-20: #f6f6f6;
  --color-base-30: #e4e4e4;
  --color-base-60: #707070;
  --color-base-70: #5c5c5c;
  --color-base-100: #222222;
  --color-accent: hsl(258, 88%, 66%);
  --color-accent-hover: hsl(258, 88%, 58%);

  /* Okabe & Ito colorblind-safe palette, same values as the source site */
  --color-orange: #e69f00;
  --color-skyblue: #56b4e9;
  --color-green: #009e73;
  --color-blue: #0072b2;
  --color-vermillion: #d55e00;
  --color-purple: #cc79a7;
  --color-yellow: #7a6a00;
  --color-black: #000000;
  --color-teal: #106b6b;
  --color-indigo: #5b3fa0;
  --color-lime: #218807;
  --color-magenta: #9329a3;

  --bg: var(--color-base-00);
  --fg: var(--color-base-100);
  --muted: var(--color-base-60);
  --border: var(--color-base-30);
  --code-bg: var(--color-base-20);

  /* A separate, colorblind-safe palette for code/token highlighting only.
     Derived from the Okabe & Ito colors above, but several are darkened
     because the originals (orange/skyblue/green/vermillion/purple/lime)
     fall below AA contrast (4.5:1) at normal text size against --code-bg. */
  --syntax-muted: #5c5c5c;
  --syntax-keyword: #0072b2;
  --syntax-builtin: #106b6b;
  --syntax-function: #aa4b00;
  --syntax-decorator: #a53f77;
  --syntax-type: #1674a9;
  --syntax-string: #007e5c;
  --syntax-number: #5b3fa0;
  --syntax-preproc: #7a6a00;
  --syntax-escape: #1e7a06;
  --syntax-prompt: var(--color-accent-hover);

  --font-text-size: 15px;
  --line-height-normal: 1.5;
  --line-height-tight: 1.3;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--font-text-size);
  line-height: var(--line-height-normal);
  -webkit-text-size-adjust: 100%;
}

main {
  max-width: 40rem;
  margin: 0 auto;
  padding: 3rem 1.25rem 4rem;
}

/* A small "known rough edge" marker. build.py's wrap_bug_banners() merges
   each <!-- ADD BUG BANNER HERE --> placeholder with the paragraph right
   after it into one row, so the emoji can float out into the left margin,
   vertically centered against that paragraph. Below 900px there's no margin
   to float into, so it stacks above the paragraph instead. */
.bug-row {
  position: relative;
}
.bug-emoji {
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translate(-2rem, -50%);
  font-size: 1.4rem;
  line-height: 1;
}

@media (max-width: 900px) {
  .bug-row {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.35rem;
    margin: 1rem 0;
  }
  .bug-row p {
    margin: 0;
  }
  .bug-emoji {
    position: static;
    transform: none;
    flex: none;
  }
}

/* End-to-end loop animation (the fib(33) example): a sequence diagram
   between the User/SDK/Inference Engine/LLM lifelines. The 8 <g
   class="evt evt-N"> arrow groups (N = 0..7, one per stage) fade in and
   then STAY, so the diagram progressively builds itself up over one 13s lap
   — a 1s blank beat with no arrows, then 8 stages at 1.5s each. Unlike a
   shared-keyframes-plus-delay trick, each evt-N has its OWN @keyframes
   (evt-reveal-0..7) that reveals it at a fixed point on the SAME 13s
   timeline and holds it until 100% — since none of them use
   animation-delay, they all hit that 100%->0% reset at the exact same
   instant, so every arrow clears together (leaving that blank beat) right
   before the whole diagram replays from the beginning, instead of each one
   clearing on its own staggered schedule. */
.loop-anim {
  margin: 2rem 0;
}
.loop-svg-wrap {
  overflow-x: auto;
}
.loop-svg {
  display: block;
  width: 100%;
  max-width: 680px;
  min-width: 560px;
  height: auto;
  margin: 0 auto;
}

/* Below 900px the frozen-final-frame diagram (see the .evt rule further
   down) is read as a static figure rather than scrubbed through via
   horizontal scroll, so let it shrink below 560px to fit the viewport
   instead of forcing the scrollbar. */
@media (max-width: 900px) {
  .loop-svg {
    min-width: 0;
  }
}
.lane-box {
  fill: none;
  stroke: var(--border);
}
.lane-label {
  fill: var(--fg);
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 12px;
}
.lifeline {
  stroke: var(--border);
  stroke-width: 2;
  stroke-dasharray: 5 5;
}
.loop-arrowhead-fill {
  fill: none;
  stroke: var(--muted);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.evt {
  opacity: 0;
}
.evt-0 { animation: evt-reveal-0 13s infinite; }
.evt-1 { animation: evt-reveal-1 13s infinite; }
.evt-2 { animation: evt-reveal-2 13s infinite; }
.evt-3 { animation: evt-reveal-3 13s infinite; }
.evt-4 { animation: evt-reveal-4 13s infinite; }
.evt-5 { animation: evt-reveal-5 13s infinite; }
.evt-6 { animation: evt-reveal-6 13s infinite; }
.evt-7 { animation: evt-reveal-7 13s infinite; }
.evt-arrow {
  stroke: var(--muted);
  stroke-width: 2.6;
  stroke-linecap: round;
}
/* fill/fill-opacity/text-decoration on SVG elements turned out to render
   inconsistently across browsers (fine in Safari, an opaque black bar in
   Firefox, no matter how the color was spelled) — so each arrow label is a
   real HTML <a> in a <foreignObject> instead of SVG <text>, styled by the
   exact same a { } rule as the rest of the document (below), which every
   browser already renders identically and correctly. */
.evt-fo {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.evt-fo-link {
  font-family: var(--font-mono);
  font-size: 12px;
  white-space: nowrap;
}

@keyframes evt-reveal-0 {
  0%, 7.6% { opacity: 0; }
  7.692% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-1 {
  0%, 19.1% { opacity: 0; }
  19.231% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-2 {
  0%, 30.7% { opacity: 0; }
  30.769% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-3 {
  0%, 42.2% { opacity: 0; }
  42.308% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-4 {
  0%, 53.7% { opacity: 0; }
  53.846% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-5 {
  0%, 65.3% { opacity: 0; }
  65.385% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-6 {
  0%, 76.8% { opacity: 0; }
  76.923% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes evt-reveal-7 {
  0%, 88.3% { opacity: 0; }
  88.462% { opacity: 1; }
  99.9% { opacity: 1; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .evt {
    animation: none !important;
    opacity: 1;
  }
}

/* On narrow (mobile) screens the animated build-up is hard to follow at the
   diagram's cramped scaled-down size, so freeze it on its final frame — all
   stages revealed at once — instead of looping. */
@media (max-width: 900px) {
  .evt {
    animation: none;
    opacity: 1;
  }
}

/* Reading-progress bar, updated by the scroll listener in render_html(). */
#scroller {
  height: 0.4rem;
  width: 100%;
  position: fixed;
  left: 0;
  top: 0;
  background-color: var(--border);
  --scroller-width: 100%;
  z-index: 10;
}

#scroller::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: var(--scroller-width);
  background-color: var(--color-accent);
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
  transition: width 0.1s ease-out;
}

.header-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 0 1.5rem;
}

h1, h2, h3, h4, h5 {
  font-family: var(--font-sans);
  font-weight: 600;
  line-height: var(--line-height-tight);
  color: var(--fg);
}

h1 { font-size: 20px; margin: 0; }
h2 { font-size: 17px; margin: 2rem 0 0.5rem; }
h3 { font-size: 15px; margin: 1.5rem 0 0.25rem; }
h4 { font-size: 14.5px; margin: 1.25rem 0 0.25rem; }
h5 { font-size: 14px; margin: 1rem 0 0.25rem; }

.fold-arrow {
  display: inline-block;
  margin-right: 0.35em;
  color: var(--color-accent);
  cursor: pointer;
  user-select: none;
}
.fold-arrow:hover { color: var(--color-accent-hover); }
.fold-arrow:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 2px;
}

.fold.collapsed > :not(:first-child) { display: none; }
.fold.collapsed > :first-child::after {
  content: " \2026";
  color: var(--muted);
  font-weight: 400;
}

.meta {
  margin: 0;
  min-width: 0;
  color: var(--muted);
  text-align: right;
}

p, ul, ol { margin: 1rem 0; }
/* Adjacent margins collapse to the larger value, so both sides of the
   paragraph/list junction need shrinking, not just the list's top margin. */
p + ul, p + ol { margin-top: 0.35rem; }
p:has(+ ul), p:has(+ ol) { margin-bottom: 0.35rem; }

a {
  color: var(--fg);
  text-decoration-line: underline;
  text-decoration-color: color-mix(in srgb, var(--color-orange) 38%, transparent);
  text-decoration-thickness: 0.6em;
  text-underline-offset: -0.42em;
  text-decoration-skip-ink: none;
}
a:hover { text-decoration-color: var(--color-orange); }

code {
  font-family: var(--font-mono);
  background: var(--code-bg);
  padding: 0.15em 0.4em;
  border-radius: 4px;
}

pre {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem;
  font-size: 10pt;
  line-height: 125%;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

pre code {
  background: none;
  padding: 0;
}

/* The jinja snippet is long and annotated, so it gets a smaller font than
   other code blocks (see render_jinja_block / JINJA_FENCE_RE in build.py). */
.jinja-code {
  font-size: 8pt;
}

blockquote {
  margin: 1rem 0;
  padding: 4px 1rem;
  border-left: 3px solid var(--border);
  color: var(--muted);
}

hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 2.25rem 0;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.5rem 0;
}

th, td {
  text-align: left;
  padding: 8px 1rem;
  vertical-align: middle;
}

thead th {
  font-size: 13px;
  font-weight: 400;
  border-bottom: 2px solid var(--fg);
}

img { max-width: 100%; }

del {
  color: var(--muted);
  text-decoration-thickness: 0.15em;
  font-weight: 600;
}

@media (max-width: 480px) {
  main { padding: 2rem 1rem 3rem; }
  .header-row { flex-wrap: wrap; }
  .meta { text-align: left; }
}

/* Pygments (codehilite) syntax colors: the --syntax-* palette above, kept
   separate from the page's --color-* theme palette. */
.codehilite .c, .codehilite .c1, .codehilite .cm, .codehilite .ch, .codehilite .cs { color: var(--syntax-muted); font-style: italic; }
.codehilite .cp { color: var(--syntax-preproc); }
.codehilite .k, .codehilite .kn, .codehilite .kd, .codehilite .kc, .codehilite .kr, .codehilite .kp { color: var(--syntax-keyword); font-weight: 600; }
.codehilite .kt { color: var(--syntax-type); }
.codehilite .nb, .codehilite .bp { color: var(--syntax-builtin); }
.codehilite .nf, .codehilite .fm { color: var(--syntax-function); }
.codehilite .nc { color: var(--syntax-function); font-weight: 600; }
.codehilite .nd { color: var(--syntax-decorator); }
.codehilite .ne { color: var(--syntax-function); font-weight: 600; }
.codehilite .s, .codehilite .s1, .codehilite .s2, .codehilite .sd, .codehilite .sa, .codehilite .sb, .codehilite .dl, .codehilite .sh, .codehilite .sx { color: var(--syntax-string); }
.codehilite .se, .codehilite .si { color: var(--syntax-escape); }
.codehilite .m, .codehilite .mi, .codehilite .mf, .codehilite .mh, .codehilite .mo, .codehilite .il { color: var(--syntax-number); }
.codehilite .o, .codehilite .ow { color: var(--fg); }
.codehilite .gp { color: var(--syntax-prompt); font-weight: 600; }
.codehilite .go { color: var(--syntax-muted); }
.codehilite .gh, .codehilite .gu { color: var(--syntax-number); font-weight: 600; }
.codehilite .gd { color: var(--syntax-function); }
.codehilite .gi { color: var(--syntax-string); }
.codehilite .gr, .codehilite .err { color: var(--syntax-function); }
.codehilite .w { color: inherit; }

/* Qwen3 tokenizer visualization: each <span> is one real token from the
   Qwen3 tokenizer. Plain content tokens are unstyled text; structural
   chat-template tokens are single added tokens in the vocabulary (rather
   than ordinary BPE merges) and get a light touch of color, grouped by
   role: <|im_start|>/<|im_end|> turn markers, <think>/</think>, and the
   <tool_call>/<tool_response> pairs. */
.qwen3-tokens .tk-special { color: var(--color-accent-hover); }
.qwen3-tokens .tk-im { color: var(--color-accent-hover); font-weight: 700; }
.qwen3-tokens .tk-think { color: var(--syntax-builtin); }
.qwen3-tokens .tk-tool { color: var(--syntax-function); }
