/* ============================================================
   SUB FOCUS presents CIRCULAR SOUND
   O2 Arena London 26.03.27 / Co-op Live Manchester 27.03.27
   Sign-up page.

   Visual language taken from the supplied artwork: a portrait
   drawn entirely in concentric contour lines, and a logotype
   built from nested neon tubes, over a near-black field with a
   petrol-blue bloom. Everything here is additive light on black.

   Two consequences run through this file:

   1. The lockup ships FLATTENED ONTO BLACK (no alpha) and is
      keyed in with mix-blend-mode: screen. Neon is additive, so
      screen is not a compression trick standing in for
      transparency - it is how the artwork actually behaves. It
      also sidesteps the aliased-alpha halo that drop-shadowed
      cutouts get, and cuts the 1080px variant from 702KB to
      192KB. Fallback if a browser ignores blend modes is a black
      rectangle on a near-black page: near-invisible.

   2. Signature: the show picker. Two shows, so no dropdown -
      each show is a sonar dial whose concentric rings are the
      contour lines of the portrait, pinging outward once on
      selection. That is the only loud element on the page;
      everything around it is hairlines and quiet type.
   ============================================================ */

/* League Spartan (OFL), self-hosted. No Google Fonts, no CDN, nothing
   third-party at render time - that is a GDPR constraint, not a
   performance one, so it does not get traded away.

   This replaces Jost, and the reason is worth writing down, because
   the old declaration was not doing what it said. The four
   "Jost-*.woff2" files that shipped here were the CYRILLIC slice of
   the Google Fonts subset - 76 Cyrillic glyphs, one Latin letter,
   no digits - and the @font-face carried no unicode-range. So the
   browser dutifully downloaded 17KB of Cyrillic, found no Latin in
   it, and set the entire page in the fallback: Helvetica Neue on
   Apple, Arial on Windows, Roboto on Android. Every word anyone has
   ever seen on this page was a grotesque. That, and not the choice
   of geometric, is why the type never looked like the poster.

   The artwork's supporting type - "SUB FOCUS PRESENTS", "26.03.27
   LONDON THE O2 ARENA" - is Futura or a Futura clone. Measured off
   lockup-1240.jpg at matched cap height it has the whole Futura
   signature: small high bowls on P and R, a straight diagonal R
   leg, a circular O (width 1.02x the cap), sharp apexes on A M N, a
   flat-based 2, and a narrow E. League Spartan is the OFL revival
   of ATF Spartan, which was ATF's own 1939 Futura copy - so it is
   the closest free face to the artwork by descent, not just by eye,
   and it matched on every one of those glyphs. Jost lost on three:
   its caps are wider, its M is unsplayed, and its C closes further.
   League Spartan's x-height is 0.62 of the cap where Futura is
   ~0.63, which is what makes the mixed-case lines read Futura and
   not merely geometric; Poppins (0.79) and Urbanist (0.71) were
   never in it.

   Three things were changed when building these subsets, each
   deliberately:

   1. Vertical metrics normalised to 0.90em / -0.22em. League
      Spartan ships an unusually tight 0.70em / -0.22em box. Every
      line-height in this file is an explicit ratio so the line
      boxes were safe, but the baseline inside them was not - the
      stock metrics would have lifted text ~1.7px in each of the
      flex rows the panel is built from. 0.90/-0.22 is where Arial,
      Helvetica Neue and Roboto all sit, so the webfont and the
      fallback now place text on the same baseline and the swap
      costs nothing when it is still loading.

   2. Digits made tabular in the outlines. League Spartan has
      proportional figures and no tnum feature, so
      font-variant-numeric below would have been a no-op and the OTP
      field would have reflowed on every keystroke. The advances are
      equalised in the font instead.

   3. Kerning kept explicitly through the subsetter. The display
      line is set in caps, where it does the most work.

   The CIRCULAR SOUND logotype is never re-typeset - it stays as
   supplied artwork. */
@font-face {
  font-family: "League Spartan";
  src: url("/assets/fonts/LeagueSpartan-400.woff2?v=20260806-14") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "League Spartan";
  src: url("/assets/fonts/LeagueSpartan-500.woff2?v=20260806-14") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "League Spartan";
  src: url("/assets/fonts/LeagueSpartan-600.woff2?v=20260806-14") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "League Spartan";
  src: url("/assets/fonts/LeagueSpartan-700.woff2?v=20260806-14") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  /* Every value below was sampled from the supplied artwork, not picked. */
  --neon:      #6DA9CE;                      /* the contour lines */
  --neon-hi:   #A3D2E7;                      /* the lit core of the tubes */
  --steel:     #36668D;                      /* the falloff */
  --ice:       #DCEEF8;                      /* headline ink */
  --muted:     rgba(220, 238, 248, 0.62);
  --void:      #04070A;                      /* the poster's black */
  --err:       #FF8E7A;

  --hair:      rgba(109, 169, 206, 0.22);
  --hair-lit:  rgba(109, 169, 206, 0.55);
  --panel-bg:  rgba(6, 10, 14, 0.80);        /* translucent: the bloom shows through */
  --field-bg:  rgba(220, 238, 248, 0.055);

  /* Flattened opaque equivalent of --field-bg over --panel-bg over the page.
     A box-shadow cannot be semi-transparent, so the Chrome autofill override
     has to paint this, NOT the field's own rgba. Sanity check: an autofilled
     field must be indistinguishable from an empty one beside it. */
  --field-flat: #13181D;

  /* Neon tube ends are round, so the radii are too. Restrained: two values. */
  --r:      14px;
  --r-sm:   10px;

  --font:      "League Spartan", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  color: var(--ice);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* html carries the base colour; body stays transparent so it can never
   paint over the z-index:-1 background layer (gotcha 16). */
html { background: var(--void); }
body {
  position: relative;
  background: transparent;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

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

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

/* ============================================================
   BACKGROUND - the client's own bloom plates, viewport-shaped
   ============================================================ */
.bg {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 118svh;
  z-index: -1;
  background: url("/assets/images/bg-mobile.webp?v=20260806-14") center top / cover no-repeat,
              var(--void);
}
/* The plates are already dark, so this is a floor rather than a scrim: it
   stops the bloom washing out behind the panel and settles the page into
   the void at the bottom. */
.bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(4, 7, 10, 0.00) 0%,
    rgba(4, 7, 10, 0.18) 46%,
    rgba(4, 7, 10, 0.62) 78%,
    var(--void) 100%);
}
@media (max-aspect-ratio: 7/10) and (max-width: 500px) {
  .bg { background-image: url("/assets/images/bg-mobile-sm.webp?v=20260806-14"); }
}

/* Landscape: the wide plate. Keyed on aspect ratio ALONE, deliberately -
   gating on min-width too would strand landscape phones (667x375) on the
   tall portrait art. */
@media (min-aspect-ratio: 7/10) {
  .bg {
    position: fixed;
    inset: 0;
    height: auto;
    background: url("/assets/images/bg-desktop.webp?v=20260806-14") center center / cover no-repeat,
                var(--void);
  }
  /* The plate's bloom sits right of centre, which is where the panel lands.
     Leaning the floor right keeps the form legible over the brightest part
     without dimming the lockup on the left. */
  .bg::after {
    background:
      linear-gradient(90deg, rgba(4, 7, 10, 0.00) 0%, rgba(4, 7, 10, 0.10) 44%, rgba(4, 7, 10, 0.48) 100%),
      linear-gradient(180deg, rgba(4, 7, 10, 0.18) 0%, rgba(4, 7, 10, 0) 34%, rgba(4, 7, 10, 0.42) 100%);
  }
}
@media (min-aspect-ratio: 7/10) and (max-width: 1500px) {
  .bg { background-image: url("/assets/images/bg-desktop-sm.webp?v=20260806-14"); }
}

/* ============================================================
   SHEET / STAGE
   ============================================================ */
.sheet {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  min-height: 100svh;
  /* The portrait breaks out to 100vw on phones (see .brand). `clip` rather than
     `hidden` so this never turns into a scroll container and steals the sticky/
     scroll behaviour of anything inside it. */
  overflow-x: clip;
}
.stage {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  width: 100%;
}
.stage__inner {
  flex: 1 0 auto;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(26px, 4vw, 56px) clamp(18px, 5vw, 60px);
  display: flex;
  flex-direction: column;
  gap: clamp(22px, 4vw, 40px);
}

/* ============================================================
   ARTWORK - the poster's two elements, placed as the poster
   places them: the portrait bled off one edge, the logotype
   held with the copy.

   Both are flattened on black; screen drops the black and
   leaves only the light, so the neon glows over the bloom
   instead of sitting on it in a rectangle. See the file header.
   ============================================================ */

/* PORTRAIT / PHONES: one image does everything. The <picture> above serves the
   client's full portrait lockup here (face, logotype and both dates together),
   run edge to edge and uncropped, so the whole poster reads at the top of the
   page and the separate logo-and-dates lockup is redundant (hidden below).

   The full-bleed breakout uses margin alone, NOT the usual
   `margin-left:50%; transform:translateX(-50%)`. This element also carries the
   page-load `rise` animation, whose `to { transform: none }` overwrites any
   transform declared here, and the band silently lands half a viewport to the
   right. Margin has no such collision. */
.brand {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  overflow: hidden;
}
.brand__face {
  display: block;
  width: 100%;
  height: auto;
  mix-blend-mode: screen;
}
/* No bottom feather in portrait: the show dates sit at the very bottom of this
   artwork and a fade would eat them. The desktop block adds a LEFT-edge feather
   instead, where there is nothing but halftone field to dissolve. */

/* The client's logo-dates asset: logotype AND both show dates in one, framed by
   the poster's vertical light streaks. Landscape and desktop only - in portrait
   the hero above already contains both. The dates being stated in the artwork
   and offered again in the picker is the poster's own logic: the artwork
   announces, the form asks which one you want. */
.signup__lockup { display: none; }

/* ============================================================
   COMING-SOON HOLDING PAGE (index.html)
   Same shell as the sign-up build, with the panel replaced by
   one line, so the two pages are visually continuous when the
   sign-up goes live in place of this one.
   ============================================================ */
.soon { max-width: 460px; margin: clamp(10px, 2vw, 18px) auto 0; text-align: center; }
.soon__eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--neon);
}
.soon__line { margin: 0; color: var(--muted); font-size: 15px; }
@media (min-width: 768px) and (min-aspect-ratio: 7/10) {
  .soon { margin-left: 0; margin-right: auto; text-align: left; }
}

/* Promoter mark under the panel */
.signup__marks {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 18px auto 0;
}
.signup__louder {
  display: block;
  width: clamp(58px, 6vw, 76px);
  height: auto;
  opacity: 0.78;
}

/* ============================================================
   PANEL - glass over the bloom, edged like a lit neon tube.
   The edge is an inset box-shadow rather than a positioned frame
   pseudo-element: a frame stacks above the form children and makes
   the country dropdown unclickable (gotcha 5).
   ============================================================ */
.panel {
  position: relative;
  isolation: isolate;
  width: 100%;
  max-width: 520px;
  margin: 0 auto;
  border-radius: var(--r);
  background: var(--panel-bg);
  backdrop-filter: blur(18px) saturate(1.2);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
  box-shadow:
    inset 0 0 0 1px var(--hair),
    0 0 42px rgba(54, 102, 141, 0.14),
    0 24px 60px rgba(0, 0, 0, 0.55);
}
.panel__inner {
  position: relative;
  z-index: 1;
  padding: clamp(24px, 3.6vw, 34px) clamp(20px, 3.2vw, 32px) clamp(26px, 3.6vw, 34px);
}

/* --- state machine: exactly one state visible --- */
.panel__state { display: none; }
.panel[data-form-state="idle"]    .panel__idle    { display: block; }
.panel[data-form-state="otp"]     .panel__otp     { display: block; }
.panel[data-form-state="success"] .panel__success { display: block; }

/* --- panel type --- */
.panel__eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--neon);
}
.panel__eyebrow-hair {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--hair-lit), rgba(109, 169, 206, 0));
}
.panel__eyebrow-hair:first-child {
  background: linear-gradient(270deg, var(--hair-lit), rgba(109, 169, 206, 0));
}
.panel__title {
  margin: 0 0 12px;
  font-size: clamp(30px, 4.2vw, 42px);
  /* Caps, and a notch lighter than the old 600. Futura at display
     size is an all-caps face with a small optical weight - the
     artwork's own caps run a stem about 0.11 of the cap height,
     which is lighter than anything on this page was set at. On a
     near-black field light type blooms and gains apparent weight, so
     500 lands where the poster looks. Uppercase is done in CSS, not
     in the markup, so the sentence-case source still reads correctly
     to a screen reader and the change is one line to revert. */
  font-weight: 500;
  line-height: 1.03;
  /* Caps need the tracking that lowercase does not. The artwork's
     own caps lines are set tight (they are fitted to the poster
     width, about -0.03em); 0.03em is the smallest positive value
     that keeps the counters open at 30px without drifting into the
     wide-tracked look the eyebrows already own. */
  letter-spacing: 0.03em;
  text-transform: uppercase;
  text-wrap: balance;
  /* Lit, not filled. Neon is additive, the same reasoning as the
     screen-blended lockup. Three stacked shadows, not one: a 6px
     near-white core that keeps the letterform crisp, an 18px neon
     halo, and a 54px petrol falloff that ties the type into the
     background bloom. A single large blur reads as fog and eats the
     counters; the stack reads as a tube. Deliberately kept off the
     lede, the fields and the buttons - those have to stay legible
     rather than glow, and glow on body copy is what makes a page
     look like a 2004 winamp skin. */
  text-shadow:
    0 0 6px  rgba(220, 238, 248, 0.55),
    0 0 18px rgba(163, 210, 231, 0.45),
    0 0 54px rgba(109, 169, 206, 0.34);
}
.panel__title em {
  font-style: normal;          /* the neon IS the emphasis */
  color: var(--neon-hi);
  /* The lit line of the two, so it runs a hotter core and a wider
     falloff - the same relationship the lockup has between its inner
     tube and its outer rings. */
  text-shadow:
    0 0 5px  rgba(255, 255, 255, 0.42),
    0 0 16px rgba(163, 210, 231, 0.72),
    0 0 40px rgba(109, 169, 206, 0.50),
    0 0 76px rgba(54, 102, 141, 0.34);
}
/* Class-based only. A state-scoped element rule (.panel__success p {...})
   would out-specify .panel__eyebrow and silently restyle it (gotcha 7). */
/* Caps, to match the poster. Caps carry no ascender/descender profile
   for the eye to read word-shapes off, so a paragraph set in them needs
   paying for in three places or it turns into a wall: a size drop (caps
   at 15px read as big as lowercase at ~17), tracking to stop the
   verticals fusing, and extra leading because every line now has the
   same flat top and bottom edge. All three are here. */
.panel__lede {
  margin: 0 0 20px;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.6;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.panel__lede strong { color: var(--ice); font-weight: 600; }
.panel__success-note {
  margin: 0 0 12px;
  color: var(--neon-hi);
  font-size: 15px;
}

/* ============================================================
   SIGNATURE - the show picker.

   Two shows, so no dropdown. Each show is a sonar dial: the
   concentric rings are lifted straight off the contour-line
   portrait in the artwork. Unselected they sit at hairline
   weight; on selection they light and ping outward ONCE,
   staggered ring by ring.

   Built on real radio inputs, so keyboard, screen readers and
   the required-field validation all work with no JS.
   ============================================================ */
.shows {
  margin: 0 0 18px;
  padding: 0;
  border: 0;
  min-width: 0;               /* fieldset default blocks grid shrink in Firefox */
}
/* The header carries the same hairline treatment as the panel eyebrow, so the
   picker reads as part of the panel's system rather than as a stray form label.
   The "one or both" note is a bordered chip, not a sentence underneath - the
   picker is multi-select and the cards do not look like checkboxes, so that
   has to be said, but it should not read as body copy. */
.shows__legend {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0 0 10px;
}
.shows__legend-row { display: flex; align-items: center; gap: 10px; }
.shows__legend-text {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--neon);
}
.shows__legend-hair {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--hair-lit), rgba(109, 169, 206, 0));
}
.shows__legend-note {
  flex: none;
  padding: 3px 9px;
  border: 1px solid var(--hair);
  border-radius: 999px;
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.shows__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.show { position: relative; display: block; cursor: pointer; }
/* The input stays in the layout and focusable - never display:none, which
   would drop it out of the tab order and kill :focus-visible. */
.show input {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  -webkit-appearance: none;
          appearance: none;
}
.show__face {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  height: 100%;
  padding: 14px 8px 12px;
  border-radius: var(--r-sm);
  background: rgba(220, 238, 248, 0.03);
  box-shadow: inset 0 0 0 1px var(--hair);
  text-align: center;
  transition: box-shadow 0.18s ease, background-color 0.18s ease;
}
.show__rings {
  display: block;
  width: 30px; height: 30px;
  margin-bottom: 5px;
  overflow: visible;
  fill: none;
  stroke: var(--steel);
  stroke-width: 2.4;
  transition: stroke 0.18s ease;
}
.show__day {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.show__date {
  font-size: clamp(15px, 4.2vw, 18px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--ice);
}
.show__city {
  margin-top: 3px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--neon);
  transition: color 0.18s ease;
}
/* Caps. The venue sits directly under .show__city, which is already
   caps at 0.14em, so this takes LESS tracking than the city, not more -
   otherwise the two lines read as one block of equal weight and the
   card loses its hierarchy. */
.show__venue {
  font-size: 10.5px;
  line-height: 1.35;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  text-wrap: balance;
}

.show:hover .show__face { background: rgba(220, 238, 248, 0.055); }
.show input:focus-visible + .show__face {
  outline: 2px solid var(--neon);
  outline-offset: 2px;
}

/* Selected: the tube lights. */
.show input:checked + .show__face {
  background: rgba(109, 169, 206, 0.10);
  box-shadow:
    inset 0 0 0 1px var(--hair-lit),
    0 0 24px rgba(109, 169, 206, 0.20);
}
.show input:checked + .show__face .show__rings { stroke: var(--neon-hi); }
.show input:checked + .show__face .show__city  { color: var(--neon-hi); }

/* Selected tick. The second confirmation that this is a multi-select: two lit
   cards with two ticks reads unambiguously as "both", where colour alone could
   be mistaken for a hover state. Round, to stay in the circular language. */
.show__face::after {
  content: "";
  position: absolute;
  top: 8px; right: 8px;
  width: 15px; height: 15px;
  border-radius: 50%;
  background: var(--neon-hi);
  /* the tick itself, drawn as a mask so it inherits nothing and needs no glyph */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 15'%3E%3Cpath d='M4 7.6l2.3 2.3L11 5.2' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 15px 15px no-repeat,
          linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 15'%3E%3Cpath d='M4 7.6l2.3 2.3L11 5.2' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 15px 15px no-repeat,
               linear-gradient(#000, #000);
          mask-composite: exclude;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.16s ease, transform 0.16s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
.show input:checked + .show__face::after { opacity: 1; transform: scale(1); }

/* The ping. Checking a radio enters the state, so the animation runs then
   and only then - no JS needed to retrigger it. */
@keyframes sonar {
  0%   { transform: scale(0.24); opacity: 0; }
  32%  { opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}
.show input:checked + .show__face .show__rings circle {
  transform-origin: 32px 32px;
  animation: sonar 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.show input:checked + .show__face .show__rings circle:nth-child(2) { animation-delay: 0.06s; }
.show input:checked + .show__face .show__rings circle:nth-child(3) { animation-delay: 0.12s; }
.show input:checked + .show__face .show__rings circle:nth-child(4) { animation-delay: 0.18s; }

/* ============================================================
   FORM
   ============================================================ */
.form { margin: 0; }
.form__row { margin: 0 0 15px; }
/* sr-only, never display:none - the placeholder carries the meaning
   visually but screen readers still need the label. */
.form__label {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.form__input {
  display: block;
  width: 100%;
  padding: 14px 15px;
  background: var(--field-bg);
  border: 1px solid var(--hair);
  border-radius: var(--r-sm);
  color: var(--ice);
  font-family: var(--font);
  font-size: 16px;            /* >=16px stops iOS zoom-on-focus */
  line-height: 1.3;
  transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
}
.form__input::placeholder { color: rgba(220, 238, 248, 0.34); }
.form__input:focus {
  outline: none;
  border-color: var(--hair-lit);
  background: rgba(220, 238, 248, 0.075);
  box-shadow: 0 0 0 3px rgba(109, 169, 206, 0.12);
}

/* Chrome paints autofilled inputs pale lavender, which blows a hole in a dark
   form. :-webkit-autofill ignores background and background-color; a huge inset
   box-shadow is the only reliable override, and it must be the FLATTENED opaque
   colour (--field-flat), not the field's own rgba. The 9999s transition stops
   Chrome flashing the lavender in before the shadow paints. (gotcha 30) */
.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(--ice) !important;
  caret-color: var(--ice);
  transition: background-color 9999s ease-in-out 0s;
}

.form__input--otp {
  letter-spacing: 0.42em;
  font-size: 22px;
  font-weight: 500;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.form__row--check { margin-top: 16px; }
/* Caps, on request, to match the poster. Same three compensations as
   the lede - size down, tracked, extra leading - and they matter more
   here than anywhere else on the page: this is the consent line, it is
   the one piece of copy that has to survive being actually read, and
   all-caps consent text that is hard to read is exactly what a
   regulator calls a dark pattern. The underlined privacy link inside
   it keeps its own colour so it still reads as a link once the
   word-shape cue is gone. */
.form__check {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  cursor: pointer;
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.6;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.form__check input {
  flex: 0 0 auto;
  width: 17px; height: 17px;
  margin: 2px 0 0;
  accent-color: var(--neon);
  cursor: pointer;
}
.form__check a { color: var(--neon); text-decoration: underline; }
.form__check a:hover { color: var(--neon-hi); }

.form__error {
  margin: 6px 0 0;
  color: var(--err);
  font-size: 13px;
}
.form__error--global { margin-top: 12px; }

.form__resend {
  margin: 14px 0 0;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}
.form__resend-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--neon);
  font-family: var(--font);
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
}
.form__resend-btn:disabled { opacity: 0.5; cursor: default; }

/* --- buttons --- */
.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin: 18px 0 0;
  padding: 15px 18px;
  border: 0;
  border-radius: var(--r-sm);
  font-family: var(--font);
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  transition: background-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease;
}
.btn--primary {
  background: var(--neon-hi);
  color: #04141D;
  box-shadow: 0 0 26px rgba(163, 210, 231, 0.26);
  /* shrink type before it can wrap - never let a CTA break to two lines
     (gotcha 26) */
  white-space: nowrap;
  font-size: clamp(0.85rem, 3.4vw, 1.02rem);
  letter-spacing: clamp(0.06em, 0.5vw, 0.14em);
  text-transform: uppercase;
}
.btn--primary:hover:not(:disabled) {
  background: #C8E6F5;
  box-shadow: 0 0 34px rgba(163, 210, 231, 0.4);
}
.btn--primary:disabled { opacity: 0.6; cursor: wait; box-shadow: none; }
.btn--whatsapp {
  background: rgba(109, 169, 206, 0.1);
  box-shadow: inset 0 0 0 1px var(--hair-lit);
  color: var(--neon-hi);
  white-space: nowrap;
  font-size: clamp(0.8rem, 3.2vw, 0.95rem);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.btn--whatsapp:hover { background: rgba(109, 169, 206, 0.18); }
.btn__icon { width: 20px; height: 20px; flex: 0 0 auto; }

/* ============================================================
   intl-tel-input - neon dark theme, searchable dropdown
   ============================================================ */
.iti { width: 100%; }
.iti input.form__input { padding-left: calc(52px + 0.7rem); } /* repadPhone() re-measures */

.iti__selected-country { background: transparent; color: var(--ice); border-radius: var(--r-sm) 0 0 var(--r-sm); }
.iti__selected-country:hover { background: rgba(220, 238, 248, 0.05); }
.iti__selected-dial-code { color: var(--muted); font-size: 15px; }
.iti__arrow { border-top-color: var(--neon); }
.iti__arrow--up { border-bottom-color: var(--neon); }

.iti__dropdown-content {
  background: #080D12;
  border: 1px solid var(--hair-lit);
  border-radius: var(--r-sm);
  overflow: hidden;
  color: var(--ice);
  box-shadow: 0 24px 54px rgba(0, 0, 0, 0.66), 0 0 30px rgba(54, 102, 141, 0.14);
}
/* The horizontal padding is RESERVED SPACE, not decoration: the vendor puts
   .iti__search-icon at left:8px and .iti__search-clear at right:4px, both
   absolutely positioned. A padding SHORTHAND wipes that reservation and the
   magnifier lands on top of the word "Search" (gotcha 33). Set the vertical
   padding, keep the horizontal. */
.iti__search-input {
  padding: 11px 30px 11px 32px;
  background: rgba(220, 238, 248, 0.05);
  border: none;
  border-bottom: 1px solid var(--hair);
  color: var(--ice);
  font-family: var(--font);
  font-size: 15px;
}
.iti__search-input::placeholder { color: rgba(220, 238, 248, 0.36); }
.iti__search-input:focus { outline: none; border-bottom-color: var(--neon); background: rgba(220, 238, 248, 0.075); }
.iti__country-list { background: transparent; }
.iti__country { padding: 9px 13px; font-size: 14.5px; }
.iti__country.iti__highlight { background: rgba(109, 169, 206, 0.18); }
.iti__country:hover { background: rgba(109, 169, 206, 0.1); }
.iti__dial-code { color: var(--muted); }
.iti--inline-dropdown .iti__dropdown-content { z-index: 50; }
/* The hidden state lives in the vendor stylesheet; until that loads the whole
   country list flashes open on first paint (gotcha 19). */
.iti__dropdown-content.iti__hide { display: none !important; }

/* ============================================================
   FOOTER - basslayerz pattern, full width
   ============================================================ */
.foot {
  flex-shrink: 0;
  width: 100%;
  margin: 0;
  padding: 16px clamp(16px, 4vw, 40px) 20px;
  border-top: 1px solid var(--hair);
  background: rgba(4, 7, 10, 0.66);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.foot__left { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.foot__legal { margin: 0; font-size: 12px; color: var(--muted); letter-spacing: 0.02em; }
.foot__legal a { color: var(--muted); text-decoration: none; }
.foot__legal a:hover { color: var(--neon); }
.site-built { margin: 0; }
.site-built a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
  text-decoration: none;
  padding: 5px 11px;
  border: 1px solid var(--hair);
  border-radius: 999px;
}
.site-built a:hover { border-color: var(--neon); color: var(--ice); }
.site-built img { display: block; opacity: 0.9; }
@media (max-width: 640px) {
  .foot { justify-content: center; text-align: center; }
  .foot__left { justify-content: center; }
}

/* ============================================================
   LAYOUT - two columns in landscape, stacked in portrait.
   Aspect-ratio thresholds, not width alone: width-only breakpoints
   leave gaps at squarish window shapes where neither treatment
   applies (gotcha 20).
   ============================================================ */
@media (min-width: 768px) and (min-aspect-ratio: 7/10) {
  /* BOTH columns are fractional, and the portrait is sized by ITS OWN COLUMN
     rather than by the viewport. That coupling is the entire point of this
     block. The previous version sized the artwork in svh while the form column
     was a fixed px width, so the two scaled independently: at some window
     shapes the face crossed into the panel, at others it shrank and left a
     dead reserved gap beside it. Resizing the browser walked through both.

     Consequence for future tuning: change the COLUMN RATIO below, never give
     .brand__face its own width/height. The moment the image gets independent
     dimensions the drift comes back. */
  .stage__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    align-items: center;
    gap: clamp(20px, 3vw, 44px);
    padding-right: 0;          /* the portrait runs to the edge, so no right gutter */
    /* Tighter than the base stage padding: every pixel here is one the sign-up
       button needs to stay above the fold on an ~800px viewport. */
    padding-top: clamp(18px, 2vw, 28px);
    padding-bottom: clamp(18px, 2vw, 28px);
  }

  /* Copy left, portrait right - the poster's own arrangement. DOM order stays
     artwork-then-form (which keeps the portrait stack in poster-then-form
     order), so the swap is explicit grid placement rather than reordered
     markup. Tab order is unaffected: the artwork column holds nothing
     focusable.

     .signup is the constrained box here, NOT .panel. Everything in the column -
     lockup, panel, promoter mark - then shares one axis and rides in from the
     left edge together, which is where the poster holds its copy. Constraining
     .panel instead lets the lockup centre on the full column and the two fall
     out of line. */
  .signup {
    grid-column: 1; grid-row: 1;
    width: 100%;
    max-width: 470px;
    margin-left: auto;         /* carried in off the left margin */
    margin-right: 0;
  }
  .panel { max-width: none; margin: 0; }
  .signup__lockup { display: block; width: min(100%, 330px); height: auto;
                    margin: 0 auto 6px; mix-blend-mode: screen; }
  .signup__marks { justify-content: center; }

  .brand {
    grid-column: 2; grid-row: 1;
    /* undo the portrait breakout - here the grid column does the positioning */
    width: auto;
    margin-left: 0;
    /* center, NOT stretch. Stretching makes .brand the full stage height while
       object-fit:contain letterboxes the artwork inside it, so the edge feather
       below lands in the empty letterbox and the image's real top and bottom
       edges stay hard. Letting the box wrap the image puts the feather exactly
       on those edges, and the left-edge gradient gets the right height for
       free. */
    align-self: center;
    height: auto;
    min-height: 0;
    /* Bleed to the TRUE viewport edge. .stage__inner is capped at 1280px and
       centred, so on anything wider there is (100vw - 1280px)/2 of dead space
       to its right; this pulls the column across it. min(0px, ...) stops it
       becoming a POSITIVE margin below 1280px, where padding-right:0 has
       already put the column on the edge. */
    margin-right: min(0px, calc((1280px - 100vw) / 2));
    /* Bleed top and bottom too, by cancelling the stage's vertical padding. */
    margin-top: calc(-1 * clamp(18px, 2vw, 28px));
    margin-bottom: calc(-1 * clamp(18px, 2vw, 28px));
  }

  /* Absolutely positioned, NOT a flex/block child with height:100%.
     .brand computes height:auto (its real height comes from align-self:stretch),
     so a percentage height on a normal-flow child cannot resolve - that is what
     silently dropped the earlier size cap and left the image laying out at its
     `sizes` width. inset:0 against the positioned .brand gives a definite box
     with no percentage to resolve. */
  .brand__face {
    position: static;
    display: block;
    /* Width comes from the grid column and height follows the aspect ratio, so
       the artwork is coupled to the layout and to nothing else. max-height is a
       ceiling only, for ultrawide windows (past ~21:9) where a column-width
       image would out-run the viewport; it can only ever make the artwork
       narrower, so it cannot reintroduce an overlap. */
    width: 100%;
    height: auto;
    max-height: 88svh;
    margin-left: auto;
    object-fit: contain;
    object-position: right center;
    /* The supplied artwork carries a faint halftone field across its whole
       frame, not just around the face. Under mix-blend-mode:screen every dim
       pixel lifts the page behind it, so the image's box shows as a soft
       rectangle. That field is real artwork, not compression noise, so the
       edges are feathered rather than crushed. Vertical only: the right edge
       is meant to be hard (it is the viewport bleed) and the left is handled
       by .brand::after below. */
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 90%, transparent 100%);
            mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 90%, transparent 100%);
  }
  /* Feather the LEFT edge so the portrait dissolves toward the copy column
     instead of butting against it on a straight line. */
  .brand::after {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 26%;
    background: linear-gradient(90deg, var(--void) 0%, rgba(4, 7, 10, 0) 100%);
    pointer-events: none;
    z-index: 1;
  }

  /* Desktop compaction. The lockup stands ~225px tall before the panel even
     starts, so at the base type scale the sign-up button lands below the fold
     on an 800px-tall viewport - the single worst place to lose a conversion. */
  .panel__inner { padding: 22px 26px 24px; }
  .panel__title { font-size: clamp(24px, 2.1vw, 29px); margin-bottom: 8px; }
  .panel__lede  { margin-bottom: 13px; font-size: 14px; }
  .panel__eyebrow { margin-bottom: 11px; }
  .shows { margin-bottom: 13px; }
  .show__face { padding: 10px 8px 9px; }
  .show__rings { width: 26px; height: 26px; margin-bottom: 4px; }
  .form__row { margin-bottom: 11px; }
  .form__input { padding: 12px 15px; }
  .form__row--check { margin-top: 12px; }
  .btn { margin-top: 13px; padding: 13px 18px; }
}

/* Portrait tablets: single column, scaled up */
@media (min-width: 768px) and (max-aspect-ratio: 7/10) {
  .panel { max-width: 560px; }
}

/* Portrait phones and tablets */
@media (max-aspect-ratio: 7/10), (max-width: 767px) {
  .stage__inner {
    padding-top: 0;            /* the hero starts at the very top */
    /* Small, because the hero artwork already ends on its own dates line and a
       standard section gap leaves the panel visibly adrift from it. */
    gap: 4px;
  }
  /* The supplied canvas carries a little empty space under the dates; this
     closes it so the panel sits directly beneath them. */
  .signup { margin-top: -10px; }
}

/* Phones: panel runs the full frame width */
@media (max-width: 767px) {
  .stage__inner { padding-left: 14px; padding-right: 14px; }
  .panel { max-width: none; }
}

/* Narrow phones: the two show cards stay side by side (only two of them, and
   stacking would cost a whole row of height), so the type steps down instead. */
@media (max-width: 380px) {
  .show__face { padding: 12px 5px 10px; }
  .show__face::after { top: 6px; right: 6px; width: 13px; height: 13px; }
  .show__rings { width: 26px; height: 26px; }
  .show__venue { font-size: 9.5px; letter-spacing: 0.06em; }
  .show__city { font-size: 11px; letter-spacing: 0.1em; }
}

/* Landscape phones are a first-class layout, not an afterthought: a rotated
   phone satisfies the desktop width/aspect conditions but has almost no
   height (gotcha 27). Placed AFTER the desktop blocks so it wins at equal
   specificity. */
@media (min-width: 640px) and (min-aspect-ratio: 7/10) and (max-height: 520px) {
  .stage__inner {
    display: grid;
    grid-template-columns: minmax(0, 0.82fr) minmax(0, 1fr);
    align-items: start;
    gap: clamp(18px, 3vw, 30px);
    padding: 12px clamp(14px, 3vw, 28px);
    min-height: 0;
  }
  /* Keep the desktop bleed, just against a much shorter stage. align-self must
     stay `stretch` (inherited from the desktop block) - `center` here would
     shrink-wrap the column and turn the bleed back into a floating rectangle,
     which is exactly what the old landscape block did. */
  .brand {
    margin-top: -12px;
    margin-bottom: -12px;
  }
  .signup__lockup { display: block; width: min(100%, 300px); height: auto;
                    margin: 0 0 5px -3%; mix-blend-mode: screen; }
  .panel__inner { padding: 13px 15px 15px; }
  .panel__title { font-size: clamp(17px, 2.4vw, 22px); margin-bottom: 5px; }
  .panel__lede { margin-bottom: 9px; font-size: 12.5px; }
  .panel__eyebrow { margin-bottom: 7px; font-size: 10px; letter-spacing: 0.2em; }
  .shows { margin-bottom: 9px; }
  .shows__legend { margin-bottom: 6px; }
  .shows__legend-text { font-size: 9.5px; letter-spacing: .16em; }
  .shows__legend-note { font-size: 8.5px; padding: 2px 7px; }
  .shows__grid { gap: 8px; }
  .show__face { padding: 8px 6px 7px; gap: 0; }
  .show__face::after { top: 5px; right: 5px; width: 12px; height: 12px; }
  .show__rings { width: 20px; height: 20px; margin-bottom: 3px; stroke-width: 2.8; }
  .show__day { font-size: 9px; letter-spacing: 0.14em; }
  .show__date { font-size: 13px; }
  .show__city { font-size: 10px; margin-top: 1px; letter-spacing: 0.1em; }
  .show__venue { font-size: 9px; line-height: 1.3; letter-spacing: 0.05em; }
  .form__row { margin-bottom: 8px; }
  .form__input { padding: 9px 12px; }
  .form__check { font-size: 10.5px; line-height: 1.5; letter-spacing: 0.04em; }
  .form__row--check { margin-top: 9px; }
  .btn { margin-top: 10px; padding: 10px 14px; }
  .signup__marks { margin-top: 9px; }
  .signup__louder { width: 46px; }
  .foot { padding: 8px 20px 10px; }
}

/* ============================================================
   MOTION - one orchestrated page-load reveal, plus the sonar ping
   ============================================================ */
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.brand  { animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; }
.signup { animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.14s both; }

/* ============================================================
   ACCESSIBILITY
   ============================================================ */
a:focus-visible,
button:focus-visible,
input:focus-visible,
.iti__selected-country:focus-visible {
  outline: 2px solid var(--neon);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}
