/* ============================================================
   Page components — the buyer-facing and staff-facing screens.

   base.css carries the tokens and a handful of global elements. Everything
   below is the vocabulary the pages themselves were written against and which
   had never been written: cards, heroes, rows, badges, grids. Without it the
   ticket a buyer opens at the door, the confirmation page, the scanner and the
   organiser profile all rendered as unstyled stacked divs.

   Two rules kept it from becoming 293 bespoke blocks:

     · Pages reuse the same shapes under different names (`order-card`,
       `upsell-card`, `event-card`, `confirmed-card` are one card), so the
       shared behaviour is declared once against a grouped selector and only
       the genuine differences are spelled out.
     · Everything is built from the base.css tokens, so a tenant's branding
       flows through exactly as it does elsewhere.

   Scoped `<style>` in a page does NOT work on these screens: their markup is
   injected with `set:html`, so Astro cannot stamp its scoping attribute and a
   scoped sheet matches nothing at all. That is why this is a plain stylesheet.
   ============================================================ */

/* ── Dashboard token aliases ──────────────────────────────────────────────
   ticket.astro, scanner.astro and door.astro were written against the
   *dashboard's* token names, which do not exist on the frontend — so their
   inline `style="…var(--bdr)…"` attributes resolved to nothing. Aliased here
   rather than rewritten in three files, so the pages keep working whichever
   vocabulary a future edit reaches for. */
:root {
  --acc: var(--color-primary);
  --acc-lo: var(--color-accent);
  --acc-tint: color-mix(in oklab, var(--color-primary) 14%, transparent);
  --bdr: var(--color-border);
  --bdr-hi: color-mix(in oklab, var(--color-border) 65%, var(--color-text));
  --bg-1: var(--color-bg);
  --bg-2: var(--color-bg-alt);
  --tx: var(--color-text);
  --tx2: var(--color-text-muted);
  --tx3: color-mix(in oklab, var(--color-text-muted) 80%, transparent);
  /* The dashboard's names for the semantic tones base.css already defines —
     one definition of "green", not two that can drift. */
  --green: var(--tone-ok);
  --green-tint: color-mix(in oklab, var(--tone-ok) 15%, transparent);
  --red: var(--tone-bad);
  --red-tint: color-mix(in oklab, var(--tone-bad) 15%, transparent);
  --amber: var(--tone-warn);
  --amber-tint: color-mix(in oklab, var(--tone-warn) 15%, transparent);
}

/* ── Utilities ───────────────────────────────────────────────────────────── */

/* Deliberately NOT `!important`.
   These pages ship an element as `class="confirmed-page is-hidden"` and reveal
   it by setting an inline `style.display = "block"` *without removing the
   class*. An inline style beats a class selector, so plain `display: none` is
   exactly right — and `!important` would outrank the inline rule and leave the
   confirmation page permanently blank. */
.is-hidden { display: none; }

.spinner-wrap {
  display: grid;
  place-items: center;
  padding: 4rem 1rem;
}

/* ── Page shells ─────────────────────────────────────────────────────────── */

.main-wrap,
.portal-wrap,
.confirmed-page,
.pass-layout,
.transfer-page {
  max-width: 46rem;
  margin: 0 auto;
  padding: 2rem 1.25rem 5rem;
}
@media (max-width: 40rem) {
  .main-wrap,
  .portal-wrap,
  .confirmed-page,
  .pass-layout,
  .transfer-page { padding: 1.25rem 1rem 4rem; }
}

.section-heading,
.section-title,
.confirmed-section-title,
.upsell-strip__title,
.feed-title {
  font-family: var(--font-display, inherit);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 2rem 0 .85rem;
}

/* ── Heroes ──────────────────────────────────────────────────────────────── */

.confirmed-hero,
.transfer-hero,
.review-hero,
.pass-confirmed-hero {
  text-align: center;
  padding: 1.5rem 0 1.75rem;
}

.confirmed-hero__icon,
.transfer-hero__icon,
.review-hero__icon,
.review-done__icon,
.claim-emoji {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  margin: 0 auto 1rem;
  font-size: 1.75rem;
  line-height: 1;
  border-radius: 999px;
  background: color-mix(in oklab, var(--color-primary) 16%, transparent);
}

.confirmed-hero__title,
.transfer-hero__title,
.review-hero__title,
.transfer-success__title,
.review-done__title,
.claim-title {
  font-family: var(--font-display, inherit);
  font-size: clamp(1.5rem, 4.5vw, 2rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin-bottom: .5rem;
}

.confirmed-hero__subtitle,
.transfer-hero__subtitle,
.transfer-success__subtitle,
.review-done__sub,
.review-hero__event,
.claim-sub,
.confirmed-share-intro {
  color: var(--color-text-muted);
  font-size: .95rem;
  max-width: 34rem;
  margin-inline: auto;
}

/* ── Cards ───────────────────────────────────────────────────────────────── */

.confirmed-card,
.transfer-card,
.order-card,
.upsell-card,
.event-card,
.review-card,
.claim-card,
.magic-card,
.result-card,
.feed-card,
.count-card,
.progress-card,
.stat-box,
.config-box,
.pass-event-row,
.confirmed-info,
.confirmed-share-panel,
.refund-form-panel,
.setup-form {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.confirmed-card,
.transfer-card,
.order-card,
.upsell-card,
.event-card,
.review-card,
.claim-card,
.magic-card,
.feed-card,
.pass-event-row {
  margin-bottom: 1rem;
  overflow: hidden;
}

.confirmed-card__head,
.transfer-card__head,
.order-card__header {
  display: flex;
  gap: .85rem;
  align-items: center;
  padding: .9rem 1.15rem;
  border-bottom: 1px solid var(--color-border);
}

.confirmed-card__body,
.transfer-card__body,
.upsell-card__body,
.event-card__body,
.review-card,
.claim-card,
.magic-card,
.confirmed-info,
.confirmed-share-panel,
.refund-form-panel,
.setup-form,
.config-box { padding: 1.15rem; }

.order-card__footer,
.upsell-card__footer {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .9rem;
  align-items: center;
  justify-content: space-between;
  padding: .85rem 1.15rem;
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-alt);
}

.order-card__title,
.upsell-card__name,
.event-card__title,
.confirmed-event-title,
.transfer-event-title,
.pass-event-row__title,
.feed-item__name,
.result-card__name,
.search-row__name {
  font-weight: 700;
  line-height: 1.25;
}

.order-card__meta,
.upsell-card__desc,
.event-card__date,
.event-card__venue,
.confirmed-event-meta,
.transfer-event-meta,
.transfer-event-date,
.pass-event-row__meta,
.pass-event-row__date,
.feed-item__type,
.feed-item__time,
.search-row__sub,
.result-card__meta,
.ticket-row__sub,
.col-desc,
.org-bio,
.past-item__meta,
.confirmed-share-note,
.reentry-hint,
.scan-hint {
  color: var(--color-text-muted);
  font-size: .85rem;
}

.order-card__total,
.upsell-card__price,
.event-card__price,
.pass-price-display {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.pass-price-display { font-size: 1.6rem; }

/* Card images, and the placeholder for an event with no artwork. A missing
   image must still hold its space, or every card in a grid is a different
   height and the grid reads as broken rather than sparse. */
.order-card__img,
.upsell-card__img,
.event-card__img,
.past-item__img,
.order-card__img-placeholder,
.upsell-card__img-placeholder,
.event-card__img-placeholder {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
}
.order-card__img,
.order-card__img-placeholder,
.past-item__img {
  width: 3.5rem;
  height: 3.5rem;
  aspect-ratio: 1;
  border-radius: var(--radius);
  flex: 0 0 auto;
}
.order-card__img-placeholder,
.upsell-card__img-placeholder,
.event-card__img-placeholder {
  background: linear-gradient(
    135deg,
    color-mix(in oklab, var(--color-primary) 22%, var(--color-bg-alt)),
    var(--color-bg-alt)
  );
}

/* ── Grids ───────────────────────────────────────────────────────────────── */

.event-grid,
.upsell-grid,
.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.event-card,
.upsell-card { margin-bottom: 0; }

.event-card {
  display: block;
  color: inherit;
  text-decoration: none;
  transition: border-color .15s, transform .15s;
}
.event-card:hover {
  border-color: var(--color-primary);
  text-decoration: none;
  color: inherit;
  transform: translateY(-2px);
}
.event-card__badge {
  display: inline-block;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--color-primary);
  background: color-mix(in oklab, var(--color-primary) 14%, transparent);
  border-radius: 999px;
  padding: .15rem .5rem;
  margin-bottom: .4rem;
}

/* ── Badges ──────────────────────────────────────────────────────────────── */

.badge,
.status-badge,
.pass-free-badge,
.offline-badge,
.search-row__badge {
  display: inline-block;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .02em;
  border-radius: 999px;
  padding: .2rem .6rem;
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.badge--valid,
.status-badge--success,
.pass-free-badge,
.checked-in {
  color: var(--green);
  background: var(--green-tint);
  border-color: color-mix(in oklab, var(--green) 40%, transparent);
}
.badge--checked-in {
  color: var(--amber);
  background: var(--amber-tint);
  border-color: color-mix(in oklab, var(--amber) 40%, transparent);
}
.status-badge--past { opacity: .75; }
.offline-badge {
  color: var(--amber);
  background: var(--amber-tint);
  border-color: color-mix(in oklab, var(--amber) 40%, transparent);
}

/* ── Messages ────────────────────────────────────────────────────────────── */

.error-box,
.confirmed-error,
.transfer-error,
.transfer-claim-error,
.error-banner,
.refund-msg,
.review-msg,
.name-edit-msg,
.msg,
.upsell-success-msg,
.confirmed-loading,
.transfer-loading,
.search-loading,
.search-empty,
.ck-saved {
  border-radius: var(--radius);
  padding: .8rem .95rem;
  font-size: .9rem;
  margin: 0 0 1rem;
}
.error-box,
.confirmed-error,
.transfer-error,
.transfer-claim-error,
.error-banner {
  color: var(--red);
  background: var(--red-tint);
  border: 1px solid color-mix(in oklab, var(--red) 35%, transparent);
}
.ck-saved,
.upsell-success-msg {
  color: var(--green);
  background: var(--green-tint);
  border: 1px solid color-mix(in oklab, var(--green) 35%, transparent);
}
.confirmed-loading,
.transfer-loading,
.search-loading,
.search-empty {
  color: var(--color-text-muted);
  text-align: center;
  padding: 2.5rem 1rem;
}

/* ── Action rows ─────────────────────────────────────────────────────────── */

.actions,
.confirmed-actions,
.ticket-row__actions,
.result-card__actions,
.config-box__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}
.actions { flex-direction: column; margin-top: 1.25rem; }
.actions .btn { width: 100%; text-align: center; }
.confirmed-actions { margin-top: 1.25rem; }

.btn--ghost,
.btn--dir-in,
.print-btn,
.refund-toggle-link,
.top-bar__btn,
.search-header__close,
.bottom-bar__fullscreen,
.btn--fullscreen {
  font: inherit;
  font-size: .85rem;
  color: var(--color-text-muted);
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: .55rem 1rem;
  cursor: pointer;
}
.btn--ghost:hover,
.print-btn:hover,
.top-bar__btn:hover { border-color: var(--color-primary); color: var(--color-primary); }

.btn--danger,
.btn--claim {
  font: inherit;
  font-weight: 600;
  border-radius: var(--radius);
  padding: .55rem 1rem;
  cursor: pointer;
}
.btn--danger {
  color: var(--red);
  background: transparent;
  border: 1px solid color-mix(in oklab, var(--red) 40%, transparent);
}
.btn--danger:hover { background: var(--red-tint); }
.btn--claim {
  color: #fff;
  background: var(--color-primary);
  border: 0;
  padding: .8rem 1.4rem;
  font-size: 1rem;
}

.refund-toggle-link,
.org-link {
  border: 0;
  padding: 0;
  text-decoration: underline;
  color: var(--color-primary);
  background: none;
  cursor: pointer;
}

/* ── Tabs ────────────────────────────────────────────────────────────────── */

.section-tabs {
  display: flex;
  gap: .25rem;
  margin: 1.25rem 0 1rem;
  border-bottom: 1px solid var(--color-border);
}
.section-tab {
  font: inherit;
  font-size: .9rem;
  font-weight: 600;
  color: var(--color-text-muted);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: .55rem .85rem;
  cursor: pointer;
}
.section-tab:hover { color: var(--color-text); }
.section-tab.active {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

/* ── Modals ──────────────────────────────────────────────────────────────── */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0% 0 0 / 0.6);
  display: grid;
  place-items: center;
  padding: 1.25rem;
  z-index: 50;
}
.modal {
  width: 100%;
  max-width: 26rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 1.25rem;
}

/* ── The ticket itself ───────────────────────────────────────────────────────
   A boarding pass: header, a torn perforation, details, then the QR. The tear
   is drawn with two notches punched out of the card's own edges rather than an
   image, so it takes the tenant's background colour automatically. */

.logo {
  display: block;
  text-align: center;
  font-family: var(--font-display, inherit);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--color-text);
  padding: 1.5rem 1rem .25rem;
}
.logo span { color: var(--color-primary); }

#root { max-width: 26rem; margin: 0 auto; padding: .75rem 1.25rem 4rem; }

.pass {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.pass__event-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
}
.pass__header { padding: 1.25rem 1.25rem 1rem; }
.pass__title {
  font-family: var(--font-display, inherit);
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin-bottom: .4rem;
}
.pass__meta {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  font-size: .85rem;
  color: var(--color-text-muted);
}

/* The perforation. The notches are circles in the page background sitting half
   outside the card, which reads as a punched hole on any backdrop. */
.pass__tear {
  position: relative;
  height: 1px;
  background: repeating-linear-gradient(
    to right,
    var(--color-border) 0 6px,
    transparent 6px 12px
  );
  margin: 0 .5rem;
}
.pass__notch {
  position: absolute;
  top: 50%;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 999px;
  background: var(--color-bg-alt);
  transform: translateY(-50%);
}
.pass__notch--left { left: -1.05rem; }
.pass__notch--right { right: -1.05rem; }

.pass__body { padding: 1.1rem 1.25rem; }

.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1.5rem;
  margin-bottom: .9rem;
}
.field-row:last-child { margin-bottom: 0; }
.field { flex: 1 1 8rem; min-width: 0; }
.field__label {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: .15rem;
}
.field__value { font-weight: 600; overflow-wrap: anywhere; }

.pass__qr {
  text-align: center;
  padding: .5rem 1.25rem 1.5rem;
  border-top: 1px solid var(--color-border);
}
/* The QR is served as a PNG by the API. Given a white quiet zone of its own so
   it scans against a dark theme — a dark-on-dark code does not read. */
.pass__qr img,
.pass__qr canvas {
  display: block;
  width: 11rem;
  height: 11rem;
  margin: 1.1rem auto .6rem;
  background: #fff;
  border-radius: var(--radius);
  padding: .5rem;
  box-sizing: content-box;
}
.pass__ticket-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .72rem;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.tip {
  margin-top: 1rem;
  font-size: .8rem;
  color: var(--color-text-muted);
  text-align: center;
}

/* ── My tickets ──────────────────────────────────────────────────────────── */

.portal-header { margin-bottom: .5rem; }

.order-card__status { margin-left: auto; flex: 0 0 auto; }

.ticket-rows { padding: .25rem 1.15rem; }
.ticket-row {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .85rem;
  align-items: center;
  padding: .85rem 0;
  border-bottom: 1px solid var(--color-border);
}
.ticket-row:last-child { border-bottom: 0; }
.ticket-row__type {
  flex: 1 1 10rem;
  min-width: 0;
  font-weight: 600;
  font-size: .9rem;
}
.ticket-row__name { font-weight: 600; font-size: .9rem; }
.ticket-row__actions { flex: 1 1 100%; }
.ticket-row__actions .btn { font-size: .8rem; padding: .35rem .7rem; }

.name-edit-form {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: center;
  flex: 1 1 100%;
  margin-top: .4rem;
}
.name-edit-form input { flex: 1 1 10rem; }
.name-edit-msg { margin: .4rem 0 0; flex: 1 1 100%; }

/* ── Confirmation ────────────────────────────────────────────────────────── */

.confirmed-share-row {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: center;
  margin: .75rem 0;
}
.confirmed-share-input { flex: 1 1 14rem; }

.confirmed-viral { margin-top: 1.25rem; text-align: center; }
.confirmed-viral__title { font-weight: 700; margin-bottom: .6rem; }
.confirmed-viral__btns {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  justify-content: center;
}
.confirmed-viral__btn {
  font: inherit;
  font-size: .85rem;
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: .4rem .85rem;
  cursor: pointer;
  text-decoration: none;
}
.confirmed-viral__btn:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
  text-decoration: none;
}

.loyalty-strip__inner {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  align-items: center;
  justify-content: space-between;
  padding: .85rem 1.15rem;
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--color-primary) 10%, transparent);
  border: 1px solid color-mix(in oklab, var(--color-primary) 28%, transparent);
  margin-bottom: 1rem;
  font-size: .9rem;
}

.upsell-card__qty { width: 4.5rem; }

.cal-dropdown { position: relative; display: inline-block; }
.cal-dropdown__menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + .35rem);
  left: 0;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  gap: .15rem;
  padding: .4rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.cal-dropdown__menu a {
  padding: .4rem .55rem;
  border-radius: 4px;
  font-size: .875rem;
}
.cal-dropdown__menu a:hover { background: var(--color-bg-alt); text-decoration: none; }

.refund-request-wrap { margin-top: 1rem; }

/* ── Organiser profile & collections ─────────────────────────────────────── */

.org-banner {
  width: 100%;
  aspect-ratio: 3 / 1;
  object-fit: cover;
  border-radius: var(--radius-lg);
  display: block;
}
.org-header { text-align: center; margin-bottom: 1.5rem; }
.org-avatar,
.org-avatar-placeholder {
  width: 5rem;
  height: 5rem;
  border-radius: 999px;
  object-fit: cover;
  display: grid;
  place-items: center;
  margin: -2.5rem auto .75rem;
  border: 3px solid var(--color-bg-alt);
  background: var(--color-bg);
  font-size: 1.75rem;
  font-weight: 700;
  color: var(--color-primary);
}
.org-name {
  font-family: var(--font-display, inherit);
  font-size: 1.6rem;
  letter-spacing: -0.02em;
  margin-bottom: .4rem;
}
.org-bio { max-width: 34rem; margin: 0 auto .75rem; }
.org-links { display: flex; gap: .85rem; justify-content: center; flex-wrap: wrap; }

.past-list { display: grid; gap: .5rem; }
.past-item {
  display: flex;
  gap: .75rem;
  align-items: center;
  padding: .6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
  opacity: .8;
}
.past-item:hover { opacity: 1; text-decoration: none; color: inherit; }
.past-item__title { font-weight: 600; font-size: .9rem; }

.col-hero,
.col-hero-placeholder {
  width: 100%;
  aspect-ratio: 3 / 1;
  object-fit: cover;
  border-radius: var(--radius-lg);
  display: block;
  background: linear-gradient(
    135deg,
    color-mix(in oklab, var(--color-primary) 30%, var(--color-bg-alt)),
    var(--color-bg-alt)
  );
}
.col-header { text-align: center; margin: 1.25rem 0 1.5rem; }
.col-title {
  font-family: var(--font-display, inherit);
  font-size: clamp(1.5rem, 4.5vw, 2rem);
  letter-spacing: -0.02em;
  margin-bottom: .4rem;
}
.col-desc { max-width: 34rem; margin-inline: auto; }

/* ── Scanner (door staff, on a phone) ────────────────────────────────────────
   Designed for one hand in bad light: full-bleed camera, a result card that
   fills the screen, and targets big enough to hit without looking.

   The page's state model, which the CSS has to match because the JS only ever
   toggles *some* of it:
     · the camera layer (#viewfinder, the shades, .scan-overlay) is always
       mounted and full-bleed, sitting behind everything;
     · #setup-screen covers it and is dismissed with `style.display = "none"`;
     · #permission-error is revealed with `style.display = "flex"`;
     · .result-card, .search-overlay, .cache-strip and .offline-badge are shown
       by adding `.visible` / `.active` / `.show`.
   Without that, every layer rendered at once — the setup form, the camera
   error and the viewfinder stacked down the page. */

/* The camera layer. */
#viewfinder {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: #000;
  overflow: hidden;
}
#viewfinder video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* A scratch buffer for the jsQR path, never shown. */
#viewfinder canvas { display: none; }

/* Full-screen panels over the camera. */
#setup-screen,
#permission-error {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: 1.5rem 1.25rem;
  text-align: center;
  background: var(--color-bg-alt);
  overflow-y: auto;
}
/* Hidden until the camera is actually refused — the JS reveals it with
   `style.display = "flex"`, so a plain `display: none` here is what it
   overrides. */
#permission-error { display: none; }
#setup-screen h1,
#permission-error h2 {
  font-family: var(--font-display, inherit);
  font-size: 1.5rem;
  letter-spacing: -0.01em;
}
#setup-screen > p,
#permission-error > p {
  color: var(--color-text-muted);
  font-size: .9rem;
  max-width: 24rem;
}
.setup-form {
  width: 100%;
  max-width: 22rem;
  text-align: left;
  display: grid;
  gap: .7rem;
  margin-top: .75rem;
}
.setup-form label { display: block; font-size: .8rem; font-weight: 600; margin-bottom: .25rem; }
.setup-form input[type="text"],
.setup-form input[type="password"],
.setup-form select {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  color: var(--color-text);
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: .65rem .8rem;
}
.reentry-toggle {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .9rem;
  cursor: pointer;
}

/* Chrome above the camera. */
.top-bar,
.stats-bar,
.cache-strip { position: relative; z-index: 20; }
.top-bar { position: fixed; top: 0; left: 0; right: 0; }
.stats-bar { position: fixed; bottom: 0; left: 0; right: 0; }

/* Shown only when there is something to say. */
.cache-strip { display: none; }
.cache-strip.visible { display: flex; }
.offline-badge { display: none; }
.offline-badge.show { display: inline-flex; align-items: center; gap: .35rem; }

.search-overlay,
#search-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  flex-direction: column;
  background: var(--color-bg-alt);
}
#search-overlay.active { display: flex; }
.search-loading { display: none; }
.search-loading.active { display: block; }

.top-bar {
  display: flex;
  gap: .75rem;
  align-items: center;
  padding: .7rem 1rem;
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
}
.top-bar__event {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  font-size: .9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.top-bar__right,
.bottom-bar__info { display: flex; gap: .5rem; align-items: center; }
.top-bar__time { font-variant-numeric: tabular-nums; font-size: .85rem; color: var(--color-text-muted); }

.stats-bar,
.stats-row {
  display: flex;
  gap: .5rem;
  padding: .75rem 1rem;
}
.stat,
.stat-box,
.count-card {
  flex: 1 1 0;
  text-align: center;
  padding: .7rem .5rem;
  border-radius: var(--radius);
}
.stat { background: var(--color-bg); border: 1px solid var(--color-border); }
.stat__num,
.stat-box__num,
.count-num {
  font-size: 1.4rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.stat__label,
.stat-box__label,
.count-label {
  font-size: .7rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.count-sub { font-size: .8rem; color: var(--color-text-muted); }

/* The aiming square. The shades dim the camera everywhere outside it so the eye
   goes to the middle, and the line sweeps to say "still looking". They are
   siblings of .scan-overlay in the markup, not children, so each is positioned
   against the viewport rather than a wrapper.

   `pointer-events: none` throughout — this whole layer sits over the camera and
   must never swallow a tap meant for the buttons above it. */
.scan-shade-top, .scan-shade-bottom, .scan-shade-left, .scan-shade-right {
  position: fixed;
  z-index: 10;
  pointer-events: none;
  /* A literal black scrim: it darkens a camera image, so it cannot follow the
     tenant's palette without ceasing to do its job. */
  background: oklch(0% 0 0 / 0.55);
}
.scan-shade-top { top: 0; left: 0; right: 0; height: 28vh; }
.scan-shade-bottom { bottom: 0; left: 0; right: 0; height: 32vh; }
.scan-shade-left { top: 28vh; bottom: 32vh; left: 0; width: calc(50vw - 35vh); }
.scan-shade-right { top: 28vh; bottom: 32vh; right: 0; width: calc(50vw - 35vh); }

.scan-overlay {
  position: fixed;
  inset: 0;
  z-index: 11;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.scan-frame {
  position: relative;
  width: min(70vw, 70vh);
  aspect-ratio: 1;
  border: 2px solid var(--color-primary);
  border-radius: var(--radius-lg);
  box-shadow: 0 0 0 1px oklch(0% 0 0 / 0.4);
  overflow: hidden;
}
.scan-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--color-primary);
  box-shadow: 0 0 12px var(--color-primary);
  animation: scan-sweep 2.2s ease-in-out infinite;
}
@keyframes scan-sweep {
  0%, 100% { top: 4%; }
  50% { top: 94%; }
}
@media (prefers-reduced-motion: reduce) { .scan-line { animation: none; top: 50%; } }
.scan-hint {
  margin-top: 1.25rem;
  padding: .45rem .9rem;
  border-radius: 999px;
  background: oklch(0% 0 0 / 0.55);
  color: #fff;
  font-size: .85rem;
}

/* The overlay is always in the DOM; the *card* is what carries `.visible`, so
   the backdrop has to key off that too or it sits over the camera permanently
   and nothing can be scanned. */
.result-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 1.25rem;
  background: oklch(0% 0 0 / 0.75);
  visibility: hidden;
  opacity: 0;
  transition: opacity .12s;
}
.result-overlay:has(.result-card.visible) { visibility: visible; opacity: 1; }
.result-card {
  width: 100%;
  max-width: 24rem;
  padding: 1.5rem 1.25rem;
  text-align: center;
  display: none;
}

/* The outcome, read in one glance from arm's length: a big coloured disc and
   the attendee's name. Colour is never the only signal — the icon (✓ ↩ ⚠ ✗)
   and the words say the same thing, which matters on a dim door and for anyone
   who does not separate red from green. */
.result-card__status {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  margin-bottom: .5rem;
}
.result-card__icon {
  display: grid;
  place-items: center;
  width: 4rem;
  height: 4rem;
  border-radius: 999px;
  font-size: 2rem;
  line-height: 1;
  color: #fff;
  background: var(--color-text-muted);
}
.result-card__icon.success { background: var(--tone-ok); }
.result-card__icon.warning { background: var(--tone-warn); color: var(--tone-warn-ink); }
.result-card__icon.error   { background: var(--tone-bad); }
.result-card.visible { display: block; }
.result-card__icon { font-size: 3rem; line-height: 1; margin-bottom: .5rem; }
.result-card__name { font-size: 1.25rem; margin-bottom: .25rem; }
.result-card__details {
  display: grid;
  gap: .5rem;
  text-align: left;
  margin: 1rem 0;
  padding: .85rem;
  border-radius: var(--radius);
  background: var(--color-bg-alt);
}
.result-card__detail { display: flex; justify-content: space-between; gap: 1rem; font-size: .875rem; }
.result-card__detail-label { color: var(--color-text-muted); }
.result-card__detail-value { font-weight: 600; text-align: right; overflow-wrap: anywhere; }
.result-card__actions { justify-content: center; }

.search-header {
  display: flex;
  gap: .5rem;
  align-items: center;
  padding: .75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}
.search-header__input { flex: 1 1 auto; }
.search-results { max-height: 60vh; overflow-y: auto; }
.search-row {
  display: flex;
  gap: .75rem;
  align-items: center;
  width: 100%;
  text-align: left;
  padding: .75rem 1rem;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.search-row:hover { background: var(--color-bg-alt); }
.search-row__avatar {
  flex: 0 0 auto;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: color-mix(in oklab, var(--color-primary) 18%, transparent);
  font-weight: 700;
  font-size: .85rem;
}
.search-row__info { flex: 1 1 auto; min-width: 0; }

.cache-strip {
  display: flex;
  gap: .5rem;
  align-items: center;
  justify-content: center;
  padding: .4rem 1rem;
  font-size: .78rem;
  color: var(--color-text-muted);
  background: var(--color-bg-alt);
  border-bottom: 1px solid var(--color-border);
}
.pending-dot,
.pulse-dot {
  width: .5rem;
  height: .5rem;
  border-radius: 999px;
  background: var(--amber);
  animation: pulse 1.6s ease-in-out infinite;
}
.pulse-dot { background: var(--green); }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

.reentry-toggle { display: flex; gap: .5rem; align-items: center; padding: .6rem 1rem; }
.btn--dir-in { flex: 1 1 0; }

/* ── Door display (a wall screen, read from across the room) ─────────────── */

.main { padding: 1rem; }
.progress-bar-wrap {
  height: .75rem;
  border-radius: 999px;
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  overflow: hidden;
}
.progress-bar-fill {
  height: 100%;
  background: var(--green);
  transition: width .4s ease;
}
.progress-pct { font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.progress-card { padding: 1rem 1.15rem; margin-bottom: 1rem; }

.feed-list { display: grid; gap: .25rem; }
.feed-item {
  display: flex;
  gap: .75rem;
  align-items: center;
  padding: .6rem .85rem;
  border-radius: var(--radius);
}
.feed-item__avatar {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: color-mix(in oklab, var(--color-primary) 18%, transparent);
  font-weight: 700;
  font-size: .8rem;
}
.feed-item__name { flex: 1 1 auto; min-width: 0; }
.feed-item__time { flex: 0 0 auto; font-variant-numeric: tabular-nums; }

.bottom-bar {
  display: flex;
  gap: .75rem;
  align-items: center;
  justify-content: space-between;
  padding: .75rem 1rem;
  border-top: 1px solid var(--color-border);
  background: var(--color-bg);
}

.config-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 1.25rem;
  background: oklch(0% 0 0 / 0.7);
}
.config-box { width: 100%; max-width: 24rem; }
.config-box__actions { justify-content: flex-end; margin-top: 1rem; }

/* ── Check-in result (checkin.astro) ─────────────────────────────────────── */

.checkin-result__attendee { font-size: 1.15rem; font-weight: 700; margin-bottom: .5rem; }
.checkin-result__fields {
  display: grid;
  gap: .4rem;
  font-size: .875rem;
  color: var(--color-text-muted);
}
.js-checkin-btn { margin-top: .85rem; }

/* ── Embedded widget ─────────────────────────────────────────────────────────
   Runs in an iframe on someone else's site, so it stays compact and assumes
   nothing about the space around it. */

.tt-name { font-weight: 600; }
.tt-price { font-weight: 700; font-variant-numeric: tabular-nums; }
.tt-vat { font-size: .75rem; color: var(--color-text-muted); }

.qty-ctrl {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: .15rem;
}
.qty-btn {
  width: 1.9rem;
  height: 1.9rem;
  display: grid;
  place-items: center;
  font: inherit;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--color-text);
  background: var(--color-bg-alt);
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}
.qty-btn:hover:not(:disabled) { background: color-mix(in oklab, var(--color-primary) 18%, transparent); }
.qty-btn:disabled { opacity: .4; cursor: not-allowed; }
.qty-val,
.qty-value {
  min-width: 1.75rem;
  text-align: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ticket-type__qty { display: flex; align-items: center; gap: .5rem; }

.summary-row,
.summary-total {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .35rem 0;
  font-size: .9rem;
}
.summary-total {
  font-weight: 700;
  font-size: 1rem;
  border-top: 1px solid var(--color-border);
  margin-top: .35rem;
  padding-top: .6rem;
}

.tip-btn {
  font: inherit;
  font-size: .85rem;
  color: var(--color-text);
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: .35rem .8rem;
  cursor: pointer;
}
.tip-btn:hover,
.tip-btn.active {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* ── Queue / waiting room (event.astro) ──────────────────────────────────── */

.queue-room {
  max-width: 26rem;
  margin: 3rem auto;
  text-align: center;
  padding: 2rem 1.5rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.queue-room__icon { font-size: 2.5rem; line-height: 1; margin-bottom: .75rem; }
.queue-room__title {
  font-family: var(--font-display, inherit);
  font-size: 1.4rem;
  letter-spacing: -0.01em;
  margin-bottom: .4rem;
}
.queue-room__sub,
.queue-admitted__sub { color: var(--color-text-muted); font-size: .9rem; }
.queue-room__pos {
  font-size: 2.5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  margin: 1rem 0 .25rem;
  color: var(--color-primary);
}
.queue-room__label {
  font-size: .75rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.queue-room__bar {
  height: .5rem;
  border-radius: 999px;
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  overflow: hidden;
  margin: 1.25rem 0 .5rem;
}
.queue-room__bar-fill { height: 100%; background: var(--color-primary); transition: width .5s ease; }
.queue-admitted { text-align: center; padding: 1.5rem 0; }
.queue-admitted__title { font-size: 1.2rem; font-weight: 700; margin-bottom: .35rem; }

/* ── Season pass ─────────────────────────────────────────────────────────── */

.pass-hero { position: relative; border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 1.25rem; }
.pass-hero img { width: 100%; aspect-ratio: 3 / 1; object-fit: cover; display: block; }
.pass-hero__overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: 1.5rem 1.25rem 1rem;
  background: linear-gradient(to top, oklch(0% 0 0 / 0.75), transparent);
}
.pass-hero__title {
  font-family: var(--font-display, inherit);
  font-size: clamp(1.4rem, 4vw, 1.9rem);
  color: #fff;
  letter-spacing: -0.02em;
}
.pass-events-list { display: grid; gap: .5rem; }
.pass-event-row { display: flex; gap: .85rem; align-items: center; padding: .85rem 1.15rem; margin-bottom: 0; }
.pass-event-row__date { flex: 0 0 auto; text-align: center; font-variant-numeric: tabular-nums; }
.pass-event-row__meta { flex: 1 1 auto; min-width: 0; }

/* ── Review ──────────────────────────────────────────────────────────────── */

.review-form-group { margin-bottom: 1rem; }
.stars { display: flex; gap: .25rem; justify-content: center; margin: .5rem 0; }
.star-btn {
  font-size: 2rem;
  line-height: 1;
  background: none;
  border: 0;
  cursor: pointer;
  padding: .1rem;
  filter: grayscale(1);
  opacity: .4;
  transition: filter .12s, opacity .12s, transform .12s;
}
.star-btn:hover,
.star-btn.active { filter: none; opacity: 1; transform: scale(1.1); }
.star-label {
  text-align: center;
  font-size: .85rem;
  color: var(--color-text-muted);
  min-height: 1.2em;
}
.review-submit { width: 100%; }
.review-done { text-align: center; padding: 2rem 0; }

/* ── Ballot claim ────────────────────────────────────────────────────────── */

.claim-card { text-align: center; padding: 2rem 1.5rem; }
.claim-info {
  display: grid;
  gap: .5rem;
  text-align: left;
  margin: 1.25rem 0;
  padding: .95rem;
  border-radius: var(--radius);
  background: var(--color-bg-alt);
  font-size: .9rem;
}
.deadline {
  display: inline-block;
  margin-top: .75rem;
  font-size: .85rem;
  color: var(--amber);
  background: var(--amber-tint);
  border: 1px solid color-mix(in oklab, var(--amber) 35%, transparent);
  border-radius: var(--radius);
  padding: .4rem .75rem;
}

/* ── Cookery parent portal ───────────────────────────────────────────────── */

.ck-child-form,
.ck-collector-form {
  display: grid;
  gap: .85rem;
  padding: 1.15rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  margin-bottom: 1rem;
}
.ck-chips { display: flex; flex-wrap: wrap; gap: .35rem; }
/* An allergy chip. Severity is carried by the word as well as the colour —
   a register read in a hurry must not depend on hue alone. */
.ck-chip {
  font-size: .78rem;
  font-weight: 600;
  border-radius: 999px;
  padding: .2rem .6rem;
  color: var(--red);
  background: var(--red-tint);
  border: 1px solid color-mix(in oklab, var(--red) 40%, transparent);
}
.ck-chip--mild {
  color: var(--amber);
  background: var(--amber-tint);
  border-color: color-mix(in oklab, var(--amber) 40%, transparent);
}

/* ── Dev payment stub (local harness only) ───────────────────────────────── */

.amt { font-size: 1.75rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.tag { font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; color: var(--color-text-muted); }
.pay, .cancel { display: block; width: 100%; margin-top: .5rem; text-align: center; }

/* The stand-in where a QR would be but cannot exist — branding preview mode,
   with no real ticket to encode. Sized to match a rendered code so the layout
   does not shift between preview and the real thing. */
.pass__qr-placeholder {
  display: grid;
  place-items: center;
  width: 11rem;
  height: 11rem;
  margin: 1.1rem auto .6rem;
  padding: .5rem;
  box-sizing: content-box;
  border: 2px dashed var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text-muted);
  font-size: .8rem;
  text-align: center;
  line-height: 1.4;
}

/* ── Ticket transfer ─────────────────────────────────────────────────────── */

.transfer-card__label {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: .15rem;
}
.transfer-success { text-align: center; padding: 2rem 0; }
.transfer-success__icon {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  margin: 0 auto 1rem;
  font-size: 1.75rem;
  line-height: 1;
  border-radius: 999px;
  background: color-mix(in oklab, var(--tone-ok) 18%, transparent);
}

/* The widget's own layout. It renders inside an iframe on someone else's site,
   so it stays compact and claims no more room than it needs — but it still has
   to look composed rather than like a stack of naked divs. */
#widget-root,
#event-header,
#checkout-form { max-width: 30rem; margin: 0 auto; }

#event-header { padding: 1rem 1.15rem .25rem; }
#event-title {
  font-family: var(--font-display, inherit);
  font-size: 1.2rem;
  font-weight: 700;
  line-height: 1.25;
  margin-bottom: .2rem;
}
#event-meta { font-size: .85rem; color: var(--color-text-muted); margin-bottom: .75rem; }

.tt-card {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .85rem;
  align-items: center;
  padding: .85rem 1.15rem;
  margin: 0 1.15rem .6rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.tt-card > :first-child { flex: 1 1 10rem; min-width: 0; }
.tt-card .qty-ctrl { margin-left: auto; }
/* Sold out stays legible — dimmed, but the price and name must still be
   readable so a buyer can see what they missed. */
.tt-card--sold-out { opacity: .6; }
.tt-card--sold-out .qty-ctrl { display: none; }

#checkout-form { padding: .5rem 1.15rem 1.25rem; }
#discount-row { display: flex; gap: .5rem; align-items: center; margin-bottom: .6rem; }
#discount-input { flex: 1 1 auto; }
#discount-msg, #error-msg { font-size: .82rem; margin-top: .35rem; }
#checkout-btn { width: 100%; margin-top: .6rem; }

/* The widget's form controls carry no classes — they are bare `<input>` and
   `<button>` elements inside the widget's own containers, so they are styled by
   element within that scope rather than by adding classes to a page that has
   to stay small. */
#checkout-form label {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  margin-bottom: .25rem;
}
#checkout-form .form-group { margin-bottom: .7rem; }
#checkout-form input,
#discount-row input {
  width: 100%;
  font: inherit;
  font-size: 1rem; /* 16px: smaller makes iOS zoom the whole iframe on focus */
  color: var(--color-text);
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: .6rem .75rem;
}
#checkout-form input:focus-visible,
#discount-row input:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary) 28%, transparent);
}
#discount-row button {
  flex: 0 0 auto;
  font: inherit;
  font-size: .875rem;
  font-weight: 600;
  color: var(--color-text);
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: .6rem .9rem;
  cursor: pointer;
}
#discount-row button:hover { border-color: var(--color-primary); color: var(--color-primary); }
#checkout-btn {
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  color: #fff;
  background: var(--color-primary);
  border: 0;
  border-radius: var(--radius);
  padding: .8rem 1rem;
  cursor: pointer;
}
#checkout-btn:hover:not(:disabled) { background: var(--color-primary-dark); }
#checkout-btn:disabled { opacity: .6; cursor: progress; }
