/* ============================================================================
 * app.css — layout + components for the Anycast registration funnel
 * ==========================================================================*/

/* ==========================================================================
   Site header (Cloudflare lockup)
   ========================================================================== */
.site-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-bottom: 30px;
}

/* Intrinsic width/height are set on the <img> so the browser reserves the
 * right box before the PNG loads — no layout shift on the hero. */
.cf-logo {
  width: 152px;
  height: auto;
}

.cf-logo--sm {
  width: 108px;
}

@media (max-width: 480px) {
  .cf-logo {
    width: 124px;
  }

  .cf-logo--sm {
    width: 92px;
  }
}

.site-head__host {
  font-size: 0.66rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.back-link {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-arcade);
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--orange);
  text-decoration: none;
  /* 44px minimum tap target, pulled back with negative margin so it does
     not visually inflate the header. */
  min-height: 44px;
  padding: 0 14px;
  margin: -8px -14px;
}

.back-link:hover {
  color: var(--orange-bright);
}

/* ==========================================================================
   Arcade cabinet (inline SVG, recreated from the flyer art)
   ========================================================================== */
.cabinet {
  position: relative;
  width: min(230px, 54vw);
  margin: 0 auto 6px;
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
}

.cabinet__rings {
  position: absolute;
  inset: 0;
  animation: ring-spin 26s linear infinite;
}

.cabinet__art {
  position: relative;
  width: 47%;
}

/* The "PRESS START" screen text blinks like a real attract mode. */
.cabinet__press {
  animation: pulse-glow 1.5s ease-in-out infinite;
  transform-origin: center;
}

/* Elliptical floor glow beneath the cabinet. */
.cabinet::after {
  content: '';
  position: absolute;
  bottom: 6%;
  left: 50%;
  transform: translateX(-50%);
  width: 62%;
  height: 12%;
  border-radius: 50%;
  background: radial-gradient(
    ellipse,
    rgba(var(--orange-rgb), 0.32),
    transparent 70%
  );
  filter: blur(4px);
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  text-align: center;
  padding-bottom: 8px;
}

.hero__title {
  font-size: clamp(3rem, 14.5vw, 8.6rem);
  margin: 6px 0 0;
}

/* Each word is its own block so they can be spaced independently.
 * The gap is in `em` rather than px so it tracks the clamped font size —
 * and it has to clear the 3D extrude, which hangs ~0.07em below each
 * glyph. At a fixed px gap the words collide at large sizes. */
.hero__title span {
  display: block;
}

.hero__title span + span {
  margin-top: 0.16em;
}

.hero__tagline {
  margin: 30px auto 0;
  max-width: 620px;
}

/* Date / time / venue block — the flyer's centred caps stack. */
.hero__when {
  font-size: clamp(1.02rem, 3vw, 1.6rem);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 0;
  text-shadow: 0 0 22px rgba(255, 255, 255, 0.28);
}

.hero__when .dot {
  color: var(--orange);
  margin: 0 0.4em;
}

.hero__venue {
  font-size: clamp(0.98rem, 2.6vw, 1.44rem);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin: 10px 0 0;
}

.hero__address {
  font-size: clamp(0.74rem, 1.9vw, 0.94rem);
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 8px 0 0;
}

/* ==========================================================================
   Countdown — styled as an arcade score readout
   ========================================================================== */
.countdown {
  display: flex;
  justify-content: center;
  gap: clamp(8px, 2.4vw, 18px);
  margin: 34px 0 6px;
}

.countdown__unit {
  min-width: clamp(56px, 15vw, 82px);
  padding: 12px 6px 9px;
  border: 1px solid rgba(var(--orange-rgb), 0.36);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.42);
  box-shadow: inset 0 0 18px rgba(var(--orange-rgb), 0.12);
}

.countdown__num {
  font-family: var(--font-arcade);
  font-size: clamp(1rem, 3.4vw, 1.45rem);
  color: var(--orange-bright);
  text-shadow: 0 0 14px rgba(var(--orange-rgb), 0.8);
  font-variant-numeric: tabular-nums;
}

.countdown__lbl {
  display: block;
  margin-top: 8px;
  font-size: 0.56rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* Four units + gaps must fit inside a 320px viewport minus gutters and the
 * neon frame (~272px of usable width). Tighten rather than let it wrap. */
@media (max-width: 400px) {
  .countdown {
    gap: 6px;
  }

  .countdown__unit {
    min-width: 0;
    flex: 1 1 0;
    padding: 10px 2px 8px;
  }

  .countdown__lbl {
    letter-spacing: 0.1em;
    margin-top: 6px;
  }
}

/* ==========================================================================
   Primary CTA + coin slot
   ========================================================================== */
.cta {
  text-align: center;
  margin: 40px 0 14px;
}

.coin-slot {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.coin-slot__label {
  animation: pulse-glow 2s ease-in-out infinite;
}

.cta__note {
  margin: 16px 0 0;
  font-size: 0.84rem;
  color: var(--text-dim);
}

/* ==========================================================================
   Agenda
   ========================================================================== */
.agenda {
  margin-top: 46px;
}

.agenda__item {
  display: grid;
  grid-template-columns: 172px 1fr;
  gap: 18px;
  padding: 18px 0;
  border-top: 1px solid var(--hairline);
}

.agenda__item:first-of-type {
  border-top: 0;
  padding-top: 0;
}

.agenda__time {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--orange);
  letter-spacing: 0.01em;
}

.agenda__title {
  margin: 0;
  font-size: 1.04rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.agenda__desc {
  margin: 6px 0 0;
  font-size: 0.93rem;
  color: var(--text-muted);
}

.agenda__speaker {
  color: var(--orange-bright);
  font-weight: 600;
  white-space: nowrap;
}

@media (max-width: 620px) {
  .agenda__item {
    grid-template-columns: 1fr;
    gap: 6px;
  }
}

/* ==========================================================================
   Footer
   ========================================================================== */
.site-foot {
  margin-top: 60px;
  text-align: center;
  font-size: 0.8rem;
  color: var(--text-dim);
}

/* Inline footer links are only ~15px tall on their own. Padding them out to
 * 44px makes them reliably tappable; the negative margin keeps the visual
 * line spacing unchanged. */
.site-foot a {
  display: inline-block;
  color: var(--text-muted);
  min-height: 44px;
  line-height: 44px;
  padding: 0 6px;
  margin: -14px -6px;
}

/* ==========================================================================
   Registration — HUD + steps
   ========================================================================== */
.hud {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 18px;
  margin-bottom: 22px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.4);
}

.hud__level {
  font-family: var(--font-arcade);
  font-size: 0.66rem;
  color: var(--orange-bright);
  letter-spacing: 0.1em;
}

.hud__bar {
  flex: 1 1 180px;
  height: 8px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

.hud__fill {
  height: 100%;
  width: 33.33%;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--orange), var(--orange-bright));
  box-shadow: 0 0 12px rgba(var(--orange-rgb), 0.8);
  transition: width 0.4s var(--ease);
}

.hud__pips {
  display: flex;
  gap: 7px;
}

.hud__pip {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1px solid rgba(var(--orange-rgb), 0.5);
}

.hud__pip.is-done {
  background: var(--orange);
  box-shadow: 0 0 10px rgba(var(--orange-rgb), 0.9);
}

/* Step panels — only one visible at a time. */
.step {
  display: none;
}

.step.is-active {
  display: block;
  animation: rise-in 0.4s var(--ease);
}

.step__head {
  margin-bottom: 26px;
  text-align: center;
}

.step__title {
  font-family: var(--font-arcade);
  font-size: clamp(0.78rem, 2.6vw, 1rem);
  letter-spacing: 0.18em;
  color: var(--orange-bright);
  margin: 10px 0 8px;
  text-shadow: 0 0 18px rgba(var(--orange-rgb), 0.55);
}

.step__sub {
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}

/* ==========================================================================
   Form controls
   ========================================================================== */
.field {
  margin-bottom: 20px;
}

.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 20px;
}

@media (max-width: 620px) {
  .grid-2 {
    grid-template-columns: 1fr;
  }
}

.label {
  display: block;
  margin-bottom: 7px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.label .req {
  color: var(--orange);
  margin-left: 3px;
}

.hint {
  display: block;
  margin-top: 6px;
  font-size: 0.78rem;
  color: var(--text-dim);
}

.input,
.textarea,
.select {
  width: 100%;
  padding: 13px 15px;
  border-radius: var(--radius-sm);
  /* WCAG 1.4.11: the boundary that identifies a form control needs 3:1.
     0.18 gave 1.6:1 — effectively invisible to low-vision users. */
  border: 1px solid rgba(255, 255, 255, 0.44);
  background: rgba(0, 0, 0, 0.42);
  color: var(--text);
  transition:
    border-color 0.16s,
    box-shadow 0.16s;
}

.input::placeholder,
.textarea::placeholder {
  /* was #5f5a73 = 3.00:1, below the 4.5:1 minimum */
  color: #8b85a1;
}

.input:focus,
.textarea:focus,
.select:focus {
  outline: none;
  border-color: var(--orange);
  box-shadow: 0 0 0 3px rgba(var(--orange-rgb), 0.22);
}

.textarea {
  min-height: 92px;
  resize: vertical;
}

/* Arcade initials input — three fat monospace characters. */
.input--initials {
  font-family: var(--font-arcade);
  font-size: 1.5rem;
  letter-spacing: 0.34em;
  text-align: center;
  text-transform: uppercase;
  max-width: 220px;
  color: var(--orange-bright);
  padding: 16px 10px 16px 24px; /* offset the trailing letter-spacing */
}

/* -- validation ------------------------------------------------------- */
.input.is-invalid,
.textarea.is-invalid,
.select.is-invalid {
  border-color: #ff5c7a;
  box-shadow: 0 0 0 3px rgba(255, 92, 122, 0.18);
}

.error {
  display: none;
  margin-top: 7px;
  font-size: 0.78rem;
  font-weight: 600;
  color: #ff7d95;
}

.error.is-shown {
  display: block;
}

/* -- chips (dietary) -------------------------------------------------- */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}

.chip {
  position: relative;
}

.chip input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.chip > span {
  display: block;
  /* 11px vertical + ~20px line-box clears the 44px minimum tap target. */
  padding: 11px 16px;
  border-radius: 99px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(0, 0, 0, 0.36);
  font-size: 0.86rem;
  transition: all 0.16s var(--ease);
}

.chip input:checked + span {
  border-color: var(--orange);
  background: rgba(var(--orange-rgb), 0.2);
  box-shadow: 0 0 16px rgba(var(--orange-rgb), 0.36);
}

.chip input:focus-visible + span {
  outline: 3px solid var(--cyan-neon);
  outline-offset: 2px;
}

/* -- game picker grid -------------------------------------------------- */
.games {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
  gap: 10px;
}

/* At 320px the usable width inside the panel is ~244px, which is just under
 * two 124px tracks. Without this the grid collapses to a single very wide
 * column and the section becomes a long scroll. */
@media (max-width: 420px) {
  .games {
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 8px;
  }

  .game > span {
    padding: 13px 4px;
    font-size: 0.72rem;
  }
}

.game {
  position: relative;
}

.game input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

/* Direct child only. A bare `.game span` also matches the nested .glyph and
 * label spans, which gave every word its own bordered box. */
.game > span {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 8px;
  height: 100%;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(0, 0, 0, 0.36);
  font-size: 0.78rem;
  font-weight: 600;
  text-align: center;
  transition: all 0.16s var(--ease);
}

.game .glyph {
  font-size: 1.5rem;
  line-height: 1;
}

.game input:checked + span {
  border-color: var(--orange);
  background: rgba(var(--orange-rgb), 0.18);
  box-shadow: 0 0 18px rgba(var(--orange-rgb), 0.36);
  transform: translateY(-2px);
}

.game input:focus-visible + span {
  outline: 3px solid var(--cyan-neon);
  outline-offset: 2px;
}

/* -- consent checkbox -------------------------------------------------- */
.check {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 15px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

.check input {
  margin: 2px 0 0;
  width: 19px;
  height: 19px;
  accent-color: var(--orange);
  flex: none;
  cursor: pointer;
}

.check span {
  font-size: 0.88rem;
  color: var(--text-muted);
}

/* -- step navigation --------------------------------------------------- */
.step-nav {
  display: flex;
  gap: 12px;
  margin-top: 30px;
}

.step-nav .btn {
  flex: 1;
}

@media (max-width: 480px) {
  .step-nav {
    flex-direction: column-reverse;
  }
}

/* -- Turnstile ---------------------------------------------------------
   The widget itself is a cross-origin iframe, so its internals cannot be
   styled from here. All we control is the frame it sits in: give it the
   same quiet panel treatment as the rest of the form so it reads as part
   of the page rather than a bolted-on third-party box.

   data-size="flexible" makes the widget fill the container width, which is
   why this slot is a plain block with no fixed dimensions — the iframe
   sizes itself and we must not fight it. A min-height reserves space so the
   submit button does not jump down the page when the widget finally loads. */
.turnstile-slot {
  margin-top: 22px;
  padding: 14px;
  border: 1px solid rgba(var(--purple-rgb), 0.3);
  border-radius: var(--radius-sm);
  background: rgba(var(--bg-deep-rgb), 0.45);
  min-height: 78px;
  display: grid;
  gap: 10px;
  justify-items: center;
  align-content: center;
}

/* Selected by id, not by .cf-turnstile: register.js drops that class when
   it re-renders the widget explicitly, so that Turnstile's own implicit
   pass cannot render a second widget into the same box. Styling off the
   class would make the layout flicker whenever the site key is swapped. */
#turnstile-widget {
  width: 100%;
  max-width: 340px;
}

/* The iframe is a replaced element; centring it here keeps the layout tidy
   at the narrow end of `flexible`. */
.turnstile-slot iframe {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  color-scheme: dark;
}

.turnstile-slot__msg {
  margin: 0;
  text-align: center;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--orange-bright);
}

/* -- "already registered, link re-sent" --------------------------------
   Not an error: the registration exists and the attendee has a ticket
   waiting in their inbox. Styled as information (cyan) rather than as a
   failure (red/orange) so the colour does not contradict the words. */
.submit-notice {
  margin: 16px 0 0;
  padding: 14px 16px;
  border: 1px solid rgba(var(--cyan-rgb), 0.45);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: rgba(var(--cyan-rgb), 0.08);
  color: var(--text);
  font-size: 0.88rem;
  line-height: 1.6;
}

/* Takes programmatic focus so the outcome is announced and a keyboard user
   lands on it. It is not tab-reachable, so no real focus indicator is lost
   by suppressing the global ring on a paragraph nobody can tab to. */
.submit-notice:focus,
.submit-notice:focus-visible {
  outline: none;
}

/* ==========================================================================
   Confirmation
   ========================================================================== */
/* -- ticket not found --------------------------------------------------- */
/* Centre the card in the viewport rather than leaving it stranded at the
   top of a mostly empty page — this state has very little content. */
.notfound {
  display: grid;
  align-content: center;
  min-height: 58vh;
  margin-top: 24px;
}

.notfound__title {
  margin: 0 0 12px;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 5vw, 2.2rem);
  letter-spacing: 0.02em;
  color: #fff;
}

/* The heading takes programmatic focus so the state change is announced to
   screen readers. Chromium matches :focus-visible on a scripted focus() of
   a tabindex="-1" element, which painted the global cyan keyboard ring
   around a heading nobody can tab to — it read as a rendering fault. Both
   states are suppressed here. No genuine focus indicator is lost: the
   element is not keyboard reachable, and every actual control on this card
   keeps the global treatment. */
.notfound__title:focus,
.notfound__title:focus-visible {
  outline: none;
}

.win {
  text-align: center;
  margin-bottom: 34px;
}

.win__1up {
  font-family: var(--font-arcade);
  font-size: clamp(1.5rem, 7vw, 2.6rem);
  color: var(--orange-bright);
  margin: 0 0 14px;
  text-shadow:
    0 0 20px rgba(var(--orange-rgb), 0.9),
    0 0 46px rgba(var(--orange-rgb), 0.5);
  animation: pulse-glow 2.4s ease-in-out infinite;
}

.win__sub {
  font-size: clamp(1.02rem, 3vw, 1.34rem);
  font-weight: 700;
  margin: 0;
}

/* -- ticket ------------------------------------------------------------ */
.ticket {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 24px;
  align-items: center;
  padding: 26px clamp(18px, 4vw, 32px);
  border-radius: var(--radius);
  border: 1px solid var(--orange);
  background: linear-gradient(
    135deg,
    rgba(var(--orange-rgb), 0.16),
    rgba(var(--purple-rgb), 0.12)
  );
  box-shadow: 0 0 34px rgba(var(--orange-rgb), 0.26);
  overflow: hidden;
}

.ticket::before {
  content: '';
  position: absolute;
  inset: 5px;
  border: 1px dashed rgba(255, 255, 255, 0.24);
  border-radius: 10px;
  pointer-events: none;
}

.ticket__handle {
  font-size: clamp(1.3rem, 5vw, 2rem);
  font-weight: 800;
  margin: 4px 0 0;
  letter-spacing: 0.01em;
}

.ticket__ref {
  font-family: var(--font-arcade);
  font-size: 0.82rem;
  color: var(--orange-bright);
  margin: 12px 0 0;
  letter-spacing: 0.06em;
}

.ticket__initials {
  font-family: var(--font-arcade);
  font-size: clamp(1.4rem, 6vw, 2.1rem);
  color: var(--orange-bright);
  padding: 18px 20px;
  border: 2px solid var(--orange);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.44);
  text-shadow: 0 0 16px rgba(var(--orange-rgb), 0.9);
  white-space: nowrap;
}

@media (max-width: 560px) {
  .ticket {
    grid-template-columns: 1fr;
    text-align: center;
    justify-items: center;
  }
}

/* -- section headings on the confirmation page -------------------------- */
.section {
  margin-top: 44px;
}

.section__title {
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--orange);
  margin: 0 0 16px;
}

/* -- calendar buttons --------------------------------------------------- */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
}

.cal-btn {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 16px 18px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(0, 0, 0, 0.4);
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
  text-align: left;
  transition: all 0.16s var(--ease);
}

.cal-btn:hover {
  border-color: var(--orange);
  background: rgba(var(--orange-rgb), 0.14);
  transform: translateY(-2px);
  box-shadow: 0 0 20px rgba(var(--orange-rgb), 0.26);
}

.cal-btn__icon {
  font-size: 1.25rem;
  line-height: 1;
  flex: none;
}

.cal-btn__name {
  display: block;
  font-weight: 700;
  font-size: 0.92rem;
}

.cal-btn__meta {
  display: block;
  font-size: 0.72rem;
  color: var(--text-dim);
  margin-top: 2px;
}

/* -- map ---------------------------------------------------------------- */
.map {
  height: 330px;
  border-radius: var(--radius);
  border: 1px solid rgba(var(--orange-rgb), 0.4);
  overflow: hidden;
  background: #11091f;
  box-shadow: 0 0 24px rgba(var(--orange-rgb), 0.14);
}

@media (max-width: 560px) {
  .map {
    height: 260px;
  }

  /* Direction buttons go full-width so they are easy to hit and don't
     produce a ragged two-per-row wrap. */
  .directions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  .directions .btn {
    padding: 13px 10px;
    font-size: 0.86rem;
  }
}

/* The map box holds a static image (see map.js). `position: relative`
 * anchors the attribution overlay; the .map rule above already clips to the
 * rounded corners. */
.map {
  position: relative;
}

/* The tappable facade. The whole map is one link out to the user's maps app
 * (see map.js for why that beats an embedded iframe here). */
.map__link {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  text-decoration: none;
}

/* Overlay used for BOTH the hover tint and the focus ring.
 *
 * It has to be a pseudo-element stacked above the image, not styling on the
 * link itself. An `inset box-shadow` (or a border) on `.map__link` is painted
 * in that element's own background layer, which is BELOW its children — and
 * the map image is a child filling 100% of it, so the ring is drawn and then
 * immediately covered up. The symptom is a focus ring that is completely
 * invisible while the CSS looks perfectly correct.
 *
 * `pointer-events: none` so this never intercepts the click it sits on top of. */
.map__link::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: rgba(var(--orange-rgb), 0);
  transition: background 0.18s ease;
}

/* Lift on hover so it reads as interactive. Scoped to devices that actually
 * hover — on a touchscreen `:hover` sticks after a tap, which would leave the
 * map stuck in its hover state until you tapped elsewhere. */
@media (hover: hover) {
  .map__link:hover::after {
    background: rgba(var(--orange-rgb), 0.07);
  }
  .map__link:hover .map__cta {
    transform: translateY(-2px);
  }
}

/* Focus ring, drawn on the overlay above the image.
 *
 * Not an `outline`: the parent `.map` sets `overflow: hidden` to clip the
 * image to its rounded corners, which clips an outline too — even a
 * negatively offset one — leaving a cyan hairline on two edges at best.
 *
 * Two rings, not one. The map has both near-black parkland and pale road
 * fills, so a single bright ring disappears wherever it crosses a light road.
 * The dark outer ring guarantees contrast against whatever is underneath. */
.map__link:focus-visible {
  outline: none;
}

.map__link:focus-visible::after {
  box-shadow:
    inset 0 0 0 3px var(--cyan-neon),
    inset 0 0 0 6px rgba(0, 0, 0, 0.6);
}

/* "Get directions" chip. Bottom-LEFT on purpose: the attribution overlay owns
 * the bottom-right and is a licence obligation, so it must not be covered. */
.map__cta {
  position: absolute;
  left: 12px;
  bottom: 10px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  /* 44px min touch target. The chip is decorative — the whole map is the hit
     area — but it should not read as a control too small to press. */
  min-height: 34px;
  padding: 7px 13px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  background: rgba(var(--bg-deep-rgb), 0.9);
  color: #fff;
  font-family: var(--font-ui);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  transition: transform 0.18s ease;
}

.map__cta-icon {
  color: var(--orange);
  font-size: 0.9em;
}

@media (prefers-reduced-motion: reduce) {
  .map__link::after,
  .map__cta {
    transition: none;
  }
  .map__link:hover .map__cta {
    transform: none;
  }
}

.map__img {
  display: block;
  width: 100%;
  height: 100%;
  /* The source render is 1520x802 but the box is a fixed height that changes
     at the mobile breakpoint, so the aspect ratios don't match. Cover crops
     symmetrically and keeps the pin — which is centred in the render — in
     view at every width. */
  object-fit: cover;
}

/* Licence credit for the OpenStreetMap-derived basemap. Required to be
 * visible, so it gets a solid-enough backing to stay legible over both the
 * dark landmass and the lighter road fills. */
.map__attrib {
  position: absolute;
  right: 0;
  bottom: 0;
  /* It is a link now (OSM's attribution guidance asks for one), so it needs
   * to sit above the map link that covers the whole box and it must not
   * inherit the underline every other anchor gets. */
  z-index: 2;
  text-decoration: none;
  padding: 3px 7px;
  border-top-left-radius: 6px;
  background: rgba(var(--bg-deep-rgb), 0.86);
  color: var(--text-muted);
  font-family: var(--font-ui);
  font-size: 10px;
  line-height: 1.4;
  letter-spacing: 0.02em;
}

.map__attrib:hover,
.map__attrib:focus-visible {
  color: #fff;
  text-decoration: underline;
}

.map__attrib:focus-visible {
  outline: 2px solid var(--cyan-neon);
  outline-offset: 1px;
}

/* Shown when no map image is configured, or the image fails to load. */
.map__fallback {
  display: grid;
  place-content: center;
  gap: 8px;
  height: 100%;
  padding: 24px;
  text-align: center;
  color: var(--text-muted);
}

/* Retained for the fallback card and reusable elsewhere: the teardrop pin
   shape that matches the one burned into the static render. */
.map-pin {
  width: 26px;
  height: 26px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  background: var(--orange);
  border: 2px solid #fff;
  box-shadow: 0 0 16px rgba(var(--orange-rgb), 0.95);
}

.directions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
}

/* -- know-before-you-go list -------------------------------------------- */
.tips {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.tips li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  font-size: 0.92rem;
  color: var(--text-muted);
}

.tips b {
  color: var(--text);
}

.tips .glyph {
  flex: none;
  font-size: 1.05rem;
  line-height: 1.4;
}


/* ==========================================================================
   Confetti / coin canvas overlay
   ========================================================================== */
/* Same replaced-element caveat as #starfield — needs explicit dimensions. */
#fx-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 5;
  pointer-events: none;
}

/* ==========================================================================
   COLLECTION NOTICE (Level 2)
   Deliberately quieter than a warning and louder than a hint: it must be
   read, but it is not an error state.
   ========================================================================== */
.notice {
  margin-bottom: 24px;
  padding: 16px 18px;
  border: 1px solid rgba(var(--cyan-rgb), 0.42);
  border-left-width: 3px;
  border-radius: 12px;
  background: rgba(var(--cyan-rgb), 0.07);
}

.notice__title {
  margin: 0 0 8px;
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--cyan-neon);
}

.notice__body {
  margin: 0 0 14px;
  font-size: 0.87rem;
  line-height: 1.6;
  color: #cfc9dd; /* 9.2:1 — this is copy people must actually be able to read */
}

.notice__body a {
  color: var(--cyan-neon);
}

.notice__consent {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 11px 12px;
  border: 1px solid rgba(var(--cyan-rgb), 0.38);
  border-radius: 9px;
  background: rgba(var(--bg-deep-rgb), 0.5);
  font-size: 0.88rem;
  line-height: 1.45;
  cursor: pointer;
}

.notice__consent input {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  accent-color: var(--cyan-neon);
}

/* Fields stay visibly present but inert until consent is given, so the
   attendee can see what is being asked before agreeing to provide it. */
.field.is-locked {
  opacity: 0.45;
}

.field.is-locked .input,
.field.is-locked .textarea,
.field.is-locked .chip {
  cursor: not-allowed;
}

/* Divider between the two halves of Level 2 (catering vs research). */
.step__rule {
  height: 1px;
  margin: 30px 0 26px;
  border: 0;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(var(--orange-rgb), 0.38),
    transparent
  );
}

/* ==========================================================================
   GAME PLAN CONTROLS (Level 2, lower half)
   Three config-driven inputs: session picks, excitement grid, 1-5 scale.
   All are checkbox/radio inputs visually hidden behind a styled label, so
   keyboard navigation and screen-reader semantics come for free.
   ========================================================================== */

/* -- session picks ------------------------------------------------------- */
.picks {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.pick {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 11px;
  min-height: 44px;
  padding: 13px 15px;
  border: 1px solid rgba(var(--orange-rgb), 0.62);
  border-radius: 12px;
  background: rgba(var(--bg-deep-rgb), 0.55);
  cursor: pointer;
  transition: border-color 0.18s, background 0.18s, transform 0.12s;
}

.pick:hover {
  border-color: rgba(var(--orange-rgb), 0.55);
  background: rgba(var(--orange-rgb), 0.13);
}

.pick input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/* The box is drawn, not native, so it can follow the theme accent. */
.pick__box {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border: 2px solid rgba(var(--orange-rgb), 0.6);
  border-radius: 5px;
  display: grid;
  place-items: center;
  font-size: 0.78rem;
  line-height: 1;
  color: var(--bg-deep);
  transition: background 0.16s, border-color 0.16s;
}

.pick__box::after {
  content: '\2713';
  opacity: 0;
  transform: scale(0.5);
  transition: opacity 0.14s, transform 0.14s;
}

.pick__text {
  font-size: 0.95rem;
  line-height: 1.35;
}

.pick input:checked ~ .pick__box {
  background: var(--orange);
  border-color: var(--orange);
}

.pick input:checked ~ .pick__box::after {
  opacity: 1;
  transform: scale(1);
}

.pick:has(input:checked) {
  border-color: var(--orange);
  background: rgba(var(--orange-rgb), 0.12);
}

.pick input:focus-visible ~ .pick__box {
  outline: 2px solid var(--cyan-neon);
  outline-offset: 3px;
}

/* -- excitement grid ----------------------------------------------------- */
.rategrid {
  display: grid;
  gap: 10px;
}

.rategrid__row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  border: 1px solid rgba(var(--orange-rgb), 0.5);
  border-radius: 12px;
  background: rgba(var(--bg-deep-rgb), 0.5);
}

.rategrid__label {
  font-size: 0.93rem;
  line-height: 1.35;
}

/* -- segmented control (shared by grid + scale) -------------------------- */
.seg {
  display: inline-flex;
  padding: 3px;
  border: 1px solid rgba(var(--orange-rgb), 0.62);
  border-radius: 10px;
  background: rgba(var(--bg-deep-rgb), 0.7);
}

.seg__opt {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 38px;
  padding: 0 12px;
  border-radius: 7px;
  font-family: var(--font-ui);
  font-size: 0.82rem;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.16s, color 0.16s;
}

.seg__opt:hover {
  color: var(--text);
  background: rgba(var(--orange-rgb), 0.1);
}

.seg__opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.seg__opt:has(input:checked) {
  background: var(--orange);
  color: #1a0d02;
  font-weight: 700;
}

.seg__opt:has(input:focus-visible) {
  outline: 2px solid var(--cyan-neon);
  outline-offset: 2px;
}

/* -- 1-5 scale ----------------------------------------------------------- */
.scale {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.scale__end {
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.scale .seg__opt {
  min-width: 46px;
  font-size: 0.95rem;
  font-weight: 700;
}

/* Fallback for browsers without :has() — the checked state still reads via
   the drawn box and the outline, but we also tint the input's own label. */
@supports not (selector(:has(*))) {
  .pick input:checked ~ .pick__text {
    color: var(--orange-bright);
    font-weight: 700;
  }
  .seg__opt input:checked ~ span {
    color: var(--orange-bright);
    font-weight: 700;
    text-decoration: underline;
  }

  .pick input:focus-visible ~ .pick__text,
  .seg__opt input:focus-visible ~ span {
    outline: 2px solid var(--cyan-neon);
    outline-offset: 3px;
  }
}

@media (max-width: 560px) {
  .picks {
    grid-template-columns: 1fr;
  }

  /* Stack the label above its control so the three options keep 44px. */
  .rategrid__row {
    grid-template-columns: 1fr;
    gap: 9px;
  }

  .rategrid .seg {
    display: grid;
    /* auto-fit, not repeat(3): the levels list is admin-editable. */
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  }

  .rategrid .seg__opt {
    min-width: 0;
  }

  .scale {
    gap: 8px;
  }

  .scale .seg {
    order: 2;
    flex: 1 1 100%;
    display: grid;
    /* minmax(0,1fr) lets the track shrink below the 46px min-width so a
       10-point scale still fits 320px instead of being clipped. */
    grid-template-columns: repeat(var(--scale-n, 5), minmax(0, 1fr));
  }

  .scale .seg__opt {
    min-width: 0;
    padding: 0 4px;
  }

  .scale__end {
    order: 1;
    flex: 0 0 auto;
  }

  .scale__end--high {
    order: 3;
    margin-left: auto;
  }
}

/* ==========================================================================
   PRINT
   The confirmation page is the one page an attendee is likely to print or
   save as PDF. Without this it came out blank: body text is white and the
   background is painted on a position:fixed canvas, which browsers do not
   print. Everything below strips the arcade chrome and prints the ticket.
   ========================================================================== */
@media print {
  /* Decorative layers and anything that cannot work on paper. */
  #starfield,
  #fx-canvas,
  .map,
  .directions,
  .cal-grid,
  [data-print='hide'],
  .back-link,
  .skip-link,
  .admin-bar,
  .tabs,
  .toolbar,
  .cabinet,
  .coin-slot,
  .countdown,
  .cta {
    display: none !important;
  }

  html,
  body {
    background: #fff !important;
    color: #000 !important;
  }

  body::before,
  body::after,
  .scanlines::after {
    display: none !important;
  }

  /* Blanket reset. Enumerating selectors missed bold labels inside prose
     and left them white-on-white; anything that still needs to be lighter
     is re-muted below. */
  .page,
  .page *,
  main,
  main * {
    color: #000 !important;
    text-shadow: none !important;
    box-shadow: none !important;
    -webkit-text-stroke: 0 !important;
    background-image: none !important;
    filter: none !important;
    animation: none !important;
  }

  .page {
    max-width: none;
    padding: 0;
  }

  .panel,
  .ticket,
  .card,
  .note,
  .agenda {
    border: 1px solid #999 !important;
    background: #fff !important;
    break-inside: avoid;
  }

  /* Secondary copy stays readable but recedes. */
  .muted,
  .hint,
  .lede,
  .agenda__desc,
  .hero__address,
  .ticket__meta {
    color: #444 !important;
  }

  a {
    color: #000 !important;
    text-decoration: underline;
  }

  /* A printed link is useless without its target. In-page anchors and
     mailto: already say where they go in the visible text. */
  a[href^='http']::after {
    content: ' (' attr(href) ')';
    font-size: 0.78em;
    font-weight: 400;
    word-break: break-all;
  }

  /* The logo is a white wordmark — invisible on paper. */
  .cf-logo {
    filter: invert(1) grayscale(1) !important;
    max-height: 30px;
    width: auto;
  }

  /* The map is dropped, so the written address must carry the location. */
  .venue-print {
    display: block !important;
  }

  /* Avoid a trailing blank sheet from the fixed-height decorative layers. */
  footer,
  .site-foot {
    break-before: avoid;
    page-break-before: avoid;
  }

  /* Reclaim vertical space so the ticket fits on two sheets.
   *
   * `break-before: avoid` above is only a hint — it cannot prevent a break
   * when the content genuinely does not fit, and it did not: the footer was
   * landing alone on a third sheet carrying a single line of text. At ~200
   * attendees, each printing their ticket, that is 200 wasted sheets to say
   * "hosted by Cloudflare".
   *
   * The screen layout's generous rhythm is what costs the room, and it buys
   * nothing on paper where there is no scrolling to break up. Tightening the
   * section gaps and the footer's leading margin is enough to pull the footer
   * back up without making anything feel cramped in print. */
  .section {
    margin-top: 18px !important;
    margin-bottom: 0 !important;
  }

  .section__title {
    margin-bottom: 8px !important;
  }

  .site-foot {
    margin-top: 14px !important;
    padding-top: 8px !important;
    font-size: 0.8rem;
  }

  @page {
    margin: 12mm;
  }
}
