/* ============================================================
   ONLINE EVENTS — page-specific chrome only, same "page-local classes,
   shared tokens/base classes from styles.css" convention as plans.css.

   2026-07-21 events visual redesign (`3_SUB_BATCH_PLAN.md` §5 "3.1"
   addendum): promotes the initial 3.1 port's simplified card-grid
   structure (this file's original revision, `CHANGELOG.md`'s matching
   entry) up to a structural port of `events.jsx`/`event-detail.jsx`'s own
   Featured Hero + Schedule-row listing and full editorial detail page —
   real layout/hierarchy, reusing the site's existing tokens/classes
   (`--r-card`, `--lavender`, `--couple-purple`, `.eyebrow`, `.btn-pill`,
   `.h1-hero`, `.h2-section`) rather than transcribing every one of the
   reference's ~30 hand-tuned pixel breakpoints — this is a structural
   port, the same fidelity bar every other page in this codebase (`home.css`,
   `plans.css`) is held to, not a pixel-exact clone. The reference's
   sticky-on-scroll month-divider behavior is decorative-only JS and is not
   ported (no acceptance line requires it).
   ============================================================ */

.event-shell { padding: var(--rhythm-56) 96px var(--rhythm-80); max-width: 1440px; margin: 0 auto; }

.event-hero-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: var(--rhythm-56);
  align-items: center;
}
.event-hero-image img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--r-card);
  box-shadow: var(--shadow-elevated);
}

/* Shared going-avatar cluster (`render_going_cluster()`).

   SC-8 (2026-09-01) — THE AVATARS NEVER OVERLAPPED, and the cause was here,
   not in the sizes. `.event-going`'s `gap: 12px` applied between EVERY flex
   child, including the avatars, so it cancelled each avatar's `margin-left:
   -12px` exactly: net offset 0, three circles sitting edge to edge on every
   page that renders this component. `GoingCluster` nests the avatars in their
   own gapless flex box inside the gapped row (`sections.jsx:369-384`) — the
   gap then spaces the avatar STACK from the count, which is all it was ever
   meant to do. `local-city.css:114-118` already had this right (FF-11); this
   file predates it. `.event-going-avatars` is that wrapper.

   Sizes are the design's own three tiers, restoring a step this port had
   flattened: 42/14 Featured Hero (`--lg`), 38/12 Schedule row (base), 34/11
   detail hero (`--detail`). The old `--sm` (26/10) is RETIRED — its two call
   sites were Schedule rows, whose correct size is the base. */
.event-going { display: flex; align-items: center; gap: 12px; }
.event-going-avatars { display: flex; }
.event-going-avatar {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: var(--shadow-subtle);
  object-fit: cover;
  background: var(--lavender);
  margin-left: -12px;
}
.event-going-avatar:first-child { margin-left: 0; }
.event-going-count { font: var(--type-caption); color: var(--ink-700); letter-spacing: -0.1px; }
.event-going-word { font-weight: 500; }
.event-going--lg .event-going-avatar { width: 42px; height: 42px; margin-left: -14px; }
.event-going--lg .event-going-avatar:first-child { margin-left: 0; }

/* Detail-hero tone (`GoingCluster`'s `tone="detail"`) — inverted avatar
   ring (lavender border/white fill, since the hero itself sits on
   lavender) + bold purple-dark count instead of the listing's quiet ink
   caption. Found always rendering the listing's "list" tone even on the
   detail hero during the 3.1 devsite style-fidelity pass (2026-07-22). */
.event-going--detail .event-going-avatar {
  width: 34px;
  height: 34px;
  margin-left: -11px;
  border-color: var(--lavender);
  background: #fff;
}
.event-going--detail .event-going-avatar:first-child { margin-left: 0; }
.event-going--detail .event-going-count { font: 700 16px/1.2 var(--font-display); color: var(--couple-purple-dark); letter-spacing: -0.1px; }
.event-going--detail .event-going-word { font-weight: 500; }

/* Shared host photo tile (`render_event_host_tile()`) */
.event-host-tile {
  border-radius: var(--r-card-lg);
  overflow: hidden;
  background: var(--lavender);
  box-shadow: var(--shadow-card);
  flex-shrink: 0;
}
.event-host-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* SC-8 (2026-09-01) — the design's own `HostTile` sizes: `size={320}` on the
   Featured Hero (`events.jsx:183`) and the `size = 140` default on a Schedule
   row (`events.jsx:139`, `:339`). This port had them at 200/96. Only the two
   events-index helpers render this tile, so nothing else moves. */
.event-host-tile--lg { width: 320px; height: 320px; }
.event-host-tile--sm { width: 140px; height: 140px; border-radius: var(--r-card); }
.event-host-tile--initial {
  display: grid;
  place-items: center;
  font: 700 var(--type-h2);
  color: var(--couple-purple-dark);
}

/* Featured Hero (`render_event_featured_hero()`) — the soonest event.
   Containerless at rest (no fill/shadow), same `.ev-clickable:hover`
   lavender-wash-only treatment as the Schedule row — found always-on
   lavender instead during the 3.1 devsite walkthrough (2026-07-22, real
   style-fidelity gap): `events.jsx`'s `FeaturedHero` never fills its own
   background, it only washes lavender on hover, exactly like `ScheduleRow`. */
.event-featured {
  display: grid;
  /* Track follows the tile SC-8 resized (200 -> 320, the design's own). */
  grid-template-columns: 320px 1fr auto;
  gap: 32px;
  align-items: center;
  border-radius: var(--r-card);
  padding: 20px 24px;
  /* FF-18 (2026-09-01) — `position: relative` makes this the containing block
     for the title link's stretched overlay, so the WHOLE plate is the link.
     The negative inline margin is `.ev-clickable`'s own (`events.jsx:550`,
     `:554`): the hover wash bleeds to the content edge instead of sitting
     inset, so the plate announces exactly the target that is clickable. */
  position: relative;
  margin: 0 -24px var(--rhythm-56);
  transition: background 160ms ease;
}
.event-featured:hover {
  background: var(--lavender);
}
.event-featured-info { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.event-featured-meta { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; margin-top: 4px; }
.event-hosted { display: inline-flex; align-items: baseline; gap: 8px; }
.event-featured-cta { display: flex; flex-direction: column; align-items: flex-end; gap: 12px; }

/* Month divider + schedule list (`render_event_month_divider()`).
   `--phone` is the first month's divider, which only the phone tier shows:
   above 640 the Featured Hero covers that month and a divider over it would
   be redundant, which is exactly why the design only emits it when its hero
   is absent (`events.jsx:937`'s `|| isMobile`). */
.event-month-divider--phone { display: none; }
/* The Featured Hero's phone-tier date stack — hidden above 640, where its
   one-line `.event-featured-when` shows instead (SC-8 follow-up #4).
   ⚠ SC-12 (2026-09-02): this MUST stay descendant-qualified. As a bare
   `.event-row-date--phone` it was 0-1-0 and lost, on SOURCE ORDER alone, to
   `.event-row-date { … display: flex }` 45 lines below (`:200`, also 0-1-0) —
   so the stack rendered on desktop too and the hero printed its date twice
   (owner-reported). At 0-2-0 it mirrors its own ≤640 counterpart at `:457`
   exactly: one hide/show switch, one shape, and no later `.event-row-date`
   declaration can annul it again. The `:457` show still wins at that width on
   source order, both selectors being 0-2-0. */
.event-featured .event-row-date--phone { display: none; }
.event-month-divider {
  font: var(--type-display-sm);
  color: var(--couple-purple);
  margin: 0 0 20px;
  letter-spacing: -0.5px;
}
.event-schedule-list { list-style: none; padding: 0; margin: 0 0 40px; display: flex; flex-direction: column; gap: var(--rhythm-56); }

/* Schedule row (`render_event_schedule_row()`) — 2-row grid matching
   `events.jsx`'s own `ScheduleRow` template exactly (`"date host info cta"
   "date host meta cta"`): the CTA column spans both rows on the far right.
   Found missing during the 3.1 devsite walkthrough (2026-07-22, real bug):
   this rule never declared `grid-template-areas` at all, so with 5 DOM
   children (date, host tile, info, meta, cta) auto-placed onto only 4
   declared column tracks, `.event-row-cta` silently wrapped onto its own
   implicit row under the date column instead of sitting in its own
   right-hand column — the ≤900px media query below already had the
   correct areas, the desktop base rule just never got them. */
.event-row {
  display: grid;
  grid-template-columns: 90px auto 1fr auto;
  grid-template-areas: "date host info cta" "date host meta cta";
  gap: 24px;
  align-items: center;
  border-radius: var(--r-card);
  padding: 20px 24px;
  /* FF-18 — see `.event-featured` above. Same plate, same stretched link. */
  position: relative;
  margin: 0 -24px;
  transition: background 160ms ease;
}
/* Whole-row hover — lavender wash only, no shadow/lift, matching
   `.ev-clickable:hover` (`events.jsx`) exactly. Found rendering a
   shadow+translateY lift instead during the 3.1 devsite walkthrough
   (2026-07-22, real style-fidelity gap) — the reference never lifts or
   shadows a hovered row, it only washes it in lavender; at rest it also
   has no fill/shadow at all (containerless), not the white filled card
   this used to always render as. */
.event-row:hover {
  background: var(--lavender);
}
/* SC-8 (2026-09-01) — LEFT, not centered. The design's `.ev-date` declares no
   text-align at all, on the online index (`events.jsx:321`) AND the in-person
   one (`in-person.jsx:304`), which share this class here. */
.event-row-date { grid-area: date; display: flex; flex-direction: column; gap: 4px; }
.event-row-time { font: var(--type-eyebrow); color: var(--fg-heading); letter-spacing: -0.1px; margin-top: 2px; }
.event-row .event-host-tile { grid-area: host; }
.event-row-info { grid-area: info; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.event-row-meta { grid-area: meta; display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.event-hosted-name { font: 700 16px/1.2 var(--font-display); color: var(--fg-heading); letter-spacing: -0.2px; }
.event-row-cta { grid-area: cta; display: flex; flex-direction: column; align-items: flex-end; justify-content: center; gap: 10px; }

/* ============================================================
   SC-8 WALKTHROUGH FOLLOW-UP #1 (2026-09-01) — THE MOBILE TIERS.

   Reported by the owner at the walkthrough: at mobile "the top event text
   [is] centered and should be left, and the host images are gone". Both
   confirmed against the handoff, and both are v3 inventions from the 3.1
   port rather than anything the design asks for:

     - `.event-featured` carried `text-align: center` with `-cta` and
       `-meta` centered to match. **The design centers nothing** — its ≤900
       rule is `grid-template-columns: 1fr; gap: 28px` with
       `.ev-featured-cta { align-items: flex-start }` (`events.jsx:616-630`).
     - `.event-row > .event-host-tile` was `display: none` below 900. **The
       design keeps the host portrait at every width**: its own `host`
       column at ≤900, then a 104px square in the `media` area of the ≤768
       "square-portrait header card" (`events.jsx:660-745`), then 96px at
       ≤640. It is never hidden.

   ⚠ **Ported from `local-city.css:225-267`, not re-derived.** FF-11 already
   carried this exact card to the city rows — including the two states the
   design's own mock cannot show, `.event-registered` and
   `.event-card-status`, which must take the `btn` area when the CTA wrapper
   dissolves to `display: contents` or they auto-place and wreck the grid.
   That makes this the FIFTH shape FF-11 got right on the city page that
   this file, ported at 3.1, never received (with SC-8's going cluster,
   SC-10's hover underline, FF-18's clickable row and the left date column).
   ============================================================ */
@media (max-width: 1100px) {
  /* The tile shrinks WITH its track. Design's own step (`events.jsx:604-610`). */
  .event-featured { grid-template-columns: 240px 1fr auto; gap: 40px; }
  .event-featured .event-host-tile--lg { width: 240px; height: 240px; }
  .event-hero-grid { gap: 40px; }
}
@media (max-width: 900px) {
  /* SC-8 follow-up #3 — the page hero's image. ⚠ v3 carried `order: -1`,
     hoisting the photo ABOVE the headline on every narrow screen. **The
     design does no such thing**: its ≤900 rule is one column + `gap: 40px`
     with NO order change (`events.jsx:619-623`), so the headline leads and
     the photo follows, exactly as it does on desktop. v3 also never capped
     the photo, so it ran full-bleed; the design caps it at 560 and centers
     it. Both halves of the owner's "size and placement" report. */
  .event-hero-grid { grid-template-columns: 1fr; gap: 40px; }
  .event-hero-image img { max-width: 560px; margin: 0 auto; }
  /* Stacked and LEFT — no centering anywhere (the owner's first report). */
  .event-featured { grid-template-columns: 1fr; gap: 28px; }
  .event-featured .event-host-tile--lg { width: 220px; height: 220px; }
  .event-featured-cta { align-items: flex-start; }
  .event-featured-meta { justify-content: flex-start; }
  /* The host column survives (the owner's second report). */
  .event-row {
    grid-template-columns: 80px auto 1fr;
    grid-template-areas:
      "date host info"
      "date host meta"
      "cta  cta  cta";
    column-gap: 20px;
    row-gap: 16px;
  }
  .event-row-meta { grid-area: meta; }
  .event-row-cta { grid-area: cta; justify-self: start; align-items: flex-start; margin-top: 8px; }
  /* ⚠ SC-13 (2026-09-03) — the `.event-checklist { grid-template-columns: 1fr }`
     declaration that used to sit here NEVER APPLIED and has been deleted rather
     than left in place claiming a behavior the file did not have. It was 0-1-0
     and the base `.event-checklist` (below, in the "Pre-event checklist" block)
     is 0-1-0 too and declared LATER, so the base's `1fr 1fr` won at every width
     — a media query adds NO specificity. Its replacement lives beside the base
     rule it has to beat, as `ul.event-checklist` (0-1-1). FOURTH source-order
     defeat in this file, after SC-8's going-cluster gap, SC-10's hover underline
     and SC-12's phone date stack; SC-12-D1 ruled the remedy (raise specificity,
     do not merely move the rule). */
  /* ⚠ SC-8 follow-up #3's expect-plate padding step USED TO SIT HERE and was
     defeated by source order the whole time — the base `.event-expect-shell`
     is declared later at equal specificity, so `var(--rhythm-80) 80px` won at
     every width down to 640. All four tiers moved beside that base rule at
     SC-13 follow-up #1; see the block there. */
}
@media (max-width: 768px) {
  /* The square-portrait header card. Every wrapper dissolves so its leaves
     place independently; the ≥901 render is untouched. */
  .event-row {
    grid-template-columns: 104px 35px 1fr;
    grid-template-areas:
      "media  date   date"
      "media  type   type"
      "hosted hosted hosted"
      "title  title  title"
      "price  price  price"
      "btn    btn    going";
    column-gap: 20px;
    row-gap: 14px;
    align-items: start;
  }
  .event-row > .event-host-tile { grid-area: media; width: 104px; height: 104px; align-self: start; }
  .event-row .event-row-date { grid-area: date; align-self: start; }
  .event-row .event-row-info { display: contents; }
  .event-row .event-row-type { grid-area: type; align-self: start; }
  .event-row .event-card-title-link { grid-area: title; }
  .event-row .event-row-meta { display: contents; }
  .event-row .event-hosted { grid-area: hosted; align-self: start; }
  .event-row .event-row-cta { display: contents; }
  .event-row .event-row-price { display: block; grid-area: price; justify-self: start; margin-top: 4px; margin-bottom: -4px; }
  /* ⚠ All three CTA outcomes share the `btn` area — the pill AND the two
     inert states. `render_event_cta()` returns `.event-registered` or
     `.event-card-status` instead of a pill for a registered / blocked /
     closed event, and with the wrapper on `display: contents` an unplaced
     one auto-fills the next free cell. `local-city.css:243-245` names the
     same three for the same reason. */
  .event-row .btn-pill,
  .event-row .event-registered,
  .event-row .event-card-status { grid-area: btn; align-self: center; justify-self: start; }
  .event-row .event-row-meta > .event-going {
    grid-area: going;
    align-self: center;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;
    padding-left: 20px;
  }
  .event-row .event-going-count { white-space: nowrap; }
}

/* "What to expect" timeline (`render_event_expect_timeline()`) */
.event-expect-shell {
  background: var(--lavender);
  border-radius: var(--r-section);
  padding: var(--rhythm-80) 80px;
  max-width: calc(var(--measure-intro) + 248px);
  margin: 0 auto var(--rhythm-56);
}
/* SC-13 walkthrough follow-up #1 (2026-09-03) — the owner: "should be a
   little space around the 'What to expect…' and 'Ready to meet…' elements on
   mobile". Two separate faults, and the second was latent since 3.1.

   ⚠ (1) THE PLATE HAS NO GUTTER AT ALL BELOW ~1008px. In the design both
   plates sit INSIDE `.edx-shell` (`event-detail.jsx:583-584`), whose padding
   is the page's own gutter ladder — 96 → 56 → 48 → 40 → 20px
   (`styles.css:690,722,749,771,791`). v3 renders them as TOP-LEVEL siblings of
   `<main class="page">`, which has no padding, so `max-width: 1008px` +
   `margin-inline: auto` gives them a gutter ONLY while the viewport exceeds
   1008px. At 900 and below the plate is edge-to-edge and reads as a slab
   wedged between the white page and the purple footer. `width` (not margin)
   carries the fix so `margin-inline: auto` still centres it and the
   `max-width` cap still wins on desktop; `box-sizing: border-box` is site-wide
   (`styles.css:4-8`), so the padding is inside the width.

   ⚠ (2) THE ≤1100 AND ≤900 PADDING STEPS HAD NEVER APPLIED — the SIXTH
   source-order-at-equal-specificity defeat in this file. Both sat in media
   blocks ABOVE the base rule at equal 0-1-0, so the base's
   `var(--rhythm-80) 80px` won at every width down to 640, and a 768px tablet
   was rendering a full-bleed plate with 80px of INNER padding. All four tiers
   now live here, below the base and as `div.event-expect-shell` (0-1-1) —
   SC-12-D1's ruling, applied for the third time in this item.

   ⚠ FOUR PAGES wear this class: the online detail page (twice), the online
   index, the IRL detail page (twice) and the IRL index. The design gives the
   plate the same container gutter on every one of them. */
@media (max-width: 1100px) {
  div.event-expect-shell { padding: 64px 56px; width: calc(100% - 112px); }
}
@media (max-width: 900px) {
  div.event-expect-shell { padding: 56px 40px; border-radius: 28px; width: calc(100% - 96px); }
}
@media (max-width: 768px) {
  div.event-expect-shell { width: calc(100% - 80px); }
}
@media (max-width: 640px) {
  /* 20px gutter, and the stacked gap the design uses BETWEEN plates at this
     tier (`.edx-shell-inner { gap: 56px }`, `styles.css:793`) instead of
     `--rhythm-56`, which floors at 40px on a phone. This is the "space below"
     half of the report — the closing plate sat 40px off the footer. */
  div.event-expect-shell { padding: 48px 24px; border-radius: 22px; width: calc(100% - 40px); margin-bottom: 56px; }
}
/* FF-21 (2026-09-03) - the online detail page's admin-authored custom content
   block (`render_event_custom_content()`), rendered directly above the first
   highlight block.

   ⚠ THIS ELEMENT HAS NO DESIGN EQUIVALENT. `event-detail.jsx:583-660` goes
   `.edx-shell` -> `.edx-highlights` -> `.edx-expect-shell` with nothing between
   them, and no component in that file takes per-event authored body copy. So
   every number below is BORROWED, not invented, at the owner's own instruction:
   *"centered and about the same width as the 'What to expect on date night
   (through late night)' container, but no background color and the text within
   the container should be left aligned."*

   `max-width` is therefore `.event-expect-shell`'s own expression verbatim
   (:331-337 above, itself `event-detail.jsx:643-645`), which resolves to 1008px
   - the design's reading column plus its disc gutter and insets. NOT a literal
   1008: if the plate's cap ever moves, this moves with it.

   ⚠ NO MEDIA QUERY, AND THAT IS THE POINT (FF-21-D1). This block is a child
   of `.section event-shell`, whose gutter ladder steps 56 / 48 / 40 / 20 per
   side (`styles.css:89, :146, :239, :315`) - exactly half of the plate's own
   112 / 96 / 80 / 40 width ladder (:358-382). So nesting alone reproduces the
   plate's width AND its horizontal position at 1100, 900, 768 and 640, and this
   file gains no seventh opportunity for the source-order-at-equal-specificity
   defeat it already carries six instances of.
   The one divergence, stated rather than hidden: between 1101 and ~1199 the
   shell's 96px desktop gutter is wider than the plate's, so this block is 909px
   at 1101 and 1008px from 1200 up, against the plate's flat 1008. Owner-ruled
   acceptable ("about the same width").

   The bottom margin is `--rhythm-96`, `.event-highlights`' own flex gap
   (:599 below), so the block sits in the rhythm of the stack it heads; the space
   ABOVE it is `.event-shell`'s existing `padding-top`, untouched. Type is
   `.body-lg`'s (`styles.css:1832-1837`) so the authored-HTML path and the
   generated-`<p>` path look identical. No background, per the owner. */
.event-custom-content { max-width: calc(var(--measure-intro) + 248px); margin: 0 auto var(--rhythm-96); font: var(--type-body-lg); color: var(--ink-700); letter-spacing: -0.2px; text-align: left; }
.event-custom-content > :first-child { margin-top: 0; }
.event-custom-content > :last-child { margin-bottom: 0; }
.event-custom-content p { margin: 0 0 16px; }

.event-timeline { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 32px; }
.event-timeline-item { display: grid; grid-template-columns: 64px 1fr; gap: 24px; align-items: flex-start; }
.event-step-num {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--grad-cta-purple);
  color: #fff;
  display: grid;
  place-items: center;
  font: var(--type-h3);
  box-shadow: var(--shadow-cta);
  flex-shrink: 0;
}
.event-step-content { display: flex; flex-direction: column; gap: 6px; }
.event-step-title { color: var(--couple-purple-dark); }
.event-step-body { margin: 0; }

/* "Watch our tutorial" link-out (`render_event_tutorial_cta()`) — same
   play-button-disc recipe on both the listing and detail pages. */
.event-tutorial-link { margin-top: 28px; }
.event-tutorial-watch { display: inline-flex; align-items: center; gap: 8px; }
.event-tutorial-play {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--grad-cta-purple);
  display: inline-grid;
  place-items: center;
  box-shadow: var(--shadow-cta);
  flex-shrink: 0;
}

/* Pre-event checklist (`render_event_checklist()`) */
.event-checklist {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px var(--rhythm-56);
}
.event-checklist-item { display: grid; grid-template-columns: 32px 1fr; gap: 16px; align-items: flex-start; }
.event-checklist-check {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--couple-purple);
  display: grid;
  place-items: center;
  flex-shrink: 0;
  margin-top: 2px;
}
/* SC-13 (2026-09-03) — one bullet per line at ≤900, the design's own step
   (`styles.css:756` for the detail page's `.edx-checklist`, `events.jsx:648`
   for the index's identical copy of the list). ⚠ `ul.event-checklist`, not
   `.event-checklist`: 0-1-1 beats the base above at equal source position and
   makes this rule immune to the next declaration someone appends to this file
   — SC-12-D1's ruling for this defect class, applied.
   ⚠ TWO PAGES, deliberately: `render_event_checklist()` is called from
   `templates/pages/event-detail.php` AND `templates/pages/events.php:108`, and
   the design steps both to one column at the same width. */
@media (max-width: 900px) {
  ul.event-checklist { grid-template-columns: 1fr; gap: 16px; }
}
@media (max-width: 640px) {
  .event-tutorial-link { flex-direction: column; align-items: flex-start; gap: 6px; }
  /* FF-18 — the plate's bleed narrows with the shell. `.section` drops to
     `56px 20px !important` at this width (`styles.css:315`), so the desktop
     -24px would push the plate 4px past the viewport on each side and the
     page would scroll horizontally (or `body { overflow-x: clip }` would
     silently crop the rounded corners — the exact failure mode SC-5 chased).
     -12px/12px is the design's own step at this tier (`events.jsx:755-756`). */
  .event-featured,
  .event-row { padding: 16px 12px; margin-left: -12px; margin-right: -12px; }
  /* The first month's divider appears only here — see `--phone` above. */
  .event-month-divider--phone { display: block; }

  /* SC-8 follow-up #1 — the phone tier's own column step: the portrait and
     its spacer narrow so the going cluster's 1fr survives down to 320px
     (`events.jsx:762-766`, `local-city.css:264-266`). */
  .event-row { grid-template-columns: 96px 27px 1fr; column-gap: 16px; row-gap: 12px; }
  .event-row > .event-host-tile { width: 96px; height: 96px; }
  .event-row .event-row-meta > .event-going { padding-left: 16px; }

  /* ============================================================
     SC-8 WALKTHROUGH FOLLOW-UP #2 (2026-09-01) — THE FEATURED HERO ON A PHONE.

     ⚠ **The design renders NO Featured Hero at this tier at all.**
     `events.jsx:919` is `{!isMobile && <FeaturedHero … />}` with
     `useIsMobile(640)` = `matchMedia('(max-width: 640px)')`, and `:928`
     puts `EVENTS[0]` back into the schedule list instead, `:937` giving its
     month a divider like every other. So on a phone the design is a single
     uniform list of row cards — there is no large hero lockup anywhere on it.

     Follow-up #1 left-aligned the hero but kept it a hero, which is why the
     top event still did not match. v3 renders server-side and cannot know the
     viewport, so it cannot simply drop the component the way React does.
     Duplicating the event into a second, hidden row was rejected — that puts
     two register buttons and two stretched-link overlays for one event in the
     DOM.

     Instead the hero WEARS THE ROW CARD at this tier: identical grid, areas,
     column steps and portrait size as `.event-row` above, with every wrapper
     dissolved to `display: contents` so its leaves place independently. The
     rendered result is the design's uniform phone list, from one copy of the
     markup. The 30px title is the design's own value for this exact element
     at this exact width (`events.jsx:757`).
     ============================================================ */
  .event-featured {
    grid-template-columns: 96px 27px 1fr;
    grid-template-areas:
      "media  date   date"
      "media  type   type"
      "hosted hosted hosted"
      "title  title  title"
      "price  price  price"
      "btn    btn    going";
    column-gap: 16px;
    row-gap: 12px;
    align-items: start;
  }
  .event-featured .event-host-tile--lg { grid-area: media; width: 96px; height: 96px; align-self: start; }
  /* ⚠ SC-8 follow-up #4 — THE TITLE TAKES THE ROW'S TYPE, NOT THE HERO'S.
     Follow-up #2 set `font-size: 30px` here from the design's
     `.ev-featured .h2-section` rule inside its own ≤640 block — and that is one
     of the VESTIGIAL rules follow-up #2 itself warned about, styling a
     component the design does not mount at this width. The owner's side-by-side
     shows the result: a 30px title wrapping to two lines where the design has a
     single ~18px line. On this tier the card IS a row, so the title wears the
     row's `.text-h3-tight` recipe (`styles.css:1806-1811`). */
  .event-featured .h2-section {
    font: var(--type-h3);
    letter-spacing: -0.3px;
    line-height: 1.2;
  }
  .event-featured-info { display: contents; }
  /* The hero's one-line date gives way to the row's stacked one. */
  .event-featured-when { display: none; }
  .event-featured .event-row-date--phone { display: flex; grid-area: date; align-self: start; }
  .event-featured-type { grid-area: type; align-self: start; }
  .event-featured .event-card-title-link { grid-area: title; }
  .event-featured-meta { display: contents; }
  .event-featured .event-hosted { grid-area: hosted; align-self: start; }
  .event-featured .event-going {
    grid-area: going;
    align-self: center;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;
    padding-left: 16px;
  }
  /* The hero's cluster is the 42px `--lg` tier at desktop; on the phone card
     it is the same element as a row's, so it takes the row's size too. */
  .event-featured .event-going--lg .event-going-avatar { width: 38px; height: 38px; margin-left: -12px; }
  .event-featured .event-going--lg .event-going-avatar:first-child { margin-left: 0; }
  .event-featured-cta { display: contents; }
  /* ⚠ Scoped `.event-featured .…` deliberately: the transparent base rule for
     both wrappers lives LATER in this file, and a media query adds no
     specificity — a bare `.event-featured-price` here (0-1-0) would lose to
     it and the badge would stay `display: contents`, unplaced. The row's rule
     above is already two-class for the same reason. */
  .event-featured .event-featured-price { display: block; grid-area: price; justify-self: start; }
  /* Same three CTA outcomes as the row card — the pill AND the two inert
     states, which auto-place and wreck the grid if left unassigned. */
  .event-featured .btn-pill,
  .event-featured .event-registered,
  .event-featured .event-card-status { grid-area: btn; align-self: center; justify-self: start; }
  /* ⚠ SC-8 follow-up #3 — THE REGISTER BUTTON WAS COVERING THE GOING AVATARS.
     `.btn-pill` carries `min-width: calc(240 * var(--el-px))` (`styles.css:1950`)
     and `--el-px` pins to 1 below 1100, so the hero's pill demanded **240px**
     inside a `btn` area only 96 + 16 + 27 = 139px wide — and a grid item does
     not shrink below its min-width, so it overflowed straight across the
     `going` column. Schedule rows never hit this because their CTA is
     `.btn-pill--sm`, which sets `min-width: 0` (`:1963`); the Featured Hero's
     is the plain marketing pill (`render_event_featured_hero()` passes
     `'btn-pill'`). On this card it is control chrome, so it takes the control
     metrics. `min-width` is the load-bearing line; the rest keeps it in
     proportion. */
  .event-featured .btn-pill {
    min-width: 0;
    height: 56px;
    padding: 0 28px;
    font-size: 18px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    /* ⚠ And the ROW's purple gradient, not the hero's gold. The Featured Hero
       passes the plain `.btn-pill`, whose background is `--grad-cta-yellow`
       (`styles.css:1953`); a Schedule row passes `.btn-pill--purple-grad`
       (`:1981-1985`). At this width the design has no hero, so the top event's
       CTA is a row's — purple. The owner's side-by-side shows gold on devsite
       against purple in the design. */
    background: var(--grad-cta-purple);
    color: var(--lavender);
    box-shadow: var(--shadow-cta);
  }

  /* SC-8 follow-up #3 — the expect timeline's phone reflow, which v3 never
     had: the number disc stops owning a full-height 64px left column that
     squeezes the body into a strip, and becomes a small badge inline with the
     step title, the body spanning full width beneath (`events.jsx:820-841`). */
  .event-timeline-item {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "num  title"
      "body body";
    column-gap: 12px;
    row-gap: 8px;
    align-items: center;
  }
  .event-step-num { grid-area: num; width: 36px; height: 36px; margin-top: 0; font-size: 18px; }
  .event-step-content { display: contents; }
  .event-step-title { grid-area: title; font-size: 20px; }
  .event-step-body { grid-area: body; }
}

/* Highlight blocks (`render_event_highlight_block()`) — detail page only */
.event-highlights { display: flex; flex-direction: column; gap: var(--rhythm-96); }
.event-highlight { display: grid; grid-template-columns: 1.05fr 1fr; gap: var(--rhythm-80); align-items: center; }
/* Reversed blocks swap the *track sizes*, not just visual order — the
   copy column stays the wider 1.05fr track either way (`event-detail.jsx`'s
   own `reverse ? '1fr 1.05fr' : '1.05fr 1fr'`), found only reordering
   (leaving copy squeezed into the narrower track) during the 3.1 devsite
   style-fidelity pass, 2026-07-22. */
.event-highlight--reverse { grid-template-columns: 1fr 1.05fr; }
.event-highlight--reverse .event-highlight-copy { order: 2; }
.event-highlight--reverse .event-highlight-media { order: 1; }
.event-highlight-bullets { list-style: none; padding: 0; margin: 20px 0 0; display: flex; flex-direction: column; gap: 10px; }
.event-highlight-bullets li { display: flex; align-items: flex-start; gap: 12px; font: var(--type-body); color: var(--ink-700); }
.event-highlight-check {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--couple-purple);
  display: grid;
  place-items: center;
  flex-shrink: 0;
  margin-top: 3px;
}
/* No box-shadow at rest — the design never frames a highlight photo with a
   card shadow (found over-styled during the 3.1 devsite style-fidelity pass,
   2026-07-22).
   ⚠ SC-13 follow-up #2 (2026-09-03) — **THE LAVENDER PLATE IS GONE, AND THE
   RADIUS MOVES TO THE ONE BLOCK THAT ASKS FOR IT.** The design's `HighlightBlock`
   styles its `<img>` with `width: 100%; height: auto; display: block` and NOTHING
   ELSE (`event-detail.jsx:390-393`); every other treatment arrives per-block via
   `imgStyle`, and **only the first ("Speed dating") block passes any** —
   `{ width: '85%', margin: '0 auto', borderRadius: 'var(--r-card)' }`
   (`:606-608`). **No block anywhere in the design carries a background.** All
   three of these assets are transparent cutouts, so v3's `background:
   var(--lavender)` was not an invisible loading state at all — it rendered as a
   lavender plate behind the comedians and the laptop that the design does not
   have, which is exactly what the owner's side-by-side shows. The radius is
   invisible on a cutout either way, so moving it to `--inset` costs nothing
   visible on blocks 2 and 3 and restores the design's own placement for
   block 1's opaque collage. */
.event-highlight-media img { width: 100%; height: auto; display: block; }
.event-highlight-media--inset img { width: 85%; margin: 0 auto; border-radius: var(--r-card); }
/* SC-13 follow-up #2 — **THE PHOTO GOES ABOVE THE COPY WHEN THE BLOCK STACKS.**
   The design's ≤900 rule is `.edx-highlight-copy { order: 2 }` /
   `.edx-highlight-media-col { order: 1 }` with `gap: 28px`, under its own
   comment "Highlights stack — media above copy, ignore reverse"
   (`styles.css:758-764`). v3 instead reset `order: initial`, which left source
   order — copy, then photo — so on a phone every block buried its image at the
   bottom. **This is the same defect, in the same shape, as the mobile host card
   this item already fixed**; the two rules were written together at 3.1 and were
   wrong together.
   ⚠ The base `--reverse` rules are 0-2-0 and still win here, but they already
   say `order: 2` (copy) / `order: 1` (media) — exactly what this block asks of
   every block — so reversed and normal stack identically. The `order: initial`
   reset is gone precisely because it undid that. */
@media (max-width: 900px) {
  .event-highlight,
  .event-highlight--reverse { grid-template-columns: 1fr; gap: 28px; }
  .event-highlight-copy { order: 2; }
  .event-highlight-media { order: 1; }
}

/* "Meet your hosts" grid (`render_event_host_card()`) — detail page only, real per-event host data */
.event-host-grid { display: flex; flex-direction: column; gap: var(--rhythm-56); }
.event-host-card { display: grid; grid-template-columns: 1.05fr 1fr; gap: var(--rhythm-56); align-items: center; }
/* Same "swap track sizes, not just order" fix as `.event-highlight--reverse` above. */
.event-host-card--reverse { grid-template-columns: 1fr 1.05fr; }
.event-host-card--reverse .event-host-card-copy { order: 2; }
.event-host-card--reverse .event-host-card-photo { order: 1; justify-self: start; }
.event-host-card-copy { display: flex; flex-direction: column; gap: 12px; }
.event-host-card-name { margin: 0; }
.event-host-card-photo,
.event-host-card-photo img,
.event-host-card-initial {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--r-card-lg);
  overflow: hidden;
  background: var(--lavender);
  box-shadow: var(--shadow-hero);
  object-fit: cover;
  display: block;
}
.event-host-card-initial { display: grid; place-items: center; font: 700 var(--type-h1); color: var(--couple-purple-dark); }
/* SC-13 (2026-09-03) — THE DESKTOP CAP, which v3 never had. Without it the
   photo takes its whole `1fr` track (~581px on a 1440 frame) and dwarfs the
   copy beside it — the owner's "the host image(s) are too large / way too
   large". The design caps it at its own source size and lets the column's
   slack fall BETWEEN copy and photo, so the photo still hugs the outer frame
   edge and no phantom gap opens at the frame (`event-detail.jsx:487-500`).
   ⚠ `justify-self`, not the design's `margin: 0 0 0 auto` / `0 auto 0 0`, and
   deliberately: the ≤900 block below stacks the card and an auto margin in the
   alignment axis OVERRIDES `justify-self` per spec, so auto margins here would
   have needed a 0-2-0 counter-rule inside that block just to stop every second
   (reversed) photo drifting. Grid alignment is the right tool and it resets
   with one 0-1-0 declaration. */
.event-host-card-photo { max-width: calc(460 * var(--el-px)); justify-self: end; }
/* SC-13 (2026-09-03), the owner's own re-check: "the host images for mobile …
   the design has them smaller also and left aligned, and the host name and info
   below the image". All three were v3 inventions — it stacked COPY first, then
   a 320px photo CENTERED. The design's stacked card is the exact opposite:
   `.edx-host-photo-col { order: 1 }` / `.edx-host-copy { order: 2 }`,
   `max-width: 360px`, `margin: 0` (`styles.css:784-787`), stepping to 280px at
   ≤640 (`styles.css:824`). The gap is the design's stacked 28px, not the
   desktop `--rhythm-56`.
   ⚠ The two `--reverse` rules in the base block are 0-2-0 and still win here —
   but they already say `order: 1` (photo) / `order: 2` (copy) and
   `justify-self: start`, i.e. exactly what this block asks of every card, so
   reversed and normal cards render identically once stacked. The old
   `order: initial` reset is gone precisely because it undid that. */
@media (max-width: 900px) {
  .event-host-card,
  .event-host-card--reverse { grid-template-columns: 1fr; gap: 28px; }
  .event-host-card-copy { order: 2; }
  .event-host-card-photo { order: 1; justify-self: start; }
  .event-host-card-photo, .event-host-card-photo img, .event-host-card-initial { max-width: 360px; margin: 0; }
}
/* ⚠ This block MUST stay below the ≤900 one above it. The file's own ≤640
   block sits ~200 lines EARLIER, and at 390px both match — so a 280px cap
   written up there would be silently overwritten by the 360px above at equal
   0-1-0 specificity. The same trap SC-12 and this item's checklist half were
   both caught by. */
@media (max-width: 640px) {
  .event-host-card-photo, .event-host-card-photo img, .event-host-card-initial { max-width: 280px; }
}

/* SC-11 (2026-09-02) — `cplEv_descript` in the detail hero, BOLD and
   UPPERCASE at the owner's direction. Deliberately ONLY those two
   declarations (SC-11-D5).
   ⚠ **CORRECTED 2026-09-03 (TD-2).** SC-11-D5's claim that "the paragraph
   keeps `.body-lg`'s size, colour and letter-spacing … so this class cannot
   drift into a second type ramp" IS NO LONGER TRUE, and the correction is
   recorded rather than left to be rediscovered: the owner added
   `font:var(--type-h3);` INLINE to that paragraph and to the mega prize span
   (`templates/pages/event-detail.php:115,131`, commit `cc8c4a8`), which is a
   `font:` SHORTHAND and therefore moves both onto the 24px Rubik display ramp
   — size, family, weight and line-height at once. It also restates
   `font-weight: 700`, so THIS rule's weight declaration is now redundant and
   only its `text-transform` still does work. An inline declaration beats this
   stylesheet, so do not try to win the size back from here.
   ⚠ `.body-lg`'s `font:` SHORTHAND sets `font-weight: 500` (`--type-body-lg`,
   `colors_and_type.css:313`), so this override is a real cascade contest and
   it is won by SOURCE ORDER, not specificity — both selectors are 0-1-0, and
   `head.php:33-36` emits `styles.css` before every `$extraStyles` entry.
   So it must stay in a stylesheet that loads AFTER `styles.css`: moved into
   `styles.css` above `.body-lg:1832` it would silently stop applying, with
   no error and no visible symptom but the missing caps — the SC-12 defect
   class exactly. */
.event-hero-descript {
  font-weight: 700;
  text-transform: uppercase;
}

/* Detail hero facts row + CTA */
.event-facts-row { display: flex; flex-wrap: wrap; gap: 20px var(--rhythm-56); padding: 4px 0; margin-top: 8px; }
.event-fact { display: flex; flex-direction: column; gap: 6px; }
/* SC-13 (2026-09-03) — the phone reflow v3 never had. Left-clustered at their
   natural width with a `--rhythm-56` column gap the four pairs wrapped
   1 / 2 / 1 (DATE · TIME+WHERE · COST) and stranded the right of the row — the
   owner's "the placement of the event date, time, where and cost is off".
   Growing each pair to a half-width basis flows them 2-up and spreads them
   across the available width: left edges align at 0 (Date/Where) and at the
   midline (Time/Cost). The design's own rule and its own diagnosis,
   `styles.css:775-783`. They stay flex-wrap — this is NOT the Matches
   shared-baseline grid.
   ⚠ Reaches the IRL detail page too (`in-person-event-detail.php:38`, which
   loads this file via `InPersonEventsController.php:44`), and that is
   design-faithful rather than a ripple: the design's In-Person detail
   deliberately reuses `.edx-facts-row` verbatim
   (`in-person-event-detail.jsx:7,218`). ≥769 is untouched. */
@media (max-width: 768px) {
  .event-facts-row { gap: 24px 24px; }
  .event-facts-row > .event-fact { flex: 1 1 40%; min-width: 0; }
}
/* Column, not row — the going-cluster sits below the CTA as a quiet
   caption (`event-detail.jsx`'s own `edx-cta-row`), not beside it. Found
   side-by-side during the 3.1 devsite style-fidelity pass (2026-07-22). */
.event-hero-cta { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; margin-top: 12px; }

.badge {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--r-pill);
  font: 700 12px var(--font-display);
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
/* Card title link — the heading itself is `.h2-section` (Featured Hero) or
   `.text-h3-tight` (Schedule row), same classes/sizes the reference uses
   directly (`events.jsx`), not a bespoke `.event-card-title` size (found
   pinned to a flat 22px for both contexts during the 3.1 devsite
   style-fidelity pass, 2026-07-22 — the reference sizes the Featured
   Hero's title a full step larger than a Schedule row's). Any event prize
   renders inline inside this same link, as a trailing `.grad-gold` span,
   not a separate paragraph below the title. */
.event-card-title-link {
  display: block;
  text-decoration: none;
  color: inherit;
}
/* SC-8 follow-up #3 — the badge wrappers are TRANSPARENT above the phone
   tiers, so the badge stays a direct flex child of its CTA column exactly as
   before the wrappers existed. `display: contents` is the design's own device
   for this (`events.jsx:557-559`: ".ev-price is transparent on desktop … ≤768
   it becomes a real grid item on its own row"). Without it the wrapper becomes
   the flex item and the desktop CTA column's gap/alignment shift by a hair —
   a regression introduced by the hooks themselves. The phone tiers below
   restore `display: block` and give each an area. */
.event-row-price,
.event-featured-price { display: contents; }
/* FF-18 (2026-09-01) — THE WHOLE LISTING ELEMENT IS THE LINK, the way the city
   page's rows already are (`local-city.php:127`'s `<a class="lc-event-row">`).

   ⚠ It is done with a STRETCHED OVERLAY rather than by wrapping the row in an
   `<a>`, and that is forced, not stylistic: these rows contain a real
   `<button class="js-event-register">`, and interactive content may not nest
   inside an anchor — the constraint `render_event_card_title()`'s own docblock
   has recorded since 3.1, and the reason the city page's structure cannot
   simply be copied here (its CTA is an inert `<span class="btn-pill">`).

   What the overlay buys over the design's `onClick` handler (`events.jsx:170`)
   is everything that makes the city rows feel like links and a click handler
   never does: the destination in the status bar on hover, ctrl/cmd-click and
   middle-click opening a new tab, and drag-to-bookmark — all from the browser,
   with no JS on this page at all. The `<li>` keeps its `listitem` semantics,
   the anchor stays the single tab stop, and no `stopPropagation` is needed. */
.event-card-title-link::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-card);
}
/* Every real control sits ABOVE the overlay so it keeps its own click:
   Register (`<button>`), Join / Register-when-signed-out (`<a>`) — all of them
   carry `.btn-pill` (`render_event_cta()`). The inert states deliberately do
   NOT: `.event-registered` ("You're in") and `.event-card-status` stay under
   the overlay, so clicking them navigates, exactly as on a city row. */
.event-featured .btn-pill,
.event-row .btn-pill {
  position: relative;
  z-index: 1;
}
/* SC-10 (2026-09-01) — the title must NOT underline on hover. The rule above
   sets `text-decoration: none` at specificity 0-1-0; the global
   `a:hover { text-decoration: underline }` (`colors_and_type.css:471`) is
   0-1-1 and was winning. The design cannot reproduce the underline because its
   title carries `textDecoration: 'none'` INLINE (`events.jsx:348`), which
   outranks the same global rule. `local-city.css:97` already fixed this on the
   city rows. The purple hover/focus colour below is the design's own treatment
   and is untouched. */
.event-card-title-link:hover,
.event-card-title-link:focus-visible {
  text-decoration: none;
}
.event-card-title-link:hover .h2-section,
.event-card-title-link:focus-visible .h2-section,
.event-card-title-link:hover .text-h3-tight,
.event-card-title-link:focus-visible .text-h3-tight {
  color: var(--couple-purple);
}
.event-card-title-link:focus-visible { outline: 2px solid var(--couple-purple); outline-offset: 4px; border-radius: 4px; }
.event-card-prize { margin-left: 12px; }

/* Registered-state confirmation lockup — containerless purple check
   circle + eyebrow ("You're in"), no pill/button chrome, matching the
   Confirmation page's own recipe (`event-detail.jsx`). Replaces the old
   plain-text "You're registered ✓" status line (found off-copy during the
   3.1 devsite text-fidelity pass, 2026-07-22). */
.event-registered { display: inline-flex; align-items: center; gap: 12px; color: var(--couple-purple); }
.event-registered-check {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--couple-purple);
  display: grid;
  place-items: center;
  flex-shrink: 0;
}

.event-card-status {
  font: 600 14px var(--font-body);
  color: var(--ink-500);
}

/* Paywall dialog (`#event-paywall-dialog`, `EventAccessDialog` in
   `event-access.jsx`) — the site's existing `.cd-scrim`/`.cd-panel`/
   `.cd-close` modal family (`styles.css`, ported for a future consumer but
   never actually wired to any template until now), not a bespoke
   `.modal-overlay`/`.modal-card` reinvented just for this one dialog
   (found duplicating that whole family — including a hand-rolled `×`
   glyph close button instead of the shared `CloseX` icon — during the
   3.1 devsite style-fidelity pass, 2026-07-22). */
.ea-offer { display: flex; flex-direction: column; gap: 14px; }
.ea-offer + .ea-offer { margin-top: 48px; }
.ea-offer-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.ea-offer-heading { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ea-offer-title { font: 700 var(--fs-eyebrow-lg)/1.15 var(--font-display); color: var(--fg-heading); letter-spacing: -0.3px; }
.ea-offer-price { font: 700 26px/1 var(--font-display); color: var(--fg-heading); letter-spacing: -0.6px; flex-shrink: 0; }
.ea-offer-blurb { font: var(--type-body-sm); color: var(--ink-500); margin: 0; }
.ea-offer .btn-pill, .ea-offer .btn-pill--sm { width: 100%; min-width: 0; }
.event-paywall-panel { max-width: 560px; max-height: min(92vh, 760px); overflow-y: auto; }

/* ============================================================
   REFERRED-USER NUDGE — MOVED OUT OF THIS FILE (SC-2, 2026-08-24).

   The nudge used to render as a `.referral-nudge` purple band inside
   `<main>` on both events pages, which put it UNDERNEATH the fixed nav
   (`site.css` `.nav-bar`, `position: fixed; top: 16px`) — the defect the
   owner reported 2026-08-24. It now renders in the site's one top-banner
   slot (`templates/partials/banner.php`, above the nav, cleared by the
   `body[data-banner]` offset), wearing the design's own `.top-banner`
   plate — so this file no longer styles it, and `partials/referral-nudge.php`
   is deleted.

   Recorded rather than removed silently, because the deleted rules carried
   a real decision worth keeping: the band was PURPLE rather than legacy's
   gold gradient, since on this page gold is reserved for no-cost states
   (the `EventCostBadge` "Free"/"Complimentary" tier) and a gold strip above
   the hero would read as a price claim. That reasoning still holds and is
   still satisfied: `.top-banner`'s plate is purple, with yellow used only
   as TYPE, never as the plate.
   ============================================================ */
