/* ============================================================
   vision-ml-demo — warm amber-orange, system fonts, no external assets.
   Same page skeleton and spacing as llm-demo and rag-demo, and an identity of
   its own: standard part 5 wants a colour, a background, a background image and
   an Analytics id that no sibling has.
   ============================================================ */

:root {
  /* 🔴 THIS SITE'S OWN IDENTITY (standard part 5a/5b), claimed by running
     `~/.nodejs_theme_register.py` and reading the TAKEN list — never chosen from
     memory. The family had violet, indigo, blue, sky, emerald, lime, pink and
     near-blacks; the orange was free, and it suits a page that draws shapes.
     The bar and the page are the same hue, so the tab and the page agree. */
  --bg: #1a0c02;
  --bg-soft: #221004;
  --surface: #26130a;
  --surface-2: #301a0e;
  --line: rgba(255, 246, 240, 0.1);
  --border-strong: rgba(255, 246, 240, 0.18);

  --text: #f7ece4;
  --muted: #c4a795;

  --accent: #f97316;
  --accent-deep: #ea580c;
  --accent-soft: rgba(249, 115, 22, 0.14);

  /* The second series on the charts, and the ring on the box being asked about.
     It is a cool colour on a warm page on purpose: an amber second line beside an
     orange first one is two lines a reader has to work to tell apart. */
  --alt: #60a5fa;
  --danger: #f87171;

  --radius: 14px;
  --container: 940px;

  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

/* `hidden` has to beat any display rule, or a control hidden in the markup comes
   back the moment a class gives it a display. */
[hidden] { display: none !important; }

html, body { margin: 0; padding: 0; }
html { background: var(--bg); scroll-behavior: smooth; }

body {
  min-height: 100vh;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  background:
    radial-gradient(1000px 560px at 10% -6%, rgba(249, 115, 22, 0.17), transparent 62%),
    radial-gradient(820px 480px at 96% 2%, rgba(96, 165, 250, 0.08), transparent 60%),
    radial-gradient(700px 420px at 50% 108%, rgba(234, 88, 12, 0.1), transparent 64%),
    linear-gradient(180deg, var(--bg) 0%, #200f04 52%, var(--bg) 100%);
  background-attachment: fixed;
  /* The cookie bar is fixed over the bottom of the page and takes the click, so the
     page leaves exactly the bar's own height free while the question is up — measured
     and written back by consent.js, and reset to zero the moment it is answered.
     Without it the footer cannot be clicked until a visitor answers a question about
     cookies, which is not a question anyone should have to answer to reach a link. */
  padding-bottom: var(--consent-height, 0px);
}

/* 🔴 THE ABSTRACT — standard part 5d-ii, and the half the family missed for weeks.
   Nine of ten sites painted a soft gradient wash and no geometry at all, and every one
   of them passed the check, because a wash is made of gradients too. George, 19 Sep 2026:
   *"the rest of the sites have lovely theme colors and gradients, but maybe missing the
   extra touch of a background abstract."*

   This site's drawing is a CHECKERBOARD — the tile a convolution walks over, which is the
   one picture that belongs on a page about a network reading images. It is its own fixed
   layer rather than an entry in body's gradient stack for one reason: a pattern has to
   FADE, and nothing fades a background image except a mask. Masked here, the squares
   dissolve before the fold, so there is no edge to see — and an edge is what part 5d is
   written against. */
.dvs-pattern {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: conic-gradient(
    rgba(249, 115, 22, 0.055) 0 25%,
    rgba(0, 0, 0, 0) 25% 50%,
    rgba(249, 115, 22, 0.055) 50% 75%,
    rgba(0, 0, 0, 0) 75% 100%
  );
  background-size: 72px 72px;
  -webkit-mask-image: radial-gradient(124% 80% at 50% 0%, #000 10%, rgba(0, 0, 0, 0) 78%);
  mask-image: radial-gradient(124% 80% at 50% 0%, #000 10%, rgba(0, 0, 0, 0) 78%);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition: none !important; animation: none !important; }
}

.skip { position: absolute; left: -9999px; top: 0; }
.skip:focus {
  left: 8px; top: 8px; z-index: 30;
  padding: 8px 12px; border-radius: 8px;
  background: var(--accent); color: #14092b; font-weight: 700;
}

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

.top {
  position: sticky; top: 0; z-index: 10;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 14px;
  padding: 12px 20px;
  /* The site's OWN near-black at 0.82 — part 5b does not stop at the page, and a bar
     carrying another site's black makes two sites look like one product. */
  background: rgba(26, 12, 2, 0.82);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}

.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--text); text-decoration: none; }
.brand .mark { color: var(--accent); display: inline-flex; }
/* 1.1 on the name keeps the bar at 60px; at the inherited 1.6 the two brand lines
   measure 42px instead of 35 and every heading below sits 7px lower. */
.brand b { display: block; font-size: 15px; letter-spacing: 0.2px; line-height: 1.1; }
.brand small {
  display: block; color: var(--muted); font-size: 11.5px;
  letter-spacing: 0.4px; text-transform: uppercase;
}
.top nav a { color: var(--muted); text-decoration: none; font-size: 14px; }
.top nav a:hover, .top nav a:focus-visible { color: var(--text); }

/* ---------- page ---------- */

main { max-width: var(--container); margin: 0 auto; padding: 32px 20px 60px; }

h1 { font-size: clamp(28px, 5vw, 40px); line-height: 1.1; letter-spacing: -0.5px; margin: 0 0 20px; }

.intro { color: var(--muted); font-size: 16px; margin: 0 0 26px; max-width: 62ch; }
.intro b { color: var(--text); }

/* ---------- the stage: one picture, one question ---------- */

.stage {
  display: grid;
  grid-template-columns: minmax(200px, 300px) 1fr;
  gap: 26px;
  align-items: start;
  padding: 24px;
  margin: 0 0 22px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(27, 16, 44, 0.72), rgba(14, 8, 23, 0.72));
}

/* The picture and the link that acts on it, stacked, so the link sits under the
   picture rather than floating in the column beside it. */
.picwrap { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.skip-image { align-self: center; }

.pic {
  position: relative;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.35);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.pic:hover, .pic:focus-visible { border-color: var(--border-strong); outline: none; }

/* ---------- the face boxes ---------- */

/* A crosshair over the picture, because dragging there makes a box — and that is not
   something anybody would guess from a pointer that looks the same everywhere. */
.boxes { position: absolute; inset: 0; z-index: 2; cursor: crosshair; }

.box {
  position: absolute;
  border: 2px solid rgba(167, 139, 250, 0.72);
  border-radius: 5px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
  cursor: pointer;
}
/* The box being asked about, and the box's number. Blue rather than the page's own
   orange, so "this is the one I am asking about" cannot be confused with "this is the
   page's colour". */
.box.current {
  border-color: var(--alt);
  box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.45);
}
.box.done { border-color: rgba(255, 255, 255, 0.3); }

/* Declined, not removed: the face is still there and still counted, but the person has
   dealt with it. Dimmed and struck through so a skip is visibly registered — an
   untouched-looking box is what made skipping look like it had done nothing. */
.box.skipped { border-color: rgba(255, 255, 255, 0.16); }
.box.skipped .box-num {
  background: rgba(255, 255, 255, 0.18);
  color: rgba(236, 230, 247, 0.55);
  text-decoration: line-through;
}

/* Inside the box, not above it: the picture is clipped to a rounded square, so
   anything hanging outside a box near the edge would be cut off. */
.box-num {
  position: absolute; left: 0; top: 0;
  min-width: 15px; padding: 0 4px;
  border-radius: 5px 0 5px 0;
  background: var(--accent); color: #14092b;
  font-size: 11px; font-weight: 700; line-height: 15px; text-align: center;
}
.box.current .box-num { background: var(--alt); }
.box.done .box-num { background: rgba(255, 255, 255, 0.32); color: #0e0817; }

.box-x {
  position: absolute; right: 0; top: 0;
  width: 17px; height: 17px; padding: 0;
  border: 0; border-radius: 0 0 0 5px;
  background: var(--danger); color: #2a0a0a;
  font: inherit; font-size: 12px; font-weight: 700; line-height: 1;
  display: grid; place-items: center; cursor: pointer;
}

.draft {
  position: absolute;
  border: 2px dashed var(--accent);
  background: var(--accent-soft);
  border-radius: 4px;
  pointer-events: none;
}

/* The drop target lights up while a picture is dragged anywhere over the page, so
   the gesture is something you can see rather than something you have to already
   know about. */
.pic.dragging {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.pic.dragging .pic-empty { color: var(--accent); }

.pic img { display: block; width: 100%; aspect-ratio: 1; object-fit: contain; background: #000; }
.pic-empty {
  aspect-ratio: 1;
  display: grid; place-items: center; align-content: center; gap: 6px;
  color: rgba(168, 156, 196, 0.6);
  text-align: center;
  padding: 14px;
}
.pic-empty p { margin: 0; font-size: 14.5px; }
.pic-empty p.small { font-size: 13px; color: var(--muted); }
.linkish { color: var(--accent); text-decoration: underline; }

/* A control that reads as a link, for the two places the page offers one as a BUTTON:
   the footer's cookie door and the settings offer inside the bar. Two of them drawn
   differently is how a page starts looking unfinished. */
.linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 13.5px;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
}
button.linkish:hover,
button.linkish:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

/* The quiet paragraphs of the policy, and any other small print. */
.muted { color: var(--muted); }
.small { font-size: 13.5px; }

.talk { min-width: 0; }

/* Where you are in a run of pictures. Small and above the answer, because it is
   orientation rather than something to read carefully. */
.queue {
  display: inline-block;
  margin: 0 0 8px;
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.2px;
}

.says { font-size: clamp(19px, 2.6vw, 24px); font-weight: 650; line-height: 1.3; margin: 0 0 6px; }
.says b { color: var(--accent); }
/* The head of an answer rather than the model speaking in its own voice: "My guess:" over
   its buttons is a label, so it is the size of the question it answers. The extra space on
   top keeps the box you type in and the guesses under it from reading as one block. */
.says.as-label { font-size: 14px; font-weight: 600; margin: 10px 0 6px; }
/* The names it can see, as buttons labelled with the names themselves, sitting under the
   line that introduces them and over the line that says what to do next. */
.guesses { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
.sub { color: var(--muted); font-size: 14.5px; margin: 0 0 16px; }

/* The answer is a list, so the box is wide and plainly labelled. */
.tell { margin: 0 0 14px; }

/* Which face, shown up close. On a group photo the box on the picture is too small to be
   sure which person is meant, and naming the wrong one is worse than useless — so it takes
   half the column. George, 2026-09-19: "the box preview should be take 50% of available
   width". Its own shape, not a square: what is shown is the box, exactly as it was cut. */
.who { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 12px; }
.who-face { display: flex; flex-direction: column; gap: 7px; align-items: center; width: 50%; flex: 0 0 auto; }
.who-face img {
  width: 100%; height: auto;
  border-radius: 10px;
  border: 2px solid var(--alt);
  object-fit: cover;
  background: #000;
}

.tell-label { display: block; color: var(--text); font-size: 14px; font-weight: 600; margin-bottom: 7px; }
.tell-row { display: flex; flex-wrap: wrap; gap: 8px; }
.tell-row input {
  flex: 1 1 240px; min-width: 0;
  padding: 11px 13px; font: inherit; font-size: 15.5px;
  color: var(--text); background: var(--surface-2);
  border: 1px solid var(--border-strong); border-radius: 10px;
}
.tell-row input::placeholder { color: rgba(168, 156, 196, 0.62); }
.tell-row input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

.answers { display: flex; flex-wrap: wrap; gap: 10px; }
.row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 14px; }

/* Sits above the stage card, right-aligned, so it reads as a footnote to the page's
   opening words rather than as one of the controls below it. */
.aside {
  margin: 0 0 10px;
  text-align: right;
  color: var(--muted);
  font-size: 13.5px;
}

/* A control that reads as a link.
   Quieter than a button on purpose: the way out of a panel should not carry the
   same weight as the thing the panel is asking you to do. */
.link {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-size: 13.5px;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.link:hover, .link:focus-visible { color: #c4b1fc; }

.btn {
  appearance: none; cursor: pointer;
  padding: 10px 16px; border-radius: 11px;
  border: 1px solid var(--border-strong);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text); font: inherit; font-size: 15px; font-weight: 600;
}
.btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #14092b; }
.btn.primary:hover:not(:disabled) { background: #b9a3fb; }
.btn.ghost { background: transparent; color: var(--muted); }
.btn.ghost:hover:not(:disabled) { color: var(--text); background: rgba(255, 255, 255, 0.05); }
.btn.danger { color: var(--danger); }
.btn.danger:hover:not(:disabled) { background: rgba(248, 113, 113, 0.12); }

/* ---------- what it knows so far ---------- */

.progress {
  margin: 0 0 22px;
  padding: 12px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.22);
  color: var(--muted); font-size: 14.5px;
}
.progress b { color: var(--text); }

/* It studies in the background, so this says so rather than leaving the page
   looking idle while the numbers quietly improve. */
.studying {
  margin: -12px 0 22px;
  padding-left: 16px;
  color: var(--accent);
  font-size: 13.5px;
}

/* The page saying something that is not a question. A line under the box rather than a
   toast floating over the page: it stays until the next thing happens, because a message
   that fades while it is being read has not been delivered. */
.note-line {
  margin: 0 0 12px;
  padding: 9px 12px;
  border-radius: 9px;
  border: 1px dashed var(--line);
  background: rgba(0, 0, 0, 0.2);
  color: var(--text);
  font-size: 13.5px;
}

/* ---------- the chart, and the rest of it ---------- */

.card {
  padding: 24px;
  margin: 0 0 22px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(20, 11, 34, 0.5);
}
/* The card's own headings, including the policy section's. */
.card h2 { font-size: 17px; font-weight: 650; margin: 0 0 2px; }
.card .note { color: var(--muted); font-size: 14px; margin: 0 0 14px; }
.card h3 { font-size: 14.5px; font-weight: 650; margin: 20px 0 6px; }
.card p { margin: 0 0 12px; }
.card p:last-child { margin-bottom: 0; }

canvas {
  display: block; width: 100%;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.25);
}
/* Heights live in CSS. Left to the height attribute the box's aspect ratio decides
   how tall it renders, which feeds back into the drawing code, because every chart
   reads clientHeight to size its backing store. */
#confusedChart { height: 140px; }
#rightChart { height: 150px; }
#knowsChart { height: 200px; }
#filtersChart, #mapsChart { height: 170px; }
/* Under the picture, so no border of its own — it should read as part of the picture
   card rather than a second panel. */
#progressChart { height: 64px; border: 0; background: transparent; }

/* The section is open on the page rather than behind a door — George, 2026-09-19: "more
   detail should always be open. remove caret". So no summary, no marker to draw, and the
   contents take the card's own padding. */

.why {
  color: var(--muted);
  font-size: 14.5px;
  padding: 14px 16px;
  border-radius: 10px;
  border: 1px dashed var(--line);
  background: rgba(0, 0, 0, 0.18);
}
.why b { color: var(--text); }

/* `min(280px, 100%)` rather than a bare 280px: inside a card on a narrow phone the
   inner width is under 280, and a track wider than its container is what pushed the
   right-hand chart past the edge of the page. Measured at a 280px viewport. */
.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 20px; }
.grid2 figure { margin: 0; }
.grid2 figcaption { color: var(--muted); font-size: 13px; margin-bottom: 6px; }

.controls { display: flex; flex-wrap: wrap; gap: 14px 22px; margin: 20px 0 0; }
.controls label { display: grid; gap: 4px; font-size: 13.5px; color: var(--muted); }
.controls input[type="number"] {
  width: 110px; padding: 8px 10px; font: inherit; font-size: 15px;
  color: var(--text); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 9px;
}
.controls label.check { display: flex; align-items: center; gap: 9px; grid-column: 1 / -1; }
.controls input[type="checkbox"] { width: 17px; height: 17px; accent-color: var(--accent); }

.status {
  margin: 18px 0 0;
  padding: 11px 14px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.22);
  color: var(--muted); font-size: 13.5px;
}

/* ---------- footer ---------- */

/* Three lines, in this order: brand · links · copyright. The brand line carries the link
   home and the links line does not repeat it — said twice in three lines, that is
   redundancy rather than a second chance. The copyright states the origin in full, which
   makes it the second place the domain appears in full. */
.site-footer {
  border-top: 1px solid var(--line);
  padding: 26px 20px 40px;
  text-align: center;
  color: var(--muted);
  font-size: 13.5px;
}
.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--muted);
  margin-bottom: 10px;
}
.footer-brand .mark { color: var(--accent); display: inline-flex; }
.footer-brand b { color: var(--text); }
.footer-brand a { color: var(--muted); }
.footer-links { margin: 6px 0; }
.footer-copy { margin: 10px 0 0; font-size: 12.5px; color: rgba(196, 167, 149, 0.75); }

/* ---------- the cookie bar ----------

   The same consent model as the rest of the family, in this page's colours. Three things
   here are deliberate and should stay that way.

   It sits in one row — the question, then the two answers — because a banner that stacks
   into paragraphs is a banner people resent. The two answers carry the page's own `ghost`
   class, so they are the same size, the same weight and the same distance from the reader:
   no accept-first order, no smaller decline, and no colour that quietly says which one is
   welcome. And the detail lives one link away rather than in the bar itself. */

.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 22px;
  padding: 13px 20px;
  background: rgba(38, 19, 10, 0.96);
  border-top: 1px solid var(--line);
  backdrop-filter: blur(8px);
  /* It is the last thing on the screen, so it must never be taller than the screen. On a
     short viewport — a phone in landscape is about 320px tall — the stacked layout could
     otherwise grow past the top edge, and the part above it is fixed to the window: it
     cannot be scrolled to and cannot be clicked. Capped and made scrollable, every part of
     the question stays reachable. `dvh` is the height the browser actually gives the page
     once its own toolbars are accounted for; `vh` stays first for anything that does not
     know it. */
  max-height: calc(100vh - 12px);
  max-height: calc(100dvh - 12px);
  overflow-y: auto;
}
.consentTitle {
  flex: 0 0 auto;
  margin: 0;
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
}
.consentText {
  flex: 1 1 300px;
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}
.consentActions {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  flex: 1 1 auto;
  gap: 10px;
}
.consentActions button {
  min-width: 0;
  padding: 10px 14px;
}
/* An anchor does not inherit body colour, so a link dropped into new markup gets the
   browser's default blue — which is out of place on this panel. Anything that wants to
   look like this site has to say so. */
.consentText a { color: var(--text); }

/* The question and the longer answer share ONE fixed bar and swap places inside it,
   rather than a second dialog opening over the first. That is not tidiness: this bar is
   the element whose height is measured and reserved at the bottom of the page, and the
   observer already follows it — so the taller view is measured like the shorter one
   instead of guessed at. */
.consentAsk {
  flex: 1 1 320px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 22px;
}
.consentPrefs { flex: 1 1 100%; }
/* The title's row, so the way out sits at the top right of the panel and not among the
   answers at the bottom. A panel with no dismiss is a consent wall: somebody who opened
   it to LOOK had to answer it again to get out. */
.consentHead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.consentClose {
  flex: 0 0 auto;
  background: none;
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--muted);
  font: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  padding: 3px 10px;
}
.consentClose:hover, .consentClose:focus-visible { color: var(--text); border-color: var(--text); }
/* The way to the longer answer, and quieter than the two answers on purpose: it is not a
   third answer to the question, and it must not read as one. */
.consentSettings { flex: 0 0 auto; color: var(--muted); }

/* EVERY PURPOSE IS NAMED, and there is exactly one real choice to name: this site has no
   strictly necessary cookie to describe, so it does not invent a row to look thorough. */
.consentList { list-style: none; margin: 8px 0 0; padding: 0; }
.consentRow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.consentRow > div { flex: 1 1 260px; min-width: 0; }
.consentRowName { margin: 0; font-size: 13px; font-weight: 600; color: var(--text); }
.consentRowWhy { margin: 2px 0 0; font-size: 12px; line-height: 1.5; color: var(--muted); }

/* The switch. Its ring is drawn from `aria-checked`, so what the eye sees and what a
   screen reader announces are the same attribute and cannot drift apart — nothing in the
   script toggles a class. The word beside it carries the state as well, because a state
   shown only in colour is a state some readers cannot see. */
.consentSwitch {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
}
.consentSwitch .consentDot { width: 10px; height: 10px; border-radius: 50%; background: var(--muted); }
.consentSwitch[aria-checked="true"] { border-color: var(--accent); color: var(--text); }
.consentSwitch[aria-checked="true"] .consentDot { background: var(--accent); }

/* ---------- narrow ---------- */

@media (max-width: 720px) {
  .stage { grid-template-columns: 1fr; gap: 18px; padding: 18px; }
  .picwrap { max-width: 260px; margin: 0 auto; width: 100%; }
  .pic { width: 100%; }
  main { padding: 24px 16px 50px; }
  .card { padding: 18px; }
}
