/* ============================================================
   RUN SMART — AI Beer Judge
   Portrait kiosk. Two colour worlds: beer (amber) and water (cyan).
   The palette itself carries the verdict — that's the whole idea.
   ============================================================ */

/* ============================================================
   FONTS — self-hosted, on purpose

   Both brand files used to open with
       @import url("https://fonts.googleapis.com/css2?...&display=swap");
   which has two problems this kiosk cannot afford.

   1. It is a CDN dependency at an outdoor event. The tablet may be on a
      phone hotspot, a tent router with no upstream, or a captive portal.
      The fonts then never arrive and every screen renders in Arial — and
      nothing reports an error, because a missing webfont is not an error.

   2. `display=swap` renders the fallback FIRST even when the network is
      fine. That is what the design review saw and reported as "the current
      build is showing a fallback font": not a broken build, a real
      flash-of-unstyled-text on a chained request (the browser must fetch
      brands/<id>.css before it can even discover the @import).

   server.js has whitelisted "/fonts/" in STATIC_DIRS since the beginning —
   self-hosting was always the intent, it just never got done.

   These are VARIABLE fonts. Google serves one file per family per subset
   covering every weight, which is why there are four files and not sixteen:
   the per-weight URLs in its CSS are byte-identical (verified by hash).
   171 KB total, and `font-weight` is a RANGE so every weight in the design
   interpolates from the one file.

   `font-display: swap` is kept deliberately. With a local file the swap
   window is a frame or two, and the alternative — `block` — would show
   nothing at all if a file ever failed to load.
   ============================================================ */

@font-face {
  font-family: "Work Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/work-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext carries the Hungarian and Croatian diacritics. Subotica is a
   bilingual town; without this a single "ő" or "ć" falls back to Arial for
   that one glyph, mid-word. */
@font-face {
  font-family: "Work Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/work-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url("/fonts/source-sans-3-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url("/fonts/source-sans-3-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Beer world */
  --malt:      #180D06;
  --stout:     #2E1A0B;
  --amber:     #FFB000;
  --amber-hi:  #FFD25E;
  --foam:      #FFF4DC;

  /* Water world (child mode) */
  --water:     #12D7E8;
  --water-hi:  #8DF3FB;

  /* Semantic — flipped by [data-mode] */
  --accent:    var(--amber);
  --accent-hi: var(--amber-hi);
  --ink:       var(--malt);
  --paper:     var(--foam);

  /* ---- surfaces ----
     There are two of them: the page, and the bib card that sits on it. The
     unbranded design is dark-on-light-card, so the page inverts the card. A
     brand with a light page redeclares the first pair and leaves the second
     alone — without this split, a white-grounded brand would end up with a
     white card on a white page. */
  --ground:    var(--ink);
  --on-ground: var(--paper);
  --card:      var(--paper);
  --on-card:   var(--ink);

  /* The entrance screen is its own surface: a field of accent by default, but
     a brand may want its page colour there instead. Declaring it here means a
     brand sets a token rather than overriding a rule. */
  --idle:      var(--accent);
  --on-idle:   var(--ink);

  /* Text drawn on top of the accent colour. Dark reads best on gold; a
     saturated red wants the light end instead. */
  --on-accent: var(--ink);

  /* The wordmark. RUN is outlined and SMART. is solid by default; a brand can
     fill RUN with a colour and drop the outline by setting the weight to 0. */
  --wordmark-run:        transparent;
  --wordmark-run-stroke: currentColor;
  --wordmark-run-weight: clamp(2px, 0.6vw, 6px);
  --wordmark-smart:      currentColor;

  /* Masked marks take the surrounding text colour unless a brand says
     otherwise — a wordmark is often meant to stay its own colour. */
  --logo-colour: currentColor;

  /* Secondary text on each surface. Mixed toward its own background by
     default, which reproduces the opacity these used to be drawn with; a
     brand can set an exact grey instead. */
  --muted:      color-mix(in srgb, var(--on-ground) 70%, var(--ground));
  --muted-card: color-mix(in srgb, var(--on-card) 62%, var(--card));
  --muted-idle: color-mix(in srgb, var(--on-idle) 65%, var(--idle));

  --display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --mono: "Space Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --gap: clamp(16px, 2.4vh, 34px);
  --pad: clamp(20px, 4vw, 56px);
  --radius: 4px;

  /* Weight of the display face and how airy the headings are. Brands differ
     here more than they differ in colour. */
  --display-weight: 900;
  --display-weight-light: 500;
  --display-stretch: 125%;
  --display-tracking: -0.02em;
  --btn-radius: 999px;

  /* ---- derived, do not set these in a brand file ----
     Everything translucent is mixed from the primitives above, so a brand
     only ever has to declare the handful of solid colours. */
  --ink-a05:    color-mix(in srgb, var(--ink) 5%,  transparent);
  --ink-a12:    color-mix(in srgb, var(--ink) 12%, transparent);
  --ink-a30:    color-mix(in srgb, var(--ink) 30%, transparent);
  --ink-a35:    color-mix(in srgb, var(--ink) 35%, transparent);
  --rule:       color-mix(in srgb, var(--on-ground) 14%, transparent);
  --accent-a50: color-mix(in srgb, var(--accent) 50%, transparent);
}

body[data-mode="kid"] {
  --accent: var(--water);
  --accent-hi: var(--water-hi);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  overflow: hidden;
  background: var(--ground);
  color: var(--on-ground);
  font-family: var(--display);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  overscroll-behavior: none;
}

#app { position: fixed; inset: 0; }

/* ---------- screens ---------- */
.screen {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  padding: var(--pad);
}
.screen.is-active { display: flex; }

/* ---------- shared type ---------- */
.eyebrow {
  font-family: var(--mono);
  font-size: clamp(11px, 1.5vw, 18px);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--muted-idle);
}
.eyebrow--light { color: var(--accent); opacity: 0.8; }

/* ============================================================
   IDLE
   ============================================================ */
.screen--idle {
  background: var(--idle);
  color: var(--on-idle);
  justify-content: center;
}
.screen--idle::before {
  /* faint bottle-cap ridging, sits behind everything */
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    90deg,
    var(--ink-a05) 0 2px,
    transparent 2px 14px
  );
  pointer-events: none;
}
.idle-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap);
  text-align: center;
  width: 100%;
}

.wordmark {
  display: flex;
  flex-direction: column;
  line-height: 0.82;
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  font-size: clamp(64px, 19vw, 220px);
}
.wordmark__run {
  color: var(--wordmark-run);
  -webkit-text-stroke: var(--wordmark-run-weight) var(--wordmark-run-stroke);
}
.wordmark__smart { color: var(--wordmark-smart); }

.idle-question {
  font-family: var(--display);
  font-weight: var(--display-weight-light);
  font-stretch: 100%;
  font-size: clamp(24px, 6.5vw, 68px);
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  white-space: pre-line;   /* brand copy supplies the line break */
  opacity: 0.9;
}
.idle-question::after { content: " 🍺"; }

/* ---------- buttons ---------- */
.btn {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border: none;
  border-radius: var(--btn-radius);
  cursor: pointer;
  transition: transform 120ms ease;
}
.btn:active { transform: scale(0.96); }
.btn:focus-visible { outline: 4px solid var(--accent-hi); outline-offset: 6px; }

.btn--start, .btn--shoot, .btn--again {
  font-size: clamp(22px, 5.4vw, 54px);
  padding: clamp(18px, 2.6vh, 34px) clamp(48px, 12vw, 120px);
}
.btn--start {
  background: var(--ink);
  color: var(--accent);
  animation: pulse 2.6s ease-in-out infinite;
}
.btn--shoot, .btn--again {
  background: var(--accent);
  color: var(--on-accent);
}
.btn--ghost {
  background: none;
  color: var(--muted);
  font-size: clamp(13px, 2vw, 20px);
  font-weight: 700;
  padding: 12px 24px;
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.035); }
}

/* ---------- partners ---------- */
/* The company mark, alone at the top of the entrance screen. Larger than the
   partner row below it, because it is the host, not a credit. */
.brandmark {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(15px, 2.6vw, 26px);
  letter-spacing: 0.14em;
  line-height: 1.3;
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-bottom: clamp(4px, 1.2vh, 16px);
}
.brandmark .partner__logo,
.brandmark .partner__img {
  height: clamp(34px, 7.4vw, 78px);
}

.partners {
  display: flex;
  gap: clamp(20px, 6vw, 64px);
  align-items: center;
  margin-top: clamp(12px, 3vh, 40px);
}
.partner {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(13px, 2.2vw, 22px);
  letter-spacing: 0.14em;
  line-height: 1.3;
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
}

/* ---- logos ----
   The mark is a CSS mask filled with currentColor, not an <img>. An SVG loaded
   through <img> is an isolated document, so its currentColor resolves to black
   and stops following the brand tokens — which is exactly what we need it to
   do when a palette changes. Masking reads the alpha channel, not the colour,
   so a full-colour mark flattens to one colour here for free. `--logo` and the
   aspect ratio are set inline by whatever renders the slot. */
.partner__logo {
  display: block;
  height: clamp(20px, 3.4vw, 38px);
  width: auto;
  background-color: var(--logo-colour);
  -webkit-mask: var(--logo) center / contain no-repeat;
  mask: var(--logo) center / contain no-repeat;
}
.partner--sm .partner__logo { height: clamp(20px, 3.4vw, 40px); }

/* Marks that carry their own colours are drawn untouched, at the same sizes
   the masked ones use. */
.partner__img {
  display: block;
  height: clamp(20px, 3.4vw, 38px);
  width: auto;
}
.partner--sm .partner__img { height: clamp(20px, 3.4vw, 40px); }

.partner span {
  font-weight: 400;
  font-size: 0.62em;
  letter-spacing: 0.2em;
  opacity: 0.6;
}
.partner--sm { font-size: clamp(10px, 1.6vw, 15px); color: var(--on-card); }

.fineprint {
  font-family: var(--mono);
  font-size: clamp(9px, 1.2vw, 13px);
  letter-spacing: 0.1em;
  color: var(--muted-idle);
  margin-top: 4px;
}

/* ============================================================
   CAMERA
   ============================================================ */
.screen--camera { padding: 0; justify-content: flex-end; }
.cam-stage { position: absolute; inset: 0; overflow: hidden; background: #000; }
#video {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scaleX(-1); /* mirror: guests expect a mirror */
}
.cam-frame {
  position: absolute;
  inset: 8vh 8vw auto;
  height: 62vh;
  border: 3px dashed var(--accent-a50);
  border-radius: 200px 200px 160px 160px;
}
.cam-controls {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: var(--pad);
  background: linear-gradient(to top, var(--ground) 55%, transparent);
}
.cam-hint {
  font-family: var(--mono);
  font-size: clamp(12px, 1.8vw, 18px);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.countdown {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  font-size: 46vh;
  line-height: 1;
  color: var(--accent);
  text-shadow: 0 0 60px rgba(0, 0, 0, 0.6);
  background: var(--ink-a35);
}
.countdown.is-on { display: flex; }
.countdown span { animation: tick 1s ease-out; }
@keyframes tick {
  0%   { transform: scale(1.7); opacity: 0; }
  25%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(0.9); opacity: 0.85; }
}

.flash {
  position: fixed; inset: 0;
  background: #fff;
  pointer-events: none;
  animation: flash 420ms ease-out forwards;
  z-index: 99;
}
@keyframes flash { from { opacity: 1; } to { opacity: 0; } }

/* ============================================================
   ANALYZING
   ============================================================ */
.screen--analyzing { justify-content: center; gap: clamp(24px, 5vh, 60px); }
.scanline {
  width: 76%;
  height: 3px;
  background: var(--accent);
  box-shadow: 0 0 24px var(--accent);
  animation: sweep 1.4s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
@keyframes sweep {
  0%, 100% { transform: translateY(-3vh) scaleX(0.35); opacity: 0.4; }
  50%      { transform: translateY(3vh)  scaleX(1);    opacity: 1; }
}
.metrics { list-style: none; width: 100%; max-width: 760px; }
.metric {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: clamp(10px, 1.8vh, 20px) 0;
  border-bottom: 1px solid var(--rule);
  opacity: 0;
  transform: translateY(10px);
}
.metric.is-on { opacity: 1; transform: none; transition: all 260ms ease-out; }
.metric__name {
  font-family: var(--mono);
  font-size: clamp(13px, 2.4vw, 24px);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.metric__val {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(22px, 5vw, 48px);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   RESULT — a race bib
   ============================================================ */
.screen--result { justify-content: center; gap: clamp(14px, 2.4vh, 30px); }

.bib {
  width: 100%;
  max-width: 820px;
  background: var(--card);
  color: var(--on-card);
  padding: clamp(18px, 3vh, 34px) clamp(18px, 4vw, 44px) clamp(12px, 2vh, 22px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.6vh, 20px);
  /* notched corners, like a bib punched for safety pins */
  clip-path: polygon(
    0 22px, 22px 0, calc(100% - 22px) 0, 100% 22px,
    100% calc(100% - 22px), calc(100% - 22px) 100%, 22px 100%, 0 calc(100% - 22px)
  );
  animation: bibIn 420ms cubic-bezier(0.2, 0.9, 0.3, 1) both;

  /* Set by fitBib() in app.js, which measures this card against the screen it
     has to fit into. Defaults to 1, so the card renders exactly as designed
     wherever it already fits, and nothing here depends on JavaScript having
     run. See the measurement table in app.js for why this exists: the tallest
     result overflows every mainstream portrait tablet, and body{overflow:hidden}
     cuts the verdict and the QR off the bottom without a single visible sign.
     Scaling from the centre keeps the card centred as it shrinks.

     This is the INDEPENDENT `scale` property, not `transform: scale()`, and
     that is not a style preference. `bibIn` above animates `transform`, and an
     animation overrides the element's own `transform` for as long as it runs —
     with `fill: both` the card would ignore the fit for the first 420 ms and
     then jump. `scale`, `rotate` and `translate` are separate properties that
     compose with `transform`, so the entrance animation and the fit no longer
     fight over one declaration. */
  scale: var(--bib-scale, 1);
  transform-origin: center center;
}
@keyframes bibIn { from { opacity: 0; transform: translateY(24px); } }

.bib__head {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  border-bottom: 3px solid var(--on-card);
  padding-bottom: 8px;
}
.bib__event {
  font-weight: var(--display-weight); font-stretch: var(--display-stretch);
  font-size: clamp(18px, 3.6vw, 34px);
  letter-spacing: -0.01em;
}
.bib__year {
  font-family: var(--mono);
  font-size: clamp(9px, 1.4vw, 13px);
  letter-spacing: 0.24em;
  color: var(--muted-card);
}

.bib__photo {
  width: clamp(150px, 38vw, 300px);
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  border: clamp(4px, 0.9vw, 9px) solid var(--accent);
  background: var(--stout);
  flex-shrink: 0;
}
.bib__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

.bib__character {
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  font-size: clamp(26px, 7.4vw, 62px);
  line-height: 0.94;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.bib__character::before { content: "🏆 "; }

/* stats */
.stats { list-style: none; width: 100%; display: flex; flex-direction: column; gap: 6px; }
.stat { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: baseline; }
.stat__name {
  font-family: var(--mono);
  font-size: clamp(11px, 2vw, 19px);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.stat__val {
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(15px, 3vw, 28px);
  font-variant-numeric: tabular-nums;
}
.stat__bar {
  grid-column: 1 / -1;
  height: clamp(6px, 1vh, 11px);
  background: var(--ink-a12);
  overflow: hidden;
}
.stat__fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 700ms cubic-bezier(0.2, 0.8, 0.3, 1);
}

.bib__comment {
  font-weight: 500;
  font-size: clamp(15px, 3.4vw, 30px);
  line-height: 1.15;
  text-align: center;
  font-style: italic;
  text-wrap: balance;
  opacity: 0.85;
}
.bib__comment::before { content: "“"; }
.bib__comment::after { content: "”"; }

/* verdict stamp */
/* A rotated box still occupies its unrotated layout space, so the corners of
   the stamp hang past it and land on the neighbouring lines. The gap and the
   padding are what keep them clear, and both scale with the stamp. */
.verdict {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 3.4vw, 30px);
  padding-top: clamp(8px, 2vw, 20px);
}
.verdict__stamp {
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  font-size: clamp(28px, 8vw, 74px);
  line-height: 1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--accent);
  border: clamp(4px, 0.8vw, 8px) solid var(--accent);
  padding: clamp(6px, 1.2vh, 14px) clamp(14px, 3vw, 30px);
  transform: rotate(-6deg);
  text-align: center;
  text-wrap: balance;
  animation: stamp 520ms cubic-bezier(0.2, 1.5, 0.4, 1) 400ms both;
}
@keyframes stamp {
  from { opacity: 0; transform: rotate(-22deg) scale(2.6); filter: blur(6px); }
  to   { opacity: 1; transform: rotate(-6deg) scale(1);    filter: blur(0); }
}
.verdict__sub {
  font-family: var(--mono);
  font-size: clamp(11px, 2vw, 18px);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted-card);
  text-align: center;
}

/* The one-in-ten bonus exercise. Reuses --accent rather than declaring a new
   token: brands.test.js requires styles.css :root to declare anything a brand
   sets, and --accent already flips to --water in kid mode, which is exactly
   the behaviour this wants. */
.challenge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: clamp(6px, 1.4vh, 14px);
  padding: clamp(8px, 1.4vh, 14px) clamp(14px, 3vw, 26px);
  border: 2px dashed var(--ink-a30);
  text-align: center;
}
/* .bib children are flex items, so `hidden` alone loses to display:flex.
   Same reason .takeaway[hidden] exists. */
.challenge[hidden] { display: none; }

.challenge__label {
  font-family: var(--mono);
  font-size: clamp(10px, 1.7vw, 15px);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted-card);
}
/* Emoji through ::before, following .bib__character. Never an <img> or an
   inline <svg>: the guest's photo is the only image on a card, and
   cards.test.js counts them. */
.challenge__label::before { content: "💪 "; }

.challenge__text {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  font-size: clamp(16px, 3.6vw, 32px);
  line-height: 1.05;
  text-transform: uppercase;
  color: var(--accent);
  text-wrap: balance;
}
.challenge__sub {
  font-family: var(--mono);
  font-size: clamp(10px, 1.6vw, 14px);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-card);
}

.bib__tear {
  width: 100%;
  border-top: 3px dashed var(--ink-a30);
  margin-top: 4px;
}
.bib__foot {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}

/* ============================================================
   ERROR
   ============================================================ */
.screen--error { justify-content: center; gap: var(--gap); text-align: center; }
.error-title {
  font-weight: var(--display-weight); font-stretch: var(--display-stretch);
  font-size: clamp(28px, 7vw, 60px);
  text-transform: uppercase;
  color: var(--accent);
}
.error-body {
  font-family: var(--mono);
  font-size: clamp(13px, 2.4vw, 22px);
  line-height: 1.5;
  max-width: 26ch;
  opacity: 0.75;
}

/* ============================================================
   TAKE-HOME QR  (kiosk result screen)
   ============================================================ */
.takeaway {
  /* a <button>, so the browser's own chrome has to go */
  border: none;
  background: none;
  font: inherit;
  color: inherit;
  padding: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 2vw, 20px);
}
.takeaway[hidden] { display: none; }

.takeaway__qr {
  width: clamp(64px, 13vw, 108px);
  height: auto;
  flex-shrink: 0;
  /* The SVG is drawn in black on transparent, so it inherits the card. */
  padding: 6px;
  background: var(--paper);
  border: 2px solid var(--ink-a30);
  border-radius: var(--radius);
}
.takeaway__note {
  font-family: var(--mono);
  font-size: clamp(10px, 1.7vw, 15px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.4;
  max-width: 22ch;
  color: var(--muted-card);
}

.takeaway { cursor: pointer; }

/* ============================================================
   QR, FULL SCREEN
   Tapping the code enlarges it, because a phone camera at arm's length in
   daylight wants a bigger target than the card can spare. It carries the
   character and the verdict as well, so what is on screen while someone is
   scanning still reads as their result and not as a bare barcode.
   ============================================================ */
.qrfull {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--pad);
  background: var(--ground);
  animation: qrIn 200ms ease both;
}
.qrfull[hidden] { display: none; }
@keyframes qrIn { from { opacity: 0; } }

.qrfull__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(12px, 2.4vh, 26px);
  text-align: center;
  max-width: 720px;
}

.qrfull .brandmark { color: var(--on-ground); margin-bottom: 0; }
.qrfull .brandmark .partner__logo,
.qrfull .brandmark .partner__img { height: clamp(38px, 8vw, 84px); }

/* Always on the light end of the palette, whatever the brand ground is —
   a scanner needs the quiet zone to be light. */
.qrfull__code {
  width: min(62vw, 46vh);
  height: auto;
  padding: clamp(10px, 2.4vw, 22px);
  background: var(--paper);
  border-radius: var(--radius);
}

.qrfull__character {
  font-weight: var(--display-weight);
  font-stretch: var(--display-stretch);
  font-size: clamp(20px, 5vw, 44px);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--display-tracking);
  color: var(--on-ground);
  text-wrap: balance;
}
.qrfull__verdict {
  font-weight: var(--display-weight);
  font-size: clamp(15px, 3.4vw, 30px);
  text-transform: uppercase;
  color: var(--accent);
  text-wrap: balance;
}
.qrfull__note {
  font-family: var(--mono);
  font-size: clamp(11px, 2vw, 17px);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  text-wrap: balance;
}

/* ============================================================
   CARD PAGE  (/c/<id>, opened on the guest's own phone)
   ============================================================ */
body.card-page {
  height: auto;
  min-height: 100%;
  overflow: auto;              /* the kiosk locks scrolling; a phone must not */
  user-select: auto;
  -webkit-user-select: auto;
  background: var(--ground);
  padding: clamp(16px, 5vw, 48px) clamp(12px, 4vw, 40px);
}
.card-page__inner {
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 3vw, 24px);
}

/* The photo is the age check. On the kiosk staff see the guest standing
   there; on a phone the face on screen is what they compare against, so it
   gets more room here than it does on the kiosk. */
.card-page .bib__photo {
  width: clamp(180px, 52vw, 280px);
}
.card-page .bib { animation: none; }

.card-page__note {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(15px, 4.2vw, 21px);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--accent);
  text-wrap: balance;
}
.card-page__expiry {
  font-family: var(--mono);
  font-size: clamp(10px, 3vw, 13px);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  color: var(--muted);
}

/* ============================================================
   DEBUG
   ============================================================ */
.debug {
  position: fixed; left: 8px; bottom: 8px; z-index: 200;
  max-width: 44ch; max-height: 30vh; overflow: auto;
  font-family: var(--mono); font-size: 10px; line-height: 1.4;
  color: #7CFF9E; background: rgba(0, 0, 0, 0.8);
  padding: 8px; white-space: pre-wrap;
}

/* Not-a-real-result marker, so a rehearsal screenshot can never be mistaken
   for a real one. Rehearsal paths only: "fallback" is deliberately absent
   because it is a legitimate production path and a guest must not see this. */
body[data-source="stub"] .screen--result::before,
body[data-source="mock"] .screen--result::before {
  position: fixed; top: 0; left: 0; right: 0; z-index: 300;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.22em;
  text-transform: uppercase; text-align: center;
  padding: 5px 8px;
  color: var(--ink); background: #FF4D4D;
}
body[data-source="stub"] .screen--result::before { content: "Stubbed result — no AI was called"; }
body[data-source="mock"] .screen--result::before { content: "Mock result — no AI was called"; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Landscape fallback — the kiosk is portrait, but don't break in a demo */
@media (orientation: landscape) {
  .wordmark { font-size: clamp(48px, 9vw, 130px); }
  .bib { max-width: 62vw; }
  .bib__photo { width: clamp(120px, 14vw, 210px); }
}
