/* ==================================================================== *
 * widgets.css — the interactive figures.
 *
 * House rules for every widget on this site:
 *   · it works from the keyboard alone;
 *   · with animation off it still shows its final, readable state;
 *   · colour follows the site's one rule — sky is the client, mint the
 *     server, rose a failure — so a reader who learned the palette in
 *     chapter 2 can read a chapter 7 diagram without a legend.
 * ==================================================================== */

/* ---- shared controls ----------------------------------------------- */

.w { --w-gap: .5rem; }
.w-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: var(--w-gap); }
.w-ctl .sp { flex: 1 1 auto; }
.w-step {
  font-family: var(--f-code); font-size: var(--t-xs); color: var(--ink-sub);
  min-width: 4.2rem; text-align: center; font-variant-numeric: tabular-nums;
}
.w-seg { display: inline-flex; border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; }
.w-seg button {
  font: inherit; font-family: var(--f-code); font-size: var(--t-xs);
  padding: .34rem .62rem; border: 0; background: transparent;
  color: var(--ink-dim); cursor: pointer; line-height: 1;
  border-right: 1px solid var(--rule);
  transition: background .12s var(--ease), color .12s var(--ease);
}
.w-seg button:last-child { border-right: 0; }
.w-seg button:hover { background: var(--card); color: var(--ink); }
.w-seg button[aria-pressed="true"] {
  background: color-mix(in oklab, var(--sky) var(--wash), var(--card));
  color: var(--sky-ink); font-weight: 600;
}
.w-range { accent-color: var(--sky-ink); }
.w-in {
  font: inherit; font-family: var(--f-code); font-size: var(--t-sm);
  padding: .3rem .5rem; border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--card); color: var(--ink); min-width: 0;
}
.w-in:focus-visible { border-color: var(--rule-strong); }

/* ---- SVG conventions ------------------------------------------------ */

:where(.w, .cw) svg text { font-family: var(--f-code); fill: var(--ink-dim); }
:where(.w, .cw) svg .lbl-s   { font-size: 10px; letter-spacing: .04em; }
:where(.w, .cw) svg .lbl-xs  { font-size: 8.6px; letter-spacing: .06em; text-transform: uppercase; }
/* An annotation that names a function is code, not a label — it keeps its
   own case, or accept() shouts back as ACCEPT(). */
:where(.w, .cw) svg .lbl-fn  { font-size: 8.8px; letter-spacing: .02em; fill: var(--ink-sub); }
:where(.w, .cw) svg .lbl-m   { font-size: 11.5px; }
:where(.w, .cw) svg .lbl-b   { font-size: 11.5px; font-weight: 650; fill: var(--ink); }
:where(.w, .cw) svg .wire    { stroke: var(--rule-strong); stroke-width: 1; }
:where(.w, .cw) svg .wire-d  { stroke: var(--rule-strong); stroke-width: 1; stroke-dasharray: 3 4; }
:where(.w, .cw) svg .box     { fill: var(--card); stroke: var(--rule-strong); stroke-width: 1; }
:where(.w, .cw) svg .box-r   { fill: var(--recessed); stroke: var(--rule); stroke-width: 1; }

/* An actor or a segment takes its colour from one attribute, so the JS
   never writes a hex and the palette rule is enforced by CSS rather than
   by discipline. Unscoped on purpose: data-side is our own attribute, and
   requiring a `.w` ancestor silently drained the colour out of every
   diagram rendered inside a code walkthrough. */
[data-side="client"] { --c: var(--sky);   --ci: var(--sky-ink); }
[data-side="server"] { --c: var(--mint);  --ci: var(--mint-ink); }
[data-side="error"]  { --c: var(--rose);  --ci: var(--rose-ink); }
[data-side="sctp"]   { --c: var(--lilac); --ci: var(--lilac-ink); }
[data-side="neutral"]{ --c: var(--butter);--ci: var(--butter-ink); }

:where(.w, .cw) svg .seg-l   { stroke: var(--ci, var(--ink-dim)); stroke-width: 1.5; }
:where(.w, .cw) svg .seg-h   { fill: color-mix(in oklab, var(--c, var(--butter)) var(--wash-strong), var(--card));
                  stroke: color-mix(in oklab, var(--ci, var(--ink-dim)) 34%, var(--rule)); }
:where(.w, .cw) svg .seg-t   { fill: var(--ci, var(--ink)); font-weight: 650; font-size: 10.5px; }
:where(.w, .cw) svg .head    { fill: var(--ci, var(--ink-dim)); }

/* Something not yet reached in a stepped figure recedes rather than
   disappearing — the shape of the whole exchange stays visible, so you
   always know how much is left. */
:where(.w, .cw) .fade   { opacity: .16; }
:where(.w, .cw) .fade-t { transition: opacity .28s var(--ease); }
@media (prefers-reduced-motion: reduce) { :where(.w, .cw) .fade { opacity: .3; } }

/* ---- codewalk ------------------------------------------------------- */

.cw { margin: var(--sp-6) 0; border: 1px solid var(--rule); border-radius: var(--radius-lg); overflow: hidden; background: var(--card); }
.cw-hd {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);
  border-bottom: 1px solid var(--rule);
  background: color-mix(in oklab, var(--recessed) 46%, var(--card));
  font-family: var(--f-code); font-size: var(--t-xs); color: var(--ink-sub);
}
.cw-hd .file { color: var(--ink-dim); font-weight: 650; }
.cw-hd .sp { flex: 1 1 auto; }

/* The code pane needs room for the longest line in the listing — about
   62 characters — or it hands you a horizontal scrollbar on a component
   whose entire job is to be read at a glance. 1.5fr at the mono size
   below clears that without starving the note pane. */
.cw-body { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
@media (max-width: 940px) { .cw-body { grid-template-columns: minmax(0, 1fr); } }

/* Capped and scrolled vertically so a forty-line listing does not leave
   the note pane stranded beside 700px of nothing — the component scrolls
   the active line into view, so the cap costs no legibility.

   The font is a step smaller than body mono on purpose: the listings run
   to ~62 characters and this is what makes them fit the pane without a
   horizontal scrollbar. */
.cw-code {
  margin: 0; padding: var(--sp-3) 0; background: var(--recessed);
  overflow: auto;
  font-family: var(--f-code); font-size: var(--t-xs); line-height: 1.75;
  border-right: 1px solid var(--rule);
  max-height: 32rem;
}
@media (max-width: 940px) { .cw-code { border-right: 0; border-bottom: 1px solid var(--rule); max-height: 24rem; } }

.cw-ln {
  display: flex; gap: var(--sp-3); padding: 0 var(--sp-4) 0 var(--sp-3);
  white-space: pre; cursor: default; border-left: 2px solid transparent;
  min-width: max-content;   /* so a highlighted row spans the full scroll width */
}
.cw-ln[data-step] { cursor: pointer; }
.cw-ln[data-step]:hover { background: color-mix(in oklab, var(--butter) 14%, transparent); }
.cw-ln .g {
  flex: none; width: 2ch; text-align: right; color: var(--ink-sub);
  font-variant-numeric: tabular-nums; user-select: none;
}
.cw-ln.on {
  background: color-mix(in oklab, var(--butter) var(--wash-strong), var(--recessed));
  border-left-color: var(--butter-ink);
}
.cw-ln.on .g { color: var(--butter-ink); font-weight: 700; }
.cw-ln.done .g { color: var(--mint-ink); }

.cw-side { display: flex; flex-direction: column; min-width: 0; }
.cw-side .cw-note { min-height: 0; }
.cw-note { padding: var(--sp-4) var(--sp-4) var(--sp-3); font-size: var(--t-sm); flex: 1 1 auto; }
.cw-note dl { margin: 0; display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: .55rem var(--sp-3); }
@media (max-width: 1100px) and (min-width: 941px) {
  /* Between those widths the two-column note is too tight for a label
     and a sentence side by side, so the label goes above its text. */
  .cw-note dl { grid-template-columns: minmax(0, 1fr); gap: .15rem; }
  .cw-note dd { margin-bottom: .55rem; }
}
.cw-note dt {
  font-family: var(--f-code); font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; padding-top: .18em;
}
/* Prefixed, because `.plain` and `.warn` are page-level callout blocks:
   an unprefixed `dt class="plain"` inherited the whole yellow box. */
.cw-note dt.cw-what  { color: var(--sky-ink); }
.cw-note dt.cw-why   { color: var(--lilac-ink); }
.cw-note dt.cw-plain { color: var(--butter-ink); }
.cw-note dt.cw-trap  { color: var(--rose-ink); }
.cw-note dd { margin: 0; line-height: 1.55; }
.cw-note dd code { font-size: .86em; }

.cw-state {
  border-top: 1px solid var(--rule);
  background: color-mix(in oklab, var(--recessed) 34%, var(--card));
  padding: var(--sp-3) var(--sp-4);
  font-family: var(--f-code); font-size: var(--t-xs);
}
.cw-state .row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; margin-bottom: var(--sp-2); }
.cw-state .row:last-child { margin-bottom: 0; }
.cw-state .kv { color: var(--ink-sub); }
.cw-state .kv b { color: var(--ink); font-weight: 650; }
.cw-state svg { display: block; width: 100%; height: auto; }

.cw-out { border-top: 1px solid var(--rule); }
.cw-out .term { margin: 0; border: 0; border-radius: 0; }

/* ---- byte / memory cells -------------------------------------------- */

.bytes { display: flex; gap: 2px; flex-wrap: wrap; font-family: var(--f-code); }
.byte {
  min-width: 3.6rem; text-align: center; padding: .3rem .2rem;
  border: 1px solid var(--rule); border-radius: 2px;
  background: var(--card); font-size: var(--t-sm);
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.byte .a { display: block; font-size: var(--t-xs); color: var(--ink-sub); }
.byte.hi { background: color-mix(in oklab, var(--butter) var(--wash-strong), var(--card)); border-color: var(--butter-ink); }
.byte.sky { background: color-mix(in oklab, var(--sky) var(--wash), var(--card)); border-color: color-mix(in oklab, var(--sky-ink) 30%, var(--rule)); }
.byte.mint{ background: color-mix(in oklab, var(--mint) var(--wash), var(--card)); border-color: color-mix(in oklab, var(--mint-ink) 30%, var(--rule)); }
.byte.rose{ background: color-mix(in oklab, var(--rose) var(--wash), var(--card)); border-color: color-mix(in oklab, var(--rose-ink) 30%, var(--rule)); }
.byte.pad { background: var(--recessed); color: var(--ink-sub); }

/* struct field map */
.smap { display: grid; gap: 2px; font-family: var(--f-code); font-size: var(--t-sm); }
.smap .f {
  display: grid; grid-template-columns: 4.6rem 1fr; gap: var(--sp-3);
  padding: .38rem .6rem; border: 1px solid var(--rule); border-radius: 2px;
  background: var(--card); cursor: default;
  transition: background .14s var(--ease);
}
.smap .f .o { color: var(--ink-sub); font-size: var(--t-xs); white-space: nowrap; }
.smap .f:hover, .smap .f.on { background: color-mix(in oklab, var(--butter) var(--wash), var(--card)); }
.smap .f.pad { background: var(--recessed); color: var(--ink-sub); }

/* ---- filter chips ---------------------------------------------------- */

.chips { display: flex; flex-wrap: wrap; gap: .35rem; }
.chip {
  font: inherit; font-family: var(--f-code); font-size: var(--t-xs);
  padding: .28rem .55rem; line-height: 1; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 999px;
  background: var(--card); color: var(--ink-dim);
  transition: background .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.chip:hover { border-color: var(--rule-strong); color: var(--ink); }
.chip[aria-pressed="true"] {
  background: color-mix(in oklab, var(--sky) var(--wash-strong), var(--card));
  border-color: color-mix(in oklab, var(--sky-ink) 38%, var(--rule));
  color: var(--sky-ink); font-weight: 600;
}

/* A selected row in a filterable table. Butter, because the row you have
   picked is the one whose definition is showing underneath. */
.fig tbody tr.on,
.fig tbody tr.on:hover {
  background: color-mix(in oklab, var(--butter) var(--wash), transparent);
  box-shadow: inset 2px 0 0 var(--butter-ink);
}

/* A table inside a figure DOES scroll in its own box, so it is the one
   place a sticky header is correct — pinned to that box, not the page. */
.fig .tw thead th { position: sticky; top: 0; z-index: 2; }

/* ==================================================================== *
 * The codewalk earns more width than a paragraph.
 *
 * It is two panes side by side — a listing that must not wrap and a note
 * beside it — so at the prose measure the code pane lands at ~510px and
 * a 62-character line clips. Past 1400px there is room to step it out of
 * the reading column and into the space between the rails, which is
 * where the space actually is.
 *
 * The width is clamped by the rails rather than by the viewport, or a
 * wide codewalk slides underneath them.
 * ==================================================================== */
@media (min-width: 1400px) {
  .prose > .cw {
    width: min(64rem, calc(100vw - var(--rail-l) - var(--rail-r) - 4rem));
    position: relative;
    left: 50%;
    transform: translateX(-50%);
  }
}
