/* ============================================================
   518 Official — EVENT DETAIL page layout (permanent event page)
   css/pages/event-detail.css?v=5 — loads AFTER tokens.css + site.css.
   Owns events/<slug>/index.html layout ONLY (today: july-4th-518).
   Never restyles the shell, never overrides tokens, never declares
   :root. Classes are page-local (ed- prefix). Exceptions, by contract:
     .ev-hero-title / .dot — the motion.js event-page hero dialect
       (COPY_SEL names .ev-hero-title for stagger; events.css never
       loads on this page, so there is no cascade collision).
     .spec / .spec-row — the shared event fact-ledger pattern,
       duplicated here because page sheets never import each other.

   Palette law (RIZZ): cobalt chrome only — fills/strokes via
   --rizz/--rizz-line, copy via --rizz-text. No amber (commerce token;
   the merch stub is copy, not a commerce surface). Status and
   "nothing on file" labeling is deliberately NON-brand (ivory,
   dashed where a slot is held open) so honesty never reads as
   decoration.

   MEDIA LAW baked into this sheet:
     - FLYERS ARE NEVER COVER-CROPPED. .ed-hero-flyer is object-fit:
       contain, always — cropping a flyer destroys the information it
       exists to carry.
     - Every object-fit: cover surface (.ed-hero-bg, .ed-shot) carries
       its measured face-safe object-position from data/media.json.
     - The recap clip is a CONTENT video (has_audio: true): controls,
       no autoplay, and outside .vslot so no reduced-motion rule can
       hide a user-initiated video.

   TOC:
   00 Status bar      04 Lineup bill + stubs
   01 Hero + flyer    05 Admission (past, no sale)
   02 Facts + record  06 After-the-night: film, shots, empties
   03 Story + rail    07 Related + floor
   ============================================================ */

/* ---------- 00 Status notice bar ---------- */
/* Sits directly under the fixed header (main has no .page-top — the
   media hero runs flush behind it). */
.ed-status-bar {
  /* --chrome-h = ticket bar + header (css/ticket.css). The fallback keeps this
     rule correct on any page that ever ships without the ticket bar. */
  margin-top: var(--chrome-h, var(--header-h));
  background: var(--panel);
  border-bottom: 1px solid var(--line-strong);
  padding-block: 0.8rem;
}

.ed-status-bar p {
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--ivory-80);
  max-width: 90ch;
}

.ed-status-flag {
  display: inline-flex;
  margin-right: var(--sp-3);
  padding: 0.22rem 0.55rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ivory-60);
  transform: translateY(-0.08em);
}

/* ---------- 01 Hero — the flyer, over the crew's own coverage ---------- */
.ed-hero {
  position: relative;
  overflow: clip;                    /* clip NOT hidden — house law */
  padding-block: clamp(3.5rem, 10vw, 7rem);
}

/* Backdrop frame. Decorative (aria-hidden + empty alt) — the credit line
   in the hero copy carries its provenance. */
.ed-hero-bg {
  position: absolute;
  inset: 0;
  z-index: var(--z-bg);
}

.ed-hero-bg picture { display: block; width: 100%; height: 100%; }

.ed-hero-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 41.3% 15.4%;      /* FACE-SAFE CROP LAW — event-518-01 */
}

/* CINDY 2026-08-17 — CROP RESOLUTION FLOOR (measured, 390px).
   Stretched to inset:0 the backdrop box is 390x1140 on a phone. Covering a
   1600x1063 landscape room frame into that portrait box showed 22.7% of the
   picture at 0.93 SOURCE PIXELS PER CSS PIXEL — already upscaled at 1x, and
   2.15x upscaled on a DPR-2 phone. What survived was one blown-up, soft face:
   a room photograph cropped into an unattributed portrait, which is both a
   picture-quality miss and the wrong claim for coverage this page is careful
   not to attribute. So below the 860px stack point the backdrop becomes a
   BANNER sized to the copy block (which ends ~536px in) and masked out before
   the flyer starts (~572px in): 47% of the frame at ~1.95 source px/CSS px,
   and the flyer sits on flat ground with nothing competing behind it.
   min(100%, ...) keeps a shorter hero (a page with no flyer) filled as before.
   The scrim stays inset:0 — over --bg below the mask it composites to --bg,
   so there is no seam. */
@media (max-width: 859.98px) {
  .ed-hero-bg {
    bottom: auto;
    height: min(100%, clamp(420px, 140vw, 580px));
    -webkit-mask-image: linear-gradient(180deg, #000 0 62%, rgba(0, 0, 0, 0) 100%);
            mask-image: linear-gradient(180deg, #000 0 62%, rgba(0, 0, 0, 0) 100%);
  }
}

/* Scrim over the backdrop frame: bottom-heavy black so every line of hero
   copy sits on a measured-dark ground. Neutral rgba scrims are the allowed
   literal family. Held deliberately HEAVY (~0.82 composite at the lightest
   band) for two reasons: the flyer must out-rank the backdrop as the object
   this page is about, and the backdrop is unattributed coverage — texture,
   never a claim about who was in the room on this date. */
.ed-hero-scrim {
  position: absolute;
  inset: 0;
  z-index: var(--z-bg);
  background:
    linear-gradient(180deg, rgba(10, 10, 10, 0.9), rgba(10, 10, 10, 0.68) 45%, rgba(10, 10, 10, 0.95)),
    rgba(10, 10, 10, 0.45);
}

.ed-hero-inner { position: relative; z-index: var(--z-content); }

/* PHOTOGRAPHY FLOOR (JUAN, measured 2026-08-17): even under this hero's
   deliberately heavy ~0.82 composite scrim, --smoke at 14px measured
   4.44:1 against the lit pixels under its own glyphs — under AA, by a
   hair, which is exactly the kind of miss a flat-ground token audit never
   catches. The scrim is already as heavy as this picture can take, so the
   type steps up instead: --ivory-60 measures 6.31:1 on the same frame.
   OVER PHOTOGRAPHY THE TERTIARY FLOOR IS --ivory-60, NEVER --smoke. */
.ed-hero-inner .eyebrow { color: var(--ivory-60); }

/* Copy left, flyer right. Stacks to copy-then-flyer on small screens so the
   headline still owns the first screen. */
.ed-hero-grid {
  display: grid;
  gap: clamp(2.25rem, 5vw, 3.5rem);
  align-items: center;
}

@media (min-width: 860px) {
  .ed-hero-grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
}

.ed-hero-copy { min-width: 0; }

/* FLYER LAW: contain, never cover. The frame is an archival object —
   --flyer-ground sits darker than the poster it holds. */
.ed-hero-flyer {
  min-width: 0;
  width: 100%;
  max-width: 330px;
  margin: 0;
  /* 330px is the STACKED size — on a 390px phone that is already 85% of the
     screen. See the two-column bump below. */
  margin-inline: auto;
  padding: var(--sp-3);
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-2), inset 0 1px 0 color-mix(in srgb, var(--ivory) 6%, transparent);
}

.ed-hero-flyer picture { display: block; }

.ed-hero-flyer img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}

/* CINDY 2026-08-17 — CONTENT IS KING. Once the hero goes two-column the flyer
   owns a 520px track at 1440, and a 330px frame left 95px of dead air on each
   side of the one artifact this page exists to preserve — its printed times,
   address and bill were sub-legible at 304px of picture. 400px fills the track
   (374px of flyer) and the flyer reads. min(100%) keeps it inside the narrower
   track at the 860px switch. */
@media (min-width: 860px) {
  .ed-hero-flyer { max-width: min(100%, 400px); }
}

.ed-hero-flyer-cap {
  margin-top: var(--sp-3);
  font-size: var(--fs-micro);
  letter-spacing: 0.03em;
  line-height: 1.45;
  color: var(--ivory-60);           /* 13px — above the smoke-grey floor */
  text-align: center;
}

.ed-hero .eyebrow { margin-bottom: clamp(1.25rem, 3.5vw, 2rem); }

.ed-kicker {
  font-family: var(--font-logo);
  font-size: clamp(1.2rem, 3.6vw, 1.9rem);
  line-height: 1;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ivory-60);
  margin-bottom: clamp(0.5rem, 1.6vw, 0.9rem);
}

/* Motion-contract hero title (event dialect). Archivo Black is wide —
   ceiling tuned so a two-word title holds the 360px floor with its <br>. */
.ev-hero-title {
  font-size: clamp(1.9rem, 5vw, 3.6rem);
  line-height: 0.97;
  letter-spacing: -0.012em;
  text-wrap: initial;                /* the <br> owns the break */
  max-width: 14ch;
}

/* Terminal period — decorative cobalt mark (aria-hidden); --rizz-line
   still clears AA-large at display scale. */
.dot { color: var(--rizz-line); }

.ed-hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  margin-top: clamp(1.5rem, 3.5vw, 2.25rem);
  padding: 0;
  list-style: none;
}

.ed-hero-meta li {
  display: inline-flex;
  align-items: center;
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--bg) 62%, transparent);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-80);
}

.ed-hero-ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
}

/* --ivory-60, not --smoke: 13px grey sits under the house legibility floor,
   and this line sits over photography. Same swap site.css made on .flyer-tag. */
.ed-hero-credit {
  margin-top: clamp(1.5rem, 3.5vw, 2rem);
  font-size: var(--fs-micro);
  line-height: 1.5;
  letter-spacing: 0.04em;
  color: var(--ivory-60);
  max-width: 56ch;
}

/* Past-status stub — reads as a closed slab, not a broken button:
   dashed frame, no hover state, plain-language truth. */
.ed-ticket-stub {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0.85rem 1.4rem;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-60);
  cursor: not-allowed;
}

/* ---------- 02 Facts + archive record ---------- */
.ed-head { margin-bottom: clamp(2.25rem, 5vw, 3.5rem); }

.ed-display { font-size: var(--fs-display); text-wrap: initial; letter-spacing: -0.006em; }

.ed-head .sect-lead { max-width: 48ch; }

.ed-facts-grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 4rem);
  align-items: start;
}

@media (min-width: 980px) {
  .ed-facts-grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
}

/* Fact spec sheet — hairline label / value ledger (shared event pattern) */
.spec { border-top: 1px solid var(--line); }

.spec-row {
  display: grid;
  grid-template-columns: minmax(6rem, 8.5rem) 1fr;
  gap: var(--sp-3) var(--sp-4);
  padding-block: 0.85rem;
  border-bottom: 1px solid var(--line);
}

.spec dt {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--smoke);
  padding-top: 0.28em;
}

.spec dd {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--ivory-80);
}

/* Honesty / provenance microcopy */
.ed-note {
  margin-top: clamp(1.25rem, 3vw, 1.75rem);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--smoke);
  max-width: 52ch;
  padding-left: var(--sp-4);
  border-left: 2px solid var(--line-rizz);
}

.ed-note code {
  font-family: var(--font-logo);
  font-size: 0.86em;
  letter-spacing: 0.02em;
  color: var(--ivory-60);
}

/* Archive-record ledger — provenance rows + the one live control */
.ed-tools {
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  padding: clamp(1.25rem, 3vw, 2rem);
  box-shadow: var(--shadow-1);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.ed-tools-head {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--smoke);
}

/* Solid hairline: these rows STATE something true, they are not held-open
   slots. Dashed stays reserved for "nothing here yet" surfaces (.ed-stub). */
.ed-tool {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}

.ed-tool--live {
  border-style: solid;
  border-color: var(--line-rizz);
  gap: var(--sp-3);
}

.ed-tool-name {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ivory-80);
}

.ed-tool--live .ed-tool-name { color: var(--ivory); }

.ed-tool-sub { font-size: var(--fs-small); line-height: 1.5; color: var(--smoke); }

.ed-tool-btn { align-self: flex-start; }

/* ---------- 03 Story + lifecycle rail ---------- */
.ed-story {
  position: relative;
  overflow: clip;
  /* isolation, not decoration — see the .ed-ghost note below. */
  isolation: isolate;
}

.ed-story-grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: start;
}

@media (min-width: 980px) {
  .ed-story-grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
}

.ed-story-body > * + * { margin-top: clamp(1.25rem, 3vw, 1.75rem); }

.ed-story-body .lead { max-width: 44ch; }

.ed-story-body p:not(.lead):not(.ed-note) {
  color: var(--ivory-80);
  max-width: 58ch;
}

.ed-life {
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  padding: clamp(1.25rem, 3vw, 2rem);
  box-shadow: var(--shadow-1);
}

.ed-life-head {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--smoke);
  margin-bottom: var(--sp-4);
}

.ed-life-rail {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ed-life-rail li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding-block: 0.8rem;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--ivory-80);
}

.ed-life-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex: none;
  margin-top: 0.05em;
  border: 1px solid var(--line-rizz);
  border-radius: 50%;
  font-family: var(--font-logo);
  font-size: 0.9rem;
  line-height: 1;
  color: var(--ivory-60);
}

/* Chapter ghost numeral — decorative, parallax-driven, never read.
   CINDY 2026-08-17 — the "02" NEVER RENDERED. `.ed-ghost` is z-index -1
   (--z-bg), and `position: relative` + `overflow: clip` do NOT create a
   stacking context — so the numeral escaped to the root context and painted
   BEHIND its own section. `.ed-after` has no background of its own, so its
   "05" showed against the body and the bug read as a design choice; the
   `.sect--band` story section paints an opaque ground and swallowed its "02"
   whole. `isolation: isolate` on both sections makes each one a stacking
   context, so a negative-z child lands above the section's background and
   below its copy — which is where it was always meant to be. The band ground
   is lighter, so its numeral is mixed a touch stronger to read the same. */
.ed-ghost { display: none; }

@media (min-width: 768px) {
  .ed-ghost {
    display: block;
    position: absolute;
    top: clamp(1.5rem, 4vw, 3.5rem);
    right: var(--gutter);
    z-index: var(--z-bg);
    font-family: var(--font-logo);
    font-size: clamp(9rem, 17vw, 15rem);
    line-height: 0.8;
    letter-spacing: 0.01em;
    color: color-mix(in srgb, var(--ivory) 3.8%, transparent);
    pointer-events: none;
    user-select: none;
  }

  /* The band ground is lighter, so the same mix reads weaker on it. */
  .sect--band .ed-ghost { color: color-mix(in srgb, var(--ivory) 5.6%, transparent); }
}

/* ---------- 04 Lineup bill ----------
   PEOPLE LAW: only artists printed on this night's own flyer are listed.
   The role column is what the flyer credited, verbatim. */
.ed-bill {
  border-top: 1px solid var(--line);
  max-width: 62ch;
}

.ed-bill-row {
  display: grid;
  grid-template-columns: minmax(7rem, 10rem) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--sp-3) var(--sp-4);
  padding-block: clamp(0.9rem, 2.2vw, 1.2rem);
  border-bottom: 1px solid var(--line);
}

.ed-bill-role {
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--smoke);
}

.ed-bill-name {
  min-width: 0;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 3.4vw, 2rem);
  line-height: 1.05;
  text-transform: uppercase;
  letter-spacing: -0.008em;
  color: var(--ivory);
}

.ed-bill-time {
  font-family: var(--font-logo);
  font-size: 1.05rem;
  letter-spacing: 0.08em;
  color: var(--rizz-text);
}

.ed-bill-note { margin-top: clamp(1.5rem, 3.5vw, 2rem); }

/* ---------- 04b Stub panels (nothing-on-file surfaces) ---------- */
/* The honest "reserved" surface: solid panel, dashed inner rhythm via
   the tag, no fake thumbnails. */
.ed-stub {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: clamp(1.25rem, 3vw, 1.75rem);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--ivory) 5%, transparent);
}

.ed-stub--wide { max-width: 62ch; }

.ed-stub-tag {
  align-self: flex-start;
  padding: 0.28rem 0.6rem;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-xs);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ivory-60);
}

.ed-stub-title {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-h4);
  line-height: 1.35;
  color: var(--ivory);
}

.ed-stub-sub { font-size: var(--fs-small); line-height: 1.6; color: var(--smoke); }

.ed-stub-sub a {
  color: var(--rizz-text);
  text-decoration: none;
  border-bottom: 1px solid var(--line-rizz);
}
.ed-stub-sub a:hover { color: var(--ivory); border-bottom-color: var(--rizz-hot); }

/* ---------- 05 Admission — past, nothing on sale ---------- */
.ed-tickets {
  background:
    radial-gradient(72% 62% at 50% 0%, var(--rizz-haze), transparent 72%),
    var(--bg-2);
}

.ed-ticket-slab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  margin-bottom: clamp(2rem, 4.5vw, 3rem);
}

.ed-ticket-stub--lg {
  min-height: 56px;
  padding: 1.1rem 2rem;
  font-size: clamp(1rem, 2vw, 1.15rem);
}

.ed-ticket-note { font-size: var(--fs-small); color: var(--smoke); max-width: 46ch; }

.ed-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}

.ed-cta-row--center { justify-content: center; }

/* ---------- 06 After the night — film, coverage strip, empties ---------- */
.ed-after {
  position: relative;
  overflow: clip;
  isolation: isolate;                /* see the .ed-ghost note above */
}

/* CONTENT video (data/media.json med-recap-518-01, has_audio: true): native
   controls, no autoplay, and deliberately OUTSIDE .vslot so site.css's
   reduced-motion `.vslot video { display: none }` can never hide a video the
   visitor chose to play. */
.ed-film {
  max-width: 760px;
  margin-inline: auto;
  margin-bottom: clamp(2.25rem, 5vw, 3.25rem);
}

/* CINDY 2026-08-17 — ONE LEFT EDGE. At 1440 this section had three of them:
   the head and the coverage note at the container edge (x=80), the 2-up shots
   grid at the same edge, and the 760px player floating centred at x=340. The
   centred player read as an orphan between two left-aligned blocks. It keeps
   its size and joins the column instead — head, film, shots and note now share
   one edge, and the air that opens on the right is where the ghost numeral
   already lives. Below 980 the grid is narrow enough that the player fills it
   and `auto` margins are a no-op, so the rule stays scoped to wide screens. */
@media (min-width: 980px) {
  .ed-film { margin-inline: 0; }
  .ed-film-cap { text-align: left; }
}

.ed-film-video {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;               /* 1280 x 960 source — reserves the box, no CLS */
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
}

.ed-film-cap {
  margin-top: var(--sp-4);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--smoke);
  text-align: center;
}

/* Coverage strip. ::before sets the 3:2 floor; the picture fills absolutely
   so a grid-stretched tile has no dead gap. */
.ed-shots {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* JUAN 2026-08-17 — HIERARCHY. Four identical 309x206 tiles read as a
   contact sheet: on the page that IS this night, the crew's own coverage
   was smaller than the caption under it. Four frames don't divide into a
   lead-plus-supports block without leaving a hole, so they go 2-up at
   632x421 — 4x the picture area, no orphan cell, same 3:2 as the coverage
   strip on events.html, and roughly the recap player's width above it so
   the section reads as one column of evidence. */
@media (min-width: 980px) {
  .ed-shots {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-4);
  }
}

/* CINDY 2026-08-17 — the same hierarchy miss JUAN fixed above was still live
   at the other end: 2-up held all the way down, so on a 390px phone the crew's
   own coverage of the room was four 167x111 tiles — smaller than the caption
   underneath them, and the least important thing on the screen on the page
   that IS this night. One column below the 560 floor puts each frame at
   350x233 (4.5 source px per CSS px, sharp on any phone) and lets a picture
   be evidence rather than a thumbnail. */
@media (max-width: 559.98px) {
  .ed-shots { grid-template-columns: minmax(0, 1fr); }
}

.ed-shot {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--panel);
}

.ed-shot::before { content: ""; display: block; padding-top: 66.667%; }

.ed-shot picture { position: absolute; inset: 0; }

.ed-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* FACE-SAFE CROP LAW — measured focal points (data/media.json `focal`).
   Never place one of these frames in a cover slot without its value. */
.ed-shot--02 img { object-position: 77.5% 20.2%; }
.ed-shot--03 img { object-position: 39.3% 36.5%; }
.ed-shot--04 img { object-position: 52.1% 24.1%; }
/* ev06: 21.7%, re-derived so the square services.html tile stops halving a
   third face (note in css/pages/services.css). No-op in this 3:2 slot. */
.ed-shot--06 img { object-position: 21.7% 11.5%; }

.ed-shots-note { margin-top: clamp(1.5rem, 3.5vw, 2rem); }

/* Attributed night gallery: the crop box stays .ed-shot; the credit sits
   under it so it cannot cover a face. Unattributed nights keep li.ed-shot
   with no extra child, so this rule does not apply. */
.ed-shots > li:not(.ed-shot) {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ed-shot-credit {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--smoke);
  text-align: center;
}

.ed-after-grid {
  display: grid;
  gap: var(--sp-5);
  margin-top: clamp(2.5rem, 6vw, 4rem);
}

@media (min-width: 768px) {
  .ed-after-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 980px) {
  .ed-after-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- 07 Related + back ---------- */
.ed-rel-grid {
  display: grid;
  gap: var(--sp-5);
}

@media (min-width: 768px) {
  .ed-rel-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Whole card is the tap target — stretched-link pattern (house Click
   Protocol): the link's ::after covers the card at z-index 5. */
.ed-rel-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: clamp(1.25rem, 3vw, 1.75rem);
  background: var(--flyer-ground);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-1);
  transition: transform var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}

.ed-rel-card:hover,
.ed-rel-card:focus-within {
  transform: translateY(-4px);
  border-color: var(--line-rizz);
  box-shadow: var(--shadow-2);
}

.ed-rel-status {
  align-self: flex-start;
  padding: 0.28rem 0.6rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ivory-60);
}

.ed-rel-title {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-h4);
  line-height: 1.35;
  color: var(--ivory);
}

.ed-rel-sub { font-size: var(--fs-small); color: var(--smoke); }

.ed-rel-link {
  margin-top: auto;
  font-family: var(--font-label);
  font-weight: 600;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--rizz-text);
  text-decoration: none;
}

.ed-rel-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
}

.ed-rel-link:focus-visible { outline: none; }
.ed-rel-card:focus-within { outline: 2px solid var(--rizz-hot); outline-offset: 3px; }

.ed-back { margin-top: clamp(2rem, 4.5vw, 3rem); }

/* ---------- Small-screen floor (360) ---------- */
@media (max-width: 559.98px) {
  /* Role label above the name — three columns won't hold at 360. */
  .ed-bill-row {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    row-gap: var(--sp-1);
  }
  .ed-bill-role { grid-column: 1 / -1; }
}

@media (max-width: 420px) {
  .spec-row { grid-template-columns: 1fr; gap: 0.25rem; }
  .spec dt { padding-top: 0; }
  .ed-note { padding-left: var(--sp-3); }
  .ed-hero-ctas { align-items: stretch; flex-direction: column; }
  .ed-hero-ctas .ed-ticket-stub,
  .ed-hero-ctas .btn { width: 100%; justify-content: center; text-align: center; }
}

/* ---------- Outbound artist promo (under the recap film) ----------
   One link, data-driven (data/events.json -> artist_promo), currently only the
   Selli page carries it. LINK OUT by design: CSP is frame-src 'none', so an
   embedded player is impossible here — same law as the store. Styled as a
   quiet tool-row sibling of .ed-film-cap, not a second CTA: the page's one
   real CTA stays the booking route, and a promo link must not outrank it. */
.ed-promo {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}

.ed-promo-link {
  font-family: var(--font-label);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.95rem;
  color: var(--rizz);
  text-decoration: none;
}

.ed-promo-link:hover,
.ed-promo-link:focus-visible { text-decoration: underline; text-underline-offset: 3px; }

.ed-promo-sub {
  font-size: 0.85rem;
  color: var(--ivory-60);
}
