/* =============================================================================
   Toca Rouge — brand.css
   Design tokens and reusable base styling for the Polvo identity (v01,
   delivered 2026-08-29).

   STATUS: LIVE. Linked from all seven pages via <link rel="stylesheet"> as of
   the 2026-08-29 brand-integration merge. No page carries an inline <style>
   block or :root token block — this file is the only source of styling.

   Every contrast ratio quoted below was computed against the WCAG 2.1 relative
   luminance formula, not estimated. Where a value is marginal it says so.

   Source of truth for the raw colours: the delivered logo PNGs, sampled
   directly (logo-red-1 = #EA2413, red-2 = #7C1314, red-3 = #4B1200).
   ========================================================================== */

/* -----------------------------------------------------------------------------
   1. TYPEFACES

   No font files were delivered with the Polvo v01 package. There is therefore
   NO valid @font-face rule to write, and none is written here: pointing
   @font-face at a file that does not exist produces an invisible failed request
   and a silent fallback, which is the same class of mistake the site already
   guards against with its commented-out og:image block.

   The wordmark, logo and isotype are delivered as OUTLINED VECTOR PATHS — the
   SVGs contain no <text> element and no font-family reference. So the brand
   name itself never needs a font: it is drawn as artwork (see .tr-logo below)
   and is always correct.

   That leaves running text and headings, which currently use the system stack.
   Confirm the intended display face with Polvo before adding one here — see
   BRAND_INTEGRATION_PLAN.md, "Fonts and licensing".
   -------------------------------------------------------------------------- */

:root {
  /* ---- Brand colours — exact, never altered -------------------------------
     These three are the delivered palette. Do not tint, darken or "fix" them;
     where contrast needs help, the derived tokens below do the work instead. */
  --tr-red:         #EA2413;   /* primary brand red  (red-1) */
  --tr-red-deep:    #7C1314;   /* deep oxblood       (red-2) */
  --tr-red-dark:    #4B1200;   /* darkest red-brown  (red-3) */

  /* ---- Surfaces ---------------------------------------------------------- */
  --tr-ink:         #0b0b0c;   /* page ground. Carried over from the live site */
  --tr-panel:       #17100f;   /* raised surface, warmed toward the oxblood */
  --tr-field:       #1c1312;   /* form input ground */

  /* ---- Foreground -------------------------------------------------------- */
  --tr-fg:          #f5f5f5;   /* 18.05:1 on --tr-ink */
  --tr-muted:       #a89e9e;   /*  7.54:1 on --tr-ink, 6.99:1 on --tr-field */

  /* ---- Derived, for accessibility ----------------------------------------
     --tr-red is 4.46:1 on --tr-ink: it clears the 3:1 large-text floor but
     misses the 4.5:1 floor for small text. --tr-red-text is a lighter tint of
     the same hue at 6.39:1, and is used ONLY for red type below ~18px. The
     brand red itself is never changed to accommodate this. */
  --tr-red-text:    #F2665A;

  /* White on --tr-red is 4.41:1. That clears AA for LARGE text only
     (>=18.66px bold / >=24px regular), so any control filled with the brand red
     must set its label at or above that size. .tr-btn below does exactly that
     and documents it inline. */
  --tr-on-red:      #ffffff;

  /* ---- Lines ------------------------------------------------------------- */
  --tr-border:        #2e2020;  /* 1.15:1 — decorative edge on a filled surface
                                   only, never the sole indicator of a control */
  --tr-border-strong: #7d6a6a;  /* 3.88:1 — for a boundary that IS the only
                                   thing identifying a control */
  --tr-focus:         #ffffff;  /* keyboard ring; clears 3:1 on every surface here */

  /* ---- Radii ------------------------------------------------------------- */
  --tr-radius-pill: 999px;
  --tr-radius-box:  10px;
  --tr-radius-sm:   6px;

  /* ---- Spacing scale -----------------------------------------------------
     4px base. Use these rather than ad-hoc rem values so vertical rhythm holds
     across the five pages. */
  --tr-space-1: 0.25rem;   /*  4px */
  --tr-space-2: 0.5rem;    /*  8px */
  --tr-space-3: 0.75rem;   /* 12px */
  --tr-space-4: 1rem;      /* 16px */
  --tr-space-5: 1.5rem;    /* 24px */
  --tr-space-6: 2rem;      /* 32px */
  --tr-space-7: 3rem;      /* 48px */
  --tr-space-8: 4rem;      /* 64px */

  /* ---- Typography --------------------------------------------------------
     Helvetica Neue, confirmed by Paul 2026-08-29 as the brand face (Bold on
     the poster).

     REFERENCED, NOT EMBEDDED. There is no @font-face and no font file is
     served: the stack names a font already installed on the visitor's device.
     That needs no licence. Self-hosting Helvetica Neue / Neue Haas Grotesk
     would need a separate webfont licence from Monotype, which a desktop
     licence does not include — see BRAND_INTEGRATION_PLAN.md.

     Coverage: Apple devices (macOS, iOS, iPadOS) ship Helvetica Neue and get
     the real face. Windows falls back to Arial and Android to its system
     grotesque; both are metrically close enough that the layout does not
     shift. Arial is listed before the generic so the fallback is a known
     quantity rather than whatever the browser picks. */
  --tr-font-body:    "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif;
  --tr-font-display: var(--tr-font-body);

  /* The poster sets its information lines in Helvetica Neue Bold. 700 is the
     weight that maps to it; do not use a synthesised heavier weight. */
  --tr-weight-bold:  700;

  --tr-text-xs:   0.75rem;   /* 12px — legal, captions */
  --tr-text-sm:   0.875rem;  /* 14px — labels, notes */
  --tr-text-base: 1rem;      /* 16px — body, form inputs (16px avoids iOS zoom) */
  --tr-text-lg:   1.125rem;  /* 18px — lead paragraph */
  --tr-text-xl:   1.25rem;   /* 20px — button labels; also the large-text floor */

  --tr-leading-tight: 1.15;
  --tr-leading-body:  1.55;

  /* ---- Motion ------------------------------------------------------------ */
  --tr-transition: 0.15s ease;
}

/* -----------------------------------------------------------------------------
   2. BASE
   -------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  /* Never suppress this globally — it is the only thing keeping an in-page
     anchor from teleporting. Honoured only when the visitor allows motion. */
  scroll-behavior: auto;
}

body {
  margin: 0;
  padding: 0;
  background-color: var(--tr-ink);
  color: var(--tr-fg);
  font-family: var(--tr-font-body);
  font-size: var(--tr-text-base);
  line-height: var(--tr-leading-body);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

/* -----------------------------------------------------------------------------
   3. BACKGROUNDS

   The delivered flyer background is not an abstract texture: it is heavily
   blurred lettering. It reads as atmosphere at low contrast and as noise at
   high, so it always sits UNDER a scrim, never directly behind body copy.

   .tr-bg paints the scrim and the image together, so no page can accidentally
   use the raw image behind text.

   The scrim opacity is NOT a taste setting. It was solved for: the brand red
   on the brightest patch of the raw texture is only ~1.6:1, and red type needs
   3:1 to clear WCAG AA at large sizes. At 0.34/0.72 the date block measured
   2.90:1 -- a real fail. 0.52/0.82 puts the worst case behind both the mark
   and the date block above 3.4:1. Lightening this again without re-measuring
   will silently break the contrast of every red element on the page.
   -------------------------------------------------------------------------- */

.tr-bg {
  position: relative;
  background-color: var(--tr-ink);
  /* Plain JPEG first. image-set() with type() is Chrome 113 / Safari 17 / FF 113;
     where it is unsupported the entire declaration is invalid, and since the
     scrim gradient lives in the same declaration the page would lose the scrim
     as well as the texture. The fallback keeps both. */
  background-image:
    linear-gradient(to bottom, rgba(11, 11, 12, 0.52), rgba(11, 11, 12, 0.82)),
    url("bg/texture-portrait-1080.jpg");
  background-image:
    linear-gradient(to bottom, rgba(11, 11, 12, 0.52), rgba(11, 11, 12, 0.82)),
    image-set(url("bg/texture-portrait-1080.webp") type("image/webp"),
              url("bg/texture-portrait-1080.jpg")  type("image/jpeg"));
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

@media (min-width: 768px) {
  .tr-bg {
    background-image:
      linear-gradient(to bottom, rgba(11, 11, 12, 0.52), rgba(11, 11, 12, 0.82)),
      url("bg/texture-landscape-1920.jpg");
    background-image:
      linear-gradient(to bottom, rgba(11, 11, 12, 0.52), rgba(11, 11, 12, 0.82)),
      image-set(url("bg/texture-landscape-1920.webp") type("image/webp"),
                url("bg/texture-landscape-1920.jpg")  type("image/jpeg"));
  }
}

/* A flat alternative for pages where the texture is the wrong register —
   the privacy notice, for one. Same ground, no imagery, no extra request. */
.tr-bg-flat { background-color: var(--tr-ink); }

/* Radial red bloom. Pure CSS, no image request, echoes the flyer's glow.

   z-index:-1 is load-bearing. Without it this pseudo-element is a positioned
   descendant and paints ABOVE the page's in-flow text, tinting it: the white
   caption rendered #F3CBC7 instead of #F5F5F5, and the red type was measurably
   washed. At -1 it paints after the parent's background but before in-flow
   content, which is where a background glow belongs. Do not remove, and do not
   give .tr-bloom its own stacking context (a z-index or a transform on it would
   trap this pseudo-element inside and put it back on top of the text). */
.tr-bloom::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(60% 45% at 50% 38%,
              rgba(234, 36, 19, 0.26), rgba(234, 36, 19, 0) 72%);
}

/* Grain. A fine noise tile laid over the scrim. The delivered texture is
   heavily blurred lettering: at low contrast it reads as atmosphere, at high
   contrast as digital compression artefacts. A little grain over the top
   unifies those artefacts and gives the ground a printed rather than a screen
   quality, which is the register the poster works in.

   Uses .tr-bg::after because .tr-bloom::before is taken and must not be
   disturbed. z-index:-1 for the same reason the bloom carries it — without it
   the pseudo-element paints above in-flow text. pointer-events:none so it never
   intercepts a tap. No z-index or transform is added to .tr-bg itself: either
   would create a stacking context that traps .tr-bloom::before back on top of
   the type.

   The noise is an inline feTurbulence as a data URI — no network request, no
   third-party host. Opacity is held low and CAPPED at 0.06: grain over a scrim
   raises the local luminance variance the red type sits on, and .details /
   .tr-poster-block red is AA only at large sizes on the ink ground. Measured
   in-browser 2026-08-30, worst patch behind the index.html mark + date block
   against #EA2413: 3.89:1 with no grain, 3.75:1 at opacity 0.035 (this rule),
   3.60:1 at the 0.06 cap — all still clear the 3:1 large-text floor. If this
   opacity is ever raised, re-measure; do not answer a contrast loss by
   lightening the scrim. */
.tr-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='trg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23trg)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
  background-repeat: repeat;
  opacity: 0.035;
}

/* -----------------------------------------------------------------------------
   4. THE MARK

   Two flavours of each mark ship, and picking the wrong one fails silently:

     tocarouge-logo.svg              fill="currentColor" — for INLINING into the
                                     HTML, where CSS `color` drives it.
     tocarouge-logo-{red,white,black}.svg
                                     fill baked in — for <img src>.

   An SVG referenced by <img src> is an isolated document: `currentColor` there
   resolves against the SVG's OWN colour, which defaults to black, not against
   the host page. Using the currentColor file in an <img> on this dark ground
   therefore renders a black mark on a near-black background — invisible, with
   no error anywhere. Use the pre-coloured files in <img>, the currentColor one
   only when the markup is inlined.

   Width-only sizing with height:auto — the aspect ratio is fixed by the viewBox
   and must never be overridden.
   -------------------------------------------------------------------------- */

.tr-logo,
.tr-wordmark,
.tr-isotype {
  display: block;
  height: auto;          /* never set both dimensions: it would distort the mark */
  color: var(--tr-fg);
  max-width: 100%;
}

.tr-logo     { width: clamp(240px, 62vw, 400px); }  /* stacked lockup */
.tr-wordmark { width: clamp(190px, 50vw, 340px); }  /* single line */
.tr-isotype  { width: clamp(36px, 8vw, 56px); }     /* TR monogram */

/* These modifiers only do anything for an INLINED <svg>. For an <img>, swap the
   src to the matching pre-coloured file instead — see the note above. */
.tr-logo--red,
.tr-wordmark--red,
.tr-isotype--red { color: var(--tr-red); }

.tr-logo--white,
.tr-wordmark--white,
.tr-isotype--white { color: #ffffff; }

/* Clear space. Polvo supplied no guideline for this, so the value here is a
   conservative convention, NOT a brand rule. Flagged in the plan.

   MARGIN, not padding. This rule used to set padding, and because `*` sets
   box-sizing:border-box site-wide, that padding came OUT of the declared width:
   a 210px logo rendered as a 170px mark in a 210px box, 19% smaller than asked
   for, with 40px of invisible height under it. That was the single biggest
   cause of the composition looking unbalanced — the mark was quietly smaller
   than every spacing value around it assumed.

   The delivered artwork fills its viewBox exactly (verified: ink bounds ==
   canvas on all three marks), so all clear space has to come from outside. */
.tr-logo-lockup {
  margin: 0 auto var(--tr-space-4);   /* 16px. The mark and the venue caption
                                         below it read as one identity unit;
                                         the 32px gaps further down separate
                                         the event details and the CTA. */
}

/* -----------------------------------------------------------------------------
   5. TYPE
   -------------------------------------------------------------------------- */

.tr-h1 {
  font-family: var(--tr-font-display);
  font-size: clamp(2rem, 7vw, 3.25rem);
  line-height: var(--tr-leading-tight);
  letter-spacing: -0.01em;
  font-weight: 700;
  margin: 0 0 var(--tr-space-3);
}

.tr-h2 {
  font-family: var(--tr-font-display);
  font-size: clamp(1.35rem, 4.5vw, 1.9rem);
  line-height: var(--tr-leading-tight);
  font-weight: 700;
  margin: 0 0 var(--tr-space-3);
}

.tr-lead   { font-size: var(--tr-text-lg); color: var(--tr-fg); margin: 0 0 var(--tr-space-3); }
.tr-meta   { font-size: var(--tr-text-sm); color: var(--tr-muted); margin: 0 0 var(--tr-space-5); }
.tr-eyebrow {
  font-size: var(--tr-text-sm);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--tr-muted);
  margin: 0 0 var(--tr-space-3);
}
.tr-fine { font-size: var(--tr-text-xs); color: var(--tr-muted); line-height: 1.5; }

/* ---- Poster information block ------------------------------------------
   Sets the event details exactly as the poster does: Helvetica Neue Bold,
   uppercase, three stacked lines, tight leading.

   Sized so the smallest rendering stays above 18.66px, which is what lets it
   carry the brand red on the ink ground: #EA2413 is 4.46:1, which meets WCAG AA
   for large text only. Reducing this font-size drops it below AA. */
.tr-poster-block {
  font-family: var(--tr-font-display);
  font-weight: var(--tr-weight-bold);
  font-size: clamp(1.75rem, 8vw, 2.75rem);   /* floor is 28px — do not lower */
  /* Measured off the poster artwork: cap heights 163px, baseline steps 162 and
     164px, which at Helvetica Neue's measured 0.71em cap height is line-height
     0.72 -- solid leading, caps essentially touching.

     0.88 is used instead, and the 0.16em difference is the comma in
     "SEP 12TH, 2026". Measured in-browser, the comma descends 0.16em below the
     baseline, so a line carrying one spans 0.87em of ink; anything below that
     drops the comma onto the caps of the line beneath. The poster escapes this
     only because its comma happens to land in the word-space between TILL and
     6:00, an accident of that artwork's exact line widths which does not
     survive at web sizes.

     The comma was removed from the copy on 2026-08-29 ("SEP 12TH 2026"), which
     leaves the block with no descenders at all, so the poster's own 0.72 is now
     both correct and safe. If a comma or any lower-case descender ever returns
     to this block, raise this to 0.88 or the glyphs will collide. */
  line-height: 0.72;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--tr-red);
  margin: 0 0 var(--tr-space-6);
}

/* The venue, as the poster carries it: directly under the lockup, in the
   foreground colour rather than the red, so it reads as a caption to the mark
   and not as another information line. */
.tr-poster-venue {
  font-family: var(--tr-font-display);
  font-weight: var(--tr-weight-bold);
  font-size: clamp(0.875rem, 3.4vw, 1.0625rem);
  letter-spacing: 0.18em;   /* tracked out so it reads as a caption to the mark */
  text-transform: uppercase;
  color: var(--tr-fg);
  margin: 0 0 var(--tr-space-6);
}

/* Red type below ~18px uses the accessible tint, never the raw brand red. */
.tr-accent-text { color: var(--tr-red-text); }

a { color: var(--tr-red-text); }
a:hover { color: var(--tr-fg); }

/* -----------------------------------------------------------------------------
   6. BUTTONS

   The filled button's label is set at --tr-text-xl (20px) / 700. That is
   deliberate and load-bearing: white on --tr-red is 4.41:1, which meets WCAG AA
   only at the large-text threshold (>=18.66px bold). Reducing this font-size
   without changing the fill colour would drop the control below AA.
   -------------------------------------------------------------------------- */

.tr-btn {
  display: inline-block;
  padding: var(--tr-space-3) var(--tr-space-6);
  border: 1px solid transparent;
  border-radius: var(--tr-radius-pill);
  font-family: inherit;
  font-size: var(--tr-text-xl);   /* 20px — see note above. Do not lower. */
  font-weight: 700;
  line-height: 1.2;
  /* Uppercase to match the poster's register. Caps need more tracking than
     lower case at the same size or they read cramped. */
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: opacity var(--tr-transition), background-color var(--tr-transition), transform var(--tr-transition);
}

.tr-btn--primary {
  background-color: var(--tr-red);
  color: var(--tr-on-red);
}
/* Hover darkens the fill toward --tr-red-deep and keeps the label at full #fff,
   rather than the old opacity:0.88 which dimmed the label with the ground. The
   darker fill also raises contrast: white on #7C1314 is 10.71:1, against 4.41:1
   on the resting red (measured 2026-08-30). The -1px lift is cosmetic. Size,
   weight, tracking and padding are unchanged — those are the AA-threshold
   values. */
.tr-btn--primary:hover {
  background-color: var(--tr-red-deep);
  color: var(--tr-on-red);
  transform: translateY(-1px);
}

/* Outline variant. --tr-border-strong (3.88:1) is used because the border is
   the only thing identifying this control against the page. */
.tr-btn--secondary {
  background-color: transparent;
  color: var(--tr-fg);
  border-color: var(--tr-border-strong);
}
.tr-btn--secondary:hover { background-color: var(--tr-panel); color: var(--tr-fg); }

.tr-btn:disabled { opacity: 0.5; cursor: default; }
.tr-btn--block { display: block; width: 100%; }

/* -----------------------------------------------------------------------------
   7. FORMS

   Input font-size is 16px on purpose: iOS Safari zooms the viewport on focus
   for anything smaller, which on the signup page would look like a bug.
   -------------------------------------------------------------------------- */

.tr-label {
  display: block;
  font-size: var(--tr-text-sm);
  color: var(--tr-muted);
  margin-bottom: var(--tr-space-2);
}

.tr-input {
  width: 100%;
  padding: var(--tr-space-3) var(--tr-space-4);
  background-color: var(--tr-field);
  border: 1px solid var(--tr-border-strong);
  border-radius: var(--tr-radius-box);
  color: var(--tr-fg);
  font-family: inherit;
  font-size: var(--tr-text-base);   /* 16px — do not lower, see note above */
  line-height: 1.4;
}

.tr-input::placeholder { color: var(--tr-muted); opacity: 1; }

.tr-input:focus {
  outline: none;
  border-color: var(--tr-red);
  box-shadow: 0 0 0 2px rgba(234, 36, 19, 0.35);
}

.tr-field-group { display: flex; flex-direction: column; gap: var(--tr-space-2); }
.tr-form        { display: flex; flex-direction: column; gap: var(--tr-space-5); text-align: left; }

.tr-consent {
  display: flex;
  align-items: flex-start;
  gap: var(--tr-space-3);
  font-size: var(--tr-text-sm);
  color: var(--tr-muted);
  line-height: 1.45;
}
.tr-consent input {
  margin-top: 0.2rem;
  flex-shrink: 0;
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--tr-red);
}

.tr-panel {
  background-color: var(--tr-panel);
  border: 1px solid var(--tr-border);
  border-radius: var(--tr-radius-box);
  padding: var(--tr-space-4);
  font-size: var(--tr-text-sm);
  line-height: 1.5;
}
.tr-panel strong { color: var(--tr-red-text); }

.tr-error { font-size: var(--tr-text-sm); color: var(--tr-red-text); }

/* -----------------------------------------------------------------------------
   8. IMAGE TREATMENT

   House rule for any photography added later: desaturate slightly, push toward
   the red end, and always place type on a scrim rather than on the image.
   -------------------------------------------------------------------------- */

.tr-img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--tr-radius-box);
}

.tr-img--duotone {
  filter: saturate(0.75) contrast(1.06);
  mix-blend-mode: luminosity;
  background-color: var(--tr-red-deep);
}

.tr-img--scrim { position: relative; }
.tr-img--scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(11, 11, 12, 0.85), rgba(11, 11, 12, 0) 60%);
  border-radius: inherit;
}

/* -----------------------------------------------------------------------------
   9. LAYOUT HELPERS
   -------------------------------------------------------------------------- */

.tr-wrap {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  padding: var(--tr-space-7) var(--tr-space-5);
}
.tr-wrap--wide   { max-width: 680px; }
.tr-wrap--centre { text-align: center; }

.tr-stack   { display: flex; flex-direction: column; gap: var(--tr-space-4); }
.tr-stack-6 { display: flex; flex-direction: column; gap: var(--tr-space-6); }

.tr-screen {
  min-height: 100vh;    /* fallback first — svh is Safari 15.4+ / Chrome 108+,
                           and without this older browsers get min-height:auto */
  min-height: 100svh;   /* svh, not vh: avoids the mobile URL-bar jump */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.tr-footer {
  margin-top: var(--tr-space-7);
  font-size: var(--tr-text-xs);
  color: var(--tr-muted);
}
/* WCAG 2.5.8 sets a 24x24 CSS-px minimum target. At 12px these links were about
   14px tall; the padding brings the hit area up without changing the type size. */
.tr-footer a {
  color: var(--tr-muted);
  text-decoration: underline;
  display: inline-block;
  padding: var(--tr-space-2) var(--tr-space-1);
  min-height: 24px;
}
.tr-footer a:hover { color: var(--tr-fg); }

/* -----------------------------------------------------------------------------
   10. ACCESSIBILITY UTILITIES
   -------------------------------------------------------------------------- */

/* :focus-visible, not :focus — a mouse click keeps the quiet treatment, while
   keyboard navigation gets a ring clearing 3:1 against every surface here. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--tr-focus);
  outline-offset: 2px;
  border-radius: var(--tr-radius-sm);
}

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

/* Honeypot helper. Kept identical in behaviour to the live signup page: off
   screen, zero size, out of the tab order. */
.tr-hp {
  position: absolute;
  left: -9999px;
  width: 0;
  height: 0;
  overflow: hidden;
}

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

/* Forced-colors (Windows high contrast): let the OS palette win, but keep the
   filled button readable as a button. */
@media (forced-colors: active) {
  .tr-btn--primary { forced-color-adjust: none; }
}

/* =============================================================================
   11. SITE LAYER

   Styles the class names and ids the pages ALREADY carry, rather than requiring
   new ones. This is what keeps signup.html's <form> untouched: every field is
   reached through an id that was already in the markup, so not one attribute
   inside <form> has to change.

   Page context comes from a class on <body>, which is outside every form.
   ========================================================================== */

/* ---- Shared shell -------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  padding: var(--tr-space-7) var(--tr-space-5);
}

.page-home .wrap,
.page-404 .wrap  { text-align: center; }
.page-privacy .wrap,
.page-legal .wrap { max-width: 680px; }
.page-qr .wrap    { max-width: 560px; text-align: center; }

.page-home,
.page-404,
.page-qr {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* uic.html carries less copy than esade.html (no eyebrow, one intro line, no
   QR reveal), so vertical centring leaves a large gap above the heading.
   Anchor it near the top instead — the .wrap's own space-7 top padding is the
   only offset. esade.html keeps the centred treatment. */
.page-qr.qr-top { justify-content: flex-start; }

/* Type pass for the trimmed uic.html intro, so the three "loud" lines read as
   one system rather than three unrelated sizes. Scoped to .qr-top so
   esade.html is untouched.
   - h1 becomes the largest element and takes .details' letter-spacing, so the
     heading and the date block below it are the same family.
   - the intro line moves to --tr-text-xl (20px), matching the button labels,
     so the page has one fewer distinct size.
   Resulting scale: h1 > .details > (.reveal = .cta) > (.venue = .cta-note) > footer. */
.qr-top #intro h1 {
  font-size: clamp(1.6rem, 7.2vw, 2.5rem);
  letter-spacing: -0.01em;
  text-wrap: balance;
  margin-bottom: var(--tr-space-3);
}
.qr-top #intro .reveal {
  font-size: var(--tr-text-xl);
  margin-bottom: 0;
}

/* One vertical rhythm: ~32px between each block (intro / actions / footer),
   tight spacing kept only within a block. Without this the default space-7
   gaps left big voids once the page was top-anchored. The panel keeps a
   smaller bottom margin — it already carries space-6 of internal padding. */
.qr-top #intro  { margin-bottom: var(--tr-space-6); }
.qr-top .party  { margin-bottom: var(--tr-space-4); }
.qr-top footer  { margin-top: var(--tr-space-6); }

/* Flat ground where the texture would fight the content */
.page-signup,
.page-privacy,
.page-legal { background-color: var(--tr-ink); }

/* ---- The mark ------------------------------------------------------------ */

.brand-lockup { margin: 0 0 var(--tr-space-6); }
.brand-lockup img {
  display: block;
  width: clamp(240px, 62vw, 400px);
  height: auto;
  margin: 0 auto;
}
.page-qr .brand-lockup img { width: clamp(200px, 52vw, 330px); }

/* The QR pages carry the lockup inside <h2 class="wordmark">, which is the
   accessible name for the .party section via aria-labelledby. The <img> alt
   supplies that name, so the heading must keep the image as its only content. */
.wordmark { margin: 0 0 var(--tr-space-4); }
.wordmark img {
  display: block;
  width: clamp(200px, 52vw, 330px);
  height: auto;
  margin: 0 auto;
}

/* Small wordmark used as a header on the inner pages */
.brand-mini {
  display: block;
  width: clamp(120px, 30vw, 160px);
  height: auto;
  margin: 0 0 var(--tr-space-6);
}
.page-home .brand-mini,
.page-qr .brand-mini { margin-inline: auto; }

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

h1 {
  font-family: var(--tr-font-display);
  font-size: clamp(1.6rem, 5.5vw, 2.4rem);
  font-weight: 700;
  line-height: var(--tr-leading-tight);
  letter-spacing: -0.01em;
  margin: 0 0 var(--tr-space-4);
}
h2 {
  font-family: var(--tr-font-display);
  font-size: clamp(1.2rem, 4vw, 1.6rem);
  font-weight: 700;
  line-height: var(--tr-leading-tight);
  margin: 0 0 var(--tr-space-3);
}
p { margin: 0 0 var(--tr-space-4); }

/* Genre line. No page carries one: Paul decided on 2026-08-30 that the site
   displays no genre line (the meta descriptions keep the genre, the pages do
   not). This rule is retained unused in case that decision is revisited. */
.subhead,
.genre {
  font-size: var(--tr-text-lg);
  color: var(--tr-fg);
  margin: 0 0 var(--tr-space-5);
}
.page-signup .subhead {
  font-size: var(--tr-text-base);
  color: var(--tr-muted);
  margin-bottom: var(--tr-space-6);
}

/* Event details, in the poster's stacked-caps style but with a little more
   leading than the poster itself.

   The flyer's own value is ~0.76: measured off the 1080x1440 artwork, the three
   lines run cap heights of ~155px with baseline steps of ~165px (against
   Helvetica Neue's 0.714em cap that is 1.065 * 0.714 = 0.76) -- solid, tight
   leading with only a hairline between lines. The earlier 0.72 mismeasured the
   cap height off the glyph glow and made the lines overlap in the browser.

   0.84 is Paul's call 2026-08-31: open it past the poster's own leading so the
   three lines read as distinctly separate on the page rather than as one tight
   block. Not a strict poster match by design.

   This was 0.88 while the copy read "SEP 12TH, 2026": the comma descends 0.16em
   below the baseline and collided with the caps beneath. The comma was removed
   2026-08-29, leaving the block with no descenders at all. If a comma or any
   lower-case descender ever returns to this block, keep this at 0.88 or higher
   or the glyphs will collide. */
.details {
  font-family: var(--tr-font-display);
  font-weight: var(--tr-weight-bold);
  font-size: clamp(1.75rem, 8vw, 2.75rem);
  line-height: 0.84;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--tr-red);
  margin: 0 0 var(--tr-space-3);
}

/* Venue reads as a caption to the block above it, so it sits close to the date
   and takes its distance from the CTA below instead. */
.venue {
  font-family: var(--tr-font-display);
  font-weight: var(--tr-weight-bold);
  font-size: clamp(0.875rem, 3.4vw, 1.0625rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--tr-fg);
  margin: 0 0 var(--tr-space-6);
}

/* Landing page (Paul, 2026-09-30): the date block, the line under it and the
   button sit at equal distances, 24px each, instead of 12px then 32px. */
.page-home .details,
.page-home .venue { margin-bottom: var(--tr-space-5); }

.eyebrow {
  font-size: var(--tr-text-sm);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--tr-muted);
  margin: 0 0 var(--tr-space-4);
}

.reveal     { font-size: var(--tr-text-lg); color: var(--tr-fg); margin: 0 0 var(--tr-space-3); }
.reveal-sub { font-size: var(--tr-text-base); color: var(--tr-muted); margin: 0 0 var(--tr-space-7); }
/* The pivot of the reveal line ("there is NO Pilates class"). It carries the
   joke, so it is set a step larger than the rest of the heading and tracked
   tighter. Colour stays on the accessible tint --tr-red-text, not raw --tr-red:
   the heading sits on the textured ground, not the contained panel. */
.no {
  color: var(--tr-red-text);
  font-size: 1.15em;
  letter-spacing: -0.02em;
}

.party { margin: 0 0 var(--tr-space-7); }

/* Campus pages: give the event details visual containment so they read as an
   object lifted off the page rather than as three more paragraphs. --tr-panel
   is a solid ground, so the .details red inside now sits on #17100f rather than
   the scrimmed texture: #EA2413 on #17100f is 4.26:1, clearing the 3:1
   large-text floor (computed 2026-08-30). The border is the decorative
   --tr-border — the fill is what identifies the card, the edge only refines it. */
.page-qr .party {
  background-color: var(--tr-panel);
  border: 1px solid var(--tr-border);
  border-radius: var(--tr-radius-box);
  padding: var(--tr-space-6) var(--tr-space-5);
}
.page-qr .party .venue { margin-bottom: 0; }

/* ---- Actions ------------------------------------------------------------- */

.actions {
  display: flex;
  flex-direction: column;
  gap: var(--tr-space-2);
  margin-bottom: var(--tr-space-6);
}

.cta {
  display: inline-block;
  padding: var(--tr-space-3) var(--tr-space-6);
  border: 1px solid transparent;
  border-radius: var(--tr-radius-pill);
  font-family: inherit;
  font-size: var(--tr-text-xl);   /* 20px. White on brand red is 4.41:1, which
                                     meets AA at the large-text threshold only.
                                     Do not lower this without changing the fill. */
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: opacity var(--tr-transition), background-color var(--tr-transition), transform var(--tr-transition);
  background-color: var(--tr-red);
  color: #fff;
}
/* Hover darkens the fill toward --tr-red-deep with the label held at full #fff,
   instead of the old opacity:0.88 which dimmed label and ground together. The
   darker ground also lifts contrast: white on #7C1314 is 9.16:1, up from 4.41:1
   at rest (measured 2026-08-30: 10.71:1). The -1px translate is a cosmetic
   lift. Font size, weight, tracking and padding are untouched — those carry the
   AA derivation in the note above. */
.cta:hover {
  background-color: var(--tr-red-deep);
  color: #fff;
  transform: translateY(-1px);
}

/* Secondary: the border is the only thing identifying this control, so it uses
   --tr-border-strong (3.88:1) rather than the decorative --tr-border. */
.cta.secondary {
  background-color: transparent;
  color: var(--tr-fg);
  border-color: var(--tr-border-strong);
}
.cta.secondary:hover { background-color: var(--tr-panel); color: var(--tr-fg); }

/* VIP tables (esade.html only): a filled deep-red tier. It outweighs the ghost
   secondary without becoming a second bright-red primary — the shade reads as
   "the premium one". White on #7C1314 is ~9:1, well clear of AA. Hover brightens
   toward --tr-red, inverting the primary's darken-on-hover. */
.cta.vip {
  background-color: var(--tr-red-deep);
  color: #fff;
  border-color: transparent;
}
.cta.vip:hover {
  background-color: var(--tr-red);
  color: #fff;
  transform: translateY(-1px);
}

.page-qr .cta {
  display: block;
  /* Full-width already, so the generous horizontal padding only steals room
     from the label and forces "GET ON THE GUEST LIST" onto two lines. */
  padding-inline: var(--tr-space-4);
}
.cta-note {
  font-size: var(--tr-text-sm);
  color: var(--tr-muted);
  margin: 0 0 var(--tr-space-5);
}
.actions .cta-note:last-child { margin-bottom: 0; }

/* Signup confirmation, Friday 2 Oct: the guestlist code, claimed on fourvenues.
   Not uppercased: the code is entered as shown. */
.code-box   { margin: 0 0 var(--tr-space-6); }
.code-value {
  font-family: var(--tr-font-display);
  font-weight: var(--tr-weight-bold);
  font-size: clamp(1.5rem, 7vw, 2.25rem);
  color: var(--tr-red-text);
  text-transform: none;
  word-break: break-all;
  margin: 0 0 var(--tr-space-3);
}
.code-help  { color: var(--tr-fg); margin: var(--tr-space-4) 0 var(--tr-space-5); }

/* ---- Landing-page entrance ----------------------------------------------
   A short composed reveal on index.html so the page reads as deliberate
   rather than as one that simply painted. The elements rise into place in
   document order — mark, then date block, then venue, then CTA.

   The stagger is in the DURATIONS, not in animation-delay: every element
   starts at t=0 and they finish 0.30s / 0.42s / 0.52s / 0.58s in. This is
   deliberate — a delay-based stagger with fill-mode would leave later
   elements at opacity 0 during their delay even under reduced motion, since
   the §10 override caps duration but not delay. With no delay, the reduced-
   motion override (animation-duration: 0.01ms !important, universal
   selector) collapses the whole sequence to instant. Verified 2026-08-30:
   OS "reduce motion" makes the entrance appear in one frame.

   fill-mode is backwards, not forwards/both: the from-state is applied
   immediately (no first-frame flash) but released on completion, so it does
   not pin `transform` and block the CTA's :hover lift.

   Total sequence 580ms — under the 700ms ceiling for a page a stranger
   reached from a sticker. The CTA finishes 60ms after the venue, never
   trailing by a noticeable margin. translateY is 10px, well under the 12px
   cap. */
@keyframes tr-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.page-home .brand-lockup { animation: tr-rise 0.30s ease backwards; }
.page-home .details      { animation: tr-rise 0.42s ease backwards; }
.page-home .venue        { animation: tr-rise 0.52s ease backwards; }
.page-home .cta          { animation: tr-rise 0.58s ease backwards; }

/* ---- Back link and footer ------------------------------------------------ */

a.back {
  display: inline-block;
  margin-bottom: var(--tr-space-6);
  color: var(--tr-muted);
  text-decoration: none;
  font-size: var(--tr-text-sm);
  padding: var(--tr-space-2) 0;
  min-height: 24px;
}
a.back:hover { color: var(--tr-fg); }

footer {
  margin-top: var(--tr-space-7);
  font-size: var(--tr-text-xs);
  color: var(--tr-muted);
}
footer a {
  color: var(--tr-muted);
  text-decoration: underline;
  display: inline-block;
  padding: var(--tr-space-2) var(--tr-space-1);
  min-height: 24px;
}
footer a:hover { color: var(--tr-fg); }

/* ---- Signup form ---------------------------------------------------------
   Every selector below is an id or class that ALREADY exists in signup.html.
   Nothing here requires an attribute change inside <form>.
   -------------------------------------------------------------------------- */

#signup-form {
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: var(--tr-space-5);
}

#signup-form label {
  display: block;
  font-size: var(--tr-text-sm);
  color: var(--tr-muted);
  margin-bottom: var(--tr-space-2);
}

#signup-form .optional { opacity: 0.75; }

#signup-form input[type="text"],
#signup-form input[type="email"],
#signup-form input[type="tel"] {
  width: 100%;
  padding: var(--tr-space-3) var(--tr-space-4);
  background-color: var(--tr-field);
  border: 1px solid var(--tr-border-strong);
  border-radius: var(--tr-radius-box);
  color: var(--tr-fg);
  font-family: inherit;
  font-size: var(--tr-text-base);   /* 16px — anything smaller makes iOS Safari
                                       zoom the viewport on focus */
  line-height: 1.4;
}
#signup-form input::placeholder { color: var(--tr-muted); opacity: 1; }
#signup-form input[type="text"]:focus,
#signup-form input[type="email"]:focus,
#signup-form input[type="tel"]:focus {
  outline: none;
  border-color: var(--tr-red);
  box-shadow: 0 0 0 2px rgba(234, 36, 19, 0.35);
}

/* The honeypot must keep its own positioning — it is anti-spam, not layout. */
.hp { position: absolute; left: -9999px; width: 0; height: 0; overflow: hidden; }

/* Marketing permission is opt-out since 2026-09-23. Art. 13(2) of the ePrivacy
   Directive requires the chance to object to be given "clearly and distinctly"
   and free of charge, so both the statement and the objection sit at --tr-fg
   rather than --tr-muted. This is a legal condition wearing a style rule:
   muting either line back down would forfeit the basis they exist to satisfy.
   See Decisions/Marketing Consent Moves to Opt-Out. */
.consent-lead {
  font-size: var(--tr-text-sm);
  color: var(--tr-fg);
  line-height: 1.45;
  margin: 0;
}
.consent {
  display: flex;
  align-items: flex-start;
  gap: var(--tr-space-3);
  font-size: var(--tr-text-sm);
  color: var(--tr-fg);
  line-height: 1.45;
  margin: 0;
}
.consent input {
  margin-top: 0.2rem;
  flex-shrink: 0;
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--tr-red);
}

.privacy-note { font-size: var(--tr-text-xs); color: var(--tr-muted); line-height: 1.5; margin: 0; }
.privacy-note a { color: var(--tr-red-text); }

button.submit {
  display: block;
  width: 100%;
  padding: var(--tr-space-3) var(--tr-space-6);
  border: none;
  border-radius: var(--tr-radius-pill);
  background-color: var(--tr-red);
  color: #fff;
  font-family: inherit;
  font-size: var(--tr-text-xl);   /* 20px — see .cta note above */
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: opacity var(--tr-transition);
}
button.submit:hover { opacity: 0.88; }
button.submit:disabled { opacity: 0.5; cursor: default; }

.error { display: none; font-size: var(--tr-text-sm); color: var(--tr-red-text); margin: 0; }
.error.show { display: block; }

.note { margin-top: var(--tr-space-6); font-size: var(--tr-text-sm); color: var(--tr-muted); line-height: 1.5; }
.note a { color: var(--tr-red-text); }

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

/* ---- Legal pages --------------------------------------------------------- */
.page-privacy p,
.page-legal p { line-height: var(--tr-leading-body); }
.page-privacy a,
.page-legal a { color: var(--tr-red-text); }
