/* =============================================================================
   kiosk.css — the visitor-facing screens.

   Logical properties only (margin-inline-*, padding-block-*, inset-inline-*).
   No physical direction property appears in this file and none ever should: the
   entire product is RTL, and a physical side silently mirrors the layout on the
   one machine nobody is inspecting with devtools.
   ============================================================================= */

/* ------------------------------ reset / base ------------------------------ */

*,
*::before,
*::after { box-sizing: border-box; }

/* `hidden` is how every screen and overlay in this app is toggled, so it has to
   beat the display rules below rather than lose to them. */
[hidden] { display: none !important; }

html, body {
  block-size: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ar);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
  /* An unattended kiosk: a long-press that selects a paragraph or opens the
     callout menu is pure noise, and a stray text cursor never helps here. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

h1, h2, h3, p, dl, dd, ol, ul, figure {
  margin: 0;
}

ul, ol { padding: 0; list-style: none; }

img { max-inline-size: 100%; block-size: auto; }

button { font: inherit; color: inherit; }

bdi { font-family: var(--font-ltr); font-variant-numeric: tabular-nums; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* ------------------------------ boot fallback ----------------------------- */

.boot-fallback {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--gutter);
  background: var(--bg);
}

.boot-fallback__card {
  max-inline-size: 34rem;
  text-align: center;
  padding: var(--sp-7);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-md);
}

.boot-fallback__card h1 {
  font-size: var(--fs-title);
  margin-block-end: var(--sp-3);
}

.boot-fallback__card p { color: var(--ink-soft); }

/* --------------------------------- shell ---------------------------------- */

.kiosk {
  min-block-size: 100dvh;
  display: grid;
  /* minmax(0, 1fr), not the default `auto`: an auto grid column is sized to the
     widest row's MIN-CONTENT, so one unbreakable string in the header (the
     kiosk id) would widen the whole page and give the kiosk a horizontal
     scrollbar on a narrow panel. minmax(0, …) lets the column be narrower than
     its content and pushes the truncation down to the element that can handle
     it. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto;
  background:
    radial-gradient(120% 70% at 50% 0%, var(--bg-deep) 0%, transparent 62%),
    var(--bg);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-4);
  flex-wrap: wrap;
  padding-block: var(--sp-4);
  padding-inline: var(--gutter);
  border-block-end: 1px solid var(--line-soft);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-inline-size: 0;
}

.brand__mark {
  inline-size: 34px;
  block-size: 34px;
  border-radius: 9px;
  flex: none;
  background:
    linear-gradient(150deg, var(--accent) 0%, var(--accent-hover) 100%);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.14);
}

.brand__name {
  font-weight: 700;
  font-size: var(--fs-lead);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-inline-size: 0;
  color: var(--ink-mute);
  font-size: var(--fs-sm);
}

/* The terminal id is for the attendant, not the visitor: it truncates rather
   than ever competing for space with the service name. The full value stays in
   the element's title for anyone who needs to read it out. */
.badge {
  padding-block: 4px;
  padding-inline: var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  font-family: var(--font-ltr);
  letter-spacing: 0.02em;
  max-inline-size: 20ch;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.clock { font-variant-numeric: tabular-nums; }

.footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-3) var(--sp-5);
  padding-inline: var(--gutter);
  color: var(--ink-mute);
  font-size: var(--fs-sm);
}

.footer__help { margin-inline-start: auto; }

/* ----------------------------- step indicator -----------------------------
   A visitor must always know how many screens are left. Completed steps keep
   their accent so the trail behind them reads as progress, not as history.   */

.steps {
  padding-block: var(--sp-4);
  padding-inline: var(--gutter);
  border-block-end: 1px solid var(--line-soft);
  background: var(--surface);
}

.steps__list {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--sp-2);
  max-inline-size: var(--page-max);
  margin-inline: auto;
}

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding-block-start: var(--sp-2);
  color: var(--ink-mute);
  font-size: var(--fs-sm);
  text-align: center;
  /* The connector between step dots. An ::before on a flex column is the only
     piece of chrome here that has to know about direction — and it does not,
     because it is drawn as a full-width rule behind the dot. */
  position: relative;
}

.step::before {
  content: "";
  position: absolute;
  inset-block-start: calc(var(--sp-2) + 21px);
  inset-inline: -50%;
  block-size: 2px;
  background: var(--line);
  z-index: 0;
}

.step:first-child::before { inset-inline-start: 50%; }
.step:last-child::before { inset-inline-end: 50%; }

.step__num {
  position: relative;
  z-index: 1;
  inline-size: 44px;
  block-size: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--line);
  font-weight: 700;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.step__label {
  max-inline-size: 12ch;
  line-height: 1.3;
}

.step.is-done .step__num {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.step.is-done::before { background: var(--accent); }

.step.is-current { color: var(--ink); font-weight: 600; }

.step.is-current .step__num {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--ring);
}

/* --------------------------------- stage ---------------------------------- */

.stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  padding-block: clamp(var(--sp-5), 4vh, var(--sp-7));
  padding-inline: var(--gutter);
}

.screen {
  display: grid;
  justify-items: center;
  inline-size: 100%;
  min-inline-size: 0;
  max-inline-size: var(--page-max);
  margin-inline: auto;
  animation: screen-in var(--dur) var(--ease) both;
}

@keyframes screen-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

.screen__title {
  font-size: var(--fs-title);
  line-height: var(--lh-tight);
  font-weight: 700;
  margin-block-end: var(--sp-3);
}

.screen__lede {
  font-size: var(--fs-lead);
  color: var(--ink-soft);
  margin-block-end: var(--sp-5);
  max-inline-size: 48ch;
}

.screen__foot {
  margin-block-start: var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}

/* --------------------------------- panels --------------------------------- */

.panel {
  inline-size: 100%;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
  padding: clamp(var(--sp-5), 4vw, var(--sp-7));
}

.panel--narrow { max-inline-size: 46rem; }

.panel--warn {
  border-color: var(--warn);
  background: linear-gradient(0deg, var(--surface), var(--surface)) padding-box;
}

.panel__icon {
  display: grid;
  place-items: center;
  inline-size: 56px;
  block-size: 56px;
  border-radius: 50%;
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 2rem;
  font-weight: 700;
  margin-block-end: var(--sp-4);
}

.notice {
  display: grid;
  gap: var(--sp-3);
  margin-block-end: var(--sp-6);
  padding-inline-start: 0;
}

.notice li {
  position: relative;
  padding-inline-start: var(--sp-6);
  color: var(--ink-soft);
}

.notice li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.7em;
  inline-size: 10px;
  block-size: 10px;
  border-radius: 50%;
  background: var(--accent);
}

/* --------------------------------- buttons --------------------------------
   SPEC §10: every touch target is at least 64px tall. There is no small button
   variant in this file on purpose — if something is too minor to deserve 64px,
   it is too minor to be on a kiosk screen.                                    */

.btn {
  min-block-size: var(--touch);
  min-inline-size: var(--touch);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding-block: var(--sp-3);
  padding-inline: var(--sp-6);
  border: 2px solid transparent;
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}

.btn:active:not(:disabled) { transform: scale(0.985); }

.btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-sm);
}

.btn--primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn--secondary {
  background: var(--surface);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn--ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--ink-soft);
}

.btn--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--ink-mute);
  padding-inline: var(--sp-4);
  font-weight: 500;
}

.btn--wide { inline-size: 100%; }

.btn--hero {
  min-block-size: var(--touch-lg);
  font-size: var(--fs-lead);
  padding-inline: var(--sp-8);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}

.actions {
  display: grid;
  gap: var(--sp-3);
  margin-block-start: var(--sp-5);
}

.actions--row {
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: stretch;
}

@media (max-width: 640px) {
  .actions--row { grid-auto-flow: row; }
}

/* ---------------------------------- hero ---------------------------------- */

.screen--hero { align-content: center; }

.hero {
  text-align: center;
  display: grid;
  justify-items: center;
  gap: var(--sp-4);
  padding-block: clamp(var(--sp-5), 6vh, var(--sp-8));
}

.hero__eyebrow {
  color: var(--accent-ink);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.hero__title {
  font-size: var(--fs-hero);
  line-height: var(--lh-tight);
  font-weight: 700;
  max-inline-size: 18ch;
}

.hero__lede {
  font-size: var(--fs-lead);
  color: var(--ink-soft);
  max-inline-size: 42ch;
}

/* The release, small enough that a visitor never reads it; staff check it
   after an upgrade (docs/DEPLOY.md §10). */
.hero__build {
  justify-self: end;
  color: var(--ink-mute);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  opacity: 0.8;
}

.hero__steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--sp-3);
  inline-size: 100%;
  margin-block-start: var(--sp-5);
  text-align: start;
}

.hero__steps li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
}

.hero__stepnum {
  flex: none;
  inline-size: 32px;
  block-size: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 700;
}

/* -------------------------------- capture ---------------------------------
   The camera stage. The guide overlays darken everything OUTSIDE themselves
   with a single enormous spread shadow, which is why .stagebox clips.        */

.capture {
  inline-size: 100%;
  display: grid;
  justify-items: center;
  gap: var(--sp-4);
}

.capture__head { text-align: center; }
.capture__head .screen__lede { margin-block-end: 0; margin-inline: auto; }

.stagebox {
  position: relative;
  inline-size: 100%;
  max-inline-size: 640px;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--stage-bg);
  box-shadow: var(--shadow-md);
}

.stagebox--wide { max-inline-size: 760px; aspect-ratio: 4 / 3; }

.stagebox__video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}

/*
  Mirroring is a CSS-only affordance for the FACE step: an unmirrored preview of
  your own face feels broken — you lean one way and the image leans the other.
  It is applied to the <video> element only. The captured canvas is drawn from
  the raw video frame and is NOT mirrored, because the recognizer must see the
  face the way the camera saw it. SPEC §10 says the same thing.
  The card step never gets this class: a mirrored ID card is unreadable.
*/
.stagebox__video.is-mirrored { transform: scaleX(-1); }

.guide {
  position: absolute;
  pointer-events: none;
  border: 3px solid rgba(255, 255, 255, 0.86);
  box-shadow: 0 0 0 100vmax var(--scrim);
  transition: border-color var(--dur) var(--ease);
}

.guide--oval {
  inset-block: 6%;
  inset-inline: 25%;
  border-radius: 50%;
}

.guide--card {
  inset: 0;
  margin: auto;
  inline-size: 84%;
  /* The Egyptian national ID is an ID-1 card: 85.6 × 54 mm ≈ 1.585. Framing to
     the real ratio is what lets preprocess.py find the quadrilateral cleanly. */
  aspect-ratio: 1.585;
  border-radius: var(--r-md);
}

.guide__tick {
  position: absolute;
  inline-size: 26px;
  block-size: 26px;
  border: 5px solid var(--gold-400, #E8C97A);
}

.guide__tick--ss { inset-block-start: -5px; inset-inline-start: -5px; border-block-end: 0; border-inline-end: 0; }
.guide__tick--se { inset-block-start: -5px; inset-inline-end: -5px; border-block-end: 0; border-inline-start: 0; }
.guide__tick--es { inset-block-end: -5px; inset-inline-start: -5px; border-block-start: 0; border-inline-end: 0; }
.guide__tick--ee { inset-block-end: -5px; inset-inline-end: -5px; border-block-start: 0; border-inline-start: 0; }

.stagebox.is-steady .guide { border-color: var(--faience-400); }
.stagebox.is-warn .guide { border-color: var(--gold-400, #E8C97A); }

.pill {
  position: absolute;
  inset-block-start: var(--sp-3);
  inset-inline-start: var(--sp-3);
  max-inline-size: calc(100% - var(--sp-6));
  padding-block: var(--sp-2);
  padding-inline: var(--sp-4);
  border-radius: var(--r-pill);
  background: var(--pill-bg);
  color: var(--pill-ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  backdrop-filter: blur(4px);
}

.steady {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  block-size: 6px;
  background: rgba(255, 255, 255, 0.16);
}

.steady__bar {
  block-size: 100%;
  inline-size: 0%;
  background: var(--faience-400);
  transition: inline-size var(--dur-fast) linear;
}

.capture__error,
.form__error {
  inline-size: 100%;
  max-inline-size: 640px;
  padding: var(--sp-4);
  border-radius: var(--r-md);
  background: var(--danger-soft);
  color: var(--danger);
  border: 1px solid var(--danger);
  font-weight: 600;
}

/* ---------------------------------- form ---------------------------------- */

.form { display: grid; gap: var(--sp-5); }

.field { display: grid; gap: var(--sp-2); }

.field__label {
  font-weight: 600;
  font-size: var(--fs-body);
}

.field__input {
  min-block-size: var(--touch);
  inline-size: 100%;
  padding-block: var(--sp-3);
  padding-inline: var(--sp-4);
  border: 2px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-ar);
  font-size: var(--fs-lead);
  -webkit-user-select: text;
  user-select: text;
}

.field__input:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: var(--ring);
}

/* The national ID is a 14-digit Latin run. It is typed and displayed LTR with a
   monospace face so a transposed digit is visible at a glance. */
.field__input--ltr {
  font-family: var(--font-ltr);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.14em;
  text-align: start;
}

.field.is-flagged .field__input { border-color: var(--warn); background: var(--warn-soft); }

.field__hint {
  font-size: var(--fs-sm);
  color: var(--warn);
  font-weight: 600;
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-2);
  margin-block-start: var(--sp-3);
  max-inline-size: 26rem;
  /* The ONE place in this file that overrides the page direction, deliberately.
     A 3-column grid inherits the RTL flow and lays 1-2-3 out as 3-2-1, which is
     not a localised keypad — it is a broken one. Every phone, ATM and
     calculator in Egypt reads 1-2-3 from the left, and muscle memory does not
     translate. The digits stay LTR; only the Arabic label below opts back. */
  direction: ltr;
}

.keypad__key {
  min-block-size: var(--touch);
  border: 2px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  font-size: var(--fs-lead);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}

.keypad__key:active { background: var(--accent-soft); }
.keypad__key--wide {
  /* Reads Arabic, so it opts back out of the LTR grid above. */
  direction: rtl;
  font-size: var(--fs-body);
  font-weight: 600;
}

/* -------------------------------- choices ---------------------------------
   Request type and location. Big, flat, unmistakably tappable cards; nothing
   here is a dropdown, because a dropdown on a kiosk is a trap.               */

.choices {
  display: grid;
  gap: var(--sp-4);
}

.choices--2 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.choices--4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

.choice {
  min-block-size: var(--touch-lg);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 2px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-lead);
  font-weight: 600;
  text-align: start;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}

.choice:hover:not(:disabled) { border-color: var(--accent); }
.choice:active:not(:disabled) { transform: scale(0.99); }

.choice.is-selected {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.choice__mark {
  flex: none;
  inline-size: 40px;
  block-size: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-family: var(--font-ltr);
  font-weight: 700;
  font-size: var(--fs-body);
}

.choice.is-selected .choice__mark {
  background: var(--accent);
  color: var(--on-accent);
}

.choice__text { min-inline-size: 0; }

/* ------------------------------ suggestions --------------------------------
   The company screen's tap-to-pick names. Buttons first (.btn .btn--secondary
   gives them the 64px floor of SPEC §10); .chip only lets them sit several to
   a row, wrapping rather than scrolling — a sideways scroller on a kiosk is a
   row of buttons nobody finds.                                               */

.suggest { display: grid; gap: var(--sp-3); }

.suggest__label {
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}

.suggest__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}

.chip {
  flex-direction: column;
  gap: 0;
  max-inline-size: 100%;
  padding-inline: var(--sp-5);
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.chip:hover:not(:disabled) { background: var(--accent-soft); }

/* The English name under the Arabic one, when the company has both. */
.chip__sub {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--ink-mute);
}

/* Visitor-typed text in either script (the company name). It sits in an
   isolate (dir="auto") rather than a <bdi>, which this file sets in the Latin
   monospace face; and a long unbroken Latin name must wrap, not push the
   permit or the 72mm slip wider than its box. */
.freetext { overflow-wrap: anywhere; }

/* --------------------------------- permit ---------------------------------
   The payoff screen. Everything is subordinate to the serial.                */

.permit {
  inline-size: 100%;
  max-inline-size: 58rem;
  text-align: center;
  padding: clamp(var(--sp-5), 4vw, var(--sp-7));
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-md);
}

.permit__eyebrow {
  color: var(--ok);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.permit__title {
  font-size: var(--fs-title);
  font-weight: 700;
  margin-block-end: var(--sp-5);
}

.permit__serialbox {
  display: grid;
  justify-items: center;
  gap: var(--sp-2);
  padding-block: var(--sp-5);
  padding-inline: var(--sp-4);
  border-radius: var(--r-lg);
  background: var(--gold-soft);
  border: 2px solid var(--gold);
}

.permit__serialbox--small { padding-block: var(--sp-4); margin-block: var(--sp-4); }

.permit__seriallabel {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-soft);
  letter-spacing: 0.04em;
}

.permit__serial {
  font-size: var(--fs-serial);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: 0.04em;
  color: var(--ink);
  word-break: break-all;
}

.permit__serial--small { font-size: clamp(2.25rem, 7vw, 3.5rem); }

.permit__body {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--sp-6);
  margin-block-start: var(--sp-6);
  text-align: start;
}

@media (max-width: 720px) {
  .permit__body { grid-template-columns: 1fr; justify-items: center; }
}

.permit__qr {
  inline-size: 220px;
  block-size: 220px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: #FFFFFF;
  padding: var(--sp-2);
}

.permit__facts { display: grid; gap: var(--sp-3); inline-size: 100%; }

.permit__facts--compact { gap: var(--sp-2); text-align: start; }

.fact {
  display: grid;
  grid-template-columns: minmax(8rem, auto) 1fr;
  gap: var(--sp-4);
  padding-block-end: var(--sp-3);
  border-block-end: 1px solid var(--line-soft);
}

.fact:last-child { border-block-end: 0; padding-block-end: 0; }

.fact dt { color: var(--ink-mute); font-size: var(--fs-sm); }
.fact dd { margin: 0; font-weight: 600; }

.permit__note {
  margin-block-start: var(--sp-5);
  color: var(--ink-mute);
  font-size: var(--fs-sm);
}

/* -------------------------------- overlays -------------------------------- */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--gutter);
  background: var(--scrim);
  backdrop-filter: blur(3px);
}

.overlay__card {
  display: grid;
  justify-items: center;
  gap: var(--sp-4);
  min-inline-size: min(26rem, 90vw);
  max-inline-size: 34rem;
  padding: var(--sp-7);
  text-align: center;
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

.overlay__title { font-size: var(--fs-title); font-weight: 700; }
.overlay__text { font-size: var(--fs-lead); color: var(--ink-soft); }

.spinner {
  inline-size: 52px;
  block-size: 52px;
  border-radius: 50%;
  border: 5px solid var(--line);
  border-block-start-color: var(--accent);
  animation: spin 900ms linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  /* Still a progress signal, just not a moving one. */
  .spinner { animation-duration: 2400ms; }
  .screen { animation: none; }
}

.toast {
  position: fixed;
  inset-block-end: var(--sp-5);
  inset-inline: var(--gutter);
  z-index: 70;
  margin-inline: auto;
  max-inline-size: 42rem;
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-md);
  background: var(--danger);
  color: #FFFFFF;
  font-weight: 600;
  text-align: center;
  box-shadow: var(--shadow-lg);
  animation: screen-in var(--dur) var(--ease) both;
}

.toast.is-info { background: var(--accent); color: var(--on-accent); }

/* The print slip never shows on screen; print.css reveals it. */
.slip { display: none; }

/* ------------------------------ view switch --------------------------------
   Kiosk <-> admin console. A segmented pill whose thumb sits under the current
   side. The same markup is in admin.html with the other side current, and both
   pages name the thumb for a cross-document view transition, so in a browser
   that supports it the thumb glides across while the page cross-fades.       */

.viewswitch {
  position: relative;
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  box-shadow: inset 0 1px 2px rgba(27, 23, 16, 0.07);
  isolation: isolate;
  view-transition-name: viewswitch;
}

.viewswitch__thumb {
  position: absolute;
  z-index: 0;
  inset-block: 4px;
  inset-inline-start: 4px;
  inline-size: calc(50% - 4px);
  border-radius: var(--r-pill);
  background: linear-gradient(150deg, var(--accent) 0%, var(--accent-hover) 100%);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.14), 0 6px 16px -6px var(--accent);
  transition: transform 260ms var(--ease);
  view-transition-name: viewswitch-thumb;
}

/* The page is RTL, so the second option sits to the LEFT of the first. */
.viewswitch[data-current="admin"] .viewswitch__thumb { transform: translateX(-100%); }
.viewswitch:dir(ltr)[data-current="admin"] .viewswitch__thumb { transform: translateX(100%); }

.viewswitch__opt {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  min-block-size: 44px;
  padding-inline: var(--sp-5);
  border-radius: var(--r-pill);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
  -webkit-tap-highlight-color: transparent;
}

.viewswitch__opt:hover { color: var(--ink); }
.viewswitch__opt.is-current { color: var(--on-accent); }
.viewswitch__opt:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) { animation-duration: 200ms; }

::view-transition-group(viewswitch-thumb) {
  animation-duration: 320ms;
  animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1);
}

/* ------------------------------ card reading --------------------------------
   The instant the card is captured: a shutter flash, the photo freezes over
   the live feed, the guide turns faience and a scan line sweeps the card
   while it is read. It tells the visitor two things at once — "got it, you can
   lower the card" and "working on it" — on the very pixels being read.       */

.stagebox::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  background: #FFFFFF;
  opacity: 0;
  pointer-events: none;
}

.stagebox.is-captured::after { animation: shutter 420ms ease-out; }

@keyframes shutter {
  0%   { opacity: 0.6; }
  100% { opacity: 0; }
}

.stagebox__freeze {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
  animation: freeze-in 240ms var(--ease) both;
}

@keyframes freeze-in {
  from { filter: brightness(1.25) saturate(0.8); }
  to   { filter: none; }
}

.stagebox.is-reading .guide { border-color: var(--faience-400); }
.stagebox.is-reading .guide__tick { border-color: var(--faience-400); }

.guide__scan {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}

.guide__beam {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  block-size: 24%;
  opacity: 0;
  background: linear-gradient(to bottom,
    rgba(63, 179, 162, 0) 0%,
    rgba(63, 179, 162, 0.22) 65%,
    rgba(63, 179, 162, 0.55) 100%);
  border-block-end: 2px solid #A6F4E8;
  box-shadow: 0 8px 22px rgba(63, 179, 162, 0.45);
}

.stagebox.is-reading .guide__beam {
  opacity: 1;
  animation: beam-sweep 1300ms cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

@keyframes beam-sweep {
  0%   { transform: translateY(-100%); }
  100% { transform: translateY(420%); }
}

/* The progress strip turns indeterminate: the photo is taken, the wait is the
   read, and there is no honest percentage for a network round trip. */
.stagebox.is-reading .steady__bar {
  inline-size: 34% !important;
  transition: none;
  animation: strip-run 1100ms cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

@keyframes strip-run {
  0%   { transform: translateX(100%); }
  100% { transform: translateX(-300%); }
}

/* ------------------------------ verification --------------------------------
   The confirm screen opens on the fast engine's read; this line says what the
   slower one is doing, and then that it agrees.                              */

.verify {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  inline-size: fit-content;
  margin-block: 0 var(--sp-5);
  margin-inline: auto;
  padding-block: var(--sp-2);
  padding-inline: var(--sp-4);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 700;
  background: var(--accent-soft);
  color: var(--accent-ink);
  animation: screen-in var(--dur) var(--ease) both;
}

.verify__icon {
  flex: none;
  inline-size: 10px;
  block-size: 10px;
  border-radius: 50%;
  background: currentColor;
}

.verify.is-pending .verify__icon { animation: verify-pulse 1s ease-in-out infinite; }

.verify.is-done {
  background: color-mix(in srgb, var(--ok) 14%, var(--surface));
  color: var(--ok);
}

.verify.is-done .verify__icon,
.verify.is-review .verify__icon {
  inline-size: auto;
  block-size: auto;
  background: none;
  line-height: 1;
}

.verify.is-done .verify__icon::before { content: "✓"; }
.verify.is-review .verify__icon::before { content: "!"; }

.verify.is-review { background: var(--warn-soft); color: var(--warn); }

@keyframes verify-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.7); }
}

/* While the second read is outstanding the fields carry a faint sweep, so a
   value that is about to be confirmed does not look settled yet. */
.form.is-verifying .field__input:not(:focus) {
  background-image: linear-gradient(100deg,
    transparent 30%,
    color-mix(in srgb, var(--accent) 12%, transparent) 50%,
    transparent 70%);
  background-size: 220% 100%;
  animation: field-shimmer 1400ms linear infinite;
}

@keyframes field-shimmer {
  from { background-position: 160% 0; }
  to   { background-position: -60% 0; }
}

/* A value the settled read changed glows once, so no change goes unseen. */
.field__input.is-updated { animation: field-updated 1400ms var(--ease); }

@keyframes field-updated {
  0%   { border-color: var(--accent); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  40%  { border-color: var(--accent); box-shadow: 0 0 0 8px color-mix(in srgb, var(--accent) 18%, transparent); }
  100% { box-shadow: 0 0 0 12px transparent; }
}

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  .viewswitch__thumb { transition: none; }
  .stagebox.is-captured::after,
  .stagebox__freeze,
  .stagebox.is-reading .guide__beam,
  .stagebox.is-reading .steady__bar,
  .verify.is-pending .verify__icon,
  .form.is-verifying .field__input,
  .field__input.is-updated { animation: none !important; }
  /* Still readable as "being read": a static beam across the middle. */
  .stagebox.is-reading .guide__beam { transform: translateY(160%); }
}

@media (max-width: 720px) {
  /* On a narrow panel the switch takes its own row above the brand, centred. */
  .viewswitch { order: -1; inline-size: 100%; }
  .viewswitch__opt { padding-inline: var(--sp-3); }
}

/* --------------------------- small / short screens ------------------------ */

@media (max-width: 560px) {
  .steps__list { gap: 0; }
  .step__label { font-size: 0.8rem; max-inline-size: 8ch; }
  .step__num { inline-size: 38px; block-size: 38px; }
  .step::before { inset-block-start: calc(var(--sp-2) + 18px); }
}

/* A landscape ticket-office panel is often only 720px tall: trim the vertical
   air rather than letting the capture stage push the buttons off-screen. */
@media (max-height: 760px) {
  .stagebox { max-inline-size: 520px; }
  .stagebox--wide { max-inline-size: 620px; }
  .hero { gap: var(--sp-3); padding-block: var(--sp-4); }
  .stage { padding-block: var(--sp-4); }
}
