/* ===========================================================================
   Alexandra Palace Fireworks & Drone Festival 2026 - signup
   Rebuilt 2026-08-14 around the designer's LAYERED artwork kit (third drop).

   What the kit gave us, and why the page is shaped the way it is:
     - a background with the Palace REMOVED, carrying a dark centre vignette
     - the Palace as its own transparent PNG
     - the main lockup, and each night as its own finished lockup
   Because the Palace is separable, the content no longer has to fight the
   illustration for legibility: the vignette holds the form, and the Palace is
   re-attached at the foot of the DOCUMENT. The composition is centred and
   symmetric because both backgrounds are - a left/right split would fight the
   artwork's own axis, which is what the previous build did.

   Palette: every value below is SAMPLED from the supplied files, none invented.
   Type: the client's OWN brand faces, supplied 2026-08-14 with the instruction
   to use PalaceDisplay-Bold for titles and GranbyAP for text. The earlier
   Big Shoulders + Archivo pairing was a stand-in for exactly this and is gone.

   Token NAMES are unchanged from the previous build on purpose: the share
   block, prize cards, error states, footer, doc pages and intl-tel-input
   overrides all reference them, and re-pointing the values reskins those
   wholesale without touching a single hard-won vendor rule.
   =========================================================================== */

/* THE CLIENT'S OWN BRAND FACES, supplied 2026-08-14 with the instruction:
   PalaceDisplay-Bold for titles, GranbyAP-Medium/Bold for text. They replace
   the Big Shoulders + Archivo stand-ins entirely.

   ⚠️ Each family ships ONE weight per file, registered at its true value.
   Ask for a weight that is not here and the browser SYNTHESISES it - a smeared
   faux-bold that looks subtly wrong next to the real artwork. Every rule using
   --f-disp must therefore say font-weight:700, and --f-body must say 500 or
   700 and nothing else. Do not reintroduce 600 or 900. */
@font-face {
  font-family: "Palace Display";
  src: url("/assets/fonts/PalaceDisplay-Bold.woff2?v=20260814-12") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Granby AP";
  src: url("/assets/fonts/GranbyAP-Medium.woff2?v=20260814-12") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Granby AP";
  src: url("/assets/fonts/GranbyAP-Bold.woff2?v=20260814-12") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* Sampled from desktop_2.jpg / Date_1.png / Overlay.png */
  --ink:        #0A0520;   /* deepest sky */
  --ink-2:      #150B30;   /* dominant artwork navy */
  --horizon:    #291748;   /* violet */

  --spark:      #A3DDDD;   /* AQUA - the artwork's own call-to-action colour,
                              lifted from the night lockups' ribbons. Primary
                              accent: buttons, links, focus. 13.2:1 on --ink. */
  --ember:      #DF9F4B;   /* drone gold, from the dot-matrix DRONE lettering.
                              Saturday. 8.2:1 on --ink-2. */
  --rose:       #EC1E80;   /* hot pink from the background's lower edge.
                              Sunday. 4.5:1 on --ink-2. */

  --paper:      #FFEDD7;   /* the lockup's cream - all body/display type */
  --muted:      #AFA6C4;
  --line:       rgba(255, 237, 215, 0.14);

  /* Flattened OPAQUE equivalent of a field over the panel over the vignette.
     Used by :-webkit-autofill, whose box-shadow cannot be semi-transparent.
     Composited, not guessed: rgba(255,237,215,.05) over rgba(10,5,32,.90)
     over the vignette centre #1B1038. */
  --field-flat: #191325;

  --panel-bg:   rgba(10, 5, 32, 0.90);
  --radius:     14px;
  --radius-sm:  10px;

  --f-disp: "Palace Display", Georgia, "Times New Roman", serif;
  --f-body: "Granby AP", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html {
  -webkit-text-size-adjust: 100%;
  /* Base colour lives on <html>, NOT <body>: an opaque body background paints
     over the fixed .bg layer beneath it. (ab-birmingham launch bug.) */
  background: var(--ink);
}

body {
  min-height: 100vh; min-height: 100dvh;
  font-family: var(--f-body);
  font-size: 16px; line-height: 1.55;
  color: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

a { color: inherit; }

/* --- Background scene ---------------------------------------------------- */
/* Fixed, so the vignette stays put while the page scrolls and the content
   always sits in its dark centre. The <picture> in the markup carries both
   crops, so resizing across the breakpoint never shows an unfetched image. */
.bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.bg img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: center center;
}

.sheet {
  position: relative; z-index: 1;
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column;
}

/* --- Stage --------------------------------------------------------------- */
/* <main> spans the viewport so the prizes/press bands beneath the hero can
   reach both edges. The 1100px measure lives on .stage, the hero wrapper,
   which is also what the landscape block turns into a two-column grid. */
.page { flex: 1 0 auto; width: 100%; }

.stage {
  width: 100%; max-width: 1100px; margin: 0 auto;
  padding: clamp(22px, 4vw, 54px) clamp(18px, 4vw, 40px) 0;
}

/* ============================ BRAND ==================================== */
.brand { text-align: center; }

/* A straight placement of the supplied lockup. Never recolour, re-set or
   letter-space it - it is the client's artwork, and the faces in it are not
   licensed for web use as live type. */
/* Lockup reduced ~30% on client note, 14 Aug ("quite big, particularly on mob"):
   620 -> 434 here, 340 -> 230 in the phone block below. It is still the LCP
   element, so the preload and fetchpriority in the head stay as they are. */
.brand__lockup { width: min(100%, 434px); margin: 0 auto; }
.brand__lockup img { display: block; width: 100%; height: auto; }

.brand__bill {
  margin: clamp(12px, 2vw, 18px) 0 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 0;
  font-size: clamp(0.72rem, 1.5vw, 0.86rem);
  font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--paper);
}
.brand__bill li { display: flex; align-items: center; }
.brand__bill li:not(:last-child)::after {
  content: ""; width: 4px; height: 4px; margin: 0 12px;
  border-radius: 50%; background: var(--spark); opacity: 0.9;
}
/* Below this width the items cannot hold one line, and a wrap leaves the
   separator dangling at the end of the first line looking like a typo. Drop
   the dots and let the word gap do the separating instead.
   ⚠️ Was 460px, which was correct for FOUR items. The client added a fifth
   ("Live music & DJs", 17 Aug) and the row now breaks at 580px - measured, not
   estimated: 560 wraps with a stranded dot, 580 holds. Between the two values
   the bug was live and visible. RE-MEASURE this number if the bill changes
   again; it is a function of the item strings, not a round guess. */
@media (max-width: 580px) {
  .brand__bill { column-gap: 14px; font-size: 0.68rem; letter-spacing: 0.14em; }
  .brand__bill li:not(:last-child)::after { display: none; }
}

/* ============================ PANEL ==================================== */
.signup { position: relative; }

/* The panel thins toward its base rather than sitting on the scene as a flat
   slab, so the rays behind it stay part of the picture. It never goes fully
   transparent - the consent copy and the button have to stay legible. */
.panel {
  width: min(100%, 660px);
  margin: clamp(22px, 3.4vw, 40px) auto 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  /* A gentle thinning, not a real fade. The first pass ended at 0.74 and the
     panel's base washed out badly wherever it overlapped the scene's bright
     magenta lower half - the fine print under the night picker lost contrast
     and the effect was barely visible anyway. Legibility wins; the panel still
     reads as part of the scene rather than a flat slab. */
  background: linear-gradient(180deg,
              rgba(10, 5, 32, 0.94) 0%,
              rgba(10, 5, 32, 0.91) 62%,
              rgba(10, 5, 32, 0.87) 100%);
  backdrop-filter: blur(16px) saturate(1.05);
  -webkit-backdrop-filter: blur(16px) saturate(1.05);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.panel__inner { padding: clamp(20px, 3vw, 34px); }

/* State machine: exactly one visible. Each state carries its own full
   eyebrow + title + lede, so nothing stacks under anything else. */
.panel__state { display: none; }
.panel[data-form-state="idle"]    .panel__idle,
.panel[data-form-state="otp"]     .panel__otp,
.panel[data-form-state="success"] .panel__success { display: block; }

/* Centred to sit over the centred title (Adam, 2026-08-14). Both hairlines now
   flex equally and their gradients mirror, so the rule fades away from the
   words on each side and the text lands on the panel's centre line. The old
   version pinned a fixed 18px stub on the left, which is what held the label
   off-centre. */
.panel__eyebrow {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin: 0 0 10px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.26em;
  text-transform: uppercase; color: var(--spark);
}
.panel__eyebrow-hair { flex: 1 1 auto; height: 1px; }
.panel__eyebrow-hair:first-child {
  background: linear-gradient(90deg, transparent, var(--line));
}
.panel__eyebrow-hair:last-child {
  background: linear-gradient(90deg, var(--line), transparent);
}

/* One line, centred (Adam, 2026-08-14). The clamp is sized so "GET IN FIRST."
   holds a single line at every width - 7vw carries the small end, and the
   3.1rem cap keeps it inside the 592px panel measure at the top end.
   text-wrap:balance rather than white-space:nowrap: a nowrapped display title
   longer than the panel overflows instead of wrapping, and reads as off-centre. */
.panel__title {
  margin: 0 0 10px;
  font-family: var(--f-disp); font-weight: 700;
  font-size: clamp(1.9rem, 7vw, 3.1rem);
  line-height: 0.98; text-transform: uppercase; color: var(--paper);
  text-align: center; text-wrap: balance;
}
.panel__lede { text-align: center; }
.panel__title em { font-style: normal; color: var(--spark); }

/* The idle title only. clamp() scales with the VIEWPORT and knows nothing about
   string length, so the ceiling that suited "GET IN FIRST." (13 chars) runs the
   client's 2026-08-17 headline (31 chars) to three lines inside the 660px panel
   and pushes the first form field below the fold on a phone. Scoped to a
   modifier so the OTP and success titles, which share .panel__title, keep the
   larger setting they were tuned at. */
.panel__title--long { font-size: clamp(1.55rem, 5.4vw, 2.45rem); }

.panel__lede {
  margin: 0 0 clamp(16px, 2.4vw, 22px);
  font-size: clamp(0.9rem, 1.85vw, 1rem); line-height: 1.55;
  color: var(--muted);
}
.panel__lede strong { color: var(--paper); font-weight: 700; }
/* ⚠️ UNUSED since 2026-09-02 - the lede this marked ("VIP Experience prizes",
   client 2026-08-17) went with the prize draw, and no *** mark is left anywhere
   on the page. Kept so restoring a footnote stays an HTML-only change. Aqua so it
   matches `.terms sup`, the clause it points at - left at the lede's muted grey
   it reads as a typo rather than a reference. */
.panel__lede sup { color: var(--spark); }

/* PRESALE DATE (client, 2026-08-17: "we haven't mentioned anywhere when the
   presale will be taking place"). Deliberately a STATIC ribbon and not a
   countdown - the client asked for the date with no timer, and a timer on a
   page whose whole job is a form would also pull the eye off the fields.

   Built as an inline-block ribbon rather than a full-width bar so it sizes to
   its own text and stays optically centred under the lede at every width; a
   full-width bar reads as a second panel and competes with the night picker
   below it. The aqua hairline border is the same language as the night
   lockups' own ribbons, so it belongs to the artwork rather than to the UI. */
.panel__presale {
  display: block; margin: -4px auto clamp(16px, 2.4vw, 22px);
  text-align: center;
}
/* ⚠️ Colour encodes the STATE, not the label - swapped 2026-09-02 when this
   became a live indicator. --spark, the page's one signal colour, now lands on
   "Live now" (the strong) and the "PRESALE" label steps back to --muted. Before
   the presale opened it was the other way round, which was right for a ribbon
   whose payload was a DATE. Do not swap them back while the state is live: the
   aqua would be sitting on the word nobody needs to read. */
.panel__presale > span {
  display: inline-block; padding: 8px 18px;
  border: 1px solid rgba(163, 221, 221, 0.55);
  border-radius: 999px;
  background: rgba(163, 221, 221, 0.11);
  font-size: clamp(0.82rem, 1.7vw, 0.95rem); line-height: 1.45;
  color: var(--muted); text-wrap: balance;
}
.panel__presale strong {
  color: var(--spark); font-weight: 700;
  /* Granby ships 500/700 only - 700 here, never 600, or the browser
     synthesises a faux-bold beside the real artwork. */
}
.panel__presale em {
  font-style: normal; color: var(--muted);
  font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  font-size: 0.82em;
}
/* Live-status dot, added 2026-09-02 when the ribbon stopped announcing a date
   and started reporting a state. --spark is the same aqua as the night
   picker's drone dots, so the page reads one signal colour rather than two.
   ⚠️ Deliberately STATIC. The drone-dot ignition on night selection is this
   build's single piece of motion and it earns that on its own; a pulsing dot
   16px above it would split the attention it was built to hold. */
.panel__presale-dot {
  display: inline-block; width: 7px; height: 7px;
  margin-right: 9px; vertical-align: middle; position: relative; top: -1px;
  border-radius: 50%; background: var(--spark);
  box-shadow: 0 0 0 3px rgba(163, 221, 221, 0.16);
}

.panel__success-note {
  margin: 0 0 12px; padding: 10px 12px;
  border-left: 3px solid var(--spark);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: rgba(163, 221, 221, 0.10);
  font-size: 0.9rem; color: var(--paper);
}

/* --- Supplied state artwork (success) ----------------------------------- */
/* These are the designer's own images, and for a screen reader the image IS
   the message - the alt text carries the full sentence, so nothing here
   duplicates it as live type. */
.stateart { margin: 2px 0 14px; }
.stateart img { display: block; width: 100%; height: auto; }
.stateart--dates {
  margin: 18px 0 2px; padding-top: 16px;
  border-top: 1px solid var(--line);
}

/* --- Form --------------------------------------------------------------- */
.form { margin: 0; }

.form__grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 10px;
}
@media (max-width: 520px) {
  .form__grid { grid-template-columns: 1fr; gap: 0; }
}

.form__row { margin: 0 0 12px; }

/* sr-only labels: placeholders carry meaning visually, labels stay for screen
   readers. Never display:none - that removes them from the a11y tree. */
.form__label {
  position: absolute !important;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

.form__input {
  width: 100%; padding: 13px 14px;
  font-family: inherit; font-size: 1rem; line-height: 1.3;
  color: var(--paper);
  background: rgba(255, 237, 215, 0.05);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  outline: none;
  transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease;
  -webkit-appearance: none; appearance: none;
}
.form__input::placeholder { color: rgba(175, 166, 196, 0.75); }
.form__input:hover { border-color: rgba(255, 237, 215, 0.26); }
.form__input:focus {
  border-color: var(--spark);
  background: rgba(255, 237, 215, 0.08);
  box-shadow: 0 0 0 3px rgba(163, 221, 221, 0.22);
}

/* Chrome paints autofilled inputs pale lavender and IGNORES background /
   background-color on :-webkit-autofill. A large inset box-shadow is the only
   reliable override, and it cannot be semi-transparent - hence --field-flat.
   The 9999s transition stops Chrome flashing lavender before it paints. */
.form__input:-webkit-autofill,
.form__input:-webkit-autofill:hover,
.form__input:-webkit-autofill:focus,
.form__input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px var(--field-flat) inset !important;
          box-shadow: 0 0 0 1000px var(--field-flat) inset !important;
  -webkit-text-fill-color: var(--paper) !important;
  caret-color: var(--paper);
  transition: background-color 9999s ease-in-out 0s;
}

.form__input--otp {
  /* Granby, matching the buttons. This is the "confirm code" the client named
     alongside the sign-up and confirm buttons (client note, 14 Aug): it is the
     field the six-digit code is TYPED into, and a display serif setting digits
     at 0.42em tracking is the worst case for telling 6 from 8 or 1 from 7.
     ⚠️ 700 only - Granby ships 500 and 700, nothing between. */
  font-family: var(--f-body); font-size: 1.9rem; font-weight: 700;
  letter-spacing: 0.42em; text-align: center;
  padding: 14px 14px 14px calc(14px + 0.42em);
}

/* --- Night picker: the artwork IS the control --------------------------- */
/* The designer supplied each night as a finished lockup, so the page never
   states the dates twice - you choose a night by picking its panel. Unselected
   panels are dimmed and desaturated; that difference, not a tick box, is what
   carries selection state. A real checkbox is still underneath for keyboard
   and assistive tech. */
.form__row--nights {
  margin: 0 0 14px; padding: 0; border: 0;
  min-width: 0;   /* fieldset defaults to min-content and breaks the grid */
}

.form__legend {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  padding: 0; margin: 0 0 9px;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--paper);
}
.form__legend span {
  font-weight: 500; letter-spacing: 0.04em; text-transform: none;
  color: var(--muted);
}

.nightpick {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;
}
@media (max-width: 520px) { .nightpick { grid-template-columns: 1fr; } }

.nightpick__opt { display: block; cursor: pointer; }
.nightpick__opt input {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; margin: -1px; clip: rect(0, 0, 0, 0);
}

.nightpick__box {
  display: block; position: relative; overflow: hidden;
  padding: 10px 10px 13px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: rgba(255, 237, 215, 0.035);
  transition: border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
}
/* The artwork is ALWAYS at full strength (Adam, 2026-08-14). The first pass
   dimmed and desaturated the unselected panels, which read as disabled rather
   than as unchosen. Selection is carried entirely by the frame instead: a
   2px inset ring in the night's colour, a tint, a lift, and the dot formation
   igniting. Nothing about the client's artwork is ever altered. */
.nightpick__box img {
  display: block; width: 100%; height: auto;
}

.nightpick__opt:hover .nightpick__box {
  border-color: rgba(255, 237, 215, 0.38);
  background: rgba(255, 237, 215, 0.07);
}

.nightpick__opt--sat input:checked + .nightpick__box {
  border-color: var(--ember);
  background: rgba(223, 159, 75, 0.12);
  box-shadow: inset 0 0 0 2px var(--ember), 0 12px 30px rgba(223, 159, 75, 0.22);
}
.nightpick__opt--sun input:checked + .nightpick__box {
  border-color: var(--rose);
  background: rgba(236, 30, 128, 0.12);
  box-shadow: inset 0 0 0 2px var(--rose), 0 12px 30px rgba(236, 30, 128, 0.22);
}
.nightpick__opt input:focus-visible + .nightpick__box {
  box-shadow: 0 0 0 3px rgba(163, 221, 221, 0.4);
}

/* THE SIGNATURE: the drone formation.
   The lockup sets the word DRONE in a grid of illuminated dots - a swarm
   rendering type in the sky - and this is the only event on the fleet with a
   drone show. So selection is confirmed by a formation igniting left to right,
   in that night's colour. It is the one place this page spends its boldness;
   everything around it stays quiet. */
.nightpick__dots {
  display: flex; justify-content: center; gap: 7px; margin-top: 11px;
}
.nightpick__dots i {
  width: 6px; height: 6px; border-radius: 50%;
  background: rgba(255, 237, 215, 0.2);
  transition: background 0.2s ease, box-shadow 0.2s ease;
}
.nightpick__opt input:checked + .nightpick__box .nightpick__dots i {
  animation: ignite 0.42s ease both;
}
.nightpick__opt--sat input:checked + .nightpick__box .nightpick__dots i {
  background: var(--ember); box-shadow: 0 0 10px rgba(223, 159, 75, 0.85);
}
.nightpick__opt--sun input:checked + .nightpick__box .nightpick__dots i {
  background: var(--rose); box-shadow: 0 0 10px rgba(236, 30, 128, 0.85);
}
.nightpick__dots i:nth-child(1) { animation-delay: 0s; }
.nightpick__dots i:nth-child(2) { animation-delay: 0.04s; }
.nightpick__dots i:nth-child(3) { animation-delay: 0.08s; }
.nightpick__dots i:nth-child(4) { animation-delay: 0.12s; }
.nightpick__dots i:nth-child(5) { animation-delay: 0.16s; }

@keyframes ignite {
  from { transform: scale(0.35); opacity: 0.15; }
  to   { transform: scale(1);    opacity: 1; }
}

/* CURRENTLY UNUSED - the client removed the element it styles on 2026-08-17 so
   the * / ** notes appear only in the footer terms. Kept, with its 947-line
   companion, because this client has revised that fine print three times in
   four days; restoring it should stay a one-line HTML change. */
.nightpick__fine {
  margin: 10px 0 0;
  font-size: 0.7rem; line-height: 1.45; color: var(--muted);
}

/* --- Checkboxes --------------------------------------------------------- */
.form__row--check { margin: 0 0 12px; }

.form__check {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 0.83rem; line-height: 1.5; color: var(--muted); cursor: pointer;
}
.form__check input {
  flex: 0 0 auto; width: 17px; height: 17px; margin: 2px 0 0;
  accent-color: var(--spark); cursor: pointer;
}
.form__check a { color: var(--spark); text-decoration: underline; text-underline-offset: 2px; }
.form__check a:hover { color: var(--ember); }

/* The optional marketing opt-in (client, 2026-08-17). It sits directly beneath a
   REQUIRED box carrying near-identical furniture, so without a cue the two read
   as one bundled consent - which is exactly the pattern that invalidates the
   optional one. The word is set inline at the end of the sentence rather than as
   a floated badge so it stays in the reading order for a screen reader. */
.form__check--optional em {
  font-style: normal; white-space: nowrap;
  margin-left: 4px; padding: 1px 6px;
  border: 1px solid var(--line); border-radius: 999px;
  font-size: 0.72em; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--spark);
}

/* Honeypot. display:none ONLY - off-screen positioning gets autofilled by
   Chrome/Edge, which silently blocks real signups. */
.hp-row { display: none !important; }

/* --- Errors ------------------------------------------------------------- */
.form__error {
  margin: 6px 0 0; font-size: 0.78rem; line-height: 1.4; color: #FF9A8B;
}
.form__error--global {
  margin-top: 10px; padding: 9px 11px;
  border-radius: var(--radius-sm); background: rgba(255, 154, 139, 0.1);
}

/* --- Buttons ------------------------------------------------------------ */
.btn {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; margin: 4px 0 0; padding: 15px 20px;
  /* Granby, NOT Palace Display. The client called the display face hard to read
     on the sign-up / confirm-code / confirm buttons (client note, 14 Aug), and
     their own font direction is Palace Display for TITLES, Granby for TEXT - a
     button is UI text, not a title. Headings keep --f-disp.
     ⚠️ Granby ships 500 and 700 only, so this must stay on 700 or the browser
     synthesises a faux-bold that reads wrong beside the real artwork. */
  font-family: var(--f-body); font-size: 1.18rem; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; text-decoration: none;
  border: 0; border-radius: var(--radius-sm); cursor: pointer;
  transition: transform 0.14s ease, box-shadow 0.16s ease, filter 0.16s ease;
}
/* Solid aqua, not a gradient: the artwork states its own calls to action -
   "Early bird tickets", "Under 10s go free" - as aqua-outlined ribbons, so the
   button is the same voice. Dark navy on aqua measures 13.2:1. */
.btn--primary {
  color: var(--ink);
  background: var(--spark);
  box-shadow: 0 10px 26px rgba(163, 221, 221, 0.22);
}
.btn--primary:hover { filter: brightness(1.07); transform: translateY(-1px); }
.btn--primary:active { transform: translateY(0); }
.btn:focus-visible { outline: 3px solid var(--spark); outline-offset: 3px; }
.btn[disabled] { opacity: 0.62; cursor: not-allowed; transform: none; filter: none; }

.form__resend {
  margin: 12px 0 0; font-size: 0.82rem; text-align: center; color: var(--muted);
}
.form__resend-btn {
  padding: 0; font: inherit; color: var(--spark);
  background: none; border: 0;
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.form__resend-btn[disabled] { opacity: 0.6; cursor: not-allowed; }

/* --- Referral share block (success state) ------------------------------- */
/* Brightened 2026-08-14 (Adam): at 7% aqua this read as another quiet panel,
   and it is the one thing on the success screen we actually want acted on -
   every share is another entrant. Now a full aqua frame and a 14% wash, plus an
   aqua heading, so it reads as an invitation rather than a footnote.
   Contrast checked, not guessed: the muted body copy measures 6.45:1 against
   the brightened background (#22263A once the wash is flattened over the
   panel), comfortably past 4.5:1. */
.share {
  margin: clamp(16px, 2.4vw, 22px) 0 0;
  padding: clamp(15px, 2.1vw, 19px);
  border: 1px solid rgba(163, 221, 221, 0.45);
  border-top: 3px solid var(--spark);
  border-radius: var(--radius-sm);
  background: rgba(163, 221, 221, 0.14);
  box-shadow: 0 0 0 1px rgba(163, 221, 221, 0.08), 0 10px 30px rgba(163, 221, 221, 0.10);
}

.share__head {
  margin: 0 0 5px;
  font-family: var(--f-disp); font-weight: 700;
  font-size: 1.42rem; line-height: 1.05; text-transform: uppercase;
  color: var(--spark);
}
.share__lede { margin: 0 0 12px; font-size: 0.88rem; color: #D9CFE4; }

.share__linkrow { position: relative; display: flex; gap: 8px; align-items: stretch; }
.share__input {
  flex: 1 1 auto; min-width: 0;
  font-size: 0.86rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.01em;
}
.share__copy {
  flex: 0 0 auto; padding: 0 14px;
  font-family: var(--f-body); font-size: 0.76rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink); background: var(--spark);
  border: 0; border-radius: var(--radius-sm); cursor: pointer;
  transition: filter 0.16s ease;
}
.share__copy:hover { filter: brightness(1.08); }

.btn--share {
  margin-top: 10px;
  color: var(--ink);
  background: var(--paper);
  box-shadow: none;
  font-size: 1.05rem;
}
.btn--share:hover { filter: brightness(0.94); transform: translateY(-1px); }

.share__list {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 10px 0 0; padding: 0; list-style: none;
}
.share__list li { flex: 1 1 auto; }
.share__btn {
  display: block; padding: 9px 10px;
  font-size: 0.76rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; text-align: center; text-decoration: none;
  color: var(--paper);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: rgba(255, 237, 215, 0.05);
  transition: border-color 0.16s ease, background 0.16s ease;
}
.share__btn:hover { background: rgba(255, 237, 215, 0.1); }
.share__btn--wa:hover { border-color: #25D366; }
.share__btn--fb:hover { border-color: #1877F2; }
.share__btn--x:hover  { border-color: var(--paper); }

.share__note {
  margin: 10px 0 0;
  font-size: 0.72rem; line-height: 1.45; color: rgba(175, 166, 196, 0.85);
}

/* ============================ PRIZES =================================== */
/* ⚠️ UNUSED since 2026-09-02 - the .prizes band was removed from index.html
   when the prize draw closed, and presale.html never carried it. Kept rather
   than deleted because this project's fine print and sections have been
   revised repeatedly by the client and restoring the band should stay a
   pure HTML change. Nothing on either live page matches these selectors.
   Full-bleed dark band. Below the hero the fixed scene runs bright magenta,
   and cream on that measures only 3.7:1 - fine for the big display line, not
   for the card copy. The band gives everything below the panel a deterministic
   dark ground instead of leaving contrast at the mercy of scroll position.
   Same full-bleed technique as .press__quotes: widen the inline padding rather
   than capping max-width, so the background reaches both edges while the
   content stays on the 1280px measure. */
.prizes {
  width: 100%; margin: clamp(34px, 5vw, 64px) 0 0;
  padding-block: clamp(26px, 4vw, 44px);
  padding-inline: max(clamp(18px, 4vw, 48px), calc((100% - 1280px) / 2));
  background: rgba(10, 5, 32, 0.66);
  border-top: 1px solid var(--line);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.prizes__head {
  margin: 0 0 clamp(14px, 2vw, 20px);
  font-family: var(--f-disp); font-weight: 700;
  /* "TWO VIP EXPERIENCES TO WIN" must hold ONE line on a phone (client, 14 Aug).
     The old floor of 1.5rem stopped scaling below ~430px, so at 390px the string
     measured 381px inside 354px of usable width and wrapped. Measured against the
     real PalaceDisplay-Bold advances: the largest size that fits is 5.59vw at
     320px rising to 5.77vw at 430px, so 5.3vw keeps ~7% headroom at every phone
     width. The 0.95rem floor only engages below 281px, where 5.3vw would still
     overflow. ⚠️ Re-measure if this wording ever changes - it is tuned to this
     exact 26-character string. */
  font-size: clamp(0.95rem, 5.3vw, 2.2rem);
  line-height: 1; text-transform: uppercase; text-align: center;
  text-wrap: balance;
  color: var(--paper);
}

.prizes__grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 720px) {
  .prizes__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.prize {
  padding: clamp(16px, 2.2vw, 22px);
  border: 1px solid var(--line); border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: rgba(10, 5, 32, 0.55);
}
.prize--sat { border-left-color: var(--ember); }
.prize--sun { border-left-color: var(--rose); }

.prize__when {
  margin: 0 0 10px;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.2em;
  text-transform: uppercase;
}
.prize--sat .prize__when { color: var(--ember); }
.prize--sun .prize__when { color: var(--rose); }

.prize__list { margin: 0; padding: 0; list-style: none; }
.prize__list li {
  position: relative; padding: 0 0 0 20px; margin: 0 0 6px;
  font-size: 0.92rem; line-height: 1.45; color: var(--paper);
}
.prize__list li:last-child { margin-bottom: 0; }
.prize__list li::before {
  content: ""; position: absolute; left: 3px; top: 8px;
  width: 6px; height: 6px; border-radius: 50%;
}
.prize--sat .prize__list li::before { background: var(--ember); }
.prize--sun .prize__list li::before { background: var(--rose); }

/* ============================ PRESS ==================================== */
/* ⚠️ The gap between the sign-up panel and this band used to come from
   `.prizes`' own top margin, because `.press` sets margin:0. With the prizes
   band gone from index.html the gap is re-homed here - but SCOPED to
   `main > .press`, because presale.html moved .press OUTSIDE <main> (below the
   Palace, 1 Sep) and an unscoped margin would open a gap under the Palace
   illustration on that page. Value is the one .prizes carried, unchanged. */
main > .press { margin-top: clamp(34px, 5vw, 64px); }

/* Full-bleed. The Palace illustration used to be baked into a JPEG directly
   above this band, which is why the band's colour was sampled from that
   image's bottom row. The Palace is now its own transparent PNG lower down the
   document, so there is no seam left to match and the band can simply be a
   translucent tint over the scene. */
.press { width: 100%; margin: 0; }

.press__quotes {
  background: rgba(10, 5, 32, 0.55);
  border-top: 1px solid var(--line);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: grid; grid-template-columns: 1fr; gap: clamp(14px, 2.4vw, 24px);
  text-align: center;

  /* The BAND must stay full-bleed so it lines up with the image above it, but
     the QUOTES must stay on the page's 1280px measure. Capping the element's
     own max-width would do the second at the cost of the first - the band
     would end mid-air with page background either side of it, and the seam
     with the image would show. Widening the inline padding instead keeps the
     background edge-to-edge and moves only the content. */
  padding-block: clamp(18px, 3vw, 30px) clamp(20px, 3vw, 34px);
  padding-inline: max(clamp(18px, 4vw, 48px), calc((100% - 1280px) / 2));
}
@media (min-width: 720px) {
  .press__quotes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.press__quote { margin: 0; }

.press__quote blockquote {
  margin: 0;
  font-family: var(--f-disp); font-weight: 700;
  font-size: clamp(1.15rem, 3vw, 1.75rem);
  line-height: 1.05; text-transform: uppercase;
  color: var(--paper);
  text-wrap: balance;
}

.press__quote figcaption {
  margin-top: 8px;
  font-size: 0.74rem; font-weight: 700;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--muted);
}

/* ============================ PALACE =================================== */
/* The scene's ground. Supplied as its own transparent PNG, so it spans the
   viewport at any width instead of being locked into a fixed-ratio crop, and
   the content above it never has to compete with the illustration.
   Sits between the last content section and the footer, full-bleed. */
.palace {
  flex: 0 0 auto;
  width: 100%;
  margin: 0;
  pointer-events: none;
}
.palace img { display: block; width: 100%; height: auto; }

/* ============================ FOOTER =================================== */
/* Full-bleed footer: the band spans the viewport so it reads as a base to the
   page, while the content inside it stays on the same measure as everything
   above. Constraining the <footer> itself instead would leave the band
   floating with gaps either side at wide widths. */
.foot {
  flex: 0 0 auto;
  width: 100%;
  /* Flush to the Palace above it: the illustration's crowd silhouette is the
     page's baseline, and the footer band reads as the ground it stands on. */
  margin: 0;
  padding: clamp(20px, 3vw, 30px) clamp(18px, 4vw, 48px) clamp(24px, 3vw, 34px);
  background: rgba(10, 5, 32, 0.62);
  border-top: 1px solid var(--line);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
/* Inner content keeps the page measure. */
.foot > .terms,
.foot > .foot__bar { max-width: 1280px; margin-left: auto; margin-right: auto; }

.terms {
  padding: clamp(14px, 2vw, 18px);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: rgba(10, 5, 32, 0.45);
}
.terms p {
  margin: 0 0 8px; font-size: 0.68rem; line-height: 1.5;
  color: rgba(175, 166, 196, 0.86);
}
.terms p:last-child { margin-bottom: 0; }
.terms sup { color: var(--spark); }

.foot__bar {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 10px 18px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
}
.foot__left { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }

.foot__legal { margin: 0; font-size: 0.74rem; color: var(--muted); letter-spacing: 0.02em; }
.foot__legal a { color: var(--muted); text-decoration: none; }
.foot__legal a:hover { color: var(--spark); }

/* Standard Furiosa credit pill - same shape across the fleet. */
.site-built { margin: 0; }
.site-built a {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px;
  font-size: 0.69rem; letter-spacing: 0.04em;
  color: var(--muted); text-decoration: none;
  border: 1px solid var(--line); border-radius: 999px;
  transition: border-color 0.16s ease, color 0.16s ease;
}
.site-built a:hover { border-color: var(--spark); color: var(--paper); }
.site-built img { display: block; opacity: 0.9; }

@media (max-width: 600px) {
  .foot__bar  { justify-content: center; text-align: center; }
  .foot__left { justify-content: center; }
}

/* ============================ DOC PAGES ================================ */
.is-doc .sheet { display: block; }

.doc {
  max-width: 800px; margin: 0 auto;
  padding: clamp(24px, 5vw, 56px) clamp(18px, 5vw, 40px) clamp(40px, 6vw, 72px);
}

.doc__back {
  display: inline-block; margin-bottom: clamp(18px, 3vw, 28px);
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--spark); text-decoration: none;
}
.doc__back:hover { color: var(--ember); }

.doc__head {
  padding-bottom: clamp(16px, 2.4vw, 22px);
  margin-bottom: clamp(20px, 3vw, 30px);
  border-bottom: 1px solid var(--line);
}
.doc__eyebrow {
  margin: 0 0 4px; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.26em; text-transform: uppercase; color: var(--spark);
}
.doc__title {
  margin: 0;
  font-family: var(--f-disp); font-weight: 700;
  font-size: clamp(2.4rem, 7vw, 3.8rem);
  line-height: 0.88; text-transform: uppercase; color: var(--paper);
}
.doc__meta { margin: 10px 0 0; font-size: 0.82rem; color: var(--muted); }

.doc__body { font-size: 0.95rem; line-height: 1.68; color: rgba(255, 237, 215, 0.9); }
.doc__body h2 {
  margin: clamp(26px, 3.6vw, 38px) 0 10px;
  font-family: var(--f-disp); font-weight: 700;
  font-size: clamp(1.3rem, 3.2vw, 1.65rem);
  line-height: 1.05; text-transform: uppercase; color: var(--spark);
}
.doc__body p { margin: 0 0 12px; }
.doc__body ul, .doc__body ol { margin: 0 0 14px; padding-left: 22px; }
.doc__body li { margin: 0 0 7px; }
.doc__body strong { color: var(--paper); font-weight: 700; }
.doc__body a { color: var(--spark); text-underline-offset: 2px; }
.doc__body a:hover { color: var(--ember); }

.doc__facts { list-style: none; padding-left: 0; }
.doc__facts li {
  padding: 8px 12px; margin: 0 0 6px;
  border-left: 2px solid var(--ember);
  background: rgba(255, 237, 215, 0.04);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.doc__foot {
  margin: clamp(28px, 4vw, 44px) 0 0; padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 0.76rem; color: var(--muted);
}

/* ==================== intl-tel-input overrides ========================= */
.iti { width: 100%; display: block; }
.iti__country-container { z-index: 4; }
.iti__selected-country { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.iti__selected-country-primary { padding: 0 6px 0 12px; }
.iti__dropdown-content {
  background: var(--ink-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--paper);
}
.iti__search-input {
  /* ⚠️ NEVER collapse these into a padding shorthand with a small horizontal
     value. The vendor reserves padding-left:30px AND padding-right:28px/30px to
     clear its absolutely positioned .iti__search-icon and .iti__search-clear -
     and in this build a later vendor rule moves the icon from left:8px to
     right:8px, so BOTH sides have to stay reserved. A `padding: 11px 12px`
     shorthand wiped both and the magnifying glass sat on top of the word
     "Search". Set the vertical padding only, and leave the horizontal alone.
     (Client-reported on djagonline 2026-08-03, and again here 2026-08-14.) */
  padding-top: 11px; padding-bottom: 11px;
  padding-left: 32px; padding-right: 32px;
  color: var(--paper);
  background: rgba(255, 237, 215, 0.06);
  border: 0; border-bottom: 1px solid var(--line);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.iti__search-input::placeholder { color: var(--muted); }
.iti__country { color: var(--paper); }
.iti__country.iti__highlight, .iti__country:hover { background: rgba(163, 221, 221, 0.2); }
.iti__dial-code { color: var(--muted); }
.iti__country-list { scrollbar-width: thin; }

/* ==================== Landscape phone ================================== */
/* A phone held sideways is short, not narrow (844x390, 932x430, 667x375): it
   satisfies desktop-ish width and aspect conditions but has almost no height.
   The centred single column is wrong here - the lockup alone would eat the
   viewport and push the form off the bottom - so this is the one place the
   layout goes side by side, per the engine's landscape rule. Placed last so it
   wins at equal specificity. */
@media (max-height: 560px) and (min-aspect-ratio: 7/10) and (min-width: 640px) {
  .stage {
    display: grid; align-items: start;
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    gap: clamp(18px, 3vw, 30px);
    max-width: 1000px; padding-top: 14px;
  }
  /* align-items MUST be start, not center. The panel column is taller than a
     390px-high viewport, so the grid row is too - and centring the brand inside
     that row pushed the lockup down until it was clipped off the bottom of the
     screen with nothing visible above the fold. Caught on the 844x390 shot. */
  .brand__lockup { width: min(100%, 230px); }
  .brand__bill { margin-top: 8px; font-size: 0.62rem; letter-spacing: 0.12em; }
  .brand__bill li:not(:last-child)::after { margin: 0 8px; }

  .panel { width: 100%; margin-top: 0; }
  .panel__inner { padding: 15px 16px; }
  .panel__title { font-size: 1.7rem; }
  .panel__lede { margin-bottom: 10px; font-size: 0.82rem; }
  .form__row { margin-bottom: 9px; }
  .form__input { padding: 10px 12px; }

  .nightpick__box { padding: 7px 7px 9px; }
  .nightpick__dots { margin-top: 7px; gap: 5px; }
  .nightpick__dots i { width: 5px; height: 5px; }
  /* Was display:none. It CANNOT be hidden now that the artwork carries * and **
     (client artwork, 14 Aug) - hiding it leaves two dangling asterisks and an
     unqualified "from £10.50" on the viewport where most people read the price. */
  .nightpick__fine { font-size: 0.66rem; }
}

/* ==================== Reduced motion =================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .star { animation: none; opacity: 0.4; }
}
