/* ============================================================
   panel.css — Dossier re-skin of the capability detail panels.

   The panel markup (window.SC4_PANELS_KO / _EN / _JA) is carried over
   verbatim from the deployed site so all 12 panels stay translated in
   3 languages. Everything below re-maps that markup onto the Dossier
   tokens: one accent, one radius family, hairline rules, no emoji
   ornament, no simulated window chrome.
   ============================================================ */

.capd {
  padding: 18px 8px 60px;
  max-width: 100%;
}

/* ---------- Panel header ---------- */

.capd-hd {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  padding-bottom: 20px;
  margin-bottom: 32px;
  border-bottom: 1px solid var(--rule);
}
.capd-hd h3 {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.capd-no {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--accent-text);
  letter-spacing: 0.06em;
}
/* Decorative emoji from the previous design: removed, not restyled. */
.capd-icon, .fit-icon, .rx-icon { display: none; }

.capd-cmd,
.capd-hd code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--accent-text);
  background: var(--accent-soft);
  border-radius: var(--r-sm);
  padding: 4px 8px;
}

/* ---------- Panel prose ---------- */

/* A step below the section ledes. This one opens a dense panel rather than
   a section, and at the larger size it was pushing every line into a wrap. */
.capd-lede {
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.02rem);
  line-height: 1.72;
  color: var(--ink);
  max-width: 72ch;
  margin-bottom: 20px;
}
.capd-lede b { font-weight: 600; }

/* ch is the width of a "0", and Hangul and kana run close to twice that.
   A measure that reads well in Latin therefore cuts these two languages off
   early, which is what was breaking the composed lines mid-phrase. They get
   a measure in absolute units instead. */
html:lang(ko) .capd-lede,
html:lang(ja) .capd-lede { max-width: 56rem; }

.capd-desc {
  font-size: var(--fs-sm);
  line-height: 1.72;
  color: var(--ink-2);
  max-width: 124ch;
  margin-bottom: 28px;
}
.capd-desc b, .capd-lede b { color: var(--ink); }

.capd :is(p, div, li) > code:not([class]) {
  font-family: var(--font-mono);
  font-size: 0.88em;
  color: var(--accent-text);
  background: var(--accent-soft);
  border-radius: var(--r-sm);
  padding: 0.1em 0.36em;
}

.capd h4 {
  font-size: 0.98rem;
  letter-spacing: -0.008em;
  margin-bottom: 8px;
}
.capd h5, .capd h6 {
  font-size: var(--fs-sm);
  margin-bottom: 4px;
}

/* ---------- Shared surface for every boxed group ---------- */

.setup-wrap, .agent-heads, .modules-cloud, .rules-list, .capd-flow,
.capd-xlsx, .capd-sev, .fit-wrap, .capd-combos,
.dev-flow, .defense-table, .ack-demo, .cmp-stage,
.callout,
.tagline-strong, .mechanism-note, .hard-rule, .profile {
  margin-block: 32px;
}

/* ---------- Numbered check list (#01) ---------- */

.setup-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
  gap: clamp(28px, 3.6vw, 48px);
  align-items: start;
}
@media (max-width: 860px) { .setup-wrap { grid-template-columns: 1fr; } }

.check-list { display: grid; gap: 26px; }
.check-list li {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: 14px;
  font-size: var(--fs-sm);
  line-height: 1.7;
  color: var(--ink-2);
}

/* The second clause of each item used to be a hard <br/>, which forced the
   break at a fixed point however wide the column was. It is its own line
   now, so it wraps where it should and reads as the aside it is. */
.check-note {
  display: block;
  margin-top: 5px;
  color: var(--ink-3);
}
.check-list .check {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
}
.check-list b { color: var(--ink); font-weight: 600; }

/* ---------- Transcript blocks (terminal, ack output) ---------- */

/* .ack-output is not here: it lives inside .ack-window now, which draws the
   frame. Leaving it in would have boxed the transcript twice over. */
.terminal, .cmp-window {
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-md);
  background: var(--sheet);
  overflow: hidden;
}

/* Just enough shadow to lift the transcript off the page. Two layers: a
   tight one that reads as the edge, and a wide soft one that reads as the
   distance. Dark mode gets a darker, tighter version, since a large soft
   shadow on a near-black ground turns into a grey smudge. */
.terminal {
  box-shadow:
    0 1px 1px color-mix(in srgb, var(--ink) 5%, transparent),
    0 12px 28px -16px color-mix(in srgb, var(--ink) 30%, transparent);
}
[data-theme="dark"] .terminal {
  box-shadow:
    0 1px 1px rgba(0, 0, 0, 0.4),
    0 10px 22px -14px rgba(0, 0, 0, 0.7);
}

.terminal-bar, .cmp-titlebar {
  padding: 9px 14px;
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 55%, var(--sheet));
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 8px;
}

/* No traffic lights. Three coloured circles are a picture of somebody
   else's window manager: they carry no information, they are the loudest
   colour on a page built around one accent, and they are the first thing
   on a mock terminal that reads as generic. The bar earns its place by
   naming the command that produced the transcript, which is what a real
   title bar shows and what the comparison window beside it already does. */
.terminal-bar span:not([class]), .cmp-dot { display: none; }

/* Twemoji swaps each emoji character for a remotely hosted image element.
   Without a size the browser cannot lay the line out until that file
   arrives, so every emoji reflows the panel as it loads. On the panel with
   fifteen of them that is fifteen shifts under anything trying to scroll
   the row into place. */
.capd img.emoji {
  width: 1em;
  height: 1em;
  margin: 0 0.05em 0 0.1em;
  vertical-align: -0.1em;
}

.terminal-body, .ack-output {
  padding: 20px 18px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.95;
  color: var(--ink-2);
  overflow-x: auto;
}

/* ---------- Playback ----------
   The transcript plays a line at a time when the panel opens. Beats are
   hidden with visibility rather than display, so each one keeps its width
   and every line break stays put: the box is at its final size on the first
   frame and nothing reflows, horizontally or vertically, while it runs. */
.term-beat { visibility: hidden; }
.term-beat.on { visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .term-beat { visibility: visible; }
}
.term-caret {
  display: inline-block;
  width: 7px;
  height: 1em;
  translate: 0 0.16em;
  background: var(--accent-text);
  opacity: 0.8;
}
.terminal-body.typed .term-caret {
  animation: term-blink 1.1s steps(1, end) infinite;
}
@keyframes term-blink {
  0%, 50%      { opacity: 0.8; }
  50.01%, 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .term-caret { display: none; }
}
.terminal-body .prompt { color: var(--accent-text); font-weight: 600; }
.terminal-body .cmd    { color: var(--ink); font-weight: 600; }
.terminal-body .dim, .capd .dim { color: var(--ink-3); }
.terminal-body .y, .capd .y     { color: var(--warn); }
.terminal-body .ok, .capd .ok   { color: var(--ok); font-weight: 600; }

/* ---------- Agent heads (#02) ---------- */

.agent-heads {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
}
.agent-head {
  background: var(--sheet);
  padding: 20px;
  display: grid;
  align-content: start;
  gap: 7px;
}
.agent-head .count {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  color: var(--accent-text);
}
.agent-head p {
  font-size: var(--fs-xs);
  line-height: 1.72;
  color: var(--ink-2);
}
/* The old design tinted one card yellow to stand out. One accent now. */
.agent-head.yellow .count { color: var(--ink-3); }

/* ---------- Chips ---------- */

.modules-cloud {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 20px;
  background: var(--sheet);
}

/* ---------- Diagnostic sources (#06) ----------
   These were bordered labels inside a bordered box, which drew the same
   boundary twice around the same content. The outer box is gone and the
   labels sit on a grid, so the first and last in a row meet the panel's
   edges and the space between them is even instead of being whatever the
   words happened to leave over. */
.capd-chips {
  /* The step boxes above are a bordered block with its own inner padding;
     these labels are not, so the shared 32px between them read as tighter
     than it is. */
  margin-block: 44px 32px;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@media (max-width: 820px) {
  .capd-chips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
  .capd-chips { grid-template-columns: 1fr; }
}

/* A border cannot move, so the outline is a stroked rectangle laid over the
   label. Drawing it as tiled hairline gradients instead would have worked,
   but four of them on two axes is the grid-field pattern the detector calls
   out, and it also lets a corner land mid-dash at some sizes.

   pathLength normalises the perimeter to 100 whatever the label measures,
   so the dash rhythm is identical on every chip and the travel is a whole
   number of periods: 100 divided by a period of 5 is exactly 20. */
.capd-chips .chip2 {
  --dash-lap: 2.6s;
  --dash-ink: color-mix(in srgb, var(--accent) 62%, transparent);

  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 12px 14px;
  border: 0;
  border-radius: var(--r-sm);
  /* The sheet is white in light and near-black in dark, so a share of it
     reads as a pane over the paper either way. */
  background: color-mix(in srgb, var(--sheet) 30%, transparent);
  min-width: 0;
  /* No overflow clipping here. The outline straddles the box edge, and
     hidden overflow shaved off its outer half, which left some edges as a
     sliver that rounded away to nothing at some sizes and not others. */
}
.chip-edge {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.chip-edge rect {
  fill: none;
  stroke: var(--dash-ink);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  /* Period 2.5, and the perimeter is normalised to 100, so it divides
     exactly forty times and the loop closes wherever it starts. */
  stroke-dasharray: 1 1.5;
  animation: chip-ants var(--dash-lap) linear infinite;
}
.capd-chips .chip2 i {
  flex: none;
  color: var(--accent-text);
  font-size: 17px;
  line-height: 1;
}

/* One period of travel, in one direction round the perimeter. */
@keyframes chip-ants {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -2.5; }
}
@media (prefers-reduced-motion: reduce) {
  .chip-edge rect { animation: none; }
}
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 12px;
}
.chip, .dev-pill, .capd-flag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  white-space: nowrap;
}
.chip.blue, .dev-pill {
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  background: var(--accent-soft);
  color: var(--accent-text);
}
.chip2 {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  padding: 8px 13px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--sheet);
  font-size: var(--fs-sm);
  color: var(--ink);
  white-space: nowrap;
}
.chip2 em {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}

/* ---------- Rules list ---------- */

.rules-list {
  border-left: 2px solid var(--accent);
  padding: 4px 0 4px 20px;
}
.rules-list ul { display: grid; gap: 11px; margin-top: 12px; }
.rules-list li {
  font-size: var(--fs-sm);
  line-height: 1.66;
  color: var(--ink-2);
}
.rules-list b { color: var(--ink); font-weight: 600; }

/* ---------- Pipeline flow (#03) ---------- */

.capd-flow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
}
.capd-flow-step {
  background: var(--sheet);
  padding: 18px 16px;
  display: grid;
  align-content: start;
  gap: 5px;
}
.capd-flow-step b {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.08em;
  color: var(--accent-text);
  font-weight: 500;
}
.capd-flow-step span { font-weight: 600; font-size: var(--fs-sm); color: var(--ink); }
.capd-flow-step small { font-size: var(--fs-xs); line-height: 1.7; color: var(--ink-3); }

/* ---------- Workbook output (#03) ---------- */

.capd-xlsx {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--sheet);
}
.capd-xlsx-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 13px 16px;
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 55%, var(--sheet));
  font-size: var(--fs-sm);
}
.capd-xlsx-head .icon { display: none; }
.capd-xlsx-head code { font-family: var(--font-mono); color: var(--ink); font-weight: 600; }
.capd-xlsx-head .meta { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--ink-3); }

.capd-xlsx-rows { display: grid; }
.capd-xlsx-rows li {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 12px;
  padding: 13px 16px;
  align-items: baseline;
}
.capd-xlsx-rows li + li { border-top: 1px solid var(--rule); }
.capd-xlsx-no {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-3);
}
.capd-xlsx-rows h6 { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.capd-xlsx-rows p  { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.6; }
.capd-xlsx-rows li.warn .capd-xlsx-no { color: var(--warn); }

/* The warnings row carried a warning-sign character where the others carry
   a number. A unicode symbol standing in for an icon set is the thing the
   craft floor rules out, so it is drawn from the same Phosphor set the
   safety points use. It takes the warn colour the row already sets, which
   is why the rule above still does all the colouring. */
.capd-xlsx-no i {
  font-size: 15px;
  line-height: 1;
  vertical-align: -0.1em;
}

/* ---------- Severity rows (#04) ---------- */

.capd-sev { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: var(--r-md); overflow: hidden; }
.capd-sev-row {
  background: var(--sheet);
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: 14px;
  padding: 13px 16px;
  align-items: baseline;
}
.capd-sev-tag {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--accent-text);
}
/* The finding used to read "<b>name</b> - detail" on one line, so every
   detail began wherever its own name happened to end and the three rows
   never lined up. The name is its own line now: the separator is gone and
   both parts start at the same edge, in every row and every language. */
.capd-sev-body { display: grid; gap: 3px; }
.capd-sev-body > b {
  font-size: 0.83rem;
  font-weight: 600;
  color: var(--ink);
}
.capd-sev-text { font-size: 0.83rem; color: var(--ink-2); line-height: 1.64; }

/* ---------- Review categories (#04) ----------
   This was a 760x220 SVG carried over from the previous design: a four part
   chevron in brand gradients over drop-shadowed cards, emoji standing in
   for icons, and every word drawn as SVG text at viewBox scale, so the copy
   grew and shrank with the container instead of holding a reading size and
   never picked up the page's typeface at all. It is markup now, on the same
   hairline grid the rest of the panels are built from. */
.capd-review {
  --col-gap: clamp(20px, 2.6vw, 36px);
  --dash-run: 11px;      /* one dash plus one space */
  --dash-lap: 2.6s;      /* how long the run takes to travel it */
  /* The Why connector runs its dashes in the solid accent. These are
     dividers rather than the thing being looked at, so they take the same
     colour a step back from full strength. */
  --dash-ink: color-mix(in srgb, var(--accent) 62%, transparent);

  /* Every other block on the panel is a bordered box carrying its own inner
     padding, so the shared 32px between blocks is really 32 plus that
     padding. This one gave its box up, so its content now ends flush at the
     margin and needs the difference back, most of all underneath, where the
     bare last line of a column would otherwise sit right on the border of
     the findings box. */
  margin-block: 40px 52px;

  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--col-gap);
  row-gap: 30px;
}
.capd-review-cat {
  position: relative;
  display: grid;
  align-content: start;
  gap: 10px;
}

/* The columns are held apart by a dashed run rather than by a box. It
   travels by exactly one dash period, which is what makes the loop
   seamless: after --dash-lap the pattern is back where it started and there
   is no seam to catch. Same trick as the connector in the Why section. */
.capd-review-cat::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--col-gap) / -2);
  width: 1px;
  background-image: repeating-linear-gradient(
    to bottom,
    var(--dash-ink) 0 5px,
    transparent 5px var(--dash-run)
  );
  animation: review-dash var(--dash-lap) linear infinite;
}

/* Only between columns, never down the outside edge. auto-fit would have
   made this impossible to place: the count has to be known to know which
   cells begin a row. */
.capd-review-cat:first-child::before { content: none; }

@keyframes review-dash {
  from { background-position: 0 0; }
  to   { background-position: 0 var(--dash-run); }
}

@media (max-width: 900px) {
  .capd-review { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .capd-review-cat:nth-child(odd)::before { content: none; }
  .capd-review-cat:nth-child(even)::before { content: ''; }
}

/* Stacked, the run turns horizontal and moves across instead of down. */
@media (max-width: 520px) {
  .capd-review { grid-template-columns: 1fr; row-gap: 26px; }
  .capd-review-cat::before,
  .capd-review-cat:nth-child(even)::before {
    content: '';
    top: calc(var(--col-gap) / -2);
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    height: 1px;
    background-image: repeating-linear-gradient(
      to right,
      var(--dash-ink) 0 5px,
      transparent 5px var(--dash-run)
    );
    animation-name: review-dash-x;
  }
  .capd-review-cat:first-child::before { content: none; }
}

@keyframes review-dash-x {
  from { background-position: 0 0; }
  to   { background-position: var(--dash-run) 0; }
}

@media (prefers-reduced-motion: reduce) {
  .capd-review-cat::before { animation: none; }
}
.capd-review-cat i { color: var(--accent-text); font-size: 20px; line-height: 1; }
.capd-review-cat h6 {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--ink);
  margin-bottom: 0;
}
.capd-review-cat ul { display: grid; gap: 7px; }
.capd-review-cat li {
  font-size: var(--fs-xs);
  line-height: 1.62;
  color: var(--ink-3);
}

/* ---------- Compare stage (#05) ---------- */

.cmp-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 0;
}
@media (max-width: 820px) {
  .cmp-body { grid-template-columns: 1fr; }
  .cmp-divider { border-block: 1px solid var(--rule); padding-block: 8px; }
}
.cmp-col { padding: 18px 16px; min-width: 0; }
.cmp-divider {
  display: grid;
  place-items: center;
  padding-inline: 14px;
  border-inline: 1px solid var(--rule);
}
.cmp-vs {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--ink-3);
}
.cmp-col-head {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--rule);
}
.cmp-col-tag {
  display: grid;
  place-items: center;
  width: 20px; height: 20px;
  border-radius: var(--r-sm);
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
}
.cmp-col-tag.b { background: var(--ink-3); }
.cmp-col-head code { font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.cmp-col-meta { margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--ink-3); }

.cmp-rows { display: grid; gap: 9px; }
.cmp-rows li {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) auto;
  gap: 9px;
  align-items: baseline;
  font-size: var(--fs-xs);
}
.cmp-bullet { font-size: 9px; line-height: 1.9; }
.cmp-rows li.match .cmp-bullet { color: var(--ok); }
.cmp-rows li.diff  .cmp-bullet { color: var(--warn); }
.cmp-rows li.opp   .cmp-bullet { color: var(--danger); }
.cmp-row-label { color: var(--ink-3); }
.cmp-row-val   { font-family: var(--font-mono); color: var(--ink-2); text-align: right; }

.cmp-legend, .cmp-verdict {
  padding: 14px 16px;
  border-top: 1px solid var(--rule);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: 1.7;
}
.cmp-legend-meta { color: var(--ink-3); font-family: var(--font-mono); }
.cmp-verdict-mark { color: var(--accent-text); font-weight: 600; }

/* ---------- Fit cards (#07) ---------- */

.fit-wrap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
}
.fit-card {
  background: var(--sheet);
  padding: 22px 20px;
}
.fit-card p { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.68; }
.fit-card ul { display: grid; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--rule); }
.fit-card li {
  font-size: var(--fs-xs);
  line-height: 1.62;
  color: var(--ink-2);
  padding-left: 13px;
  position: relative;
}
.fit-card li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 5px; height: 1px;
  background: var(--accent);
}

/* ---------- Reuse funnel (#08) ----------
   Four mechanisms feeding one gate. The first version of this was five
   rectangles on a plus-shaped grid, which said the four sit AROUND the
   middle but never that they FEED it; the second was overlapping circles,
   which said it but needed a square the width of the panel to hold any
   text, so the figure ended up larger than the argument.

   This says it in the one direction the claim actually runs. The four sit
   in a row, a dashed line leaves the bottom of each, the four converge on
   a single point, and the gate is what they arrive at. Reading order and
   argument order are the same line down the page.

   The connector is not authored here. An earlier pass wrote the four
   curves into the markup against a 0-100 box stretched to fit, which
   meant their start points were four constants that only held for one
   gutter width — and, worse, that a dash was a different length on every
   part of every curve. The script draws them instead, into a viewBox
   fitted to the band's real pixel box, reading the tiles for their
   centres. See fitFlowBands in app.js.

   What that buys, besides correct dashes: the gutter below is now free.
   Change it, change the column count, and the curves still leave from the
   middle of each tile. */

.reuse-flow {
  --rx-lane-gap: 2%;

  /* 7px of ink, 5px of air, everywhere in this figure. Both halves have to
     add up to --rx-period, which is one lap of the march and the unit the
     gate's outline is normalised to.

     THE UNITS ARE NOT DECORATION. stroke-dashoffset wants a length, and
     calc() cannot promote a bare number into one: written as `12`, the
     `calc(var(--rx-period) * -1)` in the keyframe below computes to the
     number -12, the declaration is dropped as invalid, and the whole
     animation silently does nothing — which is exactly what it did. With
     a viewBox fitted 1:1 to the pixel box, 1px is 1 user unit anyway, so
     the unit costs nothing and buys a declaration that parses. */
  --rx-dash: 7px 5px;
  --rx-period: 12px;
  --dash-lap: 2.6s;

  /* One ink for the whole figure. The curves take it directly and the
     gate hands it to the shared .chip-edge rule as --dash-ink, so there
     is no second place for the two to disagree about a colour. */
  --rx-ink: var(--accent);
  --rx-curve-weight: 1.15;

  /* The paragraph above runs the full width and the figure reads as one
     object under it, so the shared 32px left them looking joined. */
  margin-block: 60px 32px;
}

.rx-lanes {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--rx-lane-gap);
}

.rx-card {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--sheet);
  padding: 16px 15px;
  display: grid;
  align-content: start;
  gap: 5px;
}

/* Four of these across the panel, so they carry the smallest type in the
   figure: they are labels on a diagram, not a passage to read, and at
   full body size they were four columns of paragraph competing with the
   gate underneath. --ink-3 at 11.5px still clears AA on this paper. */
.rx-card h5 { font-size: 0.8rem; line-height: 1.32; }
.rx-card p { font-size: 0.72rem; color: var(--ink-3); line-height: 1.58; }
.rx-icon { font-size: 18px; line-height: 1; }

/* The transaction code is a tag, not a row. It inherits the panel's inline
   <code> pill, and as a grid item that pill stretched to the full column —
   a filled bar the width of the tile, which read as a header band rather
   than as the short label it is. justify-self stops the stretch; the rest
   trims the pill down to the size of the two or three characters in it. */
.rx-card > code {
  justify-self: start;
  font-size: 0.68rem;
  padding: 0.08em 0.42em;
  letter-spacing: 0.01em;
}

/* The band between the row and the gate. preserveAspectRatio="none" lets
   the viewBox stretch to whatever width the panel is, so the four lines
   always leave their column's centre and always land on the same point,
   however wide the figure gets. What stretching costs is the angle, which
   is the one property here that carries no meaning — and the hairline is
   held at 1px by non-scaling-stroke regardless.

   The lines are curves rather than straight runs. A straight diagonal
   leaves a tile from a corner of nowhere and arrives at the gate as a
   spoke; the S-curve leaves each tile going straight DOWN out of its
   underside, flattens across, and drops into the gate going straight down
   again. Both ends read as "out of here" and "into there" rather than as
   a line drawn between two dots, which is what the sketch was showing.

   That comes from where the two control points sit: the first directly
   below the start, the second directly above the finish. Nothing else in
   the path is a free choice. */
.rx-flow {
  display: block;
  width: 100%;
  height: clamp(46px, 5vw, 78px);
  overflow: visible;
}

/* One dash spec for the whole figure: the four feeds and the gate's own
   outline take these same numbers, and because both are drawn in a
   viewBox the script fitted to their real pixel box, the same numbers are
   the same length on screen. That is the only way the two can march at
   one size and one speed — normalised path lengths would make 7 mean
   something different on a 400px curve and on a 2,400px perimeter. */
.rx-flow path,
.rx-gate > .chip-edge rect {
  stroke: var(--rx-ink);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: var(--rx-dash);
  animation: rx-march var(--dash-lap) linear infinite;
}

/* One optical correction, and the only number in this figure that is not
   derived from something.

   The gate's outline is four straight runs; a straight hairline lands on
   one row of pixels and comes out at full strength. The feeds are curves,
   and a curve is spread across two rows for most of its length, so the
   same ink at the same width renders lighter — the two read as different
   colours when nothing about them differs. Weighting the curves up closes
   the gap. It is a correction for how they are drawn, not a change to
   what they are, which is why it lives here alone and not in --rx-dash.

   If they end up looking heavier than the box instead, this is the one
   value to lower. 1 turns the correction off. */
.rx-flow path { stroke-width: var(--rx-curve-weight); }
@keyframes rx-march {
  from { stroke-dashoffset: 0; }
  /* Exactly one period a lap, so the pattern lands back on itself. */
  to   { stroke-dashoffset: calc(var(--rx-period) * -1); }
}

.rx-flow path { fill: none; }

@media (prefers-reduced-motion: reduce) {
  /* The shared .chip-edge rule is the weaker selector, so it would not
     have reached the gate's outline on its own. */
  .rx-flow path,
  .rx-gate > .chip-edge rect { animation: none; }
}

/* The gate. No border of its own — the marching rectangle is its edge, and
   a solid hairline under a dashed one is the same boundary drawn twice.
   The outline is the same .chip-edge the #06 labels and the #09 tiles use,
   fitted to its real box by the script so the corner radius stays a
   radius on a shape this much wider than it is tall. */
.rx-gate {
  /* The shared .chip-edge rule paints stroke: var(--dash-ink), and it is
     the earlier, weaker selector — the rule above overrides it. This is
     here so the outline is still amber for the one frame before that
     rule's custom properties resolve, and so nothing downstream of
     .chip-edge is left reading an undefined ink and falling back to
     black. */
  --dash-ink: var(--rx-ink);

  position: relative;
  border-radius: var(--r-lg);
  /* Was the amber tint. The dashed outline is already the accent and it
     runs the whole way round, so a filled amber inside it made the block
     the loudest thing on the panel — louder than the four tiles that are
     supposed to feed it. Part of the white sheet instead: it still reads
     as a raised pane on the ivory, and the colour is left to the edge. */
  background: color-mix(in srgb, var(--sheet) 62%, transparent);
  padding: 24px 26px;
}

/* data-edge-period on the markup tells the script to normalise this one
   outline to whole 12px periods rather than to 100, which is what puts it
   in the same units as the curves above. The number there and --rx-period
   here have to agree, and the suite checks that they do. */
.rx-gate-body { display: grid; justify-items: start; gap: 9px; }

.rx-hub-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--accent-text);
}
.rx-gate h4 { color: var(--ink); line-height: 1.3; }
.rx-hub-cmd {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--accent-text);
}
/* The gate is the widest block in the figure and the bullets are its only
   list, so they run across it rather than down one edge of it. */
.rx-hub-bullets {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px 22px;
  margin-top: 4px;
  width: 100%;
}
.rx-hub-bullets li { font-size: var(--fs-xs); line-height: 1.6; color: var(--ink-2); }

.rx-callout {
  margin-top: 18px;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: 1.7;
}

/* The opening sentence is the conclusion of the whole panel and the rest
   is the evidence for it, so it gets its own line rather than running
   into the paragraph as a bold lead-in. Only the first: the emphasis
   later in the sentence is mid-clause and has to stay inline.

   Done here rather than with a <br/> in the markup because it is a
   decision about how this block is set, not about what it says — and a
   hard break would sit in three language files and break in whichever
   one the sentence wrapped differently. */
.rx-callout > b:first-child {
  display: block;
  margin-bottom: 4px;
}

/* Four tiles across stop holding their text before the panel does, and
   two rows of two cannot converge on one point — the connector assumes a
   single row. So the band goes and the figure becomes the list it always
   was underneath, in the same order. */
@media (max-width: 900px) {
  .rx-lanes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .rx-flow { display: none; }
  .rx-gate { margin-top: 12px; }
  .rx-hub-bullets { grid-template-columns: 1fr; }
}
@media (max-width: 540px) {
  .rx-lanes { grid-template-columns: 1fr; }
}

/* ---------- Industry and country references (#09, #10) ----------
   Both are laid out flat and all at once: they are lists to be scanned for
   your own line of business, and a moving list cannot be scanned.

   The fourteen industries get a field of squares rather than a row of
   labels, because a square is the one shape that says "one of a set" —
   every tile the same size, so no industry looks more supported than
   another. Seven across by two down happens to be the only pair of whole
   rows fourteen allows.

   The motion moved into the field instead. One tile at a time lights up:
   marching dashes round its edge, the amber, and a small step forward.
   That reads as the agent picking a context out of the set, which is what
   the paragraph above it claims, rather than as decoration passing by.
   The script picks which, the stylesheet says what lighting up looks
   like. */

.capd-grid {
  /* Seven across is the shape; the tile sizes itself from the column, so
     the field holds together at any width without a fixed tile anywhere. */
  --ind-cols: 7;
  /* How far a lit tile steps forward, as a ratio of itself. */
  --ind-lift: 1.055;

  /* The page's own --ease is an expo-out: nearly all of its travel in the
     first fifth, then a long crawl. That is the right curve for something
     answering a click, and the wrong one for a light coming up by itself —
     it arrives with a snap nobody asked for. This one is symmetric and
     eased at both ends, so the tile comes up and goes back down at the
     same unhurried rate. */
  --ind-ease: cubic-bezier(0.45, 0, 0.55, 1);
  --ind-fade: 900ms;

  /* Consumed by the shared .chip-edge rules further up: the same marching
     hairline the #06 labels draw, at the radius of these squares. */
  --dash-lap: 2.6s;
  /* Full strength, not a share of it: the lit tile has dropped its fill,
     so these dashes are drawn straight onto the ivory with nothing behind
     them to lift them. This is the "fills, marks, rules, and graphics"
     use the accent is reserved for — no reading depends on seeing them. */
  --dash-ink: var(--accent);

  display: grid;
  grid-template-columns: repeat(var(--ind-cols), minmax(0, 1fr));
  gap: clamp(8px, 0.9vw, 14px);
  /* The paragraph above ends in a full-width block of text, so the shared
     32px read as tighter than it measured against a field of squares. */
  margin-block: 62px 32px;
  list-style: none;
  padding: 0;
}

/* Fourteen only divides into whole rows at seven and at two, so every
   narrower count leaves a short last row. Four and three keep the tile
   big enough to read the qualifier; two would be a column, not a field. */
@media (max-width: 900px) { .capd-grid { --ind-cols: 5; } }
@media (max-width: 680px) { .capd-grid { --ind-cols: 4; } }
@media (max-width: 460px) { .capd-grid { --ind-cols: 3; } }

.ind {
  /* How much of the white sheet a resting tile keeps. Below a full sheet
     so the field sits back and one tile can come forward out of it; the
     transparency is in the box, not in the element, because fading the
     whole tile would take the type with it — see the note at .is-lit. */
  --ind-rest-fill: 52%;

  position: relative;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 8px 6px;
  border: 1px solid color-mix(in srgb, var(--rule) 68%, transparent);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--sheet) var(--ind-rest-fill), transparent);
  text-align: center;
  /* Slow on the way in and out both — this is a light coming up, not a
     hover response, and it has no click behind it to feel prompt about. */
  transition: transform var(--ind-fade) var(--ind-ease),
              border-color var(--ind-fade) var(--ind-ease),
              background-color var(--ind-fade) var(--ind-ease);
}

.ind i {
  font-size: clamp(18px, 1.6vw, 23px);
  line-height: 1;
  color: var(--ink-3);
  transition: color var(--ind-fade) var(--ind-ease);
}
.ind b {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ink);
  line-height: 1.25;
  transition: color var(--ind-fade) var(--ind-ease);
  /* The longest name is "Construction" and the narrowest tile is a third
     of a phone; letting it break is better than letting it overflow. */
  overflow-wrap: anywhere;
}
.ind em {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  line-height: 1.3;
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

/* The lit tile drops its box entirely. The solid edge and the marching
   one would otherwise draw the same boundary twice, a hairline apart, and
   the dashes are the more interesting of the two — so the box dissolves
   as they arrive and comes back as they leave. What is left is the
   content, the amber, and a moving outline standing where the box was.

   The border goes with it, not just the fill: leaving a solid 1px behind
   the dashes is the doubled boundary this was meant to avoid.

   z-index so the step forward goes over its neighbours rather than under
   whichever comes next in source order.

   Note on the fade: this is transparency in the BOX, never opacity on the
   element. The qualifier line is --ink-3, which is 4.6:1 on this ivory to
   begin with; putting the tile itself at 72% would composite it down to
   3.0:1, under AA for text that small. Fading the surface leaves every
   line of type at full strength. */
.ind.is-lit {
  transform: scale(var(--ind-lift));
  border-color: transparent;
  background: transparent;
  z-index: 1;
}
.ind.is-lit i,
.ind.is-lit b { color: var(--accent-text); }

/* The dashes are drawn on every tile and shown on one. Running them only
   while lit would start each turn from a standstill; leaving them running
   underneath means the hairline is already in motion when it appears. */
.capd-grid .chip-edge {
  opacity: 0;
  transition: opacity var(--ind-fade) var(--ind-ease);
}
.capd-grid .is-lit .chip-edge { opacity: 1; }

/* Nothing lights up and nothing steps forward. The field is still a field
   of fourteen, which is the whole of what it has to say; the picking-out
   was the part that needed motion to read, and it does not survive being
   frozen, so it goes rather than being faked. */
@media (prefers-reduced-motion: reduce) {
  .ind, .ind i, .ind b, .capd-grid .chip-edge { transition: none; }
  .ind.is-lit { transform: none; }
  .capd-grid .chip-edge { display: none; }
}

/* ---------- The country globe (#10) ----------
   A square block holding a canvas, the sixteen places as real markup, and
   one tooltip. The canvas is the picture; the list is what the picture
   means, and it stays in the document rather than living inside the
   drawing — a canvas is one opaque image to a screen reader, and sixteen
   country references that can only be found by hovering a dot are sixteen
   country references that some readers do not have.

   So the list is here, offscreen but real: read in order by assistive
   tech, and read by the script for the marker coordinates and the tooltip
   text at the same time. One source, three uses, and it comes translated
   because it sits in the language files with everything else. */

.capd-globe {
  /* The two colours the canvas paints with. They are declared here rather
     than in the script so the globe follows the theme like everything
     else does; the script reads them back and re-reads them when
     data-theme changes. */
  --globe-dot: var(--ink-3);
  --globe-mark: var(--accent);

  /* The disc the globe sits on, back to the shape it had when it went in.

     Worth recording what was learned in between, because it constrains
     anyone raising these numbers later: the pale half of this is made of
     --sheet, which is pure white, and it sits on --paper, which is
     #F6F3EC. Four percent apart. That half is therefore close to
     invisible on the ivory at any strength, and what is actually seen in
     light is --globe-halo-edge, the amber ring at 88%. In dark the sheet
     is nine steps above the ground and does the lifting instead. */
  --globe-halo: color-mix(in srgb, var(--sheet) 58%, transparent);
  --globe-halo-edge: color-mix(in srgb, var(--accent) 10%, transparent);

  position: relative;
  width: min(100%, 430px);
  aspect-ratio: 1;
  margin: 40px auto 32px;
  cursor: grab;
  touch-action: none;
}

/* closest-side, so 100% of the gradient is half the block rather than the
   distance to a corner — which puts 88% just inside the sphere's rim, the
   same 0.92 of the half-size the renderer draws to. */
.capd-globe::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle closest-side at 50% 50%,
    var(--globe-halo) 0%,
    color-mix(in srgb, var(--globe-halo) 55%, transparent) 64%,
    var(--globe-halo-edge) 88%,
    transparent 94%);
}

/* The ink is what makes the globe read on the near black: --ink-3 sits at
   4.6:1 on that ground and --ink-2 at 8.4:1, and the second is what a
   field of one-pixel dots needs to hold together as a sphere. */
[data-theme="dark"] .capd-globe {
  --globe-dot: var(--ink-2);
  --globe-halo: color-mix(in srgb, var(--sheet) 94%, transparent);
  --globe-halo-edge: color-mix(in srgb, var(--accent) 14%, transparent);
}

.globe-face {
  display: block;
  width: 100%;
  height: 100%;
}
/* grab lives on the wrapper, which is what the drag listens on: the
   labels sit over the canvas and a drag begun on one is still a drag.
   touch-action goes with it, or a touch drag scrolls the page instead. */
.capd-globe[data-dragging] { cursor: grabbing; }

/* The labels sit over the canvas as real markup. The list itself takes no
   pointer — the globe underneath has to stay grabbable between labels —
   and each label takes it back.

   Open state is a data attribute, not :hover. The script has to know which
   label is open too, because it holds the globe still while one is: a
   card that drifts out from under the cursor while you are reading it is
   the thing this whole panel exists to avoid. One attribute set in one
   place means the stylesheet and the rotation can never disagree about
   what is open. */
.globe-places {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: none;
}

/* The pill. One line, always: it is a marker on a map and it must not
   move or change size when its card opens, or the thing it is pointing at
   moves too. */
.globe-places li {
  position: absolute;              /* left/top written per frame, in % */
  z-index: 1;
  transform: translate(-50%, -50%);
  display: block;
  padding: 2px 7px;
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--sheet) 92%, transparent);
  box-shadow: var(--lift-1);
  pointer-events: auto;
  cursor: inherit;
  white-space: nowrap;
  /* opacity is written inline every frame, and without a transition on it
     the moment a label loses its place is a jump from 1 to 0 — the "it
     just vanished" of a label being crowded out. Transitioning a value
     that is rewritten each frame is a smoothing filter on it: the fade at
     the rim still tracks, and the hand-offs between labels cross-fade. */
  transition: opacity 260ms linear,
              border-color 200ms var(--ease),
              background-color 200ms var(--ease);
}
.globe-places li[data-open] {
  z-index: 3;                      /* over its neighbours while open */
  border-color: color-mix(in srgb, var(--accent) 66%, transparent);
  background: var(--sheet);
}

.globe-places li > b {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.03em;
  color: var(--accent-text);
}

/* The card is taken out of flow, and that is the whole fix for the pills
   coming out tall and lozenge-shaped.

   It used to be a second grid column that collapsed to 0fr. A zero-width
   column clips what is in it horizontally but the content still sets the
   row's HEIGHT, so every pill was as tall as its own two-or-three-line
   detail even while showing none of it. Positioned absolutely it
   contributes nothing to the pill's box at any time, open or shut.

   opacity rather than display or visibility, so the country's name and
   its requirements stay in the accessibility tree whether or not anyone
   is pointing at them. */
.globe-places li > span {
  position: absolute;
  top: 50%;
  z-index: 1;
  display: grid;
  gap: 2px;
  width: max-content;
  max-width: 14rem;
  padding: 8px 11px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: var(--r-md);
  background: var(--sheet);
  box-shadow: var(--lift-2);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%);
  transition: opacity 200ms var(--ease);
}
.globe-places li[data-side="right"] > span { left: calc(100% + 7px); }
.globe-places li[data-side="left"]  > span { right: calc(100% + 7px); }
.globe-places li[data-open] > span { opacity: 1; }

/* A grouped pill: several places that landed on the same few pixels. Its
   card lists all of them, so the rows need separating from each other in
   a way a single card's two lines do not. */
.globe-places li[data-group] > span { gap: 9px; }
.globe-places li[data-group] > span > div { display: grid; gap: 2px; }

.globe-places li > span > em,
.globe-places li > span > div > em {
  font-style: normal;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ink);
}
.globe-places li > span > i,
.globe-places li > span > div > i {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  line-height: 1.55;
  color: var(--ink-2);
  white-space: normal;
}

/* Round the back, or crowded out by a nearer one. The script writes the
   opacity — an inline style, which beats anything here — so this rule
   only has to stop a label nobody can see from catching the pointer. */
.globe-places li[data-off] { pointer-events: none; }
.globe-places li[data-off] > span { opacity: 0; }

/* Mid-drag the labels get out of the way: a drag that crosses one should
   not open it on the way past. */
.capd-globe[data-dragging] .globe-places li { pointer-events: none; }

/* Where there is no hover to open a card with, and no room to open one
   beside a pill, it opens in the middle of the globe instead.

   Shrinking the anchored card was the other way to stop it running off a
   phone, and it does not work: a label out near the rim of a 290px globe
   has under a hundred pixels beside it, and "NF-e · SPED · CFOP ·
   ICMS/IPI/PIS/COFINS" is not going to read in that at any size worth
   setting. The middle is the one place on the block that is always clear,
   and the globe is held still while a card is open anyway, so there is
   nothing there to cover.

   The card is a child of its own label, so it cannot find the middle by
   itself — the script measures the way back and leaves it in --to-mid-*.
   Both branches of data-side are restated here because those selectors
   are the more specific ones. */
@media (hover: none), (max-width: 560px) {
  .globe-places li > span,
  .globe-places li[data-side="left"] > span,
  .globe-places li[data-side="right"] > span {
    left: 50%;
    right: auto;
    top: 50%;
    width: min(17rem, 76vw);
    max-width: none;
    padding: 10px 13px;
    transform: translate(calc(-50% + var(--to-mid-x, 0px)),
                         calc(-50% + var(--to-mid-y, 0px)));
  }
  /* Bigger than the desktop card: it is being read at arm's length on a
     phone rather than glanced at under a cursor. */
  .globe-places li > span > i,
  .globe-places li > span > div > i { font-size: 0.72rem; }
}

@media (prefers-reduced-motion: reduce) {
  .globe-places li,
  .globe-places li > span { transition: none; }
}

/* ---------- Module combos (#11) ---------- */

.capd-combos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
}
.capd-combo { background: var(--sheet); padding: 18px 16px; }
.capd-combo p { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.66; margin-top: 10px; }
.capd-combo-eq {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}
.capd-combo-eq span {
  padding: 4px 8px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink-2);
}
.capd-combo-eq .arrow { border: 0; background: none; color: var(--ink-3); padding: 0; }
.capd-combo-eq .out {
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  background: var(--accent-soft);
  color: var(--accent-text);
  font-weight: 600;
}

/* ---------- Dev flow / result (#12) ---------- */

.dev-flow { display: grid; gap: 12px; }
.dev-spine { display: grid; gap: 10px; }
.dev-step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
  padding: 14px 16px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--sheet);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: 1.62;
}

/* Two columns and three children, so the paragraph was being auto-placed
   into row two of the FIRST column — the label's column. That column is
   sized `auto`, so it came out as wide as whichever paragraph the step
   happened to carry, and the four labels ended up four different widths
   with their headings starting in four different places. Both the
   heading and the paragraph belong beside the label, not under it. */
.dev-step > h4,
.dev-step > p { grid-column: 2; }

/* And the label itself is pinned to the width of "PHASE 3" — seven
   characters of the mono face — so the column is identical whatever the
   text says, in any of the three languages. */
.dev-flow .dev-pill {
  min-width: 7ch;
  justify-content: center;
}

/* A step is a caption on a diagram, not a passage: a notch under the
   panel's body size, with the paragraph a notch under that again. */
.dev-step > h4 { font-size: 0.88rem; }
.dev-step > p { font-size: 0.78rem; line-height: 1.66; }

/* On a phone the label and the text stop sharing a line.

   Seven characters of mono plus a gap is around ninety pixels, and taking
   that off a 320px screen leaves the heading a column too narrow to set
   anything in — "Parallel Include creation + batch activation" comes out
   as five words on five lines and the step is taller than it is wide.
   Below this the label sits on its own row and the text gets the full
   width, at a size that suits the width it now has. */
@media (max-width: 600px) {
  .dev-step {
    grid-template-columns: minmax(0, 1fr);
    gap: 7px;
    align-items: start;
  }
  .dev-flow .dev-pill { justify-self: start; }
  .dev-step > h4,
  .dev-step > p { grid-column: 1; }
  .dev-step > h4 { font-size: 0.8rem; margin-bottom: 0; }
  .dev-step > p { font-size: 0.72rem; }
}
.dev-result {
  border-left: 2px solid var(--accent);
  padding: 4px 0 4px 20px;
  margin-top: 22px;
}
.dev-result-line { display: flex; gap: 10px; align-items: baseline; }
.dev-result-prompt { color: var(--accent-text); font-family: var(--font-mono); }
.dev-result-text { font-size: var(--fs-lede); color: var(--ink); line-height: 1.55; }
.dev-result-sub { margin-top: 10px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.66; }

/* ---------- Callouts ---------- */

.tagline-strong, .callout, .mechanism-note, .rx-callout, .hard-rule {
  border-left: 2px solid var(--accent);
  padding: 12px 0 12px 20px;
  font-size: var(--fs-sm);
  line-height: 1.72;
  color: var(--ink-2);
}
.tagline-strong b, .callout b, .hard-rule b { color: var(--ink); font-weight: 600; }

/* ---------- Interactive acknowledge demo (#07) ---------- */

.ack-demo { display: grid; gap: 12px; }
.ack-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ack-head i { color: var(--accent-text); font-size: 16px; line-height: 1; }

/* The verdict is the one thing to read first, so it is a label rather than
   a bold word. It carries its own ground, which is why white sits on it in
   both themes; the dark values are lifted just enough to separate the label
   from the near-black sheet without dropping the text below 4.5:1. */
.ack-verdict {
  display: inline-block;
  margin-right: 7px;
  padding: 2px 7px;
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #FFFFFF;
}
.ack-verdict.pass { background: #1F6B4B; }   /* white 6.4:1 */
.ack-verdict.deny { background: #8E2A22; }   /* white 8.4:1 */
[data-theme="dark"] .ack-verdict.pass { background: #2E8654; }   /* 4.5:1 on white, 3.7:1 on the sheet */
[data-theme="dark"] .ack-verdict.deny { background: #D0463B; }   /* 4.5:1 on white, 3.7:1 on the sheet */

/* The transcript is a window like the one in #01, title bar and all. */
.ack-window {
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-md);
  background: var(--sheet);
  overflow: hidden;
  box-shadow:
    0 1px 1px color-mix(in srgb, var(--ink) 5%, transparent),
    0 12px 28px -16px color-mix(in srgb, var(--ink) 30%, transparent);
}
[data-theme="dark"] .ack-window {
  box-shadow:
    0 1px 1px rgba(0, 0, 0, 0.4),
    0 10px 22px -14px rgba(0, 0, 0, 0.7);
}

/* Swapping innerHTML puts the new verdict on screen in one frame, which
   reads as a flicker. It fades out, swaps, and rises back in instead. */
.ack-output {
  transition: opacity 200ms var(--ease), transform 200ms var(--ease);
}
.ack-output.swapping {
  opacity: 0;
  transform: translateY(3px);
}
@media (prefers-reduced-motion: reduce) {
  .ack-output { transition: none; }
  .ack-output.swapping { opacity: 1; transform: none; }
}
.ack-buttons { display: flex; flex-wrap: wrap; gap: 8px; }
.ack-btn {
  appearance: none;
  padding: 10px 14px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--sheet);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  cursor: pointer;
  transition: color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
@media (pointer: coarse) { .ack-btn { min-height: 44px; } }
.ack-btn:hover { color: var(--ink); border-color: var(--ink-3); }
.ack-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.ack-output {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.75;
  color: var(--ink-2);
}
.ack-output b { color: var(--ink); }

/* ---------- Misc leftovers from the old sheet ---------- */

/* ---------- Defense layers (#07) ----------
   These rules used to describe a grid whose children were the cells. The
   markup is a real table, so .defense-table had exactly one child, the
   table itself: it got the cell padding and the sheet, and the table inside
   it was left completely unstyled. No collapse, no cell padding, no column
   widths, which is why the columns never lined up from one row to the next.
   Styled as the table it is. */
.defense-table {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--sheet);
  overflow-x: auto;
}
.defense-table table {
  width: 100%;
  min-width: 520px;          /* below this it scrolls rather than crushes */
  border-collapse: collapse;
  /* Fixed, so the columns are decided once and hold down the table instead
     of being re-measured per row against whatever that row happens to say. */
  table-layout: fixed;
}
.defense-table th,
.defense-table td {
  padding: 12px 16px;
  text-align: left;
  vertical-align: top;
  font-size: var(--fs-xs);
  line-height: 1.62;
  color: var(--ink-2);
  border-top: 1px solid var(--rule);
}
.defense-table thead th {
  border-top: 0;
  background: color-mix(in srgb, var(--paper) 55%, var(--sheet));
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-3);
}
/* Layer, where it sits, what it does. */
.defense-table th:nth-child(1),
.defense-table td:nth-child(1) {
  width: 82px;
  font-family: var(--font-mono);
  font-weight: 600;
}
.defense-table td:nth-child(1) { color: var(--accent-text); }
.defense-table th:nth-child(2),
.defense-table td:nth-child(2) { width: 32%; }
.defense-table td:nth-child(2) { color: var(--ink); }
.profile, .profile-row { font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.66; }
.capd .subhead { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); margin-bottom: 8px; }
.capd .bad  { color: var(--danger); }
.capd .crit { color: var(--danger); font-weight: 600; }
.capd .maj  { color: var(--warn);   font-weight: 600; }
.capd .kw   { color: var(--accent-text); font-weight: 600; }
