/*
 * AttenDyn visual identity.
 *
 * Loaded after Bootstrap 5.3, and works mostly by retargeting Bootstrap's own
 * custom properties so existing utility classes inherit the identity instead
 * of every template needing bespoke markup. Bespoke components live at the
 * bottom, under "Components".
 *
 * The subject is a pass system -- badges, QR stubs, serial numbers, a door --
 * so serial data (EK numbers, counts, times) is set in tabular monospace, and
 * the ticket screen carries a real perforation. Two densities share one
 * identity: a calm centred column for attendees on phones, and the dense,
 * high-contrast `.desk` layout for staff working a door for hours.
 *
 * On colour: teal on a cool slate ramp, the same identity the admin and the
 * sign-in page carry, so a person crossing between the three surfaces stays in
 * one product. One accent carries identity and the primary action; amber, red
 * and green stay reserved for meaning.
 *
 * The teal comes in two stops and they are not interchangeable:
 *
 *   --ad-accent       fills, borders, focus rings, chart series, the brand.
 *                     Never small type, never a filled button under white.
 *   --ad-accent-deep  teal that is *type*, and the ground of a filled accent
 *                     button. Light mode darkens it to clear 4.5:1 on white,
 *                     on paper and on its own wash; dark mode brightens it
 *                     instead, for the same reason.
 *
 * That split is why --ad-accent is not simply used everywhere: at #0d9488 it
 * measures 3.74:1 on white, which is fine behind a 2.25rem figure and not fine
 * under a 0.72rem badge.
 */

/* ==========================================================================
   Tokens
   ========================================================================== */

:root {
  /* Native widgets follow the page, not the device. Without this a light
     page on a phone set to dark gets dark scrollbars, dark date pickers and
     a dark autofill highlight over white cards. */
  color-scheme: light;

  /* A cool slate ramp. Neutrals are biased a few degrees toward the accent
     rather than being a dead grey, so surfaces and type read as belonging to
     the same family -- and warm greys under a teal accent read muddy. */
  --ad-ink: #0f172a;
  --ad-paper: #f6f9fa;
  --ad-surface: #ffffff;
  --ad-surface-sunk: #eef2f5;
  --ad-line: #e4eaee;
  --ad-line-strong: #cbd5dd;
  /* Darker than the admin's slate mute. The same token here sets 0.7rem
     eyebrows and form hints that get read on a phone held at a door in
     daylight, and it has to clear 4.5:1 on the sunk surface too, where the
     admin's #64748b measured 4.23. */
  --ad-mute: #556274;

  /* One accent, used for identity and the primary action only. Two stops --
     see the note at the top of this file for which goes where. */
  --ad-accent: #0d9488;
  --ad-accent-deep: #0a736a;
  --ad-accent-wash: #e6f5f3;
  --ad-accent-rgb: 13, 148, 136;

  /* Semantic state, kept strictly separate from the accent. Teal sits closer
     to the green that means "checked in" than the indigo it replaced did, so
     the separation that used to be automatic is now held by hand: the accent
     is blue-green and desaturated, the outcome green is yellow-green and
     brighter, and the two never sit in the same component. If they are ever
     forced together, move the accent -- green is the one carrying meaning.
     These three values are deliberately unchanged by the teal retarget: they
     already clear AA on the new grounds, and "checked in" is the last colour
     in this app that should shift under anyone working a door. */
  --ad-ok: #14724a;
  --ad-ok-wash: #e3f3ea;
  --ad-warn: #97600f;
  --ad-warn-wash: #fbf0dc;
  --ad-stop: #b02a21;
  --ad-stop-wash: #fbe9e7;

  --ad-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.04);
  --ad-shadow-md: 0 2px 4px rgba(15, 23, 42, 0.05), 0 8px 24px rgba(15, 23, 42, 0.07);
  --ad-shadow-lg: 0 4px 8px rgba(15, 23, 42, 0.06), 0 20px 48px rgba(15, 23, 42, 0.11);

  --ad-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --ad-mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", Menlo,
    Consolas, "Liberation Mono", monospace;

  --ad-radius: 10px;
  --ad-radius-sm: 7px;
  --ad-radius-lg: 16px;

  /* The selected tab has to sit *above* its strip. In light that means the
     strip recedes and the tab is the page's brightest surface; in dark the
     relationship inverts, so both are named rather than hard-coded. */
  --ad-tabstrip: var(--ad-surface-sunk);
  --ad-tab-active: var(--ad-surface);
}

/* Dark is not an afterthought here: door staff often work a dim room, and the
   desk screens are the ones on for hours. Tokens flip; components never
   define a colour of their own outside this block and its light counterpart. */
:root[data-bs-theme="dark"] {
  color-scheme: dark;
  --ad-ink: #e6edf0;
  --ad-paper: #0a1216;
  --ad-surface: #111d23;
  --ad-surface-sunk: #17262d;
  --ad-line: #1f313a;
  --ad-line-strong: #2f4650;
  --ad-mute: #90a4ac;

  /* The deep stop inverts here: on a dark ground the type-safe teal is the
     brighter one, so --ad-accent-deep stays "the one you set type in" and
     changes direction rather than changing job. */
  --ad-accent: #2dd4bf;
  --ad-accent-deep: #5eead4;
  --ad-accent-wash: #10312f;
  --ad-accent-rgb: 45, 212, 191;

  --ad-ok: #3fbf86;
  --ad-ok-wash: #14281f;
  --ad-warn: #d9a441;
  --ad-warn-wash: #2a2216;
  --ad-stop: #f4796b;
  --ad-stop-wash: #2e1a19;

  --ad-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --ad-shadow-md: 0 2px 4px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.36);
  --ad-shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.34), 0 20px 48px rgba(0, 0, 0, 0.46);

  --ad-tabstrip: var(--ad-paper);
  --ad-tab-active: var(--ad-surface-sunk);
}

/* ==========================================================================
   Bootstrap retargeting
   ========================================================================== */

:root,
:root[data-bs-theme="dark"] {
  --bs-font-sans-serif: var(--ad-sans);
  --bs-font-monospace: var(--ad-mono);

  --bs-body-bg: var(--ad-paper);
  --bs-body-color: var(--ad-ink);
  --bs-emphasis-color: var(--ad-ink);
  --bs-secondary-color: var(--ad-mute);
  --bs-tertiary-bg: var(--ad-surface-sunk);
  --bs-border-color: var(--ad-line);
  --bs-border-radius: var(--ad-radius);
  --bs-border-radius-sm: var(--ad-radius-sm);
  --bs-border-radius-lg: var(--ad-radius-lg);

  --bs-primary: var(--ad-accent);
  --bs-primary-rgb: var(--ad-accent-rgb);
  /* Link text is type, so it takes the deep stop. The hover mixes toward the
     ink, which darkens it in light and brightens it in dark -- one rule that
     gains contrast in both themes instead of losing it in one. */
  --bs-link-color: var(--ad-accent-deep);
  --bs-link-hover-color: color-mix(in srgb, var(--ad-accent-deep) 78%, var(--ad-ink));
  --bs-link-color-rgb: var(--ad-accent-rgb);

  --bs-focus-ring-color: rgba(var(--ad-accent-rgb), 0.3);
}

body {
  background-color: var(--ad-paper);
  color: var(--ad-ink);
  font-family: var(--ad-sans);
  -webkit-font-smoothing: antialiased;
  letter-spacing: -0.005em;
}

/* -- Type scale ----------------------------------------------------------- */

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-weight: 640;
  letter-spacing: -0.021em;
  text-wrap: balance;
  color: var(--ad-ink);
}

h1, .h1 { font-size: clamp(1.85rem, 1.4rem + 1.6vw, 2.5rem); letter-spacing: -0.03em; }
h2, .h2 { font-size: 1.6rem; }
h3, .h3 { font-size: 1.35rem; }
h4, .h4 { font-size: 1.15rem; }
h5, .h5 { font-size: 1.03rem; }
h6, .h6 { font-size: 0.9rem; }

p { text-wrap: pretty; }

.text-muted { color: var(--ad-mute) !important; }

/* -- Cards ---------------------------------------------------------------- */

.card {
  --bs-card-bg: var(--ad-surface);
  --bs-card-border-color: var(--ad-line);
  --bs-card-border-radius: var(--ad-radius-lg);
  --bs-card-inner-border-radius: calc(var(--ad-radius-lg) - 1px);
  --bs-card-cap-bg: transparent;
  --bs-card-color: var(--ad-ink);
  box-shadow: var(--ad-shadow-sm);
}

.card-header {
  border-bottom-color: var(--ad-line);
  font-size: 0.72rem;
  font-weight: 660;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ad-mute);
  padding-block: 0.85rem;
}

.shadow-sm { box-shadow: var(--ad-shadow-md) !important; }

/* -- Buttons -------------------------------------------------------------- */

.btn {
  --bs-btn-font-weight: 560;
  --bs-btn-border-radius: var(--ad-radius-sm);
  --bs-btn-padding-x: 1.15rem;
  --bs-btn-padding-y: 0.55rem;
  letter-spacing: -0.008em;
  transition: transform 0.06s ease, background-color 0.12s ease,
    border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}

.btn:active { transform: translateY(1px); }

/* The one filled accent button on the public side: Register, Download pass,
   Check in. It takes the deep stop rather than the accent, because white on
   #0d9488 is 3.74:1 and this is 1rem type someone reads outdoors. */
.btn-primary {
  --bs-btn-bg: var(--ad-accent-deep);
  --bs-btn-border-color: var(--ad-accent-deep);
  --bs-btn-hover-bg: color-mix(in srgb, var(--ad-accent-deep) 86%, black);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--ad-accent-deep) 86%, black);
  --bs-btn-active-bg: color-mix(in srgb, var(--ad-accent-deep) 78%, black);
  --bs-btn-active-border-color: color-mix(in srgb, var(--ad-accent-deep) 78%, black);
  --bs-btn-disabled-bg: var(--ad-accent-deep);
  --bs-btn-disabled-border-color: var(--ad-accent-deep);
  --bs-btn-color: #fff;
  --bs-btn-hover-color: #fff;
  --bs-btn-active-color: #fff;
  --bs-btn-disabled-color: #fff;
}

/* Dark inverts the pair: the ground is the accent and brightens on hover, and
   the label goes to the page ground rather than to white. */
:root[data-bs-theme="dark"] .btn-primary {
  --bs-btn-bg: var(--ad-accent);
  --bs-btn-border-color: var(--ad-accent);
  --bs-btn-hover-bg: var(--ad-accent-deep);
  --bs-btn-hover-border-color: var(--ad-accent-deep);
  --bs-btn-active-bg: var(--ad-accent-deep);
  --bs-btn-active-border-color: var(--ad-accent-deep);
  --bs-btn-disabled-bg: var(--ad-accent);
  --bs-btn-disabled-border-color: var(--ad-accent);
  --bs-btn-color: #0a1216;
  --bs-btn-hover-color: #0a1216;
  --bs-btn-active-color: #0a1216;
  --bs-btn-disabled-color: #0a1216;
}

.btn-success {
  --bs-btn-bg: var(--ad-ok);
  --bs-btn-border-color: var(--ad-ok);
  --bs-btn-hover-bg: color-mix(in srgb, var(--ad-ok) 86%, black);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--ad-ok) 86%, black);
  --bs-btn-active-bg: color-mix(in srgb, var(--ad-ok) 80%, black);
  --bs-btn-active-border-color: color-mix(in srgb, var(--ad-ok) 80%, black);
  --bs-btn-disabled-bg: var(--ad-ok);
  --bs-btn-disabled-border-color: var(--ad-ok);
}

:root[data-bs-theme="dark"] .btn-success {
  --bs-btn-color: #0a1216;
  --bs-btn-hover-color: #0a1216;
  --bs-btn-active-color: #0a1216;
  --bs-btn-disabled-color: #0a1216;
  --bs-btn-hover-bg: color-mix(in srgb, var(--ad-ok) 86%, white);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--ad-ok) 86%, white);
}

.btn-outline-primary {
  --bs-btn-color: var(--ad-accent-deep);
  --bs-btn-border-color: color-mix(in srgb, var(--ad-accent) 45%, transparent);
  --bs-btn-hover-bg: var(--ad-accent-deep);
  --bs-btn-hover-border-color: var(--ad-accent-deep);
  --bs-btn-active-bg: color-mix(in srgb, var(--ad-accent-deep) 86%, black);
  --bs-btn-active-border-color: color-mix(in srgb, var(--ad-accent-deep) 86%, black);
}

:root[data-bs-theme="dark"] .btn-outline-primary {
  --bs-btn-hover-bg: var(--ad-accent);
  --bs-btn-hover-border-color: var(--ad-accent);
  --bs-btn-active-bg: var(--ad-accent-deep);
  --bs-btn-active-border-color: var(--ad-accent-deep);
  --bs-btn-hover-color: #0a1216;
  --bs-btn-active-color: #0a1216;
}

.btn-outline-secondary {
  --bs-btn-color: var(--ad-ink);
  --bs-btn-border-color: var(--ad-line-strong);
  --bs-btn-hover-bg: var(--ad-surface-sunk);
  --bs-btn-hover-border-color: var(--ad-line-strong);
  --bs-btn-hover-color: var(--ad-ink);
  --bs-btn-active-bg: var(--ad-surface-sunk);
  --bs-btn-active-border-color: var(--ad-mute);
  --bs-btn-active-color: var(--ad-ink);
}

.btn-outline-warning {
  --bs-btn-color: var(--ad-warn);
  --bs-btn-border-color: color-mix(in srgb, var(--ad-warn) 45%, transparent);
  --bs-btn-hover-bg: var(--ad-warn);
  --bs-btn-hover-border-color: var(--ad-warn);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--ad-warn);
  --bs-btn-active-border-color: var(--ad-warn);
  --bs-btn-active-color: #fff;
}

:root[data-bs-theme="dark"] .btn-outline-warning {
  --bs-btn-hover-color: #0a1216;
  --bs-btn-active-color: #0a1216;
}

/* -- Forms ---------------------------------------------------------------- */

/* Crispy wraps option sets in a fieldset, so the question arrives as a
   <legend> rather than a label. Both are the same thing to a reader and must
   look the same. */
.form-label,
fieldset legend {
  font-size: 0.78rem;
  font-weight: 620;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  color: var(--ad-mute);
  margin-bottom: 0.4rem;
}

fieldset legend {
  float: none;
  width: auto;
  padding: 0;
  line-height: 1.4;
}

/* Crispy stamps the legend with .fs-6, which carries !important, so matching
   the surrounding labels needs the same weapon. */
fieldset > legend.form-label {
  font-size: 0.78rem !important;
}

.form-control,
.form-select {
  background-color: var(--ad-surface);
  border-color: var(--ad-line-strong);
  border-radius: var(--ad-radius-sm);
  color: var(--ad-ink);
  padding: 0.62rem 0.85rem;
  font-size: 1rem;
}

.form-control::placeholder { color: color-mix(in srgb, var(--ad-mute) 72%, transparent); }

/* Crispy renders the required marker glued to the label text. Give it air and
   a colour of its own so an uppercase label does not read as "NAME STAR". */
.asteriskField {
  margin-left: 0.25em;
  color: var(--ad-stop);
  font-weight: 700;
}

.form-control:focus,
.form-select:focus {
  background-color: var(--ad-surface);
  border-color: var(--ad-accent);
  color: var(--ad-ink);
  box-shadow: 0 0 0 3px rgba(var(--ad-accent-rgb), 0.16);
}

.form-control.is-invalid,
.form-select.is-invalid {
  border-color: var(--ad-stop);
  box-shadow: none;
}

.form-control.is-invalid:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ad-stop) 20%, transparent);
}

.form-text {
  color: var(--ad-mute);
  font-size: 0.82rem;
}

.invalid-feedback {
  color: var(--ad-stop);
  font-size: 0.82rem;
  font-weight: 500;
}

.form-check-input {
  border-color: var(--ad-line-strong);
  width: 1.15em;
  height: 1.15em;
  margin-top: 0.15em;
}

.form-check-input:checked {
  background-color: var(--ad-accent);
  border-color: var(--ad-accent);
}

.form-check-input:focus {
  border-color: var(--ad-accent);
  box-shadow: 0 0 0 3px rgba(var(--ad-accent-rgb), 0.16);
}

.form-check-label { color: var(--ad-ink); }

/* Radio groups (ratings, choice questions) read better as a row of chips than
   as a stack of dots, and they become tap targets on a phone.

   Not ad-choices: EasyList carries a global ##.ad-choices rule, so a blocker
   hid every option on the registration form and both tick boxes at the desk,
   leaving the question and its heading behind. */
.choice-chips .form-check {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.42rem 0.8rem 0.42rem 0.7rem;
  margin: 0 0.4rem 0.4rem 0;
  border: 1px solid var(--ad-line-strong);
  border-radius: 999px;
  min-height: 2.4rem;
}

.choice-chips .form-check:has(.form-check-input:checked) {
  border-color: var(--ad-accent);
  background: var(--ad-accent-wash);
}

.choice-chips .form-check-input { float: none; margin: 0; }
.choice-chips .form-check-label { margin: 0; cursor: pointer; }

/* -- Badges --------------------------------------------------------------- */

/* Bootstrap's .text-bg-* are solid blocks. Softening them to a wash plus
   coloured text keeps the desk readable when several sit side by side. */
.badge {
  --bs-badge-font-weight: 620;
  --bs-badge-padding-x: 0.62em;
  --bs-badge-padding-y: 0.42em;
  --bs-badge-border-radius: 999px;
  font-size: 0.72rem;
  letter-spacing: 0.035em;
}

.text-bg-success { background-color: var(--ad-ok-wash) !important; color: var(--ad-ok) !important; }
.text-bg-danger  { background-color: var(--ad-stop-wash) !important; color: var(--ad-stop) !important; }
.text-bg-warning { background-color: var(--ad-warn-wash) !important; color: var(--ad-warn) !important; }
.text-bg-primary { background-color: var(--ad-accent-wash) !important; color: var(--ad-accent-deep) !important; }
.text-bg-secondary {
  background-color: var(--ad-surface-sunk) !important;
  color: var(--ad-mute) !important;
}

/* -- Alerts --------------------------------------------------------------- */

.alert {
  --bs-alert-border-radius: var(--ad-radius);
  border: 1px solid transparent;
  border-left-width: 3px;
  padding: 0.9rem 1.1rem;
}

.alert-success { background: var(--ad-ok-wash); border-color: color-mix(in srgb, var(--ad-ok) 30%, transparent); border-left-color: var(--ad-ok); color: var(--ad-ok); }
.alert-danger  { background: var(--ad-stop-wash); border-color: color-mix(in srgb, var(--ad-stop) 30%, transparent); border-left-color: var(--ad-stop); color: var(--ad-stop); }
.alert-warning { background: var(--ad-warn-wash); border-color: color-mix(in srgb, var(--ad-warn) 30%, transparent); border-left-color: var(--ad-warn); color: var(--ad-warn); }
.alert-info    { background: var(--ad-accent-wash); border-color: color-mix(in srgb, var(--ad-accent) 30%, transparent); border-left-color: var(--ad-accent); color: var(--ad-accent-deep); }
.alert-secondary,
.alert-debug   { background: var(--ad-surface-sunk); border-color: var(--ad-line); border-left-color: var(--ad-line-strong); color: var(--ad-mute); }

.alert-heading { color: inherit; }

/* -- List groups ---------------------------------------------------------- */

.list-group {
  --bs-list-group-bg: var(--ad-surface);
  --bs-list-group-color: var(--ad-ink);
  --bs-list-group-border-color: var(--ad-line);
  --bs-list-group-border-radius: var(--ad-radius);
  --bs-list-group-action-hover-bg: var(--ad-surface-sunk);
  --bs-list-group-action-hover-color: var(--ad-ink);
  --bs-list-group-active-bg: var(--ad-accent-deep);
  --bs-list-group-active-border-color: var(--ad-accent-deep);
}

/* -- Toasts --------------------------------------------------------------- */

.toast {
  --bs-toast-border-radius: var(--ad-radius);
  box-shadow: var(--ad-shadow-lg);
  border: 1px solid transparent;
  border-left-width: 4px;
  font-weight: 520;
}

.toast.text-bg-success { border-left-color: var(--ad-ok); }
.toast.text-bg-danger  { border-left-color: var(--ad-stop); }
.toast.text-bg-warning { border-left-color: var(--ad-warn); }
.toast.text-bg-primary { border-left-color: var(--ad-accent); }

/* .text-bg-* now yields dark text, so the light close button would vanish. */
.toast .btn-close-white {
  filter: none;
}

/* Toasts clear the sticky desk bar. One landing on top of the event name,
   the counts or the screen tabs hides the things a desk must always be able
   to see. The bar's lower edge is measured and published as
   --ad-deskbar-edge, because both its height and its position move; the
   fallback covers a page that has no desk bar at all. */
.ad-toasts {
  position: fixed;
  top: calc(var(--ad-deskbar-edge, 4.25rem) + 0.75rem);
  right: 0;
  z-index: 1080;
  padding: 1rem;
  max-width: min(26rem, 100vw);
}

:root[data-bs-theme="dark"] .toast .btn-close { filter: invert(1) grayscale(100%) brightness(200%); }

/* -- Focus ---------------------------------------------------------------- */

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

.btn:focus-visible {
  box-shadow: 0 0 0 3px rgba(var(--ad-accent-rgb), 0.28);
  outline: none;
}

/* ==========================================================================
   Components
   ========================================================================== */

/* -- Chrome --------------------------------------------------------------- */

.ad-nav {
  background: var(--ad-surface);
  border-bottom: 1px solid var(--ad-line);
  padding-block: 0.6rem;
}

.ad-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 680;
  font-size: 1.05rem;
  letter-spacing: -0.025em;
  color: var(--ad-ink);
  text-decoration: none;
}

.ad-brand:hover { color: var(--ad-ink); }

/* Two overlapping discs -- one arriving, one already through. The same mark
   the sign-in page carries, but filled from the tokens rather than from the
   two fixed teals that page uses, because this one sits on a light surface as
   often as a dark one and has to hold its separation on both. */
.ad-brand-mark {
  width: 2.05rem;
  height: 1.16rem;
  flex: none;
}

.ad-brand-mark .ad-mark-a {
  fill: color-mix(in srgb, var(--ad-accent) 58%, var(--ad-surface));
}

.ad-brand-mark .ad-mark-b {
  fill: var(--ad-accent);
  fill-opacity: 0.92;
}

/* The wordmark takes the deep stop: at 1.05rem it is a shade under the size
   that would let it ride on the accent alone. */
.ad-brand-word span {
  color: var(--ad-accent-deep);
}

.ad-nav .nav-link {
  color: var(--ad-mute);
  font-weight: 540;
  font-size: 0.92rem;
}

.ad-nav .nav-link:hover { color: var(--ad-ink); }

.ad-user {
  font-family: var(--ad-mono);
  font-size: 0.8rem;
  color: var(--ad-mute);
}

.ad-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius-sm);
  background: transparent;
  color: var(--ad-mute);
  line-height: 1;
}

.ad-theme-toggle:hover { background: var(--ad-surface-sunk); color: var(--ad-ink); }
.ad-theme-toggle svg { width: 1rem; height: 1rem; }
:root:not([data-bs-theme="dark"]) .ad-theme-toggle .ad-icon-sun { display: none; }
:root[data-bs-theme="dark"] .ad-theme-toggle .ad-icon-moon { display: none; }

.page-footer {
  border-top: 1px solid var(--ad-line);
  padding-block: 1.4rem;
  color: var(--ad-mute);
  font-size: 0.82rem;
}

/* -- Shared bits ---------------------------------------------------------- */

/* Serial data: EK numbers, counts, times, ids. Tabular so columns line up and
   a changing count does not make the row twitch. */
.ad-serial {
  font-family: var(--ad-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.ad-eyebrow {
  font-size: 0.7rem;
  font-weight: 660;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ad-mute);
}

/* Bootstrap ships no min-width utility, and flex children need one to be
   allowed to shrink below their content width. */
.min-w-0 { min-width: 0; }

.ad-page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

/* -- Public: front door ---------------------------------------------------
 *
 * The one public page with no task on it: nothing is listed here. It says
 * what the product does, and points anyone who arrived looking for a form
 * back at the invitation that already carries their link.
 *
 * It is drawn on the sign-in page's ground, because these are the two front
 * doors of one product: the same unbroken field running deep navy through
 * teal, the same tracery, the same dark cards, and the same glass panel --
 * holding the answer here rather than a form. The values are lifted from
 * static/css/admin-login.css so the two agree, which also carries over the
 * contrast work done there.
 *
 * One thing is deliberately not lifted. The sign-in page runs its light end
 * into the bottom-right corner, which it can because nothing but one centred
 * line stands there. This page carries base.html's footer across that corner,
 * and no flat colour clears 4.5:1 over a band running teal to near-white --
 * white measured 2.81:1 on --lg-g4. So the linear stops at --pf-g3, where
 * white measures 6.29:1, and the light end becomes a bloom behind the panel.
 * Same field, re-aimed for a page that has chrome at the bottom.
 *
 * The overrides at the end of the block are what .front-door on <body> is
 * for: on this page the nav and the footer stand on the gradient instead of
 * on their own surface, so they go transparent and take light ink.
 */

.front-door {
  --pf-g1: #04141d;
  --pf-g2: #07303a;
  --pf-g3: #1d6b63;
  --pf-g4: #57a89b;
  --pf-ink-0: rgba(4, 20, 29, 0);
  --pf-glow: #e7f4ef;
  --pf-glow-0: rgba(231, 244, 239, 0);
  /* See the note above: the foot of the field is darkened so the footer can
     stand on it. White measures 2.81:1 on --pf-g4 and 9.48:1 through this. */
  --pf-scrim: rgba(4, 20, 29, 0.62);

  /* Type standing on the open gradient rather than on a card or the panel.
     The lightest ground it can land on is --pf-g3, where the sign-in page's
     #b6d3cd measures 3.95:1 -- fine inside a card, not fine out here. This
     clears 5.16:1 there, and more everywhere else. */
  --pf-on-dark: #ffffff;
  --pf-on-dark-mute: #dcece9;
  --pf-hairline: rgba(255, 255, 255, 0.14);

  --pf-card: #10504a;
  --pf-card-line: rgba(255, 255, 255, 0.1);
  --pf-card-mute: #b6d3cd;

  --pf-panel: rgba(219, 240, 233, 0.72);
  --pf-panel-line: rgba(255, 255, 255, 0.6);
  --pf-panel-ink: #0f172a;
  /* The panel is translucent, so what sits behind this type is anywhere from
     mid-teal to near-white depending on the viewport. --ad-mute does not
     clear 4.5:1 at the dark end of that range; this does. */
  --pf-panel-mute: #3c4c5c;
  --pf-cta: #0b7c72;
  --pf-cta-ink: #ffffff;

  background:
    linear-gradient(to bottom, var(--pf-ink-0) 76%, var(--pf-scrim) 100%),
    radial-gradient(64% 56% at 78% 46%, var(--pf-glow) 0%, var(--pf-glow-0) 66%),
    radial-gradient(80% 70% at 4% 0%, var(--pf-g1) 0%, var(--pf-ink-0) 62%),
    linear-gradient(122deg, var(--pf-g1) 0%, var(--pf-g2) 24%,
      var(--pf-g3) 56%, var(--pf-g4) 100%);
  background-attachment: fixed;
  /* The gradient is fixed to the viewport, so anything the viewport does not
     cover -- overscroll, a short page on a tall window -- falls through to
     this rather than to --ad-paper, which is white. */
  background-color: var(--pf-g1);
}

/* The far end goes deep teal rather than pale, the same inversion the sign-in
   page makes: this is a door someone opens in a dim hall as often as at a
   desk, and the page is otherwise the same drawing in both themes. */
:root[data-bs-theme="dark"] .front-door {
  --pf-g1: #020d12;
  --pf-g2: #05212a;
  --pf-g3: #0d4a46;
  --pf-g4: #146059;
  --pf-ink-0: rgba(2, 13, 18, 0);
  --pf-glow: #1d514a;
  --pf-glow-0: rgba(29, 81, 74, 0);
  --pf-scrim: rgba(2, 13, 18, 0.66);

  --pf-card: #093733;
  --pf-card-line: rgba(164, 220, 209, 0.16);

  --pf-panel: rgba(14, 48, 47, 0.72);
  --pf-panel-line: rgba(148, 208, 197, 0.22);
  --pf-panel-ink: #e9f1ee;
  --pf-panel-mute: #94b0ab;
  --pf-cta: #14b8a6;
  --pf-cta-ink: #04231f;
}

/* <main> keeps its container, so the composition lines up with the nav above
   it. Centring it is what makes the page sit in the field rather than at the
   top of it, and the page owns its own vertical padding so the whole
   composition still fits a laptop viewport between the nav and the footer. */
.front-door main {
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-block: 0;
}

.page-front {
  position: relative;
  width: 100%;
  padding-block: clamp(1rem, 3vh, 2rem);
}

/* Texture over the light half, and faint on purpose -- the moment it competes
   with the type it has failed. Fixed to the viewport rather than to the
   container so it crosses the full width of the field, like the sign-in
   page's does. */
.page-front-tracery {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--pf-g3);
  opacity: 0.26;
  pointer-events: none;
}

.page-front-inner {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(19rem, 0.95fr);
  column-gap: clamp(1.75rem, 4vw, 3.5rem);
  row-gap: clamp(1.25rem, 2.5vw, 1.85rem);
  align-items: start;
}

.page-front-lede {
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
}

.page-front .ad-eyebrow {
  display: block;
  margin: 0 0 0.8rem;
  color: var(--pf-on-dark-mute);
}

.page-front-title {
  margin: 0 0 clamp(0.9rem, 2vw, 1.3rem);
  max-width: 16ch;
  font-size: clamp(1.9rem, 4.1vw, 3.2rem);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.022em;
  color: var(--pf-on-dark);
}

.page-front-copy {
  /* Held short of the panel column: past about 46 characters this line would
     run under the bloom, where light type stops being readable. */
  max-width: 44ch;
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--pf-on-dark-mute);
}

/* ---- the arc ---- */

.page-front-steps {
  grid-column: 1;
  grid-row: 2;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.85rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.page-front-step {
  display: flex;
  flex-direction: column;
  padding: 1.15rem 1.1rem 0;
  border: 1px solid var(--pf-card-line);
  border-radius: var(--ad-radius-lg);
  background: linear-gradient(165deg, color-mix(in srgb, var(--pf-card) 88%, #0a2b30), var(--pf-card));
  color: var(--pf-on-dark);
  overflow: hidden;
}

.page-front-step-icon {
  width: 1.9rem;
  height: 1.9rem;
  margin-bottom: 0.8rem;
  color: var(--pf-card-mute);
}

/* The colour is stated rather than inherited: the type scale sets every
   heading to --ad-ink, which on this card is navy on near-navy. */
.page-front-step-title {
  margin: 0 0 0.35rem;
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--pf-on-dark);
}

.page-front-step-copy {
  margin: 0;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--pf-card-mute);
}

/* The card's floor, bleeding to the bottom edge the way the sign-in page's
   does, so the card reads as a window onto the thing rather than a box with a
   picture in it. Each one is the artifact that step produces -- a form, a
   pass, a door -- rather than a generic spot illustration. */
.page-front-step-art {
  display: block;
  width: 100%;
  height: auto;
  margin-top: auto;
  padding-top: 0.9rem;
  color: #6fd8c6;
  opacity: 0.72;
}

/* ---- the panel ---- */

/* The sign-in page's glass, carrying the two questions people actually
   arrive with -- "where is my event" and "where is my pass" -- where that
   page carries the form. Same place on the page, same job: the thing you
   came for. */
.page-front-panel {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  min-width: 0;
  padding: clamp(1.35rem, 2.4vw, 1.85rem);
  border: 1px solid var(--pf-panel-line);
  border-radius: var(--ad-radius-lg);
  background: var(--pf-panel);
  -webkit-backdrop-filter: blur(16px) saturate(125%);
  backdrop-filter: blur(16px) saturate(125%);
  box-shadow: var(--ad-shadow-lg);
  color: var(--pf-panel-mute);
}

.page-front-panel-title {
  margin: 0 0 0.6rem;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--pf-panel-ink);
}

.page-front-panel p {
  margin: 0 0 0.7rem;
  font-size: 0.94rem;
  line-height: 1.55;
}

.page-front-panel p:last-of-type {
  margin-bottom: 1.25rem;
}

.page-front-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.78rem 1rem;
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--pf-cta-ink);
  background: var(--pf-cta);
  border-radius: var(--ad-radius-sm);
  transition: background-color 0.14s ease, transform 0.08s ease;
}

.page-front-cta svg {
  width: 0.95rem;
  height: 0.95rem;
}

.page-front-cta:hover {
  color: var(--pf-cta-ink);
  background: color-mix(in srgb, var(--pf-cta) 86%, #000);
}

.page-front-cta:active { transform: translateY(1px); }

.page-front-cta:focus-visible {
  outline: 2px solid var(--pf-on-dark);
  outline-offset: 2px;
}

/* ---- the chrome, standing on the gradient ---- */

.front-door .ad-nav {
  background: transparent;
  border-bottom-color: var(--pf-hairline);
}

.front-door .ad-brand,
.front-door .ad-brand:hover {
  color: var(--pf-on-dark);
}

/* The sign-in page's two fixed teals rather than the token pair. The tokens
   mix the mark against --ad-surface, which is white in light mode, and the
   disc that is meant to read as "arriving" comes out nearly as pale as the
   wordmark next to it on this ground. */
.front-door .ad-brand-mark .ad-mark-a { fill: #5ec9b8; }

.front-door .ad-brand-mark .ad-mark-b {
  fill: #2f8f83;
  fill-opacity: 0.92;
}

.front-door .ad-brand-word span { color: #35c4b0; }

.front-door .ad-nav .nav-link { color: var(--pf-on-dark-mute); }
.front-door .ad-nav .nav-link:hover { color: var(--pf-on-dark); }
.front-door .ad-user { color: var(--pf-on-dark-mute); }

.front-door .ad-theme-toggle {
  border-color: rgba(255, 255, 255, 0.24);
  color: var(--pf-on-dark-mute);
}

.front-door .ad-theme-toggle:hover {
  background: rgba(255, 255, 255, 0.12);
  color: var(--pf-on-dark);
}

/* Only ever seen by a signed-in staff member who has walked back to the front
   door, but it is the one control here that would otherwise be slate on navy. */
.front-door .ad-nav .btn-outline-secondary {
  --bs-btn-color: var(--pf-on-dark-mute);
  --bs-btn-border-color: rgba(255, 255, 255, 0.3);
  --bs-btn-hover-color: var(--pf-g1);
  --bs-btn-hover-bg: var(--pf-on-dark-mute);
  --bs-btn-hover-border-color: var(--pf-on-dark-mute);
}

.front-door .page-footer {
  border-top-color: var(--pf-hairline);
  color: var(--pf-on-dark-mute);
}

/* ---- narrow ----
 *
 * The order becomes headline, answer, arc. The panel keeps its place directly
 * under the h1 rather than sinking below three cards, because someone who
 * opened the bare domain on a phone is looking for their event, and the arc
 * is the part they can scroll past.
 */

@media (max-width: 64rem) {
  .front-door {
    /* The bloom follows the panel, which is now mid-page and full width, and
       the field is re-aimed down a tall viewport rather than across a wide one. */
    background:
      linear-gradient(to bottom, var(--pf-ink-0) 80%, var(--pf-scrim) 100%),
      radial-gradient(96% 26% at 56% 44%, var(--pf-glow) 0%, var(--pf-glow-0) 68%),
      radial-gradient(90% 40% at 4% 0%, var(--pf-g1) 0%, var(--pf-ink-0) 62%),
      linear-gradient(158deg, var(--pf-g1) 0%, var(--pf-g2) 28%,
        var(--pf-g3) 62%, var(--pf-g4) 100%);
    background-attachment: fixed;
    background-color: var(--pf-g1);
  }

  .page-front-inner {
    grid-template-columns: minmax(0, 1fr);
  }

  .page-front-panel {
    grid-column: 1;
    grid-row: 2;
  }

  .page-front-steps {
    grid-column: 1;
    grid-row: 3;
  }

  .page-front-title { max-width: 20ch; }

  .page-front-tracery { opacity: 0.12; }
}

@media (max-width: 46rem) {
  .page-front-steps {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The art goes first: three illustrations stacked turns the page into a
     scroll, and the headings carry the meaning without them. */
  .page-front-step-art { display: none; }
  .page-front-step { padding-bottom: 1.15rem; }
}

@media (prefers-reduced-motion: reduce) {
  .page-front-cta { transition: none; }
  .page-front-cta:active { transform: none; }
}

/* -- Public: the pass -----------------------------------------------------
 *
 * Deliberately not "ad-column", and .page-footer is not "ad-footer", even
 * though everything else here uses the ad- prefix. Both names sit on ad
 * blocker filter lists, and .ad-column wraps the whole of <main> on the
 * public pages -- so a guest running uBlock saw a page with a header, a
 * footer's worth of blank, and nothing else. Keep new wrapper and footer
 * names clear of the prefix.
 */

.page-column {
  max-width: 34rem;
  margin-inline: auto;
}

.ad-pass {
  position: relative;
  background: var(--ad-surface);
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius-lg);
  box-shadow: var(--ad-shadow-md);
  overflow: hidden;
}

.ad-pass-body { padding: 1.9rem 1.75rem; }

.ad-pass-head { margin-bottom: 1.5rem; }
.ad-pass-head h1 { font-size: 1.4rem; margin: 0.35rem 0 0.3rem; }
.ad-pass-head .ad-pass-when { color: var(--ad-mute); font-size: 0.9rem; margin: 0; }

/* The stub: the half of the pass that is actually presented at the door.
   It gets its own ground and a perforated edge, because the two halves of a
   ticket do different jobs and a single flat card hides that. */
.ad-stub {
  position: relative;
  padding: 2rem 1.75rem 1.75rem;
  background: var(--ad-surface-sunk);
  border-top: 2px dashed var(--ad-line-strong);
}

/* The notches. Filled with the page ground and clipped by the card's own
   overflow, so they read as bites taken out of the edge. */
.ad-stub::before,
.ad-stub::after {
  content: "";
  position: absolute;
  top: -0.65rem;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: var(--ad-paper);
}

.ad-stub::before { left: -0.65rem; }
.ad-stub::after { right: -0.65rem; }

/* Always white with black modules regardless of theme: a scanner reads
   contrast, not our palette, and inverting a QR breaks a good many readers. */
.ad-qr {
  display: block;
  margin: 0 auto;
  width: 208px;
  max-width: 100%;
  height: auto;
  padding: 0.85rem;
  background: #fff;
  border-radius: var(--ad-radius-sm);
  box-shadow: var(--ad-shadow-sm);
  /* QR modules are hard pixels; smoothing them costs scan reliability. */
  image-rendering: pixelated;
}

.ad-stub-note {
  text-align: center;
  margin: 1.1rem 0 0;
  font-size: 0.92rem;
  font-weight: 540;
}

.ad-stub-meta {
  text-align: center;
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--ad-mute);
}

/* A labelled scrap of identity: "Registered to", "Staff number". */
.detail-field { margin-top: 1.25rem; }
.detail-field .ad-eyebrow { display: block; margin-bottom: 0.2rem; }
.detail-field p { margin: 0; font-size: 0.95rem; overflow-wrap: anywhere; }

/* -- Desk: staff-facing screens ------------------------------------------- */

.ad-deskbar {
  background: var(--ad-surface);
  border-bottom: 1px solid var(--ad-line);
  position: sticky;
  top: 0;
  z-index: 1030;
}

.ad-deskbar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
  padding-block: 0.9rem;
}

.ad-deskbar h1 {
  font-size: 1.12rem;
  margin: 0;
  line-height: 1.25;
}

.ad-deskbar .ad-eyebrow { display: block; margin-bottom: 0.15rem; }

.ad-desk-tabs {
  display: inline-flex;
  gap: 0.25rem;
  padding: 0.22rem;
  background: var(--ad-tabstrip);
  border-radius: var(--ad-radius-sm);
  margin-left: auto;
}

.ad-desk-tab {
  padding: 0.4rem 0.85rem;
  border-radius: 5px;
  font-size: 0.85rem;
  font-weight: 570;
  color: var(--ad-mute);
  text-decoration: none;
  white-space: nowrap;
}

.ad-desk-tab:hover { color: var(--ad-ink); }

.ad-desk-tab[aria-current="page"] {
  background: var(--ad-tab-active);
  color: var(--ad-ink);
  box-shadow: var(--ad-shadow-sm);
}

/* Connection state earns a dot rather than only a word: at a glance across a
   desk, colour and motion read faster than reading "Reconnecting". */
.ad-conn {
  display: inline-flex;
  align-items: center;
  gap: 0.42rem;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.3rem 0.7rem 0.3rem 0.6rem;
  border-radius: 999px;
  background: var(--ad-surface-sunk);
  color: var(--ad-mute);
  white-space: nowrap;
}

.ad-conn::before {
  content: "";
  width: 0.46rem;
  height: 0.46rem;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.ad-conn[data-state="live"] { background: var(--ad-ok-wash); color: var(--ad-ok); }
.ad-conn[data-state="reconnecting"] { background: var(--ad-warn-wash); color: var(--ad-warn); }
.ad-conn[data-state="offline"] { background: var(--ad-stop-wash); color: var(--ad-stop); }

/* An expired session is not a network fault, and the badge is the way back
   in: a desk phone mid-queue should not have to be told an address. */
.ad-conn[data-state="signed-out"] {
  background: var(--ad-stop-wash);
  color: var(--ad-stop);
  text-decoration: none;
  cursor: pointer;
}

.ad-conn[data-state="signed-out"]:hover { text-decoration: underline; }

.ad-conn[data-state="reconnecting"]::before { animation: ad-pulse 1.1s ease-in-out infinite; }

@keyframes ad-pulse {
  50% { opacity: 0.25; }
}

.ad-stock {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--ad-mute);
}

.ad-stock b {
  font-family: var(--ad-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.05rem;
  font-weight: 620;
  color: var(--ad-ink);
}

/* The scanner viewport. html5-qrcode injects its own markup, so this mostly
   exists to stop it looking like a 2013 file-upload widget. */
.ad-scanner {
  border-radius: var(--ad-radius);
  overflow: hidden;
  background: var(--ad-surface-sunk);
}

.ad-scanner video { display: block; width: 100% !important; border-radius: var(--ad-radius); }
.ad-scanner img[alt="Info icon"] { display: none; }
.ad-scanner > div { border: 0 !important; padding: 0 !important; }

.ad-scanner button {
  border: 1px solid var(--ad-line-strong);
  background: var(--ad-surface);
  color: var(--ad-ink);
  border-radius: var(--ad-radius-sm);
  padding: 0.45rem 0.9rem;
  font-size: 0.88rem;
  font-weight: 560;
  margin: 0.35rem 0.25rem;
}

.ad-scanner button:hover { background: var(--ad-surface-sunk); }
.ad-scanner select { max-width: 100%; }
.ad-scanner a { color: var(--ad-accent-deep); }
.ad-scanner span { color: var(--ad-mute); font-size: 0.85rem; }

/* The subject card: who is at the door right now. Deliberately the largest
   type on the screen -- it is read at arm's length, at an angle, in a hurry. */
.ad-subject {
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius-lg);
  background: var(--ad-surface);
  box-shadow: var(--ad-shadow-md);
  overflow: hidden;
}

/* State is carried by a rail down the edge as well as by the badge, so the
   card's status survives peripheral vision. */
.ad-subject { border-left: 4px solid var(--ad-line-strong); }
.ad-subject[data-state="in"] { border-left-color: var(--ad-ok); }
.ad-subject[data-state="out"] { border-left-color: var(--ad-line-strong); }

.ad-subject-body { padding: 1.5rem 1.65rem; }

.ad-subject-name {
  font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2rem);
  font-weight: 660;
  letter-spacing: -0.028em;
  margin: 0 0 0.2rem;
  overflow-wrap: anywhere;
}

.ad-subject-id {
  font-family: var(--ad-mono);
  font-size: 0.88rem;
  color: var(--ad-mute);
  margin: 0;
  overflow-wrap: anywhere;
}

.ad-subject-states {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35rem;
  flex: none;
}

.ad-subject-answers {
  margin: 1.25rem 0 0;
  padding-top: 1.1rem;
  border-top: 1px solid var(--ad-line);
  display: grid;
  grid-template-columns: minmax(7rem, auto) 1fr;
  gap: 0.5rem 1.25rem;
  font-size: 0.9rem;
}

.ad-subject-answers dt {
  font-weight: 560;
  color: var(--ad-mute);
}

.ad-subject-answers dd { margin: 0; overflow-wrap: anywhere; }

.ad-subject-answers:empty { display: none; }

/* What is going over the counter with the check-in. Read and hit at a desk
   with a queue in front of it, so the targets are larger than a form's. */
.ad-handout {
  margin-top: 1.25rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--ad-line);
}

.ad-handout-title {
  display: block;
  font-size: 0.78rem;
  font-weight: 620;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ad-mute);
  margin-bottom: 0.6rem;
}

.ad-handout .form-check {
  min-height: 2.9rem;
  padding: 0.55rem 1rem 0.55rem 0.85rem;
}

.ad-handout .form-check-input { width: 1.35em; height: 1.35em; }

/* Already handed over on an earlier day: shown, so the desk can see why it
   is not on offer, but inert. */
.ad-handout .form-check:has(.form-check-input:disabled) {
  background: var(--ad-surface-sunk);
  border-color: var(--ad-line);
}

.ad-handout .form-check:has(.form-check-input:disabled) .form-check-label {
  color: var(--ad-mute);
  cursor: default;
}

/* Explains a disabled action, so the desk is not left guessing why the
   button will not take. */
.ad-subject-hint {
  margin: 1rem 0 0;
  font-size: 0.88rem;
  color: var(--ad-warn);
}

/* Door actions are hit fast, sometimes one-handed, sometimes by someone
   holding a scanner in the other hand. 56px minimum. */
.ad-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.65rem;
  margin-top: 1.4rem;
}

.ad-actions .btn {
  min-height: 3.5rem;
  padding-inline: 1.6rem;
  font-size: 1.02rem;
  flex: 1 1 12rem;
}

/* Corrections sit under the door's actions at a fraction of their weight.
   A supervisor looking for them finds them; nobody else reads the card as a
   row of six equally likely things to press. */
.ad-undo-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.ad-undo-actions .btn {
  min-height: 2.4rem;
  padding-inline: 0.9rem;
  font-size: 0.85rem;
  border-color: var(--ad-line);
  color: var(--ad-mute);
}

.ad-undo-actions .btn:hover,
.ad-undo-actions .btn:focus-visible {
  border-color: var(--ad-warn);
  color: var(--ad-warn);
  background: transparent;
}

/* A dimmed accent button still reads as "press me". A refused action should
   look inert, and the hint beside it says why. */
.ad-actions .btn:disabled,
.ad-actions .btn.disabled {
  opacity: 1;
  background: var(--ad-surface-sunk);
  border-color: var(--ad-line);
  color: var(--ad-mute);
  cursor: not-allowed;
}

.ad-placeholder {
  padding: 2.75rem 1.5rem;
  text-align: center;
  color: var(--ad-mute);
  border: 1px dashed var(--ad-line-strong);
  border-radius: var(--ad-radius-lg);
  font-size: 0.92rem;
}

/* -- Search results ------------------------------------------------------- */

.ad-results {
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius);
  background: var(--ad-surface);
  box-shadow: var(--ad-shadow-md);
  max-height: 17rem;
  overflow-y: auto;
  padding: 0.25rem;
}

.ad-results .list-group-item {
  border: 0;
  border-radius: var(--ad-radius-sm);
  padding: 0.6rem 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-align: left;
}

.ad-results .list-group-item.active {
  background: var(--ad-accent-wash);
  color: var(--ad-ink);
}

/* -- Dashboard ------------------------------------------------------------ */

.ad-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.85rem;
}

/* Four saturated cards shout equally and so say nothing. A quiet surface lets
   the figure carry the weight, and the figure takes the colour its state
   already means elsewhere in the app -- green is "checked in" on the badges
   and on the pie chart, so it is green here too. */
.ad-stat {
  position: relative;
  background: var(--ad-surface);
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius);
  box-shadow: var(--ad-shadow-sm);
  padding: 1.1rem 1.25rem;
}

.ad-stat[data-tone="accent"] .ad-stat-value { color: var(--ad-accent); }
.ad-stat[data-tone="ok"] .ad-stat-value { color: var(--ad-ok); }
.ad-stat[data-tone="warn"] .ad-stat-value { color: var(--ad-warn); }
.ad-stat[data-tone="stop"] .ad-stat-value { color: var(--ad-stop); }

.ad-stat-label {
  display: block;
  font-size: 0.7rem;
  font-weight: 660;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ad-mute);
  margin-bottom: 0.5rem;
}

.ad-stat-value {
  font-family: var(--ad-mono);
  font-variant-numeric: tabular-nums;
  font-size: 2.25rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.04em;
  margin: 0;
}

.ad-stat-sub {
  margin: 0.4rem 0 0;
  font-size: 0.78rem;
  color: var(--ad-mute);
}

.ad-chart {
  position: relative;
  height: 240px;
}

/* The share of the list that is in, printed inside the ring rather than read
   off a legend. Chart.js cannot draw into the hole, so the figure is ordinary
   markup sitting in it -- and stays out of the way of the pointer. */
.ad-chart-dial .ad-dial-centre {
  position: absolute;
  inset: 0;
  bottom: 2.6rem; /* clear of the legend, so the figure centres on the ring */
  display: grid;
  place-content: center;
  text-align: center;
  pointer-events: none;
}

.ad-dial-centre b {
  display: block;
  font-family: var(--ad-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.9rem;
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--ad-ok);
}

.ad-dial-centre span {
  font-size: 0.7rem;
  font-weight: 660;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ad-mute);
}

/* -- Dashboard: a bar for a proportion ------------------------------------
 *
 * Used for the headline turnout and for every group row, so the same shape
 * always means the same thing: how much of this is in.
 */

.ad-meter {
  height: 5px;
  border-radius: 999px;
  background: var(--ad-surface-sunk);
  overflow: hidden;
}

.ad-meter > span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--ad-ok);
  transition: width 0.35s ease;
}

/* A group nobody has arrived from is the one worth spotting: a hairline of
   the stop colour reads as "none yet" where a bar of zero width reads as a
   rendering fault. */
.ad-meter[data-empty="true"] {
  background: var(--ad-stop-wash);
}

.ad-stat .ad-meter {
  margin-top: 0.7rem;
}

/* The three timings under the counters. One quiet line, because they are
   context for the numbers above rather than numbers in their own right. */
.ad-pace-strip {
  margin: 0.85rem 0 0;
  min-height: 1.2rem;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--ad-mute);
}

/* Title, then whatever explains it, then the downloads hard right. */
.ad-card-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}

.ad-card-note {
  font-weight: 400;
  font-size: 0.78rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ad-mute);
}

.ad-card-tools {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  margin-left: auto;
}

/* The pace line and the page-level downloads share a row: both are context
   for the counters above rather than content in their own right. */
.ad-dash-bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}

/* An offer, not a call to action. These sit in the corner of a panel someone
   is watching, so they stay quiet until the pointer finds them -- and they
   look identical whether the markup is a link (a file the server builds) or a
   button (an image the page builds). */
.ad-export {
  padding: 0.15rem 0.55rem;
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius-sm);
  background: transparent;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ad-mute);
  white-space: nowrap;
  cursor: pointer;
}

.ad-export:hover,
.ad-export:focus-visible {
  border-color: var(--ad-accent);
  color: var(--ad-accent-deep);
}

/* -- Dashboard: turnout by group ------------------------------------------
 *
 * The counters say how many are in; this says which parts of the crowd they
 * came from, which is what tells you whether to hold the opening or start.
 */

.ad-segments {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.4rem 1.75rem;
}

.ad-segment-title {
  margin: 0 0 0.7rem;
  font-size: 0.7rem;
  font-weight: 660;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ad-mute);
}

.ad-segment-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

/* Name and count on one line, the bar spanning underneath both: the eye runs
   down the bars to compare, and down the names to find one. */
.ad-segment-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.15rem 0.75rem;
}

.ad-segment-row .ad-meter {
  grid-column: 1 / -1;
}

.ad-segment-name {
  font-size: 0.87rem;
  color: var(--ad-ink);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

a.ad-segment-name:hover {
  color: var(--ad-accent-deep);
  text-decoration: underline;
}

.ad-segment-count {
  font-family: var(--ad-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.82rem;
  color: var(--ad-mute);
}

.ad-segment-count b {
  font-weight: 600;
  color: var(--ad-ok);
}

.ad-segment-more,
.ad-empty-note {
  margin: 0.7rem 0 0;
  font-size: 0.8rem;
  color: var(--ad-mute);
}

/* -- Dashboard: latest arrivals -------------------------------------------
 *
 * A counter that is not moving looks exactly like a counter nobody is adding
 * to. This panel is how you tell a quiet door from a stalled one.
 */

.ad-feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.55rem;
  align-content: start;
}

.ad-feed-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--ad-line);
}

.ad-feed-row:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.ad-feed-main {
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
}

.ad-feed-name {
  font-size: 0.89rem;
  color: var(--ad-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-feed-tag {
  flex: none;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--ad-accent-wash);
  color: var(--ad-accent-deep);
}

.ad-feed-meta {
  flex: none;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.ad-feed-time {
  display: block;
  font-family: var(--ad-mono);
  font-size: 0.8rem;
  color: var(--ad-ink);
}

.ad-feed-ago {
  display: block;
  font-size: 0.7rem;
  color: var(--ad-mute);
}

/* -- Misc ----------------------------------------------------------------- */

.notice-panel {
  max-width: 34rem;
  margin-inline: auto;
  text-align: center;
  padding: 2.75rem 1.75rem;
  background: var(--ad-surface);
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius-lg);
  box-shadow: var(--ad-shadow-md);
}

.notice-panel h1 { font-size: 1.45rem; margin-bottom: 0.5rem; }
.notice-panel p { color: var(--ad-mute); margin-bottom: 0; }

.notice-mark {
  width: 3rem;
  height: 3rem;
  margin: 0 auto 1.1rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--ad-ok-wash);
  color: var(--ad-ok);
}

.notice-mark[data-tone="warn"] { background: var(--ad-warn-wash); color: var(--ad-warn); }
.notice-mark svg { width: 1.5rem; height: 1.5rem; }

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

@media (max-width: 575.98px) {
  /* The note explains the panel to someone reading it; on a phone it lands
     under its own title and reads as a second heading. The panel is legible
     without it. */
  .ad-card-note { display: none; }
  .ad-pass-body { padding: 1.5rem 1.15rem; }
  .ad-tear { margin-inline: -1.15rem; }
  .ad-subject-body { padding: 1.25rem 1.15rem; }
  /* Side by side, the badges hold their natural width and squeeze the name
     into a column of single letters. On a phone they go under it. */
  .ad-subject-head { flex-direction: column; }
  .ad-subject-states {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-self: stretch;
  }
  .ad-desk-tabs { margin-left: 0; width: 100%; }
  .ad-desk-tab { flex: 1; text-align: center; }
}

/* -- Desk: adding a walk-in ----------------------------------------------
 *
 * Appears only after a search has come back empty, so it reads as the answer
 * to "they are not on the list" rather than as a second thing to fill in.
 */

.ad-walk-in-add {
  font-weight: 600;
  color: var(--ad-accent-deep);
}

.ad-walk-in {
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius);
  background: var(--ad-surface-sunk);
  padding: 1rem;
  display: grid;
  gap: 0.75rem;
}

.ad-walk-in-title {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ad-mute);
}

.ad-walk-in-field {
  display: grid;
  gap: 0.3rem;
}

.ad-walk-in-field label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ad-ink);
}

/* The form fields are rendered by Django, so they arrive without Bootstrap's
   classes -- style them by element rather than adding markup in the view. */
.ad-walk-in-field input,
.ad-walk-in-field select,
.ad-walk-in-field textarea {
  width: 100%;
  padding: 0.55rem 0.7rem;
  font: inherit;
  color: var(--ad-ink);
  background: var(--ad-paper);
  border: 1px solid var(--ad-line-strong);
  border-radius: var(--ad-radius-sm);
}

.ad-walk-in-field input:focus,
.ad-walk-in-field select:focus,
.ad-walk-in-field textarea:focus {
  outline: 2px solid var(--ad-accent);
  outline-offset: 1px;
  border-color: var(--ad-accent);
}

.ad-walk-in-note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ad-mute);
}

/* The one control on this form that does something rather than records
   something, so it is set apart from the fields above it and given a target
   big enough for a thumb at a door. */
.ad-walk-in-admit {
  margin: 0.85rem 0 0.25rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--ad-line);
}

.ad-walk-in-admit .form-check-input {
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.1rem;
}

.ad-walk-in-admit .form-check-label {
  font-weight: 600;
  cursor: pointer;
}

/* Indented under the admission it depends on, and visibly inert until that
   box is ticked -- a control that cannot be used should look like it. */
.ad-walk-in-extra {
  margin: 0.35rem 0 0.25rem 1.6rem;
}

.ad-walk-in-extra .form-check-input:disabled + .form-check-label {
  opacity: 0.5;
}

.ad-walk-in-extra .form-check-label {
  cursor: pointer;
}

/* ---------------------------------------------------------------------------
   The day a desk is working.

   Sits above everything on the check-in and merchandise desks, and only when
   the event runs over more than one day. Deliberately loud: a desk that
   reloads and quietly reverts to day one files every arrival after it against
   the wrong day, and the mistake is invisible until the report. The day is
   stated at heading size, with its date beside it, so a glance from arm's
   length settles it.
--------------------------------------------------------------------------- */

.ad-daybar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  margin-bottom: 1rem;
  padding: 0.9rem 1.25rem;
  border: 1px solid var(--ad-line);
  border-left: 4px solid var(--ad-accent);
  border-radius: var(--ad-radius-lg);
  background: var(--ad-surface);
  box-shadow: var(--ad-shadow-md);
}

.ad-daybar-face {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
  min-width: 0;
}

.ad-daybar-label {
  margin: 0;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ad-mute);
  flex-basis: 100%;
}

.ad-daybar-day {
  margin: 0;
  font-size: clamp(1.35rem, 1.05rem + 1.1vw, 1.75rem);
  font-weight: 680;
  letter-spacing: -0.02em;
  color: var(--ad-accent-deep);
}

.ad-daybar-date {
  margin: 0;
  font-family: var(--ad-mono);
  font-size: 0.88rem;
  color: var(--ad-mute);
}

.ad-daybar-switch {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

.ad-daybar-switch label {
  font-size: 0.8rem;
  color: var(--ad-mute);
  white-space: nowrap;
  margin: 0;
}

.ad-daybar-switch .form-select { width: auto; min-width: 11rem; }

/* Days already attended, under the name on the subject card. */
.ad-subject-days {
  margin: 0.35rem 0 0;
  font-size: 0.82rem;
  color: var(--ad-mute);
}

/* ---------------------------------------------------------------------------
   Day tabs on the live board.

   Each tab carries its own arrivals count, so the comparison the client
   actually wants -- how day two is tracking against day one -- is readable
   without switching tabs at all.
--------------------------------------------------------------------------- */

.ad-daytabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.ad-daytab {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas:
    "label count"
    "date  count";
  align-items: center;
  gap: 0 0.9rem;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--ad-line);
  border-radius: var(--ad-radius-lg);
  background: var(--ad-surface);
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.ad-daytab:hover { border-color: var(--ad-line-strong); }

.ad-daytab.is-active {
  border-color: var(--ad-accent);
  background: var(--ad-accent-wash);
}

.ad-daytab:focus-visible {
  outline: 2px solid var(--ad-accent);
  outline-offset: 2px;
}

.ad-daytab-label {
  grid-area: label;
  font-weight: 640;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
}

.ad-daytab-date {
  grid-area: date;
  font-size: 0.76rem;
  color: var(--ad-mute);
}

.ad-daytab-count {
  grid-area: count;
  font-family: var(--ad-mono);
  font-size: 1.35rem;
  font-weight: 640;
  line-height: 1;
  color: var(--ad-accent-deep);
  font-variant-numeric: tabular-nums;
}
