/* ===========================================================================
   Heaven's Glitter
   Built to concept 2 - Natural & Bold (round two).
   Mockup: design/mockup/home-864.png (720px native, built at 1200px, x1.667).

   Every value below traces to a measurement in design/SPEC.md. If a number
   here is not in SPEC.md, it is a bug - not a preference.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   VERTICAL RHYTHM, TIGHTENED 2 Sep 2026 AT JACOB'S REQUEST.

   The band padding tokens below and a handful of section paddings no longer
   match SPEC.md, and that is deliberate rather than drift. They were measured
   off the mockup and are now roughly 30% under it, because the built page
   read as too airy vertically once every section had real content in it -
   the mockup was drawn with less copy in several bands than the site ships.

   THIS IS THE ONE PLACE THE "every value traces to a measurement" RULE IS
   KNOWINGLY BROKEN. It is a client instruction, it is proportional rather
   than ad hoc, and SPEC.md still holds the measured originals, so the way
   back is to restore those numbers. Horizontal measurements are untouched -
   container width, gutters and column proportions all still trace.
   --------------------------------------------------------------------------- */

/* --------------------------------------------------------------- fonts ---
   Night Elevated Display - the client's own display face, rebuilt and
   supplied 30 Aug 2026, self-hosted (CLAUDE.md > The stack: self-host or
   Google Fonts, nothing else). It replaces Night Elevated Brush Italic, which
   replaced Permanent Marker (SPEC.md > Assumptions 1).

   It carries a `liga` substitution that maps the exact uppercase sequence
   "YOUR NIGHT, ELEVATED." to one master-drawn glyph, so the tagline is real
   lettering rather than a phrase set letter by letter. The markup writes the
   line in sentence case and `text-transform: uppercase` supplies the caps the
   ligature matches on; keep both, and keep the comma and full stop, or the
   substitution silently stops firing. `npm run font:check` asserts it fires.

   THREE THINGS CHANGED WITH THE REBUILD, and each moved a number:

   1. It is 2048 units per em, not 1000, and the phrase glyph now advances
      7.1221em against the old 6.3480. Every size that was set to render a
      measured WIDTH had to come down by that ratio - see --f-script.
   2. It is no longer flagged italic. The outlines are still drawn slanted
      (italicAngle -12), but the face declares itself Regular, so this is
      declared `normal` and `.script` no longer asks for italic. Asking would
      invite a browser to synthesise an oblique on top of the drawn slant.
   3. It gained three word-level ligatures (YOUR / NIGHT, / ELEVATED.) beside
      the full-phrase one. The full phrase is ordered first, so it still wins
      on the complete string - but a broken tagline can now land on the three
      word glyphs rather than 21 letters, which is much closer in width to the
      right answer. font-check.mjs tests all three outcomes for that reason.

   The .woff2 is generated from the supplied .ttf with fontTools; the .ttf and
   .woff are the client's files byte for byte. */
/* The client's second display face, supplied 1 Sep 2026, traced from the same
   approved pink A-Z proof sheet. Unlike the face it replaced, this one is a
   FULL ALPHABET with no GSUB table at all - 43 glyphs, A-Z, 0-9 and five
   punctuation marks - so any phrase sets in the real lettering and there is no
   ligature to lose. That is what lets the home tagline go back to live text.
   woff2 generated here from the supplied TTF (fontTools, py -3): 13.8 KB
   against the supplied WOFF's 17.5 and the TTF's 76. */
@font-face {
  font-family: "Night Elevated Display";
  src: url("/assets/fonts/NightElevatedDisplay-Regular.woff2?v=2") format("woff2"),
       url("/assets/fonts/NightElevatedDisplay-Regular.woff?v=2") format("woff"),
       url("/assets/fonts/NightElevatedDisplay-Regular.ttf") format("truetype");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

:root {
  /* --- Colour -------------------------------------------------------------
     Sampled off the mockup, never named by eye. SPEC.md > Colour. */
  --bg:            #04020C;   /* probed x845 in every band */
  --gold:          #C59746;   /* button border, eyebrow, labels */
  --gold-bright:   #E8C070;   /* gradient highlight */
  --gold-script:   #ECBC2D;   /* "Let's make it unforgettable." */
  /* ONE PINK, 3 Sep 2026. #F33CA7 is the colour of the home hero lettering,
     picked by Jacob as the site's pink; every other pink was normalised to it.
     It replaces #EC5FAF here and #EE3FC0 / #EF3FC1 / #FC38B1 across four
     separately-supplied artwork files, which had drifted apart as each new
     export arrived. NOT a measured value from the mockup - a client decision,
     and the one colour in this block that no longer traces to SPEC.md. */
  --pink:          #F33CA7;   /* icon strokes, attribution, all lettering */
  /* Held as a distinct lighter tone rather than collapsed into --pink: it is
     the hover and accent tint, and nav links need to differ from their resting
     state. Re-derived from the new pink by the same HSL step the old pair used
     (H -5, L +5.9, S +7.8), so the relationship survives the change. */
  --pink-light:    #FC51C3;   /* hover, accents, .ab-h2-pink */
  --card-line:     #633C21;   /* card border */
  --ink:           #FFFFFF;

  /* The same four colours as bare channels, so a translucent tint can go
     through the token instead of round-tripping the hex by hand. Written
     `rgba(var(--gold-rgb), .30)`, never as a fresh rgba() literal.

     This exists because the 3 Sep pink normalisation missed two rules: the
     form field border and /contact's rule were written rgba(236, 95, 175, .45)
     rather than as the hex, so a search for #EC5FAF could not see them and
     they kept the retired pink after every other pink had moved. A colour
     written out in channels is a colour that will be missed the next time one
     changes. There is exactly one place each of these four values lives now.

     Decorative gradient stops are deliberately NOT tokenised - the purple and
     amber glows in the hero and CTA radials are one-off stops that are not
     these colours, and inventing a token per stop would say they were.

     COMMA-SEPARATED, and called through the legacy rgba() rather than the
     modern `rgb(197 151 70 / .30)`. That is deliberate and was measured: the
     modern syntax is stored as float and the legacy one as 8-bit, so swapping
     the call sites over moved roughly 0.2-0.8% of subpixels on four pages by
     one level out of 255. Invisible, but it is a pixel change for no gain, and
     screenshots that diff clean are worth more than the tidier syntax. */
  --gold-rgb:      197, 151, 70;    /* --gold  #C59746 */
  --bg-rgb:        4, 2, 12;        /* --bg    #04020C */
  --pink-rgb:      243, 60, 167;    /* --pink  #F33CA7 */
  --ink-rgb:       255, 255, 255;   /* --ink   #FFFFFF */

  --ink-mut:       rgba(var(--ink-rgb), .78);
  --ink-dim:       rgba(var(--ink-rgb), .55);

  /* --- Layout -------------------------------------------------------------
     The two mockups disagree, and the events one wins. Home puts content at
     x21-785 of 864 (left gutter 21, right 79 - not centred). Events puts the
     SAME header component at x26-838 of 862: left 26, right 24, symmetric.
     A header cannot be two widths, so home's is the render drifting left, and
     the design is a near-full-width container with a 22px gutter.
       26 * 1.67 = 43 build, content 812 * 1.67 = 1356 build. */
  --wrap: 1200px;
  --gut: 36px;

  /* --- Type ---------------------------------------------------------------
     Sizes derived from measured cap heights (SPEC.md > Type scale). */
  --display: "Oswald", "Arial Narrow", sans-serif;
  --body: "Barlow Semi Condensed", "Arial Narrow", sans-serif;
  --serif: "Cormorant Garamond", Georgia, serif;
  --script: "Night Elevated Display", cursive;
  --script-fine: "Caveat", cursive;

  --f-h1: 63px;          --lh-h1: 73px;
  --f-h2: 25px;          /* section headings */
  --f-h2-perf: 35px;
  --f-h2-occ: 22px;
  /* --lh-cta IS 1.19x --f-cta AT ALL THREE SIZES, and was not before.
     Retuned 7 Sep 2026: "there is no spacing between ready to elevate and your
     next event." The measured 38 was read off a mockup that sets this heading
     as PLAIN TYPE. It does not any more - `.cta-art-elevate` is the client's
     lettering, 53px tall at every width, sitting inline at the end of line one
     with line two underneath it - so leading derived from the type alone had
     the descender swash landing on the next line's caps. Measured ink gap,
     artwork tail to the cap below: 8px here and 1px in both bands under it.

     The two bands under it were the worse bug and not a mockup question at
     all. `--f-cta` goes UP to 47 at 1180 while `--lh-cta` went DOWN to 30, and
     to 32/25 at 620 - LEADING BELOW THE FONT SIZE, which no two-line heading
     survives. Both are in blocks whose own header says they are derived from
     the desktop grid rather than measured, so there was nothing to depart
     from. It cost /about too: at 390 its heading wraps to "READY TO BRING /
     SEVEN" and those two lines touch, with no artwork involved.

     1.19 is one ratio for all three, which is what makes the gap scale with
     the type instead of being three separate numbers: 14px of ink gap at this
     size, 20 at 47px, 14 at 32px. Swept 38/44/48/52/56 at 1440, 30/40/46/52/58
     /64 at 878 and 25/34/40/46/52 at 390 before picking it; the gap tracks the
     leading 1:1, so these are the readings and not an estimate. */
  --f-cta: 37px;         --lh-cta: 44px;
  --f-nav: clamp(12px, 1.18vw, 14px);   /* 17px in the 1200 column */
  --f-eyebrow: 17px;
  --f-label: 18px;       --lh-label: 25px;
  --f-card-title: 13px;
  --f-btn: 15px;
  --f-body-hero: 19px;   --lh-body-hero: 28px;
  --f-body: 16px;        --lh-body: 23px;
  --f-body-perf: 17px;   --lh-body-perf: 29px;
  --f-small: 12px;       --lh-small: 22px;
  --f-script-fine: 35px;

  /* --- Rhythm -------------------------------------------------------------
     Section top and bottom padding are measured separately - they differ. */
  --hero-h: 690px;
  --hdr-top: 29px;
  --nav-gap: clamp(11px, 1.25vw, 15px);   /* 18px in the 1200 column */

  --team-pt: 20px;   --team-pb: 17px;
  --occ-pt: 12px;    --occ-pb: 17px;
  --perf-pt: 17px;   --perf-pb: 28px;
  /* The performance copy column. Measured off home-864.png and normalised to
     THAT render's own content box (x21-785), not to a shared scale: SPEC.md >
     Container records the home render as drifting left and setting narrow, so
     its pixels only map through its own box.
       copy text left    x399 -> 49.5% of the content box -> 714 build
       paragraph right   x723 -> 91.9%                    -> 1192 build
     The copy column starts at 44% of the 1128 content column = 652, so the
     inset is 714 - 652 = 62. It is min(,vw) so it tracks the body below 1200, the same
     idiom the header's fixed parts use. The box is 62 + 532 = 594, and needs
     no right padding now the band itself ends on the column: 532 puts the icon
     strip on its mapped 1246 and clears the 494 of ink Oswald sets the h2 in,
     which the mockup holds on one line (SPEC.md > Known differences). */
  --perf-copy-x: min(62px, 5.2vw);
  --perf-copy-w: 594px;
  --perf-p-w:   478px;
  --cta-pt: 13px;    --cta-pb: 21px;

  --card-gap: 15px;
  --card-h: 356px;
  --card-photo-h: 278px;
  --badge: 52px;

  /* --- Detail ------------------------------------------------------------- */
  --r-card: 4px;
  --r-btn: 2px;
  --r-img: 4px;
  --gold-fill: linear-gradient(100deg, #926E2B 0%, #DDB261 35%, #9F7B44 100%);
}

/* A minimal reset. Everything else belongs to the design. */
*, *::before, *::after { box-sizing: border-box; }

/* The whole site is a 1200px column, and nothing bleeds past it. The bands
   that used to run to the viewport edge - the hero photograph, the events
   tiles, both photo/copy splits - are all `inset: 0` inside a positioned
   parent, so constraining the body constrains them too, with no per-section
   changes. `position: relative` is load-bearing: the header is absolutely
   positioned, and without it the header would anchor to the viewport and be
   the one thing still bleeding.
   html carries the background so the margins either side are not white. */
html { background: var(--bg); }
body { margin: 0; max-width: var(--wrap); margin-inline: auto; position: relative; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--f-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gut);
}

/* ---------------------------------------------------------------- pieces --- */

.eyebrow {
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-eyebrow); line-height: 1;
  letter-spacing: .30em; text-transform: uppercase;
  color: var(--gold);
  margin: 0;
}

.h2 {
  font-family: var(--display); font-weight: 500;
  font-size: var(--f-h2); line-height: 1.15;
  letter-spacing: .02em; text-transform: uppercase;
  color: var(--ink);
  margin: 0;
}

/* font-feature-settings, not font-variant-ligatures, and it is load-bearing.
   Blink drops ligatures whenever letter-spacing is non-zero, so the supplied
   -.025em silently suppressed the very phrase glyph the face exists for - the
   tagline measured 205px (21 letters set individually) instead of 190px (the
   master drawing). font-variant-ligatures:common-ligatures does NOT override
   that; the low-level property does. Verified by `npm run font:check`. */
.script {
  /* Not italic: the face is drawn slanted but declares itself Regular, so
     requesting italic would only invite a synthesised second slant.
     The `font-feature-settings: "liga" 1` that used to be here went with
     Heaven's Elevated Trace on 1 Sep 2026 - Night Elevated Display has no
     GSUB table, so there is no ligature to force on or off. The -.025em
     tracking went with it too: that was the old face's measured value, and
     this one carries its own spacing. */
  font-family: var(--script); font-weight: 400; font-style: normal;
  letter-spacing: 0; word-spacing: 0.08em; font-kerning: normal;
  color: var(--pink-light);
}
.script-fine { font-family: var(--script-fine); font-weight: 600; color: var(--gold-script); }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--display); font-weight: 600;
  font-size: var(--f-btn); line-height: 1;
  letter-spacing: .10em; text-transform: uppercase;
  border-radius: var(--r-btn);
  /* NEVER WRAPS. Jacob, 7 Sep 2026: "lets never wrap the words in Contact Us
     button." It was on `.btn-ghost` only, so the outline buttons held one line
     and the gold ones broke to "CONTACT / US" whenever their box got tight -
     see the note on `.ab-cta p`, which sets a 290px body width specifically so
     that stopped happening on /about. A label that breaks in half is a broken
     button whatever it is filled with, so it belongs here on the base. */
  white-space: nowrap;
  transition: filter .15s ease, background-color .15s ease, color .15s ease;
}
.btn-gold {
  padding: 0 28px; height: 40px;
  background: var(--gold-fill);
  color: #1A1206;
  border: 0;
}
.btn-gold:hover { filter: brightness(1.08); }
.btn-ghost {
  padding: 0 28px; height: 39px; flex: none;
  border: 1px solid var(--gold);
  color: var(--gold);
  background: transparent;
}
.btn-ghost:hover { background: rgba(var(--gold-rgb), .14); }

/* The sprite carries lucide's geometry only; the stroke lives here so an icon
   inherits its colour from the block it sits in. */
.icon { display: block; width: 100%; height: 100%; stroke: currentColor; stroke-width: 1.25;
        stroke-linecap: round; stroke-linejoin: round; fill: none; }

/* A portrait photograph in a wide frame: `cover` keeps the middle, which on a
   group shot is torsos. Bias the crop up so the faces survive. */
.focus-top { object-position: center 22% !important; }

/* Placeholder for a photograph the client has not supplied yet. Deliberately
   obvious: an empty labelled frame is safer than a stock photo that ships. */
.ph {
  display: flex; align-items: center; justify-content: center;
  padding: 10px; text-align: center;
  background: repeating-linear-gradient(135deg,
    rgba(var(--ink-rgb), .035) 0 8px, rgba(var(--ink-rgb), .06) 8px 17px);
  border: 1px dashed rgba(var(--gold-rgb), .55);
  color: var(--gold);
  font-family: var(--display); font-weight: 400;
  font-size: 11px; line-height: 1.4; letter-spacing: .12em; text-transform: uppercase;
}

/* ---------------------------------------------------------------- header --- */

.site-header {
  position: absolute; inset: 0 0 auto; z-index: 3;
  padding-top: var(--hdr-top);
}
.site-header .wrap { display: flex; align-items: flex-start; gap: clamp(13px, 1.94vw, 23px); }

/* The wordmark is the client's supplied artwork (assets/wordmark.png). It
   replaced a three-layer type lockup that stood in for it: Oswald caps, a
   Caveat script and a tracked line. Sized by width only, so the aspect can
   never be squashed. Width is min(measured, vw) like the rest of the header,
   so it still measures 189px at 1440 and only shrinks below.

   The redrawn lockup supplied 29 Aug is 1.722:1, not the 2.025:1 it replaced -
   the script sits lower and ENTERTAINMENT is inset - so at the measured 189px
   it stands 110px tall against the old 93px. Width is what the mockup measures
   and what the header row constrains, so width is what is held; the extra
   height falls into the hero photo below, which the header is positioned over.
   SPEC.md > Known differences. */
.brand { display: block; line-height: 0; }
.brand img { display: block; width: clamp(125px, 13.17vw, 189px); height: auto; }

/* 40px, not the 62px this was built with: the nav sat ~20 build px right of
   the mockup on events (336 vs 356) and ~24 on services. Same offset twice, so
   it was the margin, not the page. */
.nav { display: flex; gap: var(--nav-gap); margin: 0 auto 0 clamp(10px, 1.94vw, 23px);
       padding-top: 2px; min-width: 0; }
.nav a {
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-nav); line-height: 1;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink); padding-bottom: 7px;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--pink-light); }
.nav a[aria-current="page"] { border-bottom-color: var(--pink); }

/* --- the header CTA -------------------------------------------------------
   SOLID GOLD SINCE 7 Sep 2026, on Jacob's ask: "can you use the solid gold
   background for all of contact us? dont use the transparent background one."
   It was `.btn-ghost` on all seven pages - the outline every mockup draws in
   the header - and it is `.btn-gold` now. A departure from every mockup at
   once, recorded in SPEC.md.

   The three rules that positioned it keyed off `.btn-ghost` and now key off
   `.site-header .btn`. That is not a loosening: the header has exactly one
   `.btn`, and the hamburger beside it is a `.navtoggle`, not a button class. */
.site-header .btn { flex: none; }

/* Visually hidden, still read aloud. The menu button's whole visible form is
   three bars, and a button with no text has no accessible name at all.

   NOT shared with the services cards' chevron ring, which hides its own
   "Explore" with the same declarations inside the 620 block. That one is
   hidden only at phone widths - the desktop card shows the word - so it is a
   breakpoint's doing, not a permanent property of the element, and folding
   the two together would hide it everywhere. */
.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* --- the phone header's menu button -------------------------------------
   design/mockup/mobile/services-440.png: three white bars, 25.3 x 17.1
   overall, right edge on the 36px gutter and vertically centred on the row.
   Bars measure 2.6 tall on a 7.2 pitch; drawn 3 on a 7 here, which puts the
   block back at the measured 17 and lands every bar on a whole pixel at 1x.

   `display: none` is the resting state at EVERY width, including the phone.
   It is `.site-header[data-nav]` that reveals it, and only nav.js sets that -
   so a page whose script did not load shows the nav row it always had rather
   than a button that does nothing. The same switch is what hides the row. */
.navtoggle {
  display: none; flex: none;
  /* 44 square is the touch target; the bars inside stay 25 wide. The negative
     margin is what that costs: centred in a 44px box the 25px bars sit 9.5
     inside it, so the box has to hang that far past the container edge for
     the BARS to land on the gutter the mockup measures them against. */
  width: 44px; height: 44px; margin-right: -10px;
  align-items: center; justify-content: center;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--ink);
}
.navtoggle-bars { position: relative; }
.navtoggle-bars,
.navtoggle-bars::before,
.navtoggle-bars::after {
  display: block; width: 25px; height: 3px;
  background: currentColor; border-radius: 1px;
  transition: transform .18s ease, top .18s ease, background-color .18s ease;
}
.navtoggle-bars::before, .navtoggle-bars::after { content: ""; position: absolute; left: 0; }
.navtoggle-bars::before { top: -7px; }
.navtoggle-bars::after  { top: 7px; }

/* The cross is NOT in the mockup, which draws only the closed state. It is
   here because the button is the control that closes the panel as well as
   opens it, and three bars over an open menu say nothing about how to get
   rid of it. Escape and a tap outside also close it. */
.site-header[data-nav="open"] .navtoggle-bars { background: transparent; }
.site-header[data-nav="open"] .navtoggle-bars::before { top: 0; transform: rotate(45deg); }
.site-header[data-nav="open"] .navtoggle-bars::after  { top: 0; transform: rotate(-45deg); }

/* ------------------------------------------------------------------ hero --- */

/* padding-bottom added 2 Sep 2026 with the client's brush lettering. The hero
   never had one because the mockup's content never reached the bottom: the
   old one-line outlined tagline was 79px tall at the measured 399px width,
   and the CTA row landed inside the 690px min-height. The brush lettering is
   TWO stacked lines - 2.78 aspect against 5.05, so 144px at the same width -
   and that pushed the CTA row flush onto the band boundary, touching it with
   0px to spare.

   The value is --gut, the measured container gutter, rather than a number
   invented for the job. The hero is now taller than --hero-h and that is the
   lettering's doing, not a re-measurement: 690 was measured against artwork
   the client has replaced. */
.hero { position: relative; min-height: var(--hero-h); display: flex;
        align-items: flex-start; padding-top: 206px; padding-bottom: 24px; }
.hero-media { position: absolute; inset: 0; overflow: hidden; }
.hero-media img, .hero-media .ph { width: 100%; height: 100%; object-fit: cover; }
/* `picture` is inline with auto height, so an img inside one resolves its
   `height: 100%` against a box of no height and collapses. Only /events uses
   one - it art directs a wide and a tall hero frame off a <source> - but the
   rule belongs here beside the img it exists for, and matches nothing on the
   pages that do not. Add a <picture> to any other hero and it just works. */
.hero-media picture { display: block; width: 100%; height: 100%; }
/* The mockup's stage light: a warm pool top-right over the photo. */
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(60% 70% at 78% 8%, rgba(190, 120, 255, .22), transparent 70%),
    linear-gradient(100deg, rgba(var(--bg-rgb), .92) 0%, rgba(var(--bg-rgb), .78) 28%, rgba(var(--bg-rgb), 0) 52%);
}
/* width:100% - as a flex item the container would otherwise shrink to its
   content and centre itself, pulling the H1 off the container edge. */
.hero .wrap { position: relative; z-index: 2; width: 100%; }

.hero h1 {
  margin: 0;
  font-family: var(--display); font-weight: 700;
  font-size: var(--f-h1); line-height: var(--lh-h1);
  letter-spacing: .01em; text-transform: uppercase;
}
/* The hero tagline, the client's brush lettering since 2 Sep 2026.

   399px WAS the measured value - the lettering stopped at the singer's raised
   forearm, x555 against a container starting at x156. That was a MAXIMUM set
   by the photograph, and it held while the artwork was the thin outline at a
   5.05 aspect: 399 wide made it 79 tall and it read as one line under the H1.

   The brush hand is 2.78, so the same width made a 144px block - nearly twice
   the height, sitting hard under "THE PRESENCE." and pushing the lede and the
   buttons down with it. 338 is Jacob's number, 3 Sep 2026, and it is a
   deliberate step INSIDE the measured maximum rather than past it: the
   lettering clears the forearm by more than it used to, not less.

   The 12px indent is his too. It sets the script off the H1's hard left edge
   so the two read as different voices rather than one ragged column.

   Everything below moves on its own: 338 makes the block 122px instead of
   144, and the margin-top coming 11 -> 4 takes the rest of the air out from
   under "THE PRESENCE." No margin on the lede or the buttons was touched. */
.script-art {
  display: block; margin-top: 4px; margin-left: 12px;
  width: min(338px, 100%); height: auto;
}
/* The hero lede. This was `.hero p:not([class])` while the lede was the only
   paragraph in any hero: an unscoped `.hero p` also caught the events page's
   eyebrow and won on specificity over `.eyebrow-sm`, so that eyebrow rendered
   at 19px in a 354px measure and wrapped to three lines. The home hero now has
   a second paragraph of its own - the phone eyebrow - so :not([class]) would
   have caught nothing and the lede would have lost its styling silently.
   Named directly instead, which is what the negation was standing in for. */
.hero .hero-lede {
  margin: 22px 0 0;
  max-width: 354px;
  font-size: var(--f-body-hero); line-height: var(--lh-body-hero);
  color: var(--ink-mut);
}
.hero-actions { display: flex; align-items: center; gap: 32px; margin-top: 22px; }

/* --- home hero: one DOM, two orders -------------------------------------
   design/mockup/home-864.png runs H1 -> lettering -> lede -> buttons.
   design/mockup/home-440.png runs lettering -> eyebrow -> H1 -> buttons, with
   no lede at all. The DOM is written in the phone's order and `order` puts it
   back for the desktop mockup, so neither breakpoint is the special case that
   has to undo the other.

   `align-items: stretch` is the default and is left alone deliberately: these
   were block children before, and shrink-to-fit flex items would pull the H1
   off the container edge. */
.hero-home .wrap { display: flex; flex-direction: column; }
.hero-home h1          { order: 1; }
.hero-home .script-art { order: 2; }
.hero-home .hero-lede  { order: 3; }
.hero-home .hero-actions { order: 4; }
.hero-home .hero-eyebrow { display: none; }

.play { display: inline-flex; align-items: center; gap: 12px; }
.play span:first-child {
  width: 39px; height: 39px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--pink); border-radius: 50%; color: var(--pink);
}
.play span:first-child svg { width: 18px; height: 18px; }
.play span:last-child {
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-btn); letter-spacing: .10em; text-transform: uppercase;
}

/* ------------------------------------------------------- band: one team --- */

.band { position: relative; }

.band-team { padding: var(--team-pt) 0 var(--team-pb); text-align: center; }
.band-team .h2 { margin-top: 13px; }

/* ------------------------------------------- the four-column feature grid --- */
/* These arrived 31 Aug 2026 with the "Meet Seven" band. That band was dropped
   from the home page on 3 Sep (00d3457) and the grid outlived it: .cols4 and
   .feature now sit inside .band-team above, as the four columns of the "one
   voice" band, which is their only use on the site.

   So they take the team band's padding and centring by containment rather than
   by borrowing them, and the one number of their own is the 34px top margin
   below. Still no mockup for it - SPEC.md > Assumptions 8, with /gallery and
   /404. A comment naming a band that no longer exists is how a rule that has
   quietly changed job gets edited as though it had not. */
.cols4 {
  display: grid; grid-template-columns: repeat(4, 1fr);
  margin-top: 34px;   /* was 52 - tightened 2 Sep, see the note at the top */
}
.cols4 > * + * { border-left: 1px solid rgba(var(--ink-rgb), .12); }
.feature { padding: 0 12px; }
/* The eight brand icons are supplied artwork (assets/icons-contact-sheet.png),
   so they carry their own pink and gold - no currentColor recolour. */
.feature .glyph { width: 67px; height: 67px; margin: 0 auto; object-fit: contain; }
.feature h3 {
  margin: 4px 0 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--f-label); line-height: var(--lh-label);
  letter-spacing: .06em; text-transform: uppercase; color: var(--gold);
}
.feature p { margin: 12px 0 0; color: var(--ink-mut); }

/* ----------------------------------------------------- band: occasions --- */

.band-occasions { padding: var(--occ-pt) 0 var(--occ-pb); text-align: center; }
.band-occasions .h2 {
  font-weight: 400; font-size: var(--f-h2-occ); letter-spacing: .04em;
}
.cards {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--card-gap); margin-top: 14px;
}
.card {
  height: var(--card-h);
  border: 1px solid var(--card-line); border-radius: var(--r-card);
  background: rgba(var(--ink-rgb), .02);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.card-photo { position: relative; flex: 1 1 var(--card-photo-h); min-height: 0; }
.card-photo img, .card-photo .ph { width: 100%; height: 100%; object-fit: cover; }
.card-photo::after {
  content: ""; position: absolute; inset: auto 0 0; height: 45%;
  background: linear-gradient(to top, rgba(var(--bg-rgb), .95), transparent);
}
.badge {
  position: absolute; left: 50%; bottom: -1px; z-index: 2;
  transform: translate(-50%, 50%);
  width: var(--badge); height: var(--badge);
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--gold); border-radius: 50%;
  background: rgba(var(--bg-rgb), .92); color: var(--pink);
}
.badge img { width: 25px; height: 25px; object-fit: contain; }
.card h3 {
  margin: 37px 7px 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--f-card-title); line-height: 1.2;
  letter-spacing: .03em; text-transform: uppercase; color: var(--gold);
}
.card p {
  margin: 11px 12px 15px;
  font-size: var(--f-small); line-height: var(--lh-small); color: var(--ink-mut);
}

/* --------------------------------------------------- band: performance --- */

/* ---------------------------------------------- the photo/copy splits ---
   Every band on the site that pairs a photograph with a copy column sits in
   the CONTENT COLUMN, like everything else on the page. It has to be said
   explicitly here because these bands carry no .wrap: they were designed to
   bleed off the viewport, and f563fed ("A 1200px column, nothing bleeding")
   stopped the bleed with one max-width on <body>. That was a blanket
   constraint, not a decision about these bands - so the photographs stopped
   at the BODY edge, which is one gutter proud of the column every other
   section aligns to. Neither bleeding nor aligned: at 1440 the photo started
   at 120 where the cards above it started at 156.

   .ab-story-grid never had the problem, because it is inside a .wrap. This
   gives the other four the same box. */
.band-perf, .band-cta-split, .split {
  /* The CONTENT width, not the wrap width with padding. Both put the grid in
     the same place, but only this one puts the band's own BOX there - and the
     events split draws a hairline rule on that box, which would otherwise
     overhang the column it separates by a gutter at each end.

     The min() is what keeps it honest below 1200, where the body stops being
     1200 and starts being the viewport: a flat max-width would let these bands
     run to the screen edge while every .wrap beside them still held its
     gutter, which is the same misalignment one breakpoint down. */
  width: min(calc(var(--wrap) - var(--gut) * 2), calc(100% - var(--gut) * 2));
  margin-inline: auto;
}

.band-perf {
  padding-block: var(--perf-pt) var(--perf-pb);
  display: grid; grid-template-columns: 44% 1fr; align-items: center;
}
.perf-media { grid-column: 1; align-self: stretch; position: relative; }
.perf-media img, .perf-media .ph { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.perf-media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent 55%, var(--bg) 100%);
}
.perf-copy { grid-column: 2; padding-left: var(--perf-copy-x); max-width: var(--perf-copy-w); min-width: 0; }
.perf-copy .h2 {
  font-weight: 600; font-size: var(--f-h2-perf); line-height: 1.1; letter-spacing: .01em;
}
.perf-copy p {
  margin: 21px 0 0; max-width: min(var(--perf-p-w), 100%);
  font-size: var(--f-body-perf); line-height: var(--lh-body-perf); color: var(--ink-mut);
}
.perf-copy .script-fine { display: block; margin-top: 15px; font-size: var(--f-script-fine); line-height: 1; }
.cols3 {
  display: grid; grid-template-columns: repeat(3, 1fr); margin-top: 30px;
  /* The strip runs the full copy column: the mockup's label ink alone spans
     x415-738, wider than the paragraph's x399-723, so a cap below the column
     is what left it stranded short of the h2. */
  max-width: 100%;
}
.cols3 > * + * { border-left: 1px solid rgba(var(--ink-rgb), .12); }
.stat { text-align: center; padding: 0 12px; }
.stat .glyph { width: 70px; height: 70px; margin: 0 auto; object-fit: contain; }
.stat span {
  display: block; margin-top: 7px;
  font-family: var(--display); font-weight: 500;
  font-size: var(--f-label); line-height: var(--lh-label);
  letter-spacing: .06em; text-transform: uppercase; color: var(--gold);
}


/* Sample copy the client has not confirmed. Removed with the review. */
.sample {
  display: inline-block; margin-bottom: 10px; padding: 2px 8px;
  border: 1px solid var(--pink); border-radius: 2px;
  font-family: var(--display); font-weight: 500;
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: var(--pink);
}

/* ------------------------------------------------------------ band: cta --- */

.band-cta {
  padding: var(--cta-pt) 0 var(--cta-pb);
  /* The bokeh is a supplied texture (home-cta-bokeh.jpg), as the mockup shows,
     with the gradient kept over it so the copy stays readable at any width. */
  background:
    radial-gradient(45% 120% at 82% 60%, rgba(150, 60, 190, .22), transparent 70%),
    linear-gradient(90deg, var(--bg) 12%, rgba(var(--bg-rgb), .55) 45%, rgba(var(--bg-rgb), .25)),
    url("/assets/photos/home-cta-bokeh.jpg") right center / cover no-repeat;
}
/* PACKED LEFT, NOT SPREAD. `justify-content: space-between` was dumping every
   pixel of slack into the two gaps: at 1440 the heading ended at x501 and the
   body did not start until x702, a 201px void, with another 110 before the
   button. The mockup does not do that - it sets the three blocks together in
   the left ~72% of the band and lets the bokeh have the right, and the gaps it
   draws are 38 and 54 native (x1.4764 onto the 1128 column) = 56 and 80.

   The button is no longer flush right as a result, and that is the mockup's
   own arrangement: it draws the button ending at x739 of a container that runs
   to 785. The trailing space is larger here than the 68 that scales to,
   because the label went site-wide from "CHECK YOUR DATE" to "Contact us" at
   `ff959a9` and the button sizes to its label - 192 built against 252
   measured. */
.band-cta .wrap {
  display: flex; justify-content: flex-start; align-items: center; gap: 56px;
}
.band-cta .btn-gold { margin-left: 24px; }   /* 56 + 24 = the measured 80 */
.cta-copy { max-width: 358px; }   /* the longer line measures 413px at 44px */
.cta-head {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: var(--f-cta); line-height: var(--lh-cta);
  letter-spacing: .01em; text-transform: uppercase;
}
/* The mockup gives "ELEVATE" 159 build px, and every face that has set it
   here got sized to hold that width rather than a cap height. Night Elevated
   Display advances 2.5640em for the word on paper, but its GPOS kern pairs
   pull the rendered word to 2.371em, so the size comes off the BROWSER not the
   advance table: 159 / 2.371 = 67. Third face to hold this number; the number
   has not moved. */
.cta-head .script { font-size: 67px; text-transform: uppercase; }
/* The About CTA sets its pink words as the client's outlined artwork. It sits
   inline in a serif heading, so it is sized by height against that heading's
   cap rather than by width, and nudged onto the baseline. The question mark
   is NOT in the artwork - it stays in the heading's own face, where sentence
   punctuation belongs. */
.cta-art {
  display: inline-block; vertical-align: baseline;
  width: 255px; height: auto; margin-bottom: -6px;
}
/* Retuned 3 Sep 2026 for lettering that now DRAWS ITS OWN QUESTION MARK.

   The -20px right margin that used to live here is gone with the text "?" it
   was compensating for: it closed the dead space between the old artwork's
   right edge and a mark set separately in the heading. Nothing sits after the
   image now, so there is nothing to pull in.

   WIDTH 195, DOWN FROM 290, 5 Sep 2026. At 290 the artwork rendered 87px tall
   against a 25px serif cap - three and a half times it - so it dwarfed "READY
   TO BRING SEVEN" above it and pushed the two lines so far apart they stopped
   reading as one heading. Jacob called it.

   The mockup cannot be met on both axes and this is the home hero's problem
   again: its own lettering is 155 native wide and 23 tall, which is 0.65 of
   the serif line's width and 1.53 of its cap, and the client's artwork is far
   more stacked than that (3.33 aspect against ~6.7). Holding the width gives
   275 and a block that dwarfs the line; holding the height gives 127 and a
   whisper. 195 is where the two land, and 290/235/195/160 were rendered and
   compared before picking it.

   Scoped to .ab-cta so none of it reaches the home CTA's "elevate", which
   shares .cta-art and still sets its own width. */
/* -27px is measured, not eyeballed. The artwork's word baseline sits 69.5%
   down its own box (density collapses there), leaving 30.5% of descender tail
   below it - 27px of the 87px this renders at. With vertical-align: baseline
   the image's margin-box bottom lands on the text baseline, so pulling the
   bottom margin in by exactly the tail puts the DRAWN baseline on the
   heading's own and lets the descenders hang below it, which is where
   descenders belong. -18 is that same 30.5% of the new 59px height. */
.ab-cta .cta-art { width: 195px; margin-bottom: -18px; }
/* The home CTA's "elevate", the client's own lettering from 2 Sep 2026. Width
   is the mockup's measured 159px for the word - the same number .cta-head's
   note derives its 67px font-size from, so the artwork lands where the type
   did. The text it replaced carried a 77px glyph box against this artwork's
   53px, so the nudge puts the drawn baseline where the font's baseline was. */
.cta-art-elevate {
  width: 159px; height: auto;
  vertical-align: baseline; margin-bottom: -4px;
  /* Jacob, 3 Sep 2026: down 7px to share a visual baseline with "READY TO".
     position/top rather than a bigger negative margin-bottom on purpose - a
     margin change would resize the line box and move the whole heading, where
     a relative offset moves only the ink and leaves the layout alone. */
  position: relative; top: 7px;
}
.rule { display: flex; align-items: center; gap: 7px; margin-top: 15px; }
.rule::before, .rule::after {
  content: ""; height: 1px; flex: 1 1 0; max-width: 90px;
  background: linear-gradient(90deg, transparent, var(--pink));
}
.rule::after { background: linear-gradient(90deg, var(--pink), transparent); }
.rule i { width: 6px; height: 6px; background: var(--pink); transform: rotate(45deg); }
/* 303 and 15/31, all three measured off the mockup's own body block: it sets
   x311-515 native = 205 wide (x1.4764 = 303) over THREE lines with 21 native
   of line spacing (= 31). The ink runs 13 native ascender-to-descender, which
   is ~10.4 native of em and 15 build px.
   It was 242 wide at --f-small's 12px on a 25px line, which set the same
   sentence in two cramped lines in a column too narrow to read as a block. */
.band-cta p {
  margin: 0; width: 303px; flex: none;
  font-size: 15px; line-height: 31px; color: var(--ink-mut);
}
.band-cta .btn-gold { height: 42px; }

/* ---------------------------------------------------------------- footer --- */

.site-footer { border-top: 1px solid rgba(var(--ink-rgb), .10); padding: 22px 0 33px; }
.site-footer .wrap {
  display: flex; justify-content: space-between; align-items: center; gap: 50px;
}
.site-footer .brand img { width: clamp(125px, 11.42vw, 164px); }
.site-footer p { margin: 0; font-size: var(--f-small); line-height: 23px; color: var(--ink-dim); }
.site-footer .legal {
  font-family: var(--display); font-weight: 300; letter-spacing: .06em;
  text-transform: uppercase; text-align: right;
}
/* SOCIAL MARKS AND THE LEGAL LINE ARE ONE COLUMN, which is how
   design/mockup/events-1024.png draws them - the row of marks sits directly
   over the copyright at the right end of the footer. Wrapping them together
   keeps `.site-footer .wrap` a three-item flex row, so the measured
   brand / copy / legal spacing on six pages does not move to make room. */
.footer-end { display: flex; flex-direction: column; align-items: flex-end; gap: 11px; }
.footer-social { display: flex; gap: 22px; margin: 0; padding: 0; list-style: none; }
.footer-social a { display: block; color: var(--pink); }
.footer-social a:hover { color: var(--pink-light); }
/* Solid single-path brand marks, not lucide strokes - see the note on BRAND
   in scripts/build-icons.mjs. They fill, and the shared `.icon` stroke has to
   come off or they render as a smear. Same pair of declarations
   `.page-contact .ct-social .icon` makes for the same three symbols. */
.footer-social .icon { width: 19px; height: 19px; fill: currentColor; stroke: none; }
/* NOT ON /contact, WHICH ALREADY HAS THEM. `.ct-social` sets the same three
   marks, larger and inside the labelled contact card. On a phone the two rows
   land ~400px apart with nothing between them, so the page was offering the
   identical three dead links twice. The card's set is the one that stays: it
   is bigger, it is what the contact mockup draws, and it sits with the email
   and phone rather than under a copyright. */
.page-contact .footer-social { display: none; }

/* ------------------------------------------------------------ responsive ---
   No mobile mockup was supplied (SPEC.md > Assumptions 4). Breakpoints below
   are derived from the desktop grid, not measured. */

@media (max-width: 1180px) {
  :root { --f-h1: 57px; --lh-h1: 57px; --f-cta: 47px; --lh-cta: 56px; --f-h2-perf: 32px; }
  .nav { gap: 22px; margin-left: 28px; }
  .nav a { font-size: 13px; }
  /* .brand img already shrinks with its own clamp. */
}

/* The CTA row - heading, body, button - needs ~1050px plus the 86px gutters to
   hold its heading on the two lines the mockup uses, so it stacks just under
   1150. It is its own breakpoint rather than the header's 1080 because it runs
   out first: at 1100 the heading went to three lines while the header was
   still fine. */
@media (max-width: 1150px) {
  /* The 24px that makes the desktop row's 80px body->button gap is a left
     margin, and this layout is a centred column - it would sit the button
     24px off centre. */
  .band-cta .wrap { flex-direction: column; gap: 23px; text-align: center; }
  .band-cta .btn-gold { margin-left: 0; }
  .cta-copy { max-width: none; }
  .band-cta p { width: auto; max-width: 358px; }
  .rule { justify-content: center; }
}

/* The header stops fitting on one row before anything else breaks: in the
   1200 column the nav clears the button by 120px, and by roughly 1020 they
   collide. Overlap is not overflow, so no checker catches it - it is found by
   measuring the gap between the last nav link and the button.

   The header is absolutely positioned so the hero photograph can run behind it,
   which is right while it is one 39px row. Once the nav wraps onto its own line
   the header is 179px tall at 820 and 242px at 414, against a hero whose copy
   started at 125px - so it sat ON the copy (17px over at 620, 60px at 414). So
   the wrap and the return to normal flow have to happen at the same width. */
@media (max-width: 940px) {
  /* `relative`, not `static` as this was: the menu panel is positioned
     against this box, and a static header would hand it to the nearest
     positioned ancestor - `body`, which carries `position: relative` for the
     1200 column - so `top: 100%` would open it off the BOTTOM of the page.
     It still takes part in normal flow, which is what the note above is
     about; relative with no offsets occupies exactly the space static did.

     THE `body` PREFIX IS NOT PADDING. /contact, /gallery and /404 each set
     `.page-x .site-header { position: static }` to keep their header out of
     the hero overlay, and those are two classes to this rule's one, so they
     win however this is ordered. `body .site-header[data-nav]` outweighs them
     on the element in the selector rather than on where it sits in the file,
     so a fourth page adding the same override cannot quietly break the menu.
     Scoped to [data-nav] so it only applies once nav.js has taken over. */
  .site-header { position: relative; padding: 22px 0 18px; }
  body .site-header[data-nav] { position: relative; }
  .site-header .wrap { flex-wrap: wrap; gap: 17px; }
  .nav { order: 3; width: 100%; margin: 0; flex-wrap: wrap; justify-content: center; }
  .hero { min-height: 433px; padding: 37px 0 50px; }
  .hero-inner { min-height: 392px; padding-top: 28px; }

  /* --- the hamburger header, from design/mockup/mobile/services-440.png ---
     Everything under [data-nav] is the ENHANCED header, and nav.js is the
     only thing that sets that attribute. The three rules above are what a
     visitor gets without it: the same wrapped nav row the site has always
     had. Nothing below may be relied on for the page to be navigable.

     940 rather than the mockup's own phone width because 940 is where this
     header already stops being one row - see the note above this block. Below
     it the choice was a hamburger or a wrapped row, and now there is one
     answer instead of two.

     The panel itself is NOT DRAWN ANYWHERE. The mockup shows the closed
     header only, so the open state is ours: SPEC.md > Assumptions. It is
     built from what the site already owns - the nav's own type, the gold
     hairline the bands and cards use, the page background - so it introduces
     no colour and no type size that was not already here. */
  .site-header[data-nav] .wrap {
    flex-wrap: nowrap; align-items: center;
    gap: clamp(12px, 2.4vw, 23px);           /* button -> hamburger 22.7 */
  }
  .site-header[data-nav] .navtoggle { display: inline-flex; }
  /* The nav supplied the spacer at desktop (`margin: 0 auto`). It is out of
     flow now, so the button has to push itself right. */
  .site-header[data-nav] .btn { margin-left: auto; }

  /* The row becomes the panel. Full bleed rather than inset, because the
     header is full bleed and a floating card would be the only rounded panel
     on the site. */
  .site-header[data-nav] .nav {
    position: absolute; top: 100%; left: 0; right: 0; z-index: 4;
    display: none;
    order: 0; width: auto;
    flex-direction: column; flex-wrap: nowrap;
    align-items: stretch; justify-content: flex-start;
    gap: 0;
    padding: 4px var(--gut) 12px;
    background: var(--bg);
    border-top: 1px solid rgba(var(--gold-rgb), .30);
    border-bottom: 1px solid rgba(var(--gold-rgb), .30);
    box-shadow: 0 18px 44px rgba(0, 0, 0, .55);
  }
  .site-header[data-nav="open"] .nav { display: flex; }

  .site-header[data-nav] .nav a {
    padding: 13px 0 11px;
    font-size: 17px;
    border-bottom: 1px solid rgba(var(--ink-rgb), .10);
  }
  .site-header[data-nav] .nav a:last-child { border-bottom: 0; }
  /* The pink underline the desktop nav marks the current page with would run
     the full width of a stacked row here and read as another divider. The
     colour carries it instead, which is the same signal in the same ink. */
  .site-header[data-nav] .nav a[aria-current="page"] { color: var(--pink); }

  /* The wordmark, and the one number in this header the mockup gives
     directly: 96.7 wide at 440. min() rather than a fixed width so it meets
     the desktop clamp's 125 at the top of this range and tapers below the
     mockup's width instead of crowding the button at 360 and 320. */
  .brand img { width: min(125px, 22vw); }
}

@media (max-width: 900px) {
  /* The wash turns from left-to-right into top-to-bottom, so it covers copy
     that no longer sits in the left half. It is right for the inner-page
     heroes, which are short bands with their subject high in the frame -
     /about, /services and /events all read fine under it. */
  .hero-media::after {
    background:
      radial-gradient(60% 60% at 70% 10%, rgba(190, 120, 255, .22), transparent 70%),
      linear-gradient(180deg, rgba(var(--bg-rgb), .55) 0%, rgba(var(--bg-rgb), .90) 60%);
  }
  /* ...AND NOT ON THE HOME HERO, which is the one full-height hero on the site.
     The same 180deg wash puts its .90 end across the middle of the frame, which
     is exactly where Seven stands, so the photograph went visibly flat the
     moment the viewport crossed 900 - reported at 878 on 7 Sep 2026.

     THE VERTICAL WASH IS NOT EARNING THAT. This hero's copy column is a fixed
     358px block that does not reflow between 900 and 620, so it stays in the
     left half far longer than the 900 breakpoint assumes: it ends at 45% of
     the viewport at 878 and only reaches 63% at 621, where `.hero-home`'s own
     phone block takes over and drops the wash entirely.

     Measured rather than assumed. Forcing the desktop wash across the band and
     sampling the composited backdrop behind `.hero-lede` - 90th percentile
     luminance, the brightest tenth being what actually costs legibility - the
     lede holds WCAG AA against `--ink-mut` all the way down:

       878 -> 16.2    768 -> 9.9    700 -> 6.2    660 -> 5.3    621 -> 4.7

     THE ONE CHANGE FROM THE DESKTOP WASH IS ITS LAST STOP, 52% -> 56%. At 52
     the bottom of the band fell just under AA (4.46 at 621, 4.64 at 640) as
     the copy outgrew the covered half. 56 clears it at every width and costs
     nothing at the top of the band: 16.21 against 16.20 at 878, which is the
     same photograph. 60 and 64 were measured too and buy contrast nobody
     needs at the price of dimming the frame this note exists to un-dim.

     The desktop rule above 900 is untouched - its copy ends at 27% of a 1440
     viewport and its 52% stop is the mockup's. */
  .hero-home .hero-media::after {
    background:
      radial-gradient(60% 70% at 78% 8%, rgba(190, 120, 255, .22), transparent 70%),
      linear-gradient(100deg, rgba(var(--bg-rgb), .92) 0%, rgba(var(--bg-rgb), .78) 28%, rgba(var(--bg-rgb), 0) 56%);
  }
  /* ...AND NOT ON /services EITHER, reported 7 Sep 2026 at 828: "that same
     grayed out hero that we saw at 828 px on the homepage also appears on the
     services tab." Same rule, same cause - the 180deg wash covers the whole
     frame, and this photograph is lit from the top right, which is exactly
     where it puts its .90 end.

     IT TAKES THE DESKTOP WASH UNCHANGED, at its own 52% stop, where the home
     hero needed 56. That is a reading, not an assumption: this hero's copy
     sits at the BOTTOM LEFT of a short band rather than filling the left half
     of a tall one, so the part of the frame it needs held down is the part the
     100deg wash is already darkest over. With it forced across the band and
     the backdrop sampled at 90th-percentile luminance:

       behind the h1     6.59 at 900, 6.76 at 828, 6.39 at 621
       behind the lede  15.44 at 900, 15.43 at 828, 14.25 at 621

     against WCAG AA's 4.5, and the h1 is large text, which only needs 3.
     Sampled across the full copy box, which spans 96% of the viewport - so
     those figures include the lit right-hand side of the photograph and are
     the conservative reading rather than a flattering one. */
  .hero-services .hero-media::after {
    background:
      radial-gradient(60% 70% at 78% 8%, rgba(190, 120, 255, .22), transparent 70%),
      linear-gradient(100deg, rgba(var(--bg-rgb), .92) 0%, rgba(var(--bg-rgb), .78) 28%, rgba(var(--bg-rgb), 0) 52%);
  }
  /* /about goes with it. Not reported - found by checking the other two heroes
     on this rule after /services was fixed, because three pages sharing one
     bug and one of them fixed is worse than none of them fixed. Same reading,
     same margin: h1 9.91 at 900, 9.19 at 828, 7.13 at 641; lede 16.08 / 15.05
     / 10.42, against AA's 4.5.

     /events is the one that does NOT go with them - see the note below. */
  .ab-hero .hero-media::after {
    background:
      radial-gradient(60% 70% at 78% 8%, rgba(190, 120, 255, .22), transparent 70%),
      linear-gradient(100deg, rgba(var(--bg-rgb), .92) 0%, rgba(var(--bg-rgb), .78) 28%, rgba(var(--bg-rgb), 0) 52%);
  }
  /* /events goes with them, and this one was a decision rather than a reading.
     Its hero is dimmed in this band like the other three, but "See Seven Live"
     is the client's PINK LETTERING over the bright gold arch of the
     photograph, so its contrast is artwork-against-backdrop, not a CSS
     `color`. Measured against `--pink`'s own luminance, backdrop at 90th
     percentile:

                              900    878    828    768    700    641
       180deg (was)          2.24   2.23   2.20   2.17   2.13   2.09
       100deg (this)         1.14   1.19   1.31   1.47   1.67   1.81

     Both are under the 3:1 that large text needs, and this is the lower of
     the two - so the flat wash was, by accident, the best that lettering has
     ever read. It came off anyway: Jacob, 7 Sep 2026, "artwork over photograph
     is fine for now, just fix that scrim." The trade was put to him with those
     numbers and he took the photograph.

     THE ROW THAT MATTERS FOR CONTEXT IS THE SECOND ONE: it is what has shipped
     at 901 and up since this hero was built, so this changes the band to match
     the desktop rather than introducing a new low. If the lettering is
     revisited, it is a question about the artwork over that photograph at
     EVERY width, not about this breakpoint. */
  .page-events .hero-live .hero-media::after {
    background:
      radial-gradient(60% 70% at 78% 8%, rgba(190, 120, 255, .22), transparent 70%),
      linear-gradient(100deg, rgba(var(--bg-rgb), .92) 0%, rgba(var(--bg-rgb), .78) 28%, rgba(var(--bg-rgb), 0) 52%);
  }
  .cols4, .cards { grid-template-columns: repeat(2, 1fr); row-gap: 37px; }
  /* "Consistent card heights" applies to the WHOLE 2-up range, not just the
     phone half of it. This grid has been two columns since 900 and its rows
     have been sizing independently the whole time - 3/3/2/2 lines of
     description means the top row stands a line taller than the bottom one at
     every width from here down to 620. Cheaper to fix at the top of the range
     than to explain why the same grid equalises at 620 and not at 700. */
  .cols4 { grid-auto-rows: 1fr; }
  .cols4 > * + * { border-left: 0; }
  .cols4 > :nth-child(odd) + * { border-left: 1px solid rgba(var(--ink-rgb), .12); }

  /* THE CARD STOPS BEING A FIXED BOX HERE, because that box is what was
     cutting the tops of heads off.

     `.card` is 356px tall with a 278px photo, and both numbers belong to a
     QUARTER-WIDTH desktop column: at 1440 the box renders 269x231. Every step
     down widens the card without touching its height, so a 600x750 portrait
     gets squeezed into a strip and `cover` takes that strip out of the middle:
     405x253 (1.60:1) at 900, and 546x253 (2.16:1) at 620, where only 37% of
     the source's height survived and a centred window landed on chests.

     Re-aiming the crop was the first fix and it was the wrong one - it kept
     the heads but still threw away two thirds of every photograph. The frame
     is what is wrong, so the frame is what changes: the photo takes the shape
     SPEC.md measured off the mockup, `Occasion cards` - card width 183 mean,
     photo area y798-1030 = 232, so 183/232 = 0.789. That is a PORTRAIT, and
     within 2% of these files' own 0.80, so the picture is now shown whole
     rather than cropped at all. The card grows to fit it instead of clipping.

     It is also SHORTER than what it replaces, which is the part worth
     noticing: the band at 592 goes 1535px -> 921px, because the cards stay
     two-up. Full-width one-column cards with a portrait photo would be
     3148px. */
  .band-occasions:not(.ab-rooms) .card { height: auto; }
  .band-occasions:not(.ab-rooms) .card-photo { flex: none; aspect-ratio: 183 / 232; }
  .band-perf { grid-template-columns: 1fr; }
  .perf-media { min-height: 350px; }
  /* CENTRED FROM HERE DOWN. Jacob, 7 Sep 2026: once this copy stops being the
     right-hand column of a two-column band and gets a row of its own, "that
     whole section down to the live vocal icons row" reads as left-aligned,
     because it is - the base rule's `max-width: var(--perf-copy-w)` is a 594px
     column measured against a 44/56 split that no longer exists here, so it
     was holding the heading, the paragraph, the script line and the three
     stats hard left with ~210px of dead space beside them at 878.

     The 594 STAYS and is centred rather than removed. Dropping the cap would
     let the paragraph set an 806px line at this width, which is twice the
     measure it was written for; `justify-self: center` keeps the measured
     column and puts it in the middle of the row, which is what was asked for.

     `.perf-copy p` needs its own auto margins because it carries a 478px cap
     of its own - centred text in a left-aligned box is still a left-aligned
     box. `.cols3` does not: it is `max-width: 100%` and fills the column. */
  .perf-copy {
    grid-column: 1; padding: 37px var(--gut) 0;
    justify-self: center; text-align: center;
  }
  .perf-copy p { margin-inline: auto; }
  .band-cta .wrap, .site-footer .wrap { flex-direction: column; gap: 23px; text-align: center; }
  .band-cta .btn-gold { margin-left: 0; }
  .band-cta p { width: auto; max-width: 350px; }
  .rule { justify-content: center; }
  .band-cta p { max-width: none; }
  .site-footer .legal { text-align: center; }
  .site-footer .brand { display: inline-block; }
  /* The footer stacks and centres here, so its right-aligned end column has
     to centre with everything else. */
  .footer-end { align-items: center; }
}

@media (max-width: 620px) {
  /* min(measured, vw) - Assumptions 7. Load-bearing here in a way it is
     not elsewhere: the tagline is ONE ligature glyph, so it cannot wrap, and
     a fixed size overruns the viewport instead of breaking. 12.2vw keeps it
     inside the 36px gutters down to 320. Rebased for Heaven's Elevated
     Trace: the phrase advances 7.1221em against Night Elevated's 6.3480, so
     the ceiling drops 74 -> 66 and the vw figure 12.2 -> 10.9. At 320 that
     is 34.9px and 247.5px of ink in the 248px the gutters leave. */
  :root { --f-h1: 38px; --lh-h1: 40px; --f-body-hero: 16px; --lh-body-hero: 24px;
          --f-h2: 22px; --f-h2-occ: 20px; --f-h2-perf: 25px; --f-cta: 32px; --lh-cta: 38px; }
  /* --- the four feature icons go 2x2, not 1x4 ---------------------------
     Jacob, 7 Sep 2026: "we have a bunch of icons that are vertically
     stacking... use two equal-width columns, slightly smaller icons,
     consistent card heights, about 16px between columns and 24-32px between
     rows, centered text, with each description limited to roughly 3-4 lines."

     Every number below is his. `grid-auto-rows: 1fr` is what "consistent card
     heights" means in a grid - without it each row sizes to its own tallest
     item, so the two rows differ by however much the copy differs. The type
     comes down with the column: 151px of column at 390 against the 318 the
     gutters leave, so the 16px body and 18px label of the full-width version
     would set two and three words to a line.

     `.feature`'s 12px of side padding comes off. It was breathing room in a
     quarter of 1128; here it is 24 of 151, and the descriptions need every
     pixel to hold four lines. The text is already centred by `.band-team`. */
  .cols4 {
    grid-template-columns: repeat(2, 1fr);
    column-gap: 16px; row-gap: 28px; grid-auto-rows: 1fr;
  }
  /* --- and they SCALE, rather than stepping ------------------------------
     Jacob again, same day: "once we get < 620 they shrink down tiny... there
     should be multiple steps as they get smaller and not just big and small."

     He is describing a real fault and not a preference. At 621 this grid is
     already 2-up, and the column is 274px; at 620 it is STILL 274px, and a
     flat set of phone values dropped the icon 67 -> 52 and the label 18 -> 14
     for a column that had not changed by a pixel. The shrink is earned at 390,
     where the column is 151, and nowhere near earned at 620.

     So every one of them is a clamp between two ANCHORS rather than a step:

       at 620 -> exactly what 621 renders, so nothing moves at the breakpoint
       at 360 -> the values measured to fit, and the floor below it

     Each is `clamp(floor, calc(A*vw + B), ceiling)` with A and B solved
     through those two points, so the size tracks the column continuously in
     between - the "multiple steps" is every pixel of viewport. Verified at
     620: 67.0 / 17.98 / 16.02 against 621's 67 / 18 / 16.

     The glyph goes through a custom property because it is a square and the
     img needs the same number twice. */
  .feature {
    /* padding-INLINE. The base rule is `padding: 0 12px`, so a shorthand here
       would have added 12px of top and bottom to a card that never had any. */
    padding-inline: clamp(0px, calc(4.6vw - 16.6px), 12px);
    --glyph: clamp(46px, calc(8.08vw + 16.9px), 67px);
  }
  .feature .glyph { width: var(--glyph); height: var(--glyph); }
  .feature h3 {
    font-size: clamp(13px, calc(1.92vw + 6.08px), 18px);
    line-height: 1.3; letter-spacing: .05em;
  }
  .feature p {
    margin-top: 8px;
    font-size: clamp(12.5px, calc(1.35vw + 7.65px), 16px);
    line-height: 1.4;
  }
  .cards { grid-template-columns: 1fr; }
  /* ...but NOT the occasions band. One column makes its card full-width, and
     a full-width card with the portrait photo above is a 3148px band at 592 -
     three times the scroll for the same four cards. Two-up keeps the mockup's
     own card proportion at a mobile size. Scoped to this band because .cards
     is also the About page's three rooms, which are a different card.

     `:not(.ab-rooms)` and not a bare `.band-occasions`, because /about's
     rooms section carries `band-occasions` TOO - and `.band-occasions
     .cards` outranks the `.ab-cards { 1fr }` that page relies on, so the
     bare form silently forced About back to two columns and left its 384px
     photo box at 0.30:1. Scope by the section, not by the card. */
  .band-occasions:not(.ab-rooms) .cards { grid-template-columns: repeat(2, 1fr); }
  .cols4 > * + * { border-left: 0; }
  /* --- and the three benefit icons stay in ONE row ----------------------
     "Then keep the later three benefit icons in one row. This makes the page
     far shorter and gives the icon sections more visual variety." They were
     stacking 1x3 here, which is the same shape as the band above it - two
     identical tall columns of icons was the repetition he is describing.

     Three across at 390 is 106px a column, so the label has to come down to
     fit: "UNFORGETTABLE" sets 150px at the shared 18px `--f-label` and 92 at
     11px, inside the 98 that 4px of padding leaves. The dividers stay off -
     at 8px of half-gap they would sit almost on the type. */
  .cols3 {
    /* minmax(0, 1fr) AND NOT 1fr. `1fr` is `minmax(auto, 1fr)`, so a column
       whose min-content is wider than a third takes more than a third: at 360
       "EXPERIENCED" and "UNFORGETTABLE" are single unbreakable words and were
       pushing "Live vocals / you can feel" down to 44px, where it broke into
       five lines while the other two held two. Equal columns need the zero. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: 6px; row-gap: 0;
  }
  .cols3 > * + * { border-left: 0; }
  /* Same two anchors and the same reason: 70/18 at 620 is what 621 renders,
     46/10 at 360 is what was measured to fit three across. */
  .stat {
    padding-inline: clamp(0px, calc(4.6vw - 16.6px), 12px);
    --glyph: clamp(46px, calc(9.23vw + 12.77px), 70px);
  }
  .stat .glyph { width: var(--glyph); height: var(--glyph); }
  /* 10px AND .02em ARE THE LARGEST PAIR THAT HOLDS 360. The binding word is
     "UNFORGETTABLE" against a third of the column, and it is close: swept
     11/10/9px against .04/.02/.01em and 0/6/8/10px of gap, and 11px clips at
     360 in every one of them while 10px clears it in three. Below the label,
     size buys almost nothing - the constraint is the word against the width. */
  /* The TRACKING has to come down with the size or the label clips anyway -
     .06em of 18px is 1.08px and .02em of 10px is 0.2px, so it is clamped in px
     through the same two anchors rather than left as one em value that would
     hold .06 all the way down. */
  .stat span {
    margin-top: 5px;
    font-size: clamp(10px, calc(3.08vw - 1.08px), 18px);
    line-height: 1.4;
    letter-spacing: clamp(.2px, calc(.338vw - 1.02px), 1.08px);
  }

  /* THESE RULES BELONG TO 620, NOT TO 359, and they were inside this query
     until 7 Sep 2026. cfe1e29 added the 359 breakpoint for .cols3 and
     .stat span and opened it HERE, closing 620 early, so everything below -
     the phone header, the stacked button column, and the whole phone hero -
     was re-parented into max-width: 359. All of it is measured off
     design/mockup/mobile/services-440.png at 440, as the notes below say, so
     none of it could fire on any phone: 360 Android, 375 SE, 390 iPhone 13,
     430 Pro Max all took the DESKTOP hero instead - the photograph as a
     full-bleed backdrop with the copy unreadable on top of it. The width
     sweep saw it as "size-jump at 360px ... with no structural change", which
     is exactly what a layout falling off a cliff at a breakpoint looks like
     from the outside. The 359 query now sits after this block and owns only
     the two rules it was written for. */

  /* --- the header at the mockup's own width ------------------------------
     design/mockup/mobile/services-440.png, measured at 440:

       header band     y0-57.9   -> 58 tall
       wordmark        x37.2-133.4  y5.2-56.4   96.7 x 51.7, left on the gutter
       CONTACT button  x247.1-354.7 y13.4-43.9  108.1 x 31.0, centred on the row
       hamburger       x377.4-402.3 y20.2-36.7  25.3 x 17.1, right on the gutter

     KNOWN DIFFERENCE, and it is the desktop header's difference again: the
     supplied wordmark is 1.72:1 where the mockup letters it at 1.87, so at
     the measured 96.7 width it stands 56.3 tall against the mockup's 51.7.
     Width is what the row constrains and width is what is held, so the
     header comes out ~63 rather than 58. The extra falls into the photograph
     below it, exactly as the 189px desktop lockup's does. */
  .site-header { padding: 5px 0 2px; }
  .site-header .btn { height: 31px; padding: 0 18px; }
  .hero-actions { flex-direction: column; align-items: flex-start; gap: 18px; }
  .btn-gold, .band-cta .btn-gold { width: 100%; max-width: 267px; }
  /* AND NOT THE ONE IN THE HEADER. The line above turns every gold button into
     a full-width bar at this size, which is right for a button sitting alone in
     a column and wrong for one in a header row - it would push the wordmark off
     the screen. It never applied before because the header's button was a
     ghost; it does now, so it is undone here. */
  .site-header .btn-gold { width: auto; max-width: none; }

  /* --- the home hero, from design/mockup/home-440.png ---------------------
     A different LAYOUT, not the desktop one squeezed. The photograph stops
     being a full-bleed background behind the copy and becomes a block above
     it, because the desktop treatment crops her face off at this width - the
     band is 341px tall and the copy needs the middle of it. Everything then
     centres on black underneath.

     Measured off the mockup at its own 440px viewport (1320px at 3x):

       photo band          y485-1507   1023/3 = 341 tall, full bleed  -> 1.29:1
       lettering ink       bottom y1697. The TOP could not be measured the
                                       same way: the mockup's photo is lit hard
                                       magenta, and a colour test for the pink
                                       lettering also catches the stage lights
                                       inside the picture. Reading it that way
                                       gave a top of y1260 and an 82px overlap,
                                       which put the lettering across her dress
                                       on our frame. See the note on the
                                       pull-up below.
       lettering -> eyebrow  1740-1697 = 43   -> 14
       eyebrow cap           34               -> 11   (--f-eyebrow 17px)
       eyebrow -> H1         1808-1773 = 35   -> 12
       H1 cap                114              -> 38   -> ~52px Oswald 700
       H1 line to line       1945-1808 = 137  -> 46
       H1 -> button          2100-2057 = 43   -> 14
       button height         136              -> 45, full container width
       button -> showreel    2266-2235 = 31   -> 10

     Two ink widths settle the rest, and both were measured rather than eyed:

       "THE PRESENCE."   849/3 = 283 css  -> 45px Oswald 700, not the 52 a
                                             cap-height reading suggested
       button gold       1164/3 = 388 css -> the hero runs a 26px gutter here,
                                             not the site's 36. The lettering
                                             measures the same 388, so it is
                                             the mockup's mobile hero measure
                                             and not a one-off on the button.

     The 26px gutter is scoped to this hero. Changing --gut at this breakpoint
     would have moved every band on the page for a value measured in one. */
  .hero-home { display: block; min-height: 0; padding: 0 0 30px; }
  .hero-home .hero-media { position: static; }
  /* The BOX stays the mockup's measured 1.29 band even though the file
     swapped in below 620 on 6 Sep is 5:4 (1.25). This one IS measured -
     y485-1507 at 3x, above - so it wins over the file's own ratio, and the
     difference costs 3% off the top and bottom of a frame with headroom to
     spare. Services took the opposite decision on the same day, and the
     reason is the asymmetry: its 1.80 came off a mockup drawn against a hero
     that no longer exists, and this 1.29 came off the current one. */
  .hero-home .hero-media img { aspect-ratio: 1320 / 1023; height: auto; }
  /* The desktop wash exists to hold white copy over the picture. Nothing sits
     on the picture here, and it flattened the stage light the frame is lit by. */
  .hero-home .hero-media::after { content: none; }

  .hero-home .wrap { text-align: center; align-items: center; padding-inline: 26px; }
  /* -20, SET BY EYE AGAINST THE PICTURE, and the one number here that is not
     measured. The mockup's lettering clears her because its frame has a band
     of dark stage under her hem; ours is a wider crop of home-hero.jpg where
     she fills the box to the bottom, so the same overlap lands the lettering
     across the dress. -82, -40, -20 and 0 were rendered and compared: -20 is
     the last value that still straddles the photo edge - which is the shape of
     this layout - without sitting on her. Re-judge it against the picture if
     the hero photograph changes; it belongs to this crop, not to the layout. */
  .hero-home .script-art { order: 1; margin: -20px auto 0; width: 100%; }
  /* The gaps below are the mockup's INK-to-INK measurements converted to box
     margins, which is the only way they land: our lettering is 388x157 where
     the mockup's is 388x146, and Oswald 700 at the width-matched 45px has a
     33px cap where the mockup's H1 shows 38. Half-leading and that 11px of
     extra artwork are subtracted here so the rendered INK sits on the measured
     positions. Re-derive these if the lettering is ever redrawn. */
  .hero-home .hero-eyebrow {
    display: block; order: 2; margin: 0;
    font-family: var(--display); font-weight: 500;
    font-size: var(--f-eyebrow); line-height: 1;
    letter-spacing: .14em; text-transform: uppercase; color: var(--gold);
  }
  .hero-home h1 {
    order: 3; margin: 2px 0 0;
    /* 45px sets "THE PRESENCE." to the mockup's measured 283px at 440. The
       clamp keeps that proportion down the range (10.2vw) rather than being
       exact at one width and overflowing a 320. */
    font-size: clamp(36px, 10.2vw, 45px); line-height: 1.02;
  }
  .hero-home .hero-lede { display: none; }
  /* align-self, because .wrap centres its children and this row would
     otherwise shrink to its content - taking the button's width:100% down
     with it to the width of the words "Contact us". */
  .hero-home .hero-actions {
    order: 4; margin-top: 12px; align-self: stretch;
    align-items: center; gap: 11px;
  }
  /* Full width here, against the 267px cap the other gold buttons keep: the
     mockup runs it gutter to gutter and it is the page's primary action. */
  .hero-home .hero-actions .btn-gold { max-width: none; height: 45px; }
}

/* Three across stops fitting below 360, so it stops. "UNFORGETTABLE" needs
   about 8px of type to hold a third of a 340px viewport, which is not a label
   any more, and the measured alternative was the row breaking unevenly - one
   column at its min-content and five lines deep beside two of two.

   360 is a real device width (most Android), 375 is the iPhone SE and 8, and
   both keep the row. 320 is the first SE and it stacks, with the type back up
   to something readable because it has the whole column to itself. */
@media (max-width: 359px) {
  .cols3 { grid-template-columns: 1fr; row-gap: 24px; }
  .stat span { font-size: 14px; line-height: 19px; letter-spacing: .06em; }
}

/* ===========================================================================
   Shared inner-page components
   ---------------------------------------------------------------------------
   Measured on design/mockup/events-862.png (718px native, built at 1440,
   x1.67) and traced in design/SPEC.md > Events, which is why this block used
   to be headed "Events page". It is not one any more, and the old heading sent
   anyone changing a tile or a split looking for a page that would not be
   affected. What is actually below, and who uses it:

     .hero-inner, .btn-gold-sm, .eyebrow-sm    about, services, contact, 404
     .h2-serif, .h2-serif-sm                   events, services, gallery
     .band-intro                               services
     .tile, .tile-copy, .explore               services, events
     .split, .split-media, .split-copy,        about, events, services
       .split-reverse
     .band-cta-center                          gallery

   /events itself is the page that has moved furthest away: its hero is
   .hero-live and its dates are .show, both further down under "Events page -
   live dates". It shares only .split, .h2-serif, .explore and .btn-gold-sm.

   Everything here is therefore unscoped ON PURPOSE - it is the component set,
   not a page's styling. A change to a rule in this block lands on up to five
   pages, so check them before editing, and put page-specific values in that
   page's own block below rather than here.
   =========================================================================== */

:root {
  --f-h1-inner: 75px;    --lh-h1-inner: 87px;   /* cap 46 mockup, 62 baseline */
  --f-serif-h2: 42px;                            /* cap 23 mockup */
  --f-serif-h2-sm: 25px;   /* longest line needs 423px of the panel's 421 at 31px */
  --f-eyebrow-sm: 13px;                          /* cap 7 mockup */
  --f-tile-title: 25px;
  --hero-inner-h: 562px;                         /* mockup 404 */
  --tile-h: 500px;                               /* mockup 359 */
  --tile-gap: 14px;                              /* mockup 10 */
  --tile-pad: 28px;                              /* mockup 20 */
}

.hero-inner { min-height: var(--hero-inner-h); padding-top: 172px; }
.hero-inner h1 {
  margin: 18px 0 0;
  font-family: var(--display); font-weight: 700;
  font-size: var(--f-h1-inner); line-height: var(--lh-h1-inner);
  letter-spacing: .01em; text-transform: uppercase;
}
.hero-inner .lede {
  margin: 25px 0 0; max-width: 358px;
  font-size: var(--f-body-perf); line-height: 28px; color: var(--ink-mut);
}
.hero-inner .btn { margin-top: 32px; }
.btn-gold-sm { height: 39px; }   /* sizes to its label, like the others */

.eyebrow-sm { font-size: var(--f-eyebrow-sm); letter-spacing: .16em; }

.h2-serif {
  margin: 0;
  font-family: var(--serif); font-weight: 500;
  font-size: var(--f-serif-h2); line-height: 1.15;
  letter-spacing: .01em; text-transform: uppercase; color: var(--ink);
}
.h2-serif-sm { font-size: var(--f-serif-h2-sm); }

.band-intro { padding: 27px 0 40px; text-align: center; }
.band-intro .h2-serif { margin-top: 23px; }
.tile { position: relative; height: var(--tile-h); overflow: hidden; }
/* `picture` is named alongside `img` because the five services tiles wrap
   theirs in one to swap in a wide frame below 620 - and every rule that
   places a tile image is a DIRECT-CHILD selector, so the wrapper, not the
   img, is what the tile now contains. Same reasoning as `.hero-media picture`
   above: a bare picture is inline with auto height, so an img inside one
   resolves its `height: 100%` against a collapsed box. A tile with a plain
   img is untouched by this. */
.tile > .ph, .tile > img, .tile > picture { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.tile > picture { display: block; }
.tile > picture > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.tile::after {
  content: ""; position: absolute; inset: auto 0 0; height: 55%;
  background: linear-gradient(to top, rgba(var(--bg-rgb), .96) 15%, transparent);
}
.tile-copy { position: absolute; z-index: 2; inset: auto var(--tile-pad) 25px; }
.tile-copy h3, .split-copy h3 {
  display: flex; align-items: center; gap: 12px; margin: 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--f-tile-title); line-height: 1.1;
  letter-spacing: .02em; text-transform: uppercase; color: var(--ink);
}
.tile-copy h3 img, .split-copy h3 img { width: 32px; height: 32px; flex: none; object-fit: contain; }
/* 20px, not --f-small's 15px. The events mockup's caption wraps to two lines
   at a size clearly above 12px (line 1 measures 361 build px against 275 at
   12px), and services measured 17px independently. Shared rule, so neither
   page needs its own override. */
.tile-copy p, .split-copy p {
  margin: 13px 0 0; max-width: 317px;
  font-size: 17px; line-height: 25px; color: var(--ink-mut);
}
.explore {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 15px;
  font-family: var(--display); font-weight: 400;
  font-size: 14px; letter-spacing: .14em; text-transform: uppercase; color: var(--gold);
}
.explore:hover { color: var(--gold-bright); }

/* --- photo/copy splits, full bleed on the photo side ---------------------- */
.split { display: grid; grid-template-columns: 62% 1fr; align-items: center; }
.split-media { position: relative; align-self: stretch; min-height: 308px; }
.split-media > .ph, .split-media > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.split-copy { padding: 23px var(--gut) 23px 38px; }
.split-reverse { grid-template-columns: 40% 1fr; }
.split-reverse .split-media { min-height: 354px; }

/* --- centred CTA variant -------------------------------------------------- */
.band-cta-center { padding: 38px 0 42px; }
.band-cta-center .wrap {
  flex-direction: column; justify-content: center; gap: 22px; text-align: center;
}
.band-cta-center .btn-gold { height: 42px; }

@media (max-width: 900px) {
  :root { --f-h1-inner: 50px; --lh-h1-inner: 57px; --f-serif-h2: 30px; --f-serif-h2-sm: 27px; }
  .tile { height: 383px; }
  .split, .split-reverse { grid-template-columns: 1fr; }
.split-copy 
}

@media (max-width: 620px) {
  :root { --f-h1-inner: 37px; --lh-h1-inner: 42px; --f-serif-h2: 23px; --f-tile-title: 20px; }
  .btn-gold-sm { width: 100%; max-width: 267px; }
}
/* ===========================================================================
   Services page
   Mockup: design/mockup/services-864.png (720px native, built at 1440, x1.667).
   Values traced in design/SPEC.md > Services. Everything here is scoped to
   .page-services or to a class only this page uses, so nothing below can
   reach the home or events pages.
   =========================================================================== */

:root {
  --svc-serif-h2: 26px;                       /* cap 21 build, checked against the render */
  --svc-eyebrow-hero: 17px;                   /* cap 17 build - larger than the band eyebrows */
  --svc-tile-title: 47px;                     /* cap 29 mockup, 48 build */
  --svc-tile-title-lg: 58px;                  /* cap 36 mockup, 59 build */
  /* --- hero, re-measured off design/mockup/services-1024.png, 5 Sep 2026 ---
     A SECOND MOCKUP FOR THIS PAGE, and the numbers below come off it rather
     than off services-864.png. It replaces the hero's copy entirely: the
     eyebrow, the client's pink brush lettering, a one-line H1, a lede this
     hero never had, and "Plan your event" in place of "Contact us". Nothing
     outside the hero was taken from it - the tiles, the intro band and the CTA
     below are still services-864.png's, which is why both files are live.

     IT READS 1:1, not on a scale. The mockup is 1024 wide and is a rendering
     of the site at roughly a 1024 VIEWPORT, where the 1200-capped body gives
     952 of content against the 913 its header ink spans and the 984 its card
     row does. So its components are drawn at their true pixel sizes: its
     header button is 38 tall against the built 39, its hero band 546 against
     the built 556. Type is read straight off it.

     ITS HEADER IS NOT, and that is the one thing here not taken at face
     value. It draws the wordmark 68px tall where the built one is 110 - the
     same internal inconsistency SPEC.md records for events-862.png - so its
     wordmark-to-eyebrow gap of 42 cannot be applied to a wordmark 1.6x
     bigger. `padding-top` therefore stays at the 203 measured off
     services-864.png and the copy block starts where it always did.

     THE BAND IS TALLER AS A RESULT, 556 -> 627, and that is the lettering's
     doing rather than a re-measurement - the same growth, for the same
     reason, that the home hero took when the brush artwork arrived. The copy
     block is 372 tall against the 288 it replaced, because it gained a 184px
     image and a two-line lede. 203 padding + 2 to the eyebrow's ink + 372 +
     the mockup's own 50 from button to band bottom = 627. */
  --svc-hero-h: 627px;                        /* was 556, mockup band 0-400 */
  --svc-hero-script: 404px;                   /* script ink x60-463 */
  --svc-hero-script-bleed: -8px;              /* ink x60 against copy x68 */
  /* CAP AND WIDTH AGREE HERE, which is worth recording because on this page
     they usually do not - the phone H1 below, and every size in the events
     block, had to choose one. "FOR EVERY OCCASION." measures 431 wide and cap
     40 on the mockup; Oswald 700 at 53px renders it 485 wide at cap 45, and
     431/485 and 40/45 are both 0.889. 47px is the same number read two ways,
     so this mockup's face shares Oswald's proportion.

     BOTH READINGS WERE WRONG AT FIRST PASS, and for the same reason: the
     photograph starts inside the H1's own row on both images, so a plain ink
     scan of that row returned 487 and 661 rather than 431 and 485 - and those
     two disagreed by 27%, which is what sent this looking for a condensed
     face that was never there. Scan the row's column RUNS instead: the 17
     letterforms come back as 17 runs and the picture as everything past the
     gap. Worth doing on any hero measured over a photograph. */
  --svc-hero-h1: 47px;                        /* 431 wide, cap 40 */
  --svc-lh-hero-h1: 54px;
  --svc-hero-lede-size: 18px;                 /* line 1 368 wide against 343 at 17 */
  --svc-hero-lede: 380px;                     /* line 1 ink 368, and breaks
                                                 after "experience" as drawn */
  --svc-lh-hero-lede: 24px;                   /* ink top 390 -> 414 */
  --svc-tile-gap: 17px;                       /* mockup 12-13 */
  --svc-tile-pad: 25px;                       /* mockup 17-19 */
  --svc-tile-pad-lg: 36px;                    /* mockup 26, the tall tile */
}

/* --- hero: eyebrow, two-line H1, one button. No lede, unlike events. ------ */
.hero-services { min-height: var(--svc-hero-h); padding-top: 203px; }
/* The hero photograph keeps its left half dark and empty for the eyebrow, H1
   and button, and puts the band right of centre. `cover` crops whichever axis
   has the excess, and which axis that is changes with the viewport: wide, the
   excess is vertical and only the 50% below applies; narrow, it is horizontal,
   and a centred crop lands on the empty left - the phone hero renders as a
   near-black rectangle. One declaration rather than a breakpoint, because the
   horizontal value is simply inert at the widths where it would do harm.

   62% WAS PICKED BY RENDERING IT, not reasoned: 50, 62, 70 and 78 were shot at
   414 and compared. Higher values slide the window right, which pushes Seven
   LEFT on screen and behind the headline; lower ones run her off the right
   edge. 62 is where her whole figure sits clear of both.

   This number belongs to the photograph, not to the slot. It was 78% for the
   frame before this one, whose subject sat further right. Re-shoot the ladder
   if the image is replaced again - a stale crop value is invisible on desktop
   and hides the subject on a phone, which is the one place it matters. */
.hero-services .hero-media img { object-position: 62% 50%; }
.eyebrow-pink { color: var(--pink-light); }
.hero-services .eyebrow-sm { font-size: var(--svc-eyebrow-hero); letter-spacing: .12em; }

/* The band eyebrows track much wider than the events page's - measured 288
   build px for "One team. One experience." against 226 at .16em. */
.page-services .band .eyebrow-sm { letter-spacing: .30em; }
/* The h1 now opens with the lettering, so this margin is eyebrow ink -> SCRIPT
   ink: 135 -> 137 on the mockup, 2px, and the eyebrow's own line box already
   sits ~2px below its cap at line-height 1. Zero is the measurement. */
.hero-services h1 {
  margin-top: 0;
  font-size: var(--svc-hero-h1); line-height: var(--svc-lh-hero-h1);
}
/* 404px is the lettering's ink width on the mockup, x60-463, and the -8 is the
   rest of that reading: the brush "U" swings 8px LEFT of the copy column's own
   x68 edge. The home hero indents its script 12px right instead - that one is
   a tagline set off from the H1, this one is the first half of the sentence
   and hangs on the margin the way a swash does.

   The file is trimmed flush to its own alpha, so the image box IS the ink box
   and both numbers land without a correction. */
.hero-services .script-art {
  display: block;
  margin: 0 0 0 var(--svc-hero-script-bleed);
  width: min(var(--svc-hero-script), 100%); height: auto;
}
/* Script ink bottom 324 -> H1 ink top 325 on the mockup: they touch, with 1px
   between them. The H1 text after the image is an anonymous block and cannot
   be selected, so the pull is the image's own bottom margin. Oswald's cap
   starts ~8px inside a 54px line box at 47px, so -9 would put the cap 1px
   under the lettering: the offset, not a nudge.

   IT IS -6, THREE SHORT OF THAT, and the three are the artwork's. The mockup
   draws the lettering 188 tall at the 404 width measured off it; the client's
   own file, trimmed flush to its alpha, is 184 at that width. Width is what
   was measured and width is held, so the 4px is given back here instead -
   otherwise the H1, the lede and the button all sit 3px above the rows they
   were measured onto, which is exactly what the first render did. */
.hero-services .script-art { margin-bottom: -6px; }
/* 18px, not the shared --f-body-perf's 17. Line 1 measures 368 on the mockup
   against 343 at 17px, and its ink is 15 tall against 12 - width and height
   agree on the same 7%, so it is a size difference and not a face one. */
.hero-services .lede {
  margin: 12px 0 0; max-width: var(--svc-hero-lede);
  font-size: var(--svc-hero-lede-size); line-height: var(--svc-lh-hero-lede);
  color: var(--ink-mut);
}
/* Measured from the lede's line-2 ink TOP, 414 -> button box 453 = 39, not
   from its ink bottom. The mockup's raster of this lede is 15px tall where
   the render's is 12 for the same two lines, so its ink bottom sits ~3px low
   and a gap measured off it puts the button 3px high. The tops agree to 1px
   on both lines, which is why they are the anchor.

   The button is 209 x 43 on the mockup, against .btn-gold's shared 40 tall
   and the 204 that its 28px of padding gives "PLAN YOUR EVENT". Both nudged
   here rather than on the shared rule, which four other pages use at its own
   measured height. */
.hero-services .btn-gold { margin-top: 22px; height: 43px; padding: 0 39px; }

/* --- intro ---------------------------------------------------------------- */
/* The mockup renders a magenta confetti haze into both edges of this band.
   Reproduced as gradients, not as an image asset - same treatment the home
   page gives its bokeh (SPEC.md > Assumptions). */
.band-intro-svc {
  padding: 53px 0 37px;                       /* band 667-915 build; 36 mockup above the eyebrow */
  background:
    radial-gradient(34% 88% at 0% 55%, rgba(196, 42, 146, .17), transparent 72%),
    radial-gradient(34% 88% at 100% 45%, rgba(196, 42, 146, .15), transparent 72%);
}
.h2-serif-svc { font-size: var(--svc-serif-h2); }
.band-intro-svc .h2-serif { margin-top: 19px; }
.lede-center {
  margin: 0 auto; max-width: 650px;
  font-size: var(--f-body-hero); line-height: var(--lh-body-hero);
  color: var(--ink-mut);
}

/* --- the five-tile mosaic -------------------------------------------------
   Column and row proportions are the mockup's own, as fr units rather than
   percentages so the gap comes out of the container rather than the tiles:
   row A 379:429 of 820, row B 495:314. Row A's right column stacks 206:243. */
.svc-grid-a {
  display: grid; gap: var(--svc-tile-gap);
  grid-template-columns: 379fr 429fr;
  grid-template-rows: 286px 338px;             /* mockup 206 / 243 */
}
.svc-grid-a .tile-tall { grid-row: 1 / span 2; }
.svc-grid-b {
  display: grid; gap: 15px; margin-top: 18px;
  grid-template-columns: 495fr 314fr;
  grid-template-rows: 367px;                   /* mockup 264 */
}

/* The events tiles are full-bleed and fixed-height; these sit in the
   container, carry the card border, and take their height from the grid. */
.page-services .tile {
  height: auto;
  border: 1px solid var(--card-line); border-radius: var(--r-card);
}
.page-services .tile-copy { inset: auto var(--svc-tile-pad) 23px; }
.page-services .tile-tall .tile-copy { inset: auto var(--svc-tile-pad-lg) 19px; }
/* min() rather than a plain size: the row-B "Special events" tile is the
   narrowest on the page, and at ~1024 a fixed 47px runs past its own measure
   and gets clipped by the tile. The cap is the measured value; the vw term
   only ever takes over below 1330. */
.page-services .tile-copy h3 {
  gap: 15px; letter-spacing: .04em;
  font-size: min(var(--svc-tile-title), 3.33vw);
}
.page-services .tile-copy h3 img { width: 63px; height: 63px; }

/* Weddings and Special events stack the mark above the title; the other three
   set it inline to its left. Both are in the mockup. */
.tile-copy-stack h3 { display: block; }
.tile-copy-stack h3 img { margin-bottom: 7px; }
.page-services .tile-tall .tile-copy h3 { font-size: min(var(--svc-tile-title-lg), 4.17vw); }
.page-services .tile-tall .tile-copy h3 img { width: 83px; height: 83px; }

/* Where the mark sits inline, it hangs to the LEFT of the text column: the
   caption starts under the title, not under the icon. Measured on the
   birthday tile - title and caption both begin at x830 build, the cake at
   x715. The stacked tiles have no such indent. */
.tile-copy:not(.tile-copy-stack) .tile-foot { padding-left: 78px; }

/* Caption and Explore share a row, Explore hard right. In a tile too narrow
   for both - the tall one - Explore wraps to its own line and stays right,
   which is what the mockup shows. */
.tile-foot {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: 17px 20px; margin-top: 13px;
}
/* Only the zero margin and the shrink-to-fit are page-scoped now: the shared
   .tile-copy p carries the 17px/317px the events, services and about mockups
   all measured. */
.page-services .tile-copy p { margin: 0; max-width: min(317px, 100%); }
.tile-foot .explore { margin: 0 0 0 auto; }
.page-services .explore { font-size: 17px; }   /* cap 17 build */
.page-services .explore span:first-child { border-bottom: 1px solid currentColor; padding-bottom: 4px; }

/* --- what we bring: one bordered strip, four columns --------------------- */
.band-strip { padding: 19px 0 0; }
.strip .eyebrow-sm { font-size: var(--svc-eyebrow-hero); }   /* cap 16 build */
.strip {
  padding: 22px 0 46px;                       /* was 32/68, the mockup's 23/53 - tightened 2 Sep */
  text-align: center;
  border: 1px solid rgba(var(--gold-rgb), .22); border-radius: var(--r-card);
  background: rgba(var(--ink-rgb), .02);
}
/* FOUR again as of 2 Sep 2026, which is what the mockup draws. The count has
   always followed the client's package ladder rather than a measurement: four
   capability columns at launch, cut to three on 1 Sep when the ladder became
   Solo / + DJ / + Band, and back to four now that DJ-only is a way to book.
   The divider count follows the content, so this is the only line to change. */
.strip-cols { display: grid; grid-template-columns: repeat(4, 1fr); margin-top: 33px; }
.strip-col + .strip-col { border-left: 1px solid rgba(var(--gold-rgb), .20); }
.strip-col .glyph { width: 70px; height: 70px; margin: 0 auto; object-fit: contain; }
.strip-col span {
  display: block; margin-top: 23px;
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-label); line-height: 1.2;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink);
}

/* --- DJ credit on /about -------------------------------------------------
   NOT MEASURED - no mockup draws this. Added 2 Sep 2026 with DJ D Nash's
   name. Reuses the page's own type tokens; the logo frame is provisional and
   gets re-cut to the supplied artwork's aspect. */
.dj-credit {
  display: flex; align-items: center; gap: 18px;
  margin-top: 26px; padding-top: 22px;
  border-top: 1px solid rgba(var(--gold-rgb), .22);
}
/* 440 x 328 artwork, shipped at 2x and displayed at 200. Height is left
   to auto so the mark keeps its own aspect - the 220 x 90 frame here
   before the file arrived was a guess, and the file is 4:3. */
.dj-logo { flex: none; width: 200px; height: auto; }
.dj-credit-copy { min-width: 0; }
/* Same story as `.dj-role` below, and read that comment first - this rule was
   overridden by `.ab-complete-copy p` on four properties, not one. It asked
   for margin 0, line-height 1.2 (21.6px) and `--ink`; it rendered with a 17px
   top margin, 23px of leading, a 454px measure and `--ink-mut`. Those are the
   values here, so lifting the credit out of that column changes nothing on a
   desktop that was not asked to change. Its font-size was 18 either way -
   `--f-label` and `--ab-body-sm` are the same number, which is part of why
   nobody caught this. */
.dj-name {
  margin: 17px 0 0; max-width: 454px;
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-label); line-height: var(--ab-lh-body-sm);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-mut);
}
/* THIS RULE NOW SAYS WHAT IT HAS ALWAYS RENDERED, and the five values below
   are not the five it was written with.

   Added 2 Sep 2026 it asked for 12/22, a 5px margin and `--ink-dim`, and got
   none of them. Nested inside `.ab-complete-copy`, the page's own
   `.ab-complete-copy p` (0,1,1) outranked `.dj-role` (0,1,0) on every property
   this rule did not have to repeat - so the caption has been setting at the
   page's BODY size, with a body paragraph's top margin, colour and 454px
   measure, since the day it went in.

   Lifting the credit out of that column on 7 Sep 2026 - so a phone could put
   it under the photographs - dropped the override, and the line went 18px to
   12, dimmer, and 12px higher, on a desktop where nothing had been asked to
   change. So the shipped appearance is restored EXPLICITLY rather than by
   putting the markup back: those values have been on the live page for five
   days and been looked at; the rule's original five never rendered once and
   were never judged, and this block has no mockup to settle it either way.

   What the rule originally asked for - 12/22, margin 5, `--ink-dim`, no
   measure - is a tighter, dimmer caption sitting closer under the name, and it
   is a defensible design. It is one edit away if anyone wants to look at it. */
.dj-role {
  margin: 17px 0 0; max-width: 454px;
  font-size: var(--ab-body-sm); line-height: var(--ab-lh-body-sm);
  letter-spacing: .04em; color: var(--ink-mut);
}
/* --- the phone credit: the mark alone, centred --------------------------
   Jacob, 7 Sep 2026: "lets make that logo bigger and centered... it should
   take 75% of the width... we can get rid of this text".

   700 RATHER THAN A NEW BREAKPOINT. This is where `.dj-credit` already turned
   from a row into a column, so it is the width at which this block is already
   a phone layout rather than a credit line. Above it the mark keeps its 200px
   and sits beside the name, which is the desktop arrangement and is untouched.

   The cap is the ARTWORK'S OWN WIDTH. dj-d-nash-logo.png is 440x328, and 75%
   of the content column passes 440 at a 659px viewport - so without the cap
   the mark would upscale by 7% between there and 700. 440 is measured off the
   file, not chosen.

   The name and role come off the phone, not out of the markup: the desktop
   still shows them, and nothing factual is lost here. The <img>'s own alt is
   "DJ D-Nash", so the name is still announced; the copy two paragraphs up
   already says "Seven is joined by DJ D-Nash on decks and mic"; and "DJ and MC"
   is the DJ support / MC services row directly below. */
@media (max-width: 700px) {
  .dj-credit { flex-direction: column; align-items: center; gap: 0; }
  .dj-logo { width: 75%; max-width: 440px; }
  .dj-credit-copy { display: none; }
}

/* --- corporate events & offsites ----------------------------------------
   NOT MEASURED - this block has no mockup. Added 2 Sep 2026 with the
   corporate section on /services (see the comment on that section).
   Everything here is either an existing token or a value already used by the
   `split` block it borrows, so it introduces no new magic numbers: the media
   column, its min-height and the copy padding all come from .split-reverse
   as-is, and only the list and the button offset are new. */
.band-corp { padding: 30px 0 32px; }
/* ONE COLUMN, because there is no picture beside this copy any more. The
   generated corporate ballroom that filled the media column was removed on
   6 Sep 2026 - see the note in services.html, and MANIFEST S8, which is
   unchanged and still asks for a real corporate room.

   `.split-reverse`'s 40%/1fr and the 20-line object-position ladder that
   placed the old frame went with it. Both are in git at that commit; if a
   real photograph arrives, restoring the media column is reverting one
   markup block and this rule, not re-deriving a crop.

   The copy's left padding drops to the gutter with it: `.split-copy` sets
   38px on the left for a column that sits against a photograph, and against
   the page edge that reads as a section indented for no reason. */
.corp-split { grid-template-columns: 1fr; }
.corp-copy { padding-left: var(--gut); }
.corp-split { border-top: 1px solid rgba(var(--gold-rgb), .22); }
.corp-copy { padding-top: 27px; padding-bottom: 27px; }
/* CENTRED, 7 Sep 2026: "the corporate events and offsites section is always
   left aligned". It was, and it is the shape that made it obvious - a headline
   that fills the container over a body that does not. At 1440 the h2 runs 1056
   wide and every paragraph under it stops at 583, so the band read as a wide
   title with a narrow column tucked under its left corner and 473px of nothing
   beside it. 213px of the same at 940; below ~760 the cap stops binding and
   the problem disappears, which is why it only ever looked wrong wide.

   The 583 stays and is centred rather than widened. It is a measure, not a
   layout accident - two paragraphs set across 1128 would be a 1128px line.
   Same decision as `.perf-copy` on the home page.

   The LIST keeps left-aligned items inside the centred block: its bullets are
   a left edge, and a centred bullet list has no edge to align to. */
.corp-copy { text-align: center; }
.corp-copy .rule { justify-content: center; }
/* .split-copy p caps at the mockup's measured 317px, which is right for
   the tile captions it shares the rule with and far too narrow here.
   Takes the About split's own override value rather than a new one. */
.corp-copy > p { max-width: 583px; margin-inline: auto; }
/* The list takes the paragraphs' measure now that nothing sits beside this
   copy. It used to be a 2-column grid inside a ~660px column, so its two
   columns were ~310 each and read as a pair; across the full 1128 they were
   ~550 each holding ~230 of text, which put a hand's width of nothing down
   the middle of the section. Same 583 as the paragraphs keeps the whole copy
   block one measure under a headline that is deliberately wider. */
/* WIDER THAN THE PARAGRAPHS, AND OPENED UP - "the split with the elements
   (offsites and team retreats etc...) are unnecessarily tight."

   It was 583 wide on a 9px row gap and a 26px column gap: six items in two
   columns with barely half a line between rows, and the two columns close
   enough to read as one block of text rather than as a list. 9px is less than
   half the 23px line-height it sits on, which is what made it feel packed.

   660 rather than 583 because the list is the one thing here that is not a
   measure - it is six short phrases, not a paragraph, so the reading argument
   for 583 does not apply to it. At 660 with a 64px channel the columns are
   298 each against a longest item ("Hospitality suites and after-parties") of
   ~250, so nothing wraps and the gutter is visible as a gutter. Centred under
   the paragraphs, so the wider box reads as deliberate rather than as an
   overhang. */
.corp-list { max-width: 660px; text-align: left; }
.corp-copy .eyebrow-sm { margin-bottom: 14px; }
.corp-list {
  /* `30px auto`, and the auto is load-bearing: this shorthand comes after the
     rule above and a bare `30px 0` silently reset its `margin-inline: auto`,
     so the list stayed hard left while the paragraphs centred around it. Set
     the centring where the shorthand is, not before it. */
  list-style: none; margin: 30px auto; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 64px;
}
.corp-list li {
  position: relative; padding-left: 17px;
  font-family: var(--body); font-size: var(--f-body); line-height: var(--lh-body);
  color: var(--ink-mut);
}
/* The gold rule the tile captions and the strip already use, reduced to a
   bullet, rather than a new mark nobody has seen on this site. */
.corp-list li::before {
  content: ""; position: absolute; left: 0; top: 10px;
  width: 8px; height: 1px; background: var(--gold);
}
.corp-btn { margin-top: 9px; }

@media (max-width: 900px) {
  /* One column, so the 64px channel has nothing to separate and the list
     centres as a single stack of left-aligned items. */
  .corp-list { grid-template-columns: 1fr; max-width: 340px; gap: 14px; }
}

/* --- CTA: photo bleeding off the left, copy centred in the right half ----- */
.band-cta-split { display: grid; grid-template-columns: 50% 1fr; align-items: center; }
.cta-media { position: relative; align-self: stretch; min-height: 309px; }
.cta-media > .ph, .cta-media > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.cta-copy-split { padding: 37px var(--gut) 31px; text-align: center; min-width: 0; }
.cta-copy-split h2 {
  margin: 0;
  font-family: var(--display);
  /* Weight and size are set from the mockup's WIDTH, not its cap. The face the
     services mockup letters this heading in is far more condensed and far
     lighter than Oswald - it sets "LET'S CREATE YOUR KIND" in 283 native px at
     cap 34, a width-per-cap of 8.3 against Oswald's 11.8. Both cannot be met.
     Cap 32/34 was matched first, at 600 weight, and the line then ran 518 build
     px into a 528 column: edge to edge, touching both gutters, where the mockup
     leaves a clear ~100px each side. Width is the dimension the column
     constrains, so width is what is held - the same trade, and for the same
     reason, as the hero script (SPEC.md > Known differences).
       line 1 ink   283 native x 1.3889 -> 393 build, renders 388 at 43px/300
       measure      283 of the 432-wide copy column = 65.5%, held below. */
  font-weight: 300;
  font-size: min(43px, 3.6vw); line-height: 1.26;  /* 47 baseline to baseline */
  letter-spacing: .01em; text-transform: uppercase;
  /* Wraps rather than breaking on a <br>: the measure puts the break after
     "kind", exactly where the mockup has it, and below that it re-wraps
     instead of overflowing the column. */
  max-width: min(393px, 100%); margin-inline: auto; text-wrap: balance;
}
.cta-copy-split .btn-gold { margin-top: 34px; }

/* The header is in flow below 900 (`.site-header` goes position:static), so
   the hero starts under it rather than clearing an absolute one - same 28px
   as the events hero, not the 150 an overlaid header needed. */
@media (max-width: 900px) {
  :root { --svc-tile-title: 33px; --svc-tile-title-lg: 38px; --svc-serif-h2: 23px; }
  .hero-services { min-height: 392px; padding-top: 28px; }
  .svc-grid-a, .svc-grid-b { grid-template-columns: 1fr; grid-template-rows: none; }
  .svc-grid-a .tile-tall { grid-row: auto; }
  .svc-grid-a .tile, .svc-grid-b .tile { height: 350px; }
  .band-cta-split { grid-template-columns: 1fr; }
  .cta-media { min-height: 267px; }
  .cta-copy-split { padding: 37px var(--gut) 42px; }
}

/* --- the phone layout, from design/mockup/mobile/services-440.png --------
   Supplied 4 Sep 2026 and measured in SPEC.md > Services, phone. A different
   LAYOUT, not the desktop one narrowed, and it changes both of this page's
   picture blocks:

     the five-tile occasion mosaic  -> a stack of horizontal CARDS, copy left
                                       on black, the photograph bleeding off
                                       the right behind a fade, a gold ring
                                       chevron hard right
     the four-column package strip  -> a row of four square TILES, gold ring,
                                       label over the bottom

   WHICH SET GOES IN WHICH IS JACOB'S CALL, 4 Sep 2026, and it is deliberately
   NOT the pairing the mockup's own words imply - that mockup letters its
   capabilities into the cards and its occasions into the squares. Reversing it
   costs nothing; following it would have. The package rungs have no one-line
   description anywhere on this site, so they would have shipped four cards
   with an empty body slot, and five occasions in a four-wide row leaves an
   orphan and drops every caption and Explore link on the page. This way the
   occasion caption IS the card's body line, the occasion Explore IS the
   chevron, and four rungs fill a four-wide row exactly. SPEC.md records the
   offer and the rejection.

   NONE OF THE MOCKUP'S COPY IS TAKEN. Jacob: "this is just presenting the
   layout not the content." Every word on the page is the word that was
   already there.

   The markup is untouched. Everything below restyles the tile and strip
   markup the desktop already uses, so nothing here can regress a wider
   breakpoint, and `.tile`, `.tile-copy`, `.tile-foot` and `.explore` are
   shared with /events - which is why every selector is .page-services.
   --------------------------------------------------------------------- */
@media (max-width: 620px) {
  :root { --svc-tile-title: 27px; --svc-tile-title-lg: 28px; }

  /* Scoped to `main`, NOT to the page. The mockup runs a 19px measure through
     the hero button, the card borders and the tile row alike - all three
     resolve to 18.6-19.6 on both edges - but it keeps the HEADER on the site's
     own 36: its logo starts at x37 and the hamburger ends 38 from the right.
     So this is the content measure, and the header and footer keep --gut. */
  .page-services main { --gut: 19px; }

  .page-services {
    --svcm-card-h: 91px;      /* card box y497.4-587.9 */
    --svcm-gap: 7px;          /* card->card 6.7, tile->tile 7.0 */
    --svcm-pad: 19px;         /* copy left edge 37.2 - card box 17.6 */
    --svcm-ring: 28px;        /* chevron ring diameter */
    --svcm-r: 8px;            /* corner radius, cards and tiles */
    --svcm-line: rgba(var(--gold-rgb), .85);   /* border peak 180,129,65 */
  }

  /* --- hero: the photograph becomes a block above the copy ---------------
     The same move home, about and events already made, and wanted here for
     the same reason those needed it: this hero's frame is held by
     `object-position: 62%` so the band clears the headline, and that value is
     a crop - on a phone it throws most of the picture away. Shown whole
     instead, at the FILE's own ratio rather than the mockup's measured 1.80.

     THAT FILE IS NOW A DIFFERENT SHAPE. Until 6 Sep this was the wide hero
     shown whole at 1672x941 (1.777), which is a letterbox on a phone: a
     440px column got a 247px band and the stage went to a strip. There is a
     5:4 frame of the same Bonfire Grill gig now, swapped in by the
     `<source>` on the page, so the ratio follows the file it displays -
     1402x1122, the same frame about and events already use. Still the file's
     own number and not the mockup's: services-440.png draws this band at
     1.80, but that mockup was drawn against the OLD hero and is out of date
     here in every other respect too (see the note further down this block),
     so following it would crop a supplied frame to match a picture of copy
     that no longer exists. A phone mockup of the new hero is still the ask. */
  .hero-services { display: block; min-height: 0; padding: 0 0 26px; }
  .hero-services .hero-media { position: static; }
  .hero-services .hero-media img { aspect-ratio: 1402 / 1122; height: auto; }
  /* The desktop wash holds white copy over the picture. Nothing sits on the
     picture here, and it flattens the stage light the frame is lit by. */
  .hero-services .hero-media::after { content: none; }
  .hero-services .wrap { text-align: center; }

  /* Ink-to-ink off the mockup, applied as box margins: photo->eyebrow 11,
     eyebrow->H1 8, H1->button 10. Half-leading is taken off each. */
  .hero-services .eyebrow-sm { display: block; margin-top: 8px; }
  /* THE PHONE MOCKUP IS OUT OF DATE HERE, and this block is fitted rather
     than measured. design/mockup/mobile/services-440.png was drawn 4 Sep
     against the old hero - pink eyebrow, "ENTERTAINMENT FOR EVERY OCCASION"
     over two lines, "Explore services" - and every one of those lines was
     replaced by services-1024.png the next day. Its RHYTHM still holds and is
     kept (photo above, copy centred under it, the 8/4/7 margins measured off
     it); its COPY no longer exists, so the sizes below are the desktop
     mockup's proportions carried down the column, not readings. A phone
     mockup of the new hero is the ask - see design/BRIEF.md.

     The <br> after "Entertainment" went with that copy, and so did the rule
     that hid it here. */
  .hero-services .script-art {
    margin: 0 auto; width: min(var(--svc-hero-script), 100%);
  }
  .hero-services h1 {
    margin-top: 4px;
    /* min(measured, vw) - Assumptions 7 - and the vw arm is what does the work
       here, as it did for the H1 this replaces.

       9.8vw WAS TUNED FOR THE OLD STRING and does not survive the new one.
       "FOR EVERY OCCASION." renders 393 wide at 43px, in the 402 the 19px
       gutters leave at 440: it fits, with 4px a side. The same proportion at
       414 leaves 5px, and it wrapped anyway - a period's side bearing and the
       .01em tracking are inside that margin. The desktop mockup sets this
       heading on ONE line and it should stay on one down the range, so the
       factor is cut until the fit has room to be wrong: 9.3vw renders 352 of
       376 at 414 and 332 of 352 at 390.

       The 43px cap therefore only binds above 462, which is outside this
       block. It is kept as the ceiling the desktop hands over at. */
    font-size: min(43px, 9.3vw);
    line-height: 40px;    /* H1 line to line 40 */
  }
  /* Desktop pulls the H1 text 10px up under the lettering against a 61px line
     box. The phone's box is 40, so the same proportion is -6. */
  .hero-services .script-art { margin-bottom: -6px; }
  .hero-services .lede { margin: 8px auto 0; max-width: none; }
  /* Gutter to gutter, against the 267px cap the page's other gold buttons
     keep - the mockup runs it full measure and it is the page's primary
     action. Same exception home's phone hero makes. */
  .hero-services .btn-gold { margin-top: 7px; max-width: none; height: 37px; }

  /* --- section labels: gold caps with a rule out to each gutter ----------
     The rules FLEX rather than being fixed: solving each of the mockup's two
     labels as (399 - text - 2*gap) / 2 returns 70 and 65 against a measured
     70 and 65. The gap reads 15/16 on one label and 18/20 on the other, and
     that asymmetry falls out of `letter-spacing` putting a trailing space
     inside the text's own box - so one value here reproduces both.

     No label is INVENTED for the card section, and the eyebrow that already
     sits there does NOT take this treatment. Both were tried.

     "Four ways to book" is 17 characters and takes it exactly: one line,
     rules running out to both gutters, which is what the mockup draws.

     "One voice. One unforgettable experience." is 40, against the 22 and 23
     of the mockup's own two labels. It cannot be made to fit. At .30em it is
     374px wide where the row leaves ~300, so it wraps; and once it wraps the
     rules read as two stubs marooned 70px from the end of a ragged first
     line, because the text box stays at its shrunk width and centres the
     short line inside it. Rendered, compared, and dropped. That eyebrow keeps
     the plain centred form it already had - which costs the page nothing,
     because unlike the mockup's bare label it has an h2 and a lede under it
     doing the work of introducing the section. */
  .page-services .strip .eyebrow-sm {
    display: flex; align-items: center; gap: 16px;
  }
  .page-services .strip .eyebrow-sm::before,
  .page-services .strip .eyebrow-sm::after {
    content: ""; flex: 1; height: 1px;
    background: rgba(var(--gold-rgb), .7);   /* rule peak 198,181,73 */
  }

  /* --- the occasion mosaic becomes the card stack ------------------------ */
  .band-intro-svc { padding: 22px 0 12px; }   /* button ink -> label 23, label -> card 11.4 */

  .page-services .svc-grid-a,
  .page-services .svc-grid-b {
    grid-template-columns: 1fr; grid-template-rows: none; gap: var(--svcm-gap);
  }
  .page-services .svc-grid-a .tile-tall { grid-row: auto; }
  .page-services .svc-grid-b { margin-top: var(--svcm-gap); }

  /* min-height, not height. The mockup's own third card grows to 96.7 for a
     second title line, so growing is the shape of this block - and it has to
     here, because our captions are not the mockup's: "From galas to corporate
     nights, Seven makes it extraordinary." is three times the length of
     "Seven Heaven, live and unforgettable." Fixing the height would clip the
     page's own copy to match a picture of someone else's. */
  .page-services .svc-grid-a .tile,
  .page-services .svc-grid-b .tile {
    height: auto; min-height: var(--svcm-card-h);
    display: flex; align-items: center;
    border: 1px solid var(--svcm-line); border-radius: var(--svcm-r);
  }
  /* The gradient the desktop tile lays over the bottom of its photograph, to
     hold a caption sitting on it. Nothing sits on the photograph here. */
  .page-services .svc-grid-a .tile::after,
  .page-services .svc-grid-b .tile::after { content: none; }

  /* Bleeds to the card's top, right and bottom and fades out leftwards.
     A MASK on the image, not a scrim over it, so the card's own background
     shows through rather than a black panel sitting on the photograph.
     Sampled across card 1's top strip, which is photo for its whole width:
     mean brightness 8 at x135, 25 at x174, 47 at x187, 128 at x200 - gone by
     36% of the card, opaque by 45%.

     The fade STARTS later and ENDS later than a straight reading of those
     numbers gives, and both moves are for the same reason: our copy is
     longer than the mockup's. ANNIVERSARIES and CHILDREN'S PARTIES against
     LIVE VOCALS, and "From galas to corporate nights, Seven makes it
     extraordinary." against "Seven Heaven, live and unforgettable."

     Full opacity at 45% put the photograph under the end of two titles; it
     lands at 61% here instead, inside the soft ramp the mockup draws rather
     than at the point the samples first plateau. And the box itself starts
     at 42% rather than the measured 36%, which costs nothing visible - the
     photograph is under 20% opacity out there - but keeps the whole of a
     long caption on flat black instead of over the faint head of the fade.
     At 414 that tail was showing through "Seven makes it extraordinary."

     `right: 0` and a width, NOT `inset: 0` and a width. With both left and
     right at 0 the box is over-constrained, `right` is the one dropped, and
     the photograph anchors to the WRONG edge - under the copy, with the
     chevron sitting on empty black. */
  .page-services .svc-grid-a .tile > img,
  .page-services .svc-grid-a .tile > .ph,
  .page-services .svc-grid-a .tile > picture,
  .page-services .svc-grid-b .tile > img,
  .page-services .svc-grid-b .tile > .ph,
  .page-services .svc-grid-b .tile > picture {
    inset: 0 0 0 auto; width: 58%;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 33%);
            mask-image: linear-gradient(to right, transparent 0, #000 33%);
  }

  /* THE FRAME THIS SLOT ACTUALLY IS. 58% of a 582px card at this breakpoint's
     ceiling is 338 wide against a 91px card, about 3.7:1 - and every desktop
     tile is far squarer than that: Weddings and Birthdays are 900 x 1125
     portraits, and the widest of the other three is 2:1. `cover` on a
     portrait then keeps a vertical band about a fifth of the source wide and
     throws the rest away, which is how Weddings arrived on a phone as a slice
     of one shoulder. The five `-mobile.jpg` frames swapped in above are
     700 x 350; 2:1 into 3.7:1 still crops, but vertically and off a
     landscape, which is the crop the mockup's cards are drawn with.

     `.focus-top` is turned off with them. Its `center 22%` was picked to hold
     the cake in a 900 x 1125 portrait; on a 2:1 frame 22% lifts the window
     clear of the cake it was protecting, and the wide frame needs no help. */
  .page-services .tile > picture > .focus-top { object-position: center 50% !important; }

  /* Out of the absolute corner it occupies on the desktop tile and into
     normal flow, as the card's left column. The mockup's copy ends at x206
     of a 404 card, 47%; 56 here for the same reason the fade moved - the
     titles this page has are longer than the ones the mockup drew. */
  .page-services .tile-copy {
    position: static; inset: auto; z-index: 2;
    width: 56%; padding: 14px 0 14px var(--svcm-pad);
  }
  /* The client's glyphs are hidden, not deleted: the mockup's card carries a
     title and a body line and no mark, and the desktop keeps them. They are
     decorative (alt="") so nothing is lost to a screen reader. */
  .page-services .tile-copy h3 img { display: none; }
  /* .tile-tall is named too, and not for tidiness: the desktop gives the
     Weddings tile its own `.page-services .tile-tall .tile-copy h3` size, and
     that is one class more specific than this rule. Without it that one card
     keeps the mosaic's 28/38px display size while the other four take this. */
  .page-services .tile-copy h3,
  .page-services .tile-tall .tile-copy h3 {
    display: block;
    font-size: 28px; line-height: 1.05;   /* cap 20.4 mockup */
    letter-spacing: .02em;
  }
  /* Three classes, to clear `.tile-copy:not(.tile-copy-stack) .tile-foot`,
     which hangs the caption 78px right to clear an inline glyph. There is no
     glyph here. */
  .page-services .tile-copy .tile-foot {
    display: block; margin-top: 0; padding-left: 0;
  }
  /* Cap 10.5 mockup -> 15px. The 18px leading is NOT the mockup's 16: at 15px
     that clips the descenders on "unforgettable." and "Children's". Set for
     the descender inside a block whose measured height this page cannot hold
     to anyway, the captions being longer than the mockup's. */
  .page-services .tile-copy p {
    margin-top: 6px;                      /* title ink -> body ink 9.8 */
    font-size: 15px; line-height: 18px; max-width: none;
  }

  /* Explore becomes the ring. The word is hidden from sight but kept for a
     screen reader - the chevron beside it is aria-hidden, so dropping the
     text would leave the link with no accessible name at all. Absolute
     against .tile, which is the positioned ancestor now .tile-copy is not. */
  .page-services .explore {
    position: absolute; right: 11px; top: 50%;   /* ring right edge x410.5 of a card ending 421.9 */
    transform: translateY(-50%);
    margin: 0; gap: 0;
    width: var(--svcm-ring); height: var(--svcm-ring);
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--gold-bright); border-radius: 50%;
    /* The dark disc is in the mockup, not a legibility patch invented here -
       the ring is drawn over a near-black circle you can still see the
       photograph faintly through. It earns its place twice over: the
       mockup's ring happens to fall on a dark part of its picture, and ours
       falls on a bride's dress and a lit birthday table, where a bare
       hairline disappeared completely in the first render. */
    background: rgba(var(--bg-rgb), .72);
    color: var(--gold-bright);
    font-weight: 600; font-size: 20px; line-height: 1;
  }
  .page-services .explore span:first-child {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
    border-bottom: 0; padding-bottom: 0;
  }

  /* --- the package strip becomes the four square tiles ------------------- */
  /* The bordered panel goes: in this layout the border belongs to each tile,
     and keeping the panel would draw a box around four boxes. */
  .band-strip { padding: 20px 0 0; }        /* card 3 -> label ink 20.6 */
  .page-services .strip {
    padding: 0; border: 0; background: none;
  }
  .page-services .strip-cols {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--svcm-gap); row-gap: var(--svcm-gap);
    margin-top: 12px;                       /* label -> tile row 12.4 */
  }
  /* Square, which is what the mockup draws: 4 columns and a 7px gap in the
     402 the gutters leave is (402 - 21) / 4 = 95.25, against a measured
     95.1 x 95.1. So the ratio carries the size and no width is hardcoded. */
  .page-services .strip-col {
    position: relative; aspect-ratio: 1;
    display: grid; place-items: center;
    border: 1px solid var(--svcm-line); border-radius: var(--svcm-r);
  }
  .page-services .strip-col + .strip-col { border-left: 1px solid var(--svcm-line); }
  /* Lifted off centre by the label's own height so the pair reads centred in
     the tile rather than the glyph alone. */
  .page-services .strip-col .glyph { width: 38px; height: 38px; margin: 0 0 16px; }
  /* Over the bottom of the tile, where the mockup sets its captions: ink
     bottom 926.0 against a tile bottom of ~931. Cap 9.3 -> 13px. */
  .page-services .strip-col span {
    position: absolute; left: 3px; right: 3px; bottom: 8px; margin: 0;
    font-size: 13px; line-height: 1.1; letter-spacing: .03em;
  }

  .cta-copy-split h2 { font-size: 27px; line-height: 32px; }
}

/* ===========================================================================
   About page
   Mockup: design/mockup/about-812.png (677px native, built at 1440, x1.78).
   Values traced in design/SPEC.md > About page.

   Everything below is prefixed .ab- or scoped to .page-about, so nothing here
   can reach the home, events or services pages. The shared pieces this page
   reuses unchanged: .site-header, .hero/.hero-inner, .eyebrow, .btn-gold,
   .explore, .ph, .sample, .site-footer.
   =========================================================================== */

:root {
  --ab-hero-h: 617px;      /* mockup band 0-415 */
  --ab-h2: 55px;           /* see the note below - measured cap is 64 build */
  --ab-lh-h2: 65px;        /* baseline 517 -> 566 = 49 mockup = 87 build */
  --ab-h2-sm: 37px;        /* "TELL US WHAT YOU'RE PLANNING." on one line */
  --ab-body: 19px;         /* story lede, mockup line 289 wide */
  --ab-lh-body: 26px;      /* mockup 17.5 */
  --ab-body-sm: 18px;      /* every small body line on this page */
  --ab-lh-body-sm: 23px;   /* mockup 16 */
  --ab-lede: 22px;         /* hero lede, mockup line 232 wide */
  --ab-script: 39px;       /* mockup line 1 sets 458 build wide; Caveat needs
                              39px to reach it */
  --ab-lh-script: 44px;    /* mockup 30, and the two lines nearly touch */
  --ab-h-small: 22px;      /* cap 13 mockup, the two centred section heads */
  --ab-icon: 57px;         /* mockup 38 */
  --ab-badge: 71px;        /* mockup 48, centred on the frame's bottom edge */
  --ab-frame-h: 323px;     /* talent panel, pink border 746-966 mockup */
  --ab-talents-w: 968px;  /* panel block, mockup x66-721, inset in the wrap */
  --ab-expect-w: 930px;   /* the three columns, mockup x85-712 */
  --ab-input-h: 27px;      /* mockup 18, border to border */
  /* The About enquiry form is the one place that departs from the measured
     input height: with the photograph and contact card gone the form owns the
     full container, and the client asked for it "nice and big and not
     squashed" (29 Aug 2026). Requested, not measured - so it is a separate
     token and /contact still runs the mockup's --ab-input-h. */
  --ab-input-h-lg: 48px;
}

/* The mockup's H2 cap measures 64 build px, which in Oswald 600 is a 76px
   font - and at 63px "ENTERTAINMENT THAT" sets 592px wide against a 552px
   column, so it wraps to three lines where the mockup holds two. Oswald is
   wider than the face in the render (SPEC.md records the same on the home
   page). The line break is the thing a reader sees, so the size gives way:
   55px holds the mockup's two lines at a cap of 56 instead of 64. */

/* The eyebrow is set tighter here than on the home page: "THE PEOPLE BEHIND
   THE ENERGY" measures 193 mockup px across 28 characters, which is .16em at
   17px, not the home band's .30em. */
.ab-eyebrow { letter-spacing: .16em; }

.ab-h2 {
  margin: 0;
  font-family: var(--display); font-weight: 600;
  font-size: var(--ab-h2); line-height: var(--ab-lh-h2);
  letter-spacing: .01em; text-transform: uppercase; color: var(--ink);
}

.ab-h-small {
  margin: 0; text-align: center;
  font-family: var(--display); font-weight: 400;
  font-size: var(--ab-h-small); line-height: 1.15;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink);
}

/* ------------------------------------------------------------------ hero --- */

.ab-hero { min-height: var(--ab-hero-h); padding-top: 188px; }
.ab-hero .lede {
  margin-top: 22px; max-width: 517px;
  font-size: var(--ab-lede); line-height: 25px;
}

.ab-form {
  display: grid; grid-template-columns: 1fr 1fr; gap: 18px 20px;
  margin-top: 27px; padding: 18px 19px 17px;
  border: 1px solid rgba(var(--gold-rgb), .30); border-radius: var(--r-card);
  background: rgba(var(--ink-rgb), .03);
}
.ab-field-wide, .ab-form-foot { grid-column: 1 / -1; }
.ab-field label {
  display: block;
  font-family: var(--display); font-weight: 400;
  font-size: 14px; line-height: 1; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mut);
}
.ab-field input, .ab-field select, .ab-field textarea {
  display: block; width: 100%; margin-top: 7px; padding: 0 8px;
  height: var(--ab-input-h);
  background: rgba(var(--ink-rgb), .02);
  border: 1px solid rgba(var(--pink-rgb), .45); border-radius: 2px;
  color: var(--ink);
  font-family: var(--body); font-size: 16px; line-height: 1;
}
.ab-field textarea { height: 75px; padding: 8px 8px; line-height: 20px; resize: vertical; }
.ab-field input:focus, .ab-field select:focus, .ab-field textarea:focus {
  outline: 2px solid var(--gold); outline-offset: 1px;
}
.ab-field input::placeholder, .ab-field textarea::placeholder { color: var(--ink-dim); }

/* The date and select keep their native controls: the mockup draws a pink
   calendar and chevron, and a substitute drawn here would be invented artwork.
   Both need the dark colour-scheme - the date field for its picker, and the
   select for its dropdown list. That list is painted by the OS and not by this
   stylesheet: with a dark page and no colour-scheme hint, Chrome and Edge on
   Windows put white option text on a white popup and the choices are
   unreadable. Name the scheme AND the option colours - Firefox and Safari
   honour one, Chromium the other. */
.ab-field input[type="date"], .ab-field select { color-scheme: dark; }
.ab-field select option,
.ab-field select optgroup { background-color: var(--bg); color: var(--ink); }

.ab-form-foot { text-align: center; margin-top: 5px; }
.ab-form-note {
  margin: 10px 0 0;
  font-size: var(--ab-body-sm); line-height: var(--ab-lh-body-sm); color: var(--ink-mut);
}
/* --- what a form says back ------------------------------------------------
   One line under each form, written by that page's own script: sending, sent,
   or the reason it did not. `[hidden]` explicitly, because the pages that use
   this used to put `.sample` on it - which sets display:inline-block and
   outranks the hidden attribute's UA rule, shipping the line visible. */
.ab-form-status, .signup-status {
  margin: 12px 0 0;
  font-size: var(--f-small); line-height: var(--lh-small); color: var(--ink-mut);
}
.ab-form-status[hidden], .signup-status[hidden] { display: none; }
.ab-form-status.is-good, .signup-status.is-good { color: var(--gold); }
/* Pink is this site's only alert colour and it is already the .sample chip's.
   No new red: a red nobody has seen on these pages reads as a browser error
   rather than as the site talking. */
.ab-form-status.is-bad, .signup-status.is-bad { color: var(--pink-light); }

/* The honeypot. Off-canvas rather than display:none, because a bot that skips
   hidden inputs never fills it and the whole point is that it does. Left, not
   top, so no browser ever scrolls to it on focus. `scripts/check-widths.mjs`
   and `scripts/overflow-check.mjs` are told to ignore elements parked out here
   - see the note in whichever of them this repo ships. */
.ph-trap {
  position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;
}

/* ------------------------------------------------------------------- cta --- */

/* Mockup band 1769-1849. The sparkle field is part of the render and exists
   nowhere as a file, so it is reproduced as gradients - SPEC.md > Assumptions. */
.ab-cta {
  /* padding-bottom added 3 Sep 2026. The heading's lettering now sits on the
     heading's baseline with its descenders hanging below it, which is correct
     and which the measured 118px band had no room for - the "y" of "your" was
     being clipped by the bottom border. The band grows by the descender tail
     rather than the artwork being lifted off the baseline to fit.

     36 rather than the 27 the tail actually measured: clearance was swept
     760-1440px in 40px steps and the worst case is 920-1160, where the band
     is mid-transition between its stacked and single-row layouts and the
     heading sits lowest in it. 27 left 1px there. 36 gave 10px at the worst
     width and 16-18 at the common ones.

     24 NOW, because the artwork came down 290 -> 195 and its tail with it, 27
     -> 18. 24 keeps the same ~9px of worst-case clearance the 36 bought at the
     old size; the sweep below was re-run to confirm it rather than scaled.

     PADDING-TOP: 10 ADDED 7 Sep 2026 - "have the top padding match the bottom
     padding." The two numbers still do not match and they are not supposed to,
     because what a reader sees is INK, not box. The heading's lettering hangs
     its descender below the baseline, so the bottom padding is spending 14 of
     its 24 on a tail that is not there at the top. Measured off the render at
     1440: 0/24 of padding was giving 9/19 of ink, and 10/24 gives 17/16.

     Swept 1160 to 1600 after the change - 16/14 at 1160 where min-height still
     governs the band, 17/16 from 1200 up. Descender clearance is 14-16px
     against the >=9 the sweep above was run to protect, so the band that was
     118 is 122 and nothing is clipped. Below 1150 this rule does not apply:
     that block sets a symmetric 28 and measures 31/29 of ink, which is the
     heading's own top leading and is as close as a box can get. */
  display: flex; align-items: center; min-height: 118px; padding: 10px 0 24px;
  border-top: 1px solid rgba(var(--gold-rgb), .45);
  border-bottom: 1px solid rgba(var(--gold-rgb), .45);
  background:
    radial-gradient(34% 150% at 10% 62%, rgba(196, 122, 58, .30), transparent 70%),
    radial-gradient(42% 170% at 76% 45%, rgba(150, 60, 190, .38), transparent 70%),
    radial-gradient(26% 130% at 52% 85%, rgba(120, 40, 160, .24), transparent 70%),
    var(--bg);
}
/* Packed left, for the reason the home CTA's own note gives at length - this
   band had the same `space-between` and the same result, a heading, a marooned
   line of body and a button flush right with voids between them. The mockup
   sets the three together: its heading ends at x290, body runs x330-531 and
   the button x583-752 in a container that runs 27-814, so the gaps are 40 and
   52 native and the trailing space 62 (x1.4333 onto the 1128 column = 57, 75
   and 89). */
.ab-cta .wrap {
  display: flex; align-items: center; justify-content: flex-start;
  gap: 57px; width: 100%;
}
.ab-cta .btn-gold { margin-left: 18px; }   /* 57 + 18 = the measured 75 */
/* Both flex:none. Without it `flex-start` lets the heading shrink to whatever
   is left after the body and the button, and "READY TO BRING SEVEN" breaks
   after "BRING" - a third line the mockup does not have. */
.ab-cta .cta-head, .ab-cta p { flex: none; }
/* 290 is the mockup's own body block, x330-531 native = 202 wide, and it is
   what makes the row fit: unconstrained this sentence sets one 471px line, and
   423 + 57 + 471 + 75 + 145 overruns the 1128 column by 43 - which the button
   absorbed by wrapping to "CONTACT / US". At 290 it sets two lines, as drawn,
   and the row totals 990. */
.ab-cta p { width: 290px; }

/* ------------------------------------------------------------ responsive ---
   No mobile mockup was supplied for this page either (SPEC.md > Assumptions).
   Derived from the desktop grid. The one deliberate departure: form controls
   grow to 37px, because 34 is the mockup's desktop reading and a 28px target
   under a thumb is a different question from a 28px box under a cursor. */

@media (max-width: 1180px) {
  :root { --ab-h2: 47px; --ab-lh-h2: 52px; --ab-h2-sm: 33px;
          --ab-script: 32px; --ab-lh-script: 37px; --ab-lede: 18px; --ab-body: 17px; }
}

/* 1150, NOT THE 900 THE REST OF THIS PAGE STACKS AT, and it is the type that
   sets it: `--f-cta` goes 37 -> 47px at 1180 for the home CTA, which stacks
   into a centred column 30px later at 1150. This band shares that token and
   did not share the breakpoint, so between 900 and 1180 it was holding a 47px
   heading in a single row - 423 + 57 + 290 + 75 + 145 against 928 of column at
   1000, which overran by 120. It stacks with the home CTA now, for the same
   reason and at the same width.

   Three rules, and the two resets are not optional: the button's left margin
   IS the desktop row's 75px gap and would sit it off centre in a column, and
   both fixed widths are desktop measures. */
@media (max-width: 1150px) {
  .ab-cta { padding: 28px 0; }
  .ab-cta .wrap { flex-direction: column; text-align: center; gap: 22px; }
  .ab-cta .btn-gold { margin-left: 0; }
  .ab-cta p { width: auto; max-width: 358px; }
}

@media (max-width: 900px) {
  /* The header is in normal flow below 900 (eaa6c0c), so the hero no longer
     has to clear it. The 28px is the shared `.hero-inner` value repeated, not
     a second opinion: this page's base rule sets 188px and sits later in the
     file at equal specificity, so it wins unless it is answered here. */
  .ab-hero { min-height: 467px; padding-top: 28px; }
  .ab-form { padding: 23px 18px 20px; }
  .ab-field input, .ab-field select { height: 37px; }

}

@media (max-width: 620px) {
  /* The copy carries the mockup's desktop line breaks; below this width they
     break in the wrong places, so the text wraps on its own measure instead. */
.ab-hero .lede br { display: none; }
  :root { --ab-h2: 32px; --ab-lh-h2: 37px; --ab-h2-sm: 27px; --ab-h-small: 18px; }
  .ab-form { grid-template-columns: 1fr; }

  /* --- the same hero layout the home page takes at this width -------------
     Photograph as a block above the copy rather than a background behind it,
     copy centred underneath, lettering straddling the photo's bottom edge,
     CTA gutter to gutter. The reasoning and the measurements are on
     `.hero-home` in the phone block above; this page has no mobile mockup of
     its own, so only two numbers are derived rather than copied:

       aspect      1402/1122, the mobile frame's OWN shape, so the <source>
                   image that already exists here is not cropped at all
       pull-up     16px. Home settled on -20 of its 157px block, 12.7%; this
                   lettering is 972x314, so 125px at this measure, and 12.7% of
                   that is 16. The proportion is carried, not the number - and
                   like home's it is judged against the photograph rather than
                   measured, because it is the picture it must not sit on.

     THE COPY IS REORDERED ON THE PHONE, and that is the one thing here that
     is a decision rather than a measurement. design/mockup/about-864.png runs
     eyebrow -> H1 -> lettering, and the desktop keeps that. The phone leads
     with the lettering, because straddling the photo edge is the whole shape
     of this layout and the element that does it has to come first - putting
     the eyebrow there instead would hang small gold type over the picture.
     Both taglines play the same role on their page, so both lead. Reverting
     is the four `order` values below.

     The lede stays, unlike the home hero's. That one goes because its mobile
     mockup has no lede; nothing has drawn this page at this width, so
     dropping copy here would be a content decision with no spec behind it. */
  .ab-hero { display: block; min-height: 0; padding: 0 0 30px; }
  .ab-hero .hero-media { position: static; }
  .ab-hero .hero-media picture,
  .ab-hero .hero-media img { aspect-ratio: 1402 / 1122; height: auto; }
  .ab-hero .hero-media::after { content: none; }

  .ab-hero .wrap {
    display: flex; flex-direction: column;
    text-align: center; align-items: center; padding-inline: 26px;
  }
  .ab-hero .ab-script-art{ order: 1; margin: -16px auto 0; width: 100%;
                           position: relative; }
  .ab-hero .ab-eyebrow   { order: 2; margin: 0; }
  .ab-hero h1            { order: 3; margin: 8px 0 0; }
  .ab-hero .lede         { order: 4; margin: 14px auto 0; max-width: none; }
  .ab-hero .hero-actions { order: 5; margin-top: 16px; align-self: stretch;
                           align-items: center; gap: 11px; }
  .ab-hero .hero-actions .btn-gold { max-width: none; height: 45px; }
}

/* ===========================================================================
   Contact page
   Mockup: design/mockup/contact-1536.png, REPLACED 5 Sep 2026. The file it
   replaces is kept at design/mockup/superseded/contact-1536-aug30.png, and it
   is not archived because it is finished with: everything below the hero -
   the form, the info card, the social marks - still traces to it. Only the
   hero came off the new one. Same canvas, so the two cannot both sit in
   design/mockup/ under the shot script's <page>-<width>.png rule.

   x0.833, NOT the x0.781 the old file was read at. 0.781 was the canvas
   ratio, 1200/1536; 0.833 is the CONTENT ratio, and it is what two
   independent readings of the header give: its ink spans x99-1452 = 1353
   against the built 1128, and its Contact button is 47 tall against the
   built 39. Both land on 0.833.

   The form is .ab-form / .ab-field, which are declared up in the About block
   because that is where they were first measured. /about no longer carries a
   form at all, so this page is now their ONLY consumer despite the prefix -
   an `ab-` rule changed for /contact's sake cannot break /about any more, and
   a rename would be honest, but the prefix is load-bearing in the markup and
   not worth the churn on its own. Named here so the next reader does not go
   looking for the form on /about to check they have not broken it.
   =========================================================================== */

/* --- hero ----------------------------------------------------------------
   Photograph left, copy right, across the WHOLE 1200 column.

   IT DID NOT USE THE WHOLE COLUMN BEFORE, and that is the visible half of this
   change. The copy's left padding was
     calc((100vw - var(--wrap)) / 2 + var(--gut))
   which computes the gutter as if the section spanned the viewport. It does
   not: `body` is capped at 1200 and positioned, so the section is already
   inside the column and the calc added the centring offset a SECOND time. At
   1440 that put the hero's first ink at x277 against the container's x156 -
   121px in, the exact (1440-1200)/2 the calc contributes. The gutter is just
   --gut now, and the band starts where every other band on the site does.

   THE SPLIT IS 50/50, and that is the mockup's own proportion once the form
   is taken out of it. The mockup draws photo x0-500, copy x548-1000 and its
   form panel x1024-1478; the form stays where it is on this page, below, so
   the hero is the first two of those - and the photo is half of them. */
.page-contact .ct-hero {
  display: grid; grid-template-columns: 50% 1fr; align-items: center;
  min-height: var(--ct-hero-h);
  border-bottom: 1px solid rgba(var(--gold-rgb), .30);
}
.page-contact .ct-hero-media { position: relative; align-self: stretch; }
.page-contact .ct-hero-media > .ph,
.page-contact .ct-hero-media > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  object-position: var(--ct-photo-pos) var(--ct-photo-pos-y);
}
/* The placeholder box has no subject to hold, and its label is centred. */
.page-contact .ct-hero-media > .ph { object-position: 50% 50%; }
/* The mockup fades the photograph into the copy rather than cutting it. Same
   idiom as the home and services heroes' wash, pointed the other way. */
.page-contact .ct-hero-media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(var(--bg-rgb), 0) 62%, rgba(var(--bg-rgb), .96) 100%);
}
.page-contact .ct-hero-copy { padding: 40px var(--gut) 40px var(--ct-hero-gap); }
.page-contact .ct-hero-copy h1 { margin: 0; }
/* Sized off the mockup's ink, x517-996 = 480 wide. No left overhang here: the
   lettering starts inside the copy column rather than hanging on its edge the
   way /services' and /events' do. */
.page-contact .ct-hero-copy .script-art {
  display: block; margin: 13px 0 0;      /* eyebrow ink 142 -> lettering 160 */
  width: min(var(--ct-script), 100%); height: auto;
}
/* .16em is the shared .eyebrow-sm value and this one is drawn much wider: 28
   characters over 373 native px, which is a width-per-character equal to the
   cap height where .16em gives 0.70 of it. The SIZE is unchanged - cap 13
   native is 10.8 build, which is 13px, exactly what .eyebrow-sm already sets -
   so this is tracking and not type. */
.page-contact .ct-hero-copy .eyebrow-sm { letter-spacing: var(--ct-eyebrow-track); }
.page-contact .ct-lede {
  margin: 40px 0 0; max-width: var(--ct-lede-w);   /* lettering ink -> lede 55 */
  font-size: var(--ct-lede-size); line-height: var(--ct-lh-lede); color: var(--ink-mut);
}

/* --- the three marks under the lede ---------------------------------------
   Icon over label, hairline between. The dividers sit in the middle of a 60px
   native gap on both sides of each one, so they are a border on the item
   rather than a positioned element. */
.page-contact .ct-feats {
  list-style: none; display: flex; margin: 18px 0 0; padding: 0;
}
.page-contact .ct-feats li {
  display: flex; flex-direction: column; align-items: center;
  padding: 0 var(--ct-feat-gap); text-align: center;
}
.page-contact .ct-feats li:first-child { padding-left: 0; }
.page-contact .ct-feats li + li { border-left: 1px solid rgba(var(--gold-rgb), .45); }
.page-contact .ct-feats .glyph {
  width: var(--ct-feat-icon); height: var(--ct-feat-icon); object-fit: contain;
}
.page-contact .ct-feats span {
  margin-top: 6px;                       /* icon ink 578 -> label ink 596 */
  font-family: var(--display); font-weight: 500;
  font-size: var(--ct-feat-label); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink);
}

.page-contact .ct-band { padding: 26px 0 34px; }
.page-contact .ct-grid { display: grid; grid-template-columns: 1.86fr 1fr; gap: 17px; align-items: start; }

/* .45, up from .30, sampled off the mockup's panel border. Applied to BOTH
   cards rather than only the form one: they sit side by side and two gold
   borders a step apart reads as a mistake rather than a hierarchy. */
.page-contact .ct-card {
  border: 1px solid rgba(var(--gold-rgb), var(--ct-card-line)); border-radius: var(--r-card);
  background: rgba(var(--ink-rgb), .02);
  padding: 26px 28px 28px;
}
/* The form card carries its own border, so the inner one would double up. */
.page-contact .ct-form { border: 0; background: none; padding: 0; margin-top: 18px;
                         grid-template-columns: repeat(3, 1fr); }
.page-contact .ct-form .ab-field-wide { grid-column: 1 / -1; }
/* The mockup sets its panel heading in gold caps on a wide track where this
   card sets white on .01em. Size unchanged - the card keeps its own heading at
   its own size, and only the finish is taken. --gold-script is the nearest
   token to the sampled (223,179,51); it is named for the home page's lettering
   but it is the site's bright gold, not a one-off. */
.page-contact .ct-form-card .ab-h2 {
  font-size: 30px; letter-spacing: var(--ct-h2-track); color: var(--gold-script);
}

/* --- the fields ----------------------------------------------------------
   Same fields, same grid, same labels. What changes is the finish: the border
   goes from pink at .45 to a neutral white at .26, the box gains 5px of height
   and 6px of inner padding, and the corner picks up the card's own radius.

   THE LABELS STAY. The mockup's panel has none - every field carries its name
   as a placeholder instead - and that is the one part of its look not taken
   here: a placeholder disappears the moment someone types into it, which on an
   enquiry form is the field they most need to check before sending. Style was
   the ask; dropping six labels is not one. */
.page-contact .ct-form .ab-field input,
.page-contact .ct-form .ab-field select,
.page-contact .ct-form .ab-field textarea {
  height: var(--ct-input-h); padding-inline: var(--ct-input-pad);
  border-color: rgba(var(--ink-rgb), var(--ct-field-line));
  border-radius: var(--r-card);
  background: rgba(0, 0, 0, .30);
}
.page-contact .ct-form .ab-field textarea {
  height: 80px; padding-block: 9px;      /* panel textarea 82 native -> 68 */
}
.page-contact .ct-form .ab-field input:hover,
.page-contact .ct-form .ab-field select:hover,
.page-contact .ct-form .ab-field textarea:hover {
  border-color: rgba(var(--ink-rgb), .45);
}

/* Full measure, and the arrow the mockup draws on it. The height is the shared
   button's 40 - the panel's is 37, and one measured pixel of difference is not
   worth this page having its own button height. */
.page-contact .ct-form .ab-form-foot .btn-gold {
  width: 100%; gap: 14px; background: var(--ct-btn-fill);
}
.page-contact .ct-form .ab-form-foot .btn-gold .icon {
  width: 18px; height: 18px; stroke-width: 2;
}

.page-contact .ct-info { display: flex; flex-direction: column; padding: 34px 28px; }
.page-contact .ct-info-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 26px; }
.page-contact .ct-info-list li { display: flex; align-items: center; gap: 18px; }
/* 33.5, NOT 30, AND THE TWO MARKS MATCH BECAUSE OF IT. Box size is not what the
   eye compares - ink is. The Instagram mark is a filled glyph that fills its
   viewBox edge to edge, so a 30px box draws 30px of ink. The pin is a lucide
   outline carrying padding inside its 24-unit viewBox: its artwork spans about
   21 of those units, so the identical 30px box drew 22x26 and read as the
   smaller of the two. Same box, smaller drawing.
   33.5 is the size at which the pin's ink measures 30 tall, the same as the mark
   below it - arrived at by measuring rather than by the arithmetic, which
   predicted 34.6 and came out a pixel over. Scoped to this list: /events sizes
   the same symbol for itself.

   AND THE NEGATIVE MARGIN TAKES THE OVERSIZE BACK OUT OF THE LAYOUT. Growing
   the box to fix the ink moved the problem rather than solving it: the artwork
   now sits 5px inside its own box on each side, so with the same `gap: 18` this
   row read 23px from mark to label where the Instagram row read 19, and the pin
   sat 5px right of the mark below it. Two rows that are meant to be the same
   shape, misaligned in both directions, which is what Jacob saw.
   -5px each side is that inset, measured: ink 24 wide in a 34 box. It makes the
   row lay out on the DRAWING rather than on the box, so `gap: 18` means the same
   thing in both rows and the two marks start on the same left edge. */
.page-contact .ct-info-list .icon {
  width: 33.5px; height: 33.5px; flex: none; color: var(--pink);
  margin-inline: -5px;
}
.page-contact .ct-info-list span {
  font-family: var(--display); font-weight: 500;
  font-size: 17px; letter-spacing: .06em; text-transform: uppercase; color: var(--gold);
}
.page-contact .ct-rule { margin: 30px 0 26px; border: 0; border-top: 1px solid rgba(var(--pink-rgb), .45); }
/* THE PIN ROW'S SHAPE, REUSED. Mark on the left, label beside it, the same 18px
   between them - because this block is that row twice, and the mockup's centred
   caption stopped being right when four marks became one. The label takes the
   info list's own type and its gold, so the two rows cannot drift apart into
   two nearly-identical treatments. */
.page-contact .ct-social { display: flex; }
.page-contact .ct-social a {
  display: flex; align-items: center; gap: 18px;
  color: var(--pink); text-decoration: none;
}
.page-contact .ct-social a span {
  font-family: var(--display); font-weight: 500;
  font-size: 17px; letter-spacing: .06em; text-transform: uppercase; color: var(--gold);
}
/* Only the mark moves on hover. The label is gold, and gold going pink under the
   cursor would read as a second state rather than as the same row lighting up. */
.page-contact .ct-social a:hover .icon { color: var(--pink-light); }
/* simple-icons marks are filled single paths, not lucide's strokes. */
.page-contact .ct-social .icon { width: 30px; height: 30px; flex: none; fill: currentColor; stroke: none; }

@media (max-width: 940px) {
  /* Stacked, and the photograph is first in the DOM so it leads - the same
     order home, about, services and events all take on a phone. */
  .page-contact .ct-hero { grid-template-columns: 1fr; min-height: 0; }
  /* CENTRED AND BIGGER once the copy has the whole width. Beside the photograph
     it is a left-set column in half the page; stacked under it, left-set left
     the right half of every line empty and the lettering at its beside-the-
     photo 400 in an 828px column. Jacob, 7 Sep 2026: "the words (and the image
     of words) should be bigger and centered on the page." */
  .page-contact .ct-hero-copy { padding: 30px var(--gut) 26px; text-align: center; }
  .page-contact .ct-hero-copy .script-art {
    margin-inline: auto; width: min(var(--ct-script-stacked), 100%);
  }
  .page-contact .ct-lede { margin-inline: auto; max-width: 520px; }
  .page-contact .ct-feats { justify-content: center; }
  /* A PICTURE, NOT A LETTERBOX. `min-height: 220px` on a full-width band is
     900x220 at 900 - an aspect of 4.09 against a 3:2 frame, so `cover` threw
     away two thirds of its height and left "a sliver" (his word).

     The img comes out of absolute positioning and takes the frame's own 3:2
     instead, so nothing is cropped at all below the cap; `max-height: 420px`
     is the cap, and it only binds above ~630 where 3:2 would otherwise make
     the photograph 600px tall over the copy. Same idiom as the phone heroes on
     home, services, about and events. */
  .page-contact .ct-hero-media { min-height: 0; }
  /* The crop turns vertical here - see the note on --ct-photo-pos-y. Written
     as a property, NOT as a `.page-contact { --ct-photo-pos-y: ... }` variable
     override, because the :root that sets the default sits LATER in the file:
     same specificity, so source order would win and the override would do
     nothing. This rule is `.page-contact .ct-hero-media > img` either way, and
     the one inside a media query is the later of the two.

     `img` only, deliberately: the placeholder box below keeps its own centred
     50% 50%, because a label has nothing to hold clear of an edge. */
  .page-contact .ct-hero-media > img {
    position: static; width: 100%; height: auto;
    aspect-ratio: 3 / 2; max-height: 420px; object-fit: cover;
    object-position: var(--ct-photo-pos) var(--ct-photo-pos-y-stacked);
  }
  /* The wash fades the photograph into a copy column that is no longer beside
     it. Left on, it would just darken the picture's right edge. */
  .page-contact .ct-hero-media::after { content: none; }
  .page-contact .ct-grid { grid-template-columns: 1fr; }
  /* The shared block bumps .ab-field to 37px at 900 for touch. This card's own
     rules outrank it, so the bump has to be repeated or the fields would come
     out SMALLER on a phone than they were. 940 rather than 900 because that is
     this page's breakpoint and /contact is the only page with this form. */
  .page-contact { --ct-input-h: 37px; }
}
@media (max-width: 620px) {
  /* `--ct-feat-gap` used to step to 16 here. It ramps from 940 now, and 16 is
     its floor, so this block has nothing left to say about it. */
  /* .19em is the mockup's track on a 12-character heading in a wide panel.
     This card's heading is 29 characters in a 318px column at 390, where the
     same track pushes it from two lines to three. */
  .page-contact { --ct-h2-track: .08em; }
  .page-contact .ct-form { grid-template-columns: 1fr; }
  .page-contact .ct-card { padding: 22px 18px 24px; }
  .page-contact .ct-social { gap: 26px; }
}

/* The header overlays the hero on every other page, because those heroes are
   tall photographs. This one is a 185px band, so an absolute header lands on
   the copy. The mockup shows it in normal flow with a hairline under it. */
:root {
  /* --- hero, design/mockup/contact-1536.png (5 Sep), x0.833 --------------- */
  --ct-hero-h: 494px;          /* mockup band 72-665 = 593 */
  --ct-hero-gap: 40px;         /* photo edge 500 -> copy 548 = 48 */
  --ct-script: 400px;          /* lettering ink x517-996 = 480 */
  --ct-eyebrow-track: .46em;   /* 28 chars over 373 native -> 311 build */
  --ct-lede-size: 20px;        /* line 1 418 native -> 348, against 210 at 12 */
  --ct-lh-lede: 22px;          /* ink top 423 -> 450 -> 477 = 27 */
  --ct-lede-w: 375px;         /* line 1 keeps its em dash, as drawn */
  /* THE THREE MARKS SCALE, they do not step. Jacob, 7 Sep 2026: "the icons
     below should slowly get smaller, not in steps." They were a fixed 52px
     box on a padding that jumped 35 -> 16 at one breakpoint, so the row held
     its full desktop size all the way down to 620 and then lost half its
     spacing in a single pixel of viewport.

     Both are now solved through the same two anchors the rest of this file
     uses - the measured value at the top, the value that fits at 390 - and
     both ceilings are the measured numbers, so no desktop pixel moves: at
     1440 the expressions give 64.7 and 52.1, and clamp to 52 and 35. */
  --ct-feat-icon: clamp(38px, calc(2.55vw + 28px), 52px);   /* 52 = ink 54 native -> 45 in a 52 box */
  --ct-feat-label: 11px;       /* label cap 11 native -> 9 */
  --ct-feat-gap: clamp(16px, calc(3.45vw + 2.5px), 35px);   /* 35 = labels span 416 native -> 347 */
  /* Bigger than the beside-the-photo 400, because stacked it has the whole
     column: 62vw is 558 at 940 and 242 at 390, where `min(_, 100%)` hands it
     the 318 the gutters leave anyway. */
  --ct-script-stacked: clamp(300px, 62vw, 560px);
  /* PICKED BY RENDERING IT, not reasoned - 50, 58, 66 and 74 were shot at
     1440 and compared, which is what the services hero's own crop value did.
     The frame is 3:2 and the slot is 1.21, so `cover` throws away 19% of each
     side, and the subject sits right of centre in the source: at 50 her right
     shoulder is cut by the frame edge, and at 66 her hair runs into the wash.
     74 is where the whole figure sits clear of both.
     This number belongs to THIS photograph. Re-shoot the ladder if it is
     replaced - a stale crop value is invisible until it hides the subject. */
  --ct-photo-pos: 74%;
  /* THE VERTICAL HALF OF THAT CROP, and it has to be two values because the
     crop changes AXIS when the hero stacks. Beside the copy the slot is 1.21
     against a 3:2 frame, so `cover` has width to spare and the excess is
     horizontal - the value above does the work and this one is inert. Stacked
     it is a full-width 220px band, 2:1 at 440 and 4.3:1 at 940, so the excess
     is entirely VERTICAL and this is the only value that can hold her.

     50 centres that band on her chin and takes the top of her head off, which
     is what shipped. 0/10/20/50 were shot at 440, 620 and 940 and compared:
     10 already grazes her hair at 620, 20 cuts it, and only 0 keeps the whole
     head at every width - at 940 the band is so wide that 1% of the frame is
     about 7px of source, so there is no headroom to spend. Top-aligned it is.
     Same warning as above: these belong to THIS photograph. */
  --ct-photo-pos-y: 50%;
  --ct-photo-pos-y-stacked: 0%;

  /* --- the enquiry card, restyled to the mockup's panel ------------------
     Sampled off the "Get in touch" panel the mockup draws in its hero. The
     CARD IS NOT MOVED, RESIZED OR RESHAPED and its fields are the same fields
     in the same 3-across grid - only the finish is taken. */
  --ct-card-line: .45;         /* border (90,68,41) over bg = gold at .445 */
  --ct-field-line: .26;        /* border (73,68,73) over bg = white at .26 */
  --ct-input-h: 32px;          /* field box 38 native, border to border */
  --ct-input-pad: 14px;        /* border 1056 -> placeholder 1073 = 17 */
  --ct-h2-track: .19em;        /* "GET IN TOUCH" 134 native over 12 chars */
  /* Brighter than the shared --gold-fill and peaking in the middle rather
     than at 35%: sampled (210,161,69) / (246,212,100) / (196,147,60) across
     the panel's Send button. Its own token because --gold-fill is every other
     button on the site. */
  --ct-btn-fill: linear-gradient(100deg, #D2A145 0%, #F6D464 50%, #C4933C 100%);
}

.page-contact .site-header { position: static; padding: 29px 0 22px; }
.page-contact .site-header .wrap { border-bottom: 0; }
/* The mockup left-aligns the submit, and the "1-2 business days" line already
   appears in the hero, so the form does not repeat it. */
.page-contact .ab-form-foot { text-align: left; }
.page-contact .ab-form-note { display: none; }

/* ===========================================================================
   Events page - live dates
   Mockup: design/mockup/events-862.png (862px native, built in the 1200
   column, x1.392 = 1200/862). Replaces the "turn any gathering into an event"
   layout that mockup used to carry; git has the old one at 999315c.

   Bands, measured at x=858 clear of all content, native -> build at x1.392:

     hero        0-438     439 -> 611
     rule        439-440     -> 1px --card-line
     filter      441-487     47 ->  65
     schedule    490-1251   762 -> 1061
     split      1254-1491   238 -> 331
     signup     1494-1580    87 -> 121
     cta        1583-1703   121 -> 168
     footer     1704-1823   120 -> existing .site-footer, unchanged

   A NOTE ON THE TYPE SIZES BELOW. This mockup is a generated image, and its
   letterforms are not internally consistent: read as cap height, the schedule
   heading measures 32 native (46 build, so a ~62px face); read as total width,
   the same 21-character line measures 339 native (472 build, so a ~40px face).
   Those cannot both be true. Width won every time, because a wrong width
   reflows the layout and a wrong cap height only looks slightly heavy - and
   because width is the measurement Phase 3 can actually check against a
   render. Where the two disagreed the width reading is the one recorded.
   =========================================================================== */

:root {
  /* --- Bands ------------------------------------------------------------- */
  /* --- hero, re-measured off design/mockup/events-1024.png, 5 Sep 2026 ---
     A SECOND MOCKUP FOR THIS PAGE, and it replaces the HERO only. Everything
     below - the schedule, the venue chips, the split, the signup and the CTA -
     is still events-862.png's, so both files are live and both get shot.

     What it changes: the eyebrow reads "Live vocals. Unforgettable rooms.",
     the client's pink brush lettering "See Seven Live" replaces the Oswald
     H1, the rule is centred rather than left-aligned, the next-show card moves
     out from beside the heading to UNDER it and is re-laid inside, and a gold
     hand runs down the photograph's right side.

     IT SCALES x1.27, unlike the services mockup of the same day which read
     1:1. Its header ink spans x68-955 = 887 against the built 1128, and
     "UPCOMING PERFORMANCES" SETTLES IT - a heading below the hero that this
     mockup does not change, so the same string in the same face is on both
     images: 369 wide and 28 tall here against 465 and 36 built, which is
     1.260 and 1.286. Width and height agree, independently.

     The lede was tried as that ruler first and gave 1.15, which is wrong: its
     ink runs into the photograph's left edge on the render and a plain scan
     of the row returned 368 rather than 333. It is not unchanged copy either -
     at the settled scale the mockup sets it at 21px, not the 19 it had.

     ITS HEADER IS NOT TO SCALE, the same as every generated mockup on this
     project: it draws the wordmark 68 tall, which is 86 at x1.2717 against a
     built 110. So its 28px wordmark-to-eyebrow gap is applied to the REAL
     wordmark's bottom edge (138) rather than to a scaled position, putting the
     eyebrow's ink at 174 instead of the 134 a straight scale would ask for.

     THE BAND IS 697, NOT THE 656 THE MOCKUP SCALES TO. 41 of those are that
     header difference and nothing else: the copy block itself is the mockup's
     510, and the 13 under the card is the mockup's own. */
  --ev-hero-h: 697px;        /* mockup band 0-515 = 656, + 41 for the header */
  /* THE CLEARANCE TRACKS THE HEADER, because the header is not a fixed height.
     The formula is the mockup's - wordmark bottom + its own 28 - and 172 is
     that formula solved at 1440 only. The wordmark is
     `clamp(125px, 13.17vw, 189px)` on a 1.7214 aspect, so its bottom edge
     falls with the viewport: 139 at 1440, 121 at 1200, 112 at 1081, 105 at
     1000. Held at a flat 172 the gap grows from the measured 33 to 60 on the
     way down, which is the design coming apart quietly rather than loudly.

     Solved through the same two points the wordmark's own clamp is:
     29px of header padding + clamp(72.6, 7.65vw, 109.8) of wordmark + 28.
     That holds the clearance at ~33 from 941 to 1600 - 172 at 1440 exactly, so
     no desktop pixel moves - and the floor is the value at 941, the narrowest
     width where the header is still overlaid and this padding still has a job.
     Below 940 the header is in normal flow and the events hero drops to 30. */
  --ev-hero-pt: clamp(134px, calc(7.65vw + 62px), 172px);
  --ev-hero-copy: 539px;     /* the lettering, the column's widest child */
  /* SHRINKS FROM 1000 DOWN. Jacob, 7 Sep 2026: "at about 1000 on that page,
     the see seven live image should start getting smaller." His eye was within
     40px of the crossover - measured with the copy hidden, the lit part of the
     photograph (her, and the arch behind her) begins at x566 at 1000 against a
     lettering box ending at x546, so 20px of slack; at 960 it is -3 and the
     two are touching; by 900 it is -119.

     Held at a flat 539 the lettering never moved while the frame narrowed
     around it, so the overlap grew to -186 by 641.

     Solved through 539 at 1000 - so nothing above 1000 moves - and 348 at 641,
     which is what the phone block sets one pixel lower, so there is no step
     into it either. */
  --ev-hero-script: clamp(348px, calc(53.2vw + 7px), 539px);
  --ev-hero-script-bleed: -29px;  /* ink x44 against the copy column's x67 */
  --ev-eyebrow-hero: 20px;   /* "LIVE VOCALS. UNFORGETTABLE ROOMS." 299 -> 380 */
  --ev-hero-lede: 21px;      /* line 1 289 -> 367, against 333 at the old 19 */
  --ev-lh-hero-lede: 29px;   /* ink top 253 -> 276 = 23 */
  --ev-hero-lede-w: 380px;   /* the measure line 1's 367 needs */

  /* --- the next-show card ------------------------------------------------
     Frame x57-416 / y330-506 on the mockup. It is not the old card rescaled:
     in a box 26% wider the date numeral grows 72 -> 93 while the venue
     heading stays where it was, so the date dominates where it used to share.
     The venue was first read as SHRINKING to 25, off a measurement of the old
     card that ran into the photograph behind it; measured clean it is 128 at
     25px against the mockup's 158, i.e. unchanged. */
  --ev-card-w: 458px;        /* 360 mockup */
  --ev-card-h: 225px;        /* 177 mockup */
  --ev-card-date-w: 158px;   /* frame x57 -> divider x181 = 124 */
  --ev-card-pt: 20px;        /* frame 330 -> venue ink 349 = 19 */
  --ev-card-date-pt: 11px;   /* the label starts 7 lower than the venue does */
  --ev-card-pb: 22px;        /* button 489 -> frame 506 = 17 */
  --ev-card-gap: 24px;       /* divider x181 -> detail text x200 = 19 */
  --ev-ns-label: 18px;       /* "NEXT SHOW" 87 -> 111 */
  --ev-ns-mon: 33px;         /* "OCT" 48 -> 61 */
  --ev-ns-day: 93px;         /* "09" 66 -> 84 wide, cap 60 -> 76 */
  --ev-ns-venue: 31px;       /* "SOCIAL CLUB" 124 -> 158, against 128 at 25 */
  --ev-lh-ns-venue: 32px;    /* line to line 25 */
  --ev-ns-btn-h: 46px;       /* button box 36 tall */

  --ev-filter-h: 65px;       /* mockup band 441-487 */
  --ev-sched-pt: 33px;       /* band 490 -> heading ink 514 = 24 mockup */
  --ev-sched-pb: 21px;       /* note ink 1236 -> band 1251 = 15 mockup */
  --ev-split-h: 331px;       /* mockup band 1254-1491 */
  --ev-signup-h: 121px;      /* mockup band 1494-1580 */

  /* --- Schedule table -----------------------------------------------------
     One bordered box, rules at native y 570 662 754 845 936 1026 1116 1206:
     seven rows on a 91px pitch, eight rules counting the frame. The columns
     are the two internal dividers (x137, x376), the thumbnail's own edges
     (x517-673) and the frame (x17-843), normalised onto the 1128px content
     width so they still total exactly one row. */
  --ev-row-h: 127px;         /* 91 mockup */
  --ev-col-date: 164px;      /* x17-137   = 120 mockup */
  --ev-col-venue: 326px;     /* x137-376  = 239 */
  --ev-col-time: 193px;      /* x376-517  = 141 */
  --ev-col-thumb: 213px;     /* x517-673  = 156, the image itself */
  --ev-thumb-h: 105px;       /* 77 mockup */

  /* --- Type ---------------------------------------------------------------
     Seeded from measured ink WIDTH, per the note above. */
  --ev-h2: 42px;             /* "UPCOMING PERFORMANCES" 339 mockup -> 472 */
  --ev-day: 52px;            /* "05" 36 mockup -> 50 wide */
  --ev-mon: 19px;            /* "SEP" 32 mockup -> 45 wide */
  --ev-venue: 24px;          /* "M CASINO" 80 mockup -> 111 wide */
  --ev-place: 15px;          /* "Las Vegas, NV" 69 mockup -> 96 */
  --ev-tag: 12px;            /* "CASINO SHOW" 78 mockup -> 108 */
  --ev-time: 17px;           /* "8:00 PM" 53 mockup -> 74 */
  --ev-note: 14px;           /* the "subject to change" line */
}

/* ------------------------------------------------------------------ hero ---
   design/mockup/events-1024.png, 5 Sep 2026. One copy column over the
   photograph - eyebrow, lettering, lede, rule, then the next-show card. The
   card used to sit BESIDE the heading in a 364px box; it is under it now and
   458 wide.

   The mockup also draws a gold hand down the photograph's right side, "Music
   / People / Unforgettable / Nights." with a heart. It was built and then
   DROPPED the same day at Jacob's request, so the band keeps its measured
   height and the copy column is all that is in it. */
.page-events .hero-live { min-height: var(--ev-hero-h); align-items: flex-start; padding: 0; }
/* A plain block since the gold hand came out: it was the second column, and
   `1fr auto` with nothing in the auto track still charges the 40px gap to the
   copy column. Keeps its name - the phone block below reaches for it. */
.page-events .hero-live-grid {
  padding-top: var(--ev-hero-pt);   /* clears the real header, see the tokens */
}
.page-events .hero-live-copy { max-width: var(--ev-hero-copy); min-width: 0; }
/* The h1 is the lettering and nothing else, so it carries no type of its own.
   404px was the services hero's number; this one is 539, and the -29 is the
   brush "S" swinging left of the copy column the way that one's "U" does. */
.page-events .hero-live h1 { margin: 0; }
.page-events .hero-live .script-art {
  display: block;
  margin: 6px 0 0 var(--ev-hero-script-bleed);    /* eyebrow ink 116 -> 123 */
  width: min(var(--ev-hero-script), 100%); height: auto;
}
/* 21px, not the shared --f-body-hero's 19. This paragraph looked unchanged and
   is not: line 1 measures 289 on the mockup, which is 367 at the settled x1.27
   against the 333 the old size renders. Its own tokens, because --f-body-hero
   is the home and services heroes' size too. */
.page-events .hero-live .lede {
  margin: 1px 0 0; max-width: var(--ev-hero-lede-w);  /* script ink 247 -> lede 253 */
  font-size: var(--ev-hero-lede); line-height: var(--ev-lh-hero-lede); color: var(--ink-mut);
}
.page-events .hero-live .eyebrow-sm { font-size: var(--ev-eyebrow-hero); }
/* Centred, not left-aligned. The mockup puts its midpoint at x233 against the
   card's own centre of x236 below it, so it is centred on the column rather
   than indented by a measured amount - and the component is unchanged at its
   natural 200px, which is why no width is set here. */
/* Centred on the CARD's width rather than the copy column's: the mockup puts
   its midpoint at x233 against the card's own centre of 236 below it, and the
   column is wider than both because the lettering overhangs it. The component
   itself is untouched at its natural 200px. */
.page-events .hero-live .rule {
  justify-content: center; width: min(var(--ev-card-w), 100%); margin-top: 20px;
}

/* --- the next-show card --------------------------------------------------
   Gold 1px frame over the photograph, as the mockup draws it - the fill is a
   wash rather than a solid so the stage light behind still reads through the
   corner. Two columns split by a hairline: the date, then the details with the
   button under them. The button no longer runs the card's full width. */
.page-events .nextshow {
  display: grid; grid-template-columns: var(--ev-card-date-w) 1fr;
  width: min(var(--ev-card-w), 100%); min-height: var(--ev-card-h);
  margin-top: 22px;                      /* rule 313 -> frame 330 = 17 */
  border: 1px solid var(--gold);
  padding: var(--ev-card-pt) 0 var(--ev-card-pb);
  background: linear-gradient(180deg, rgba(9, 5, 20, .90), rgba(var(--bg-rgb), .93));
}
/* The divider is the date column's own right border, so it spans exactly the
   card's padding box and needs no separate element or absolute positioning. */
.page-events .nextshow-date {
  display: flex; flex-direction: column; align-items: center;
  padding-top: var(--ev-card-date-pt);
  border-right: 1px solid rgba(var(--gold-rgb), .45);
}
.page-events .nextshow-detail { min-width: 0; padding-left: var(--ev-card-gap); }
.page-events .nextshow-label {
  margin: 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--ev-ns-label); letter-spacing: .22em;
  text-transform: uppercase; color: var(--pink);
}
.page-events .nextshow .showdate { margin-top: 12px; text-align: center; }
.page-events .nextshow .showdate-mon { font-size: var(--ev-ns-mon); display: block; }
.page-events .nextshow .showdate-day { font-size: var(--ev-ns-day); display: block; }
/* The measure is what breaks "TAP N' ASH / SOCIAL CLUB" onto the mockup's two
   lines - 130 and 158 wide - rather than a <br>, so a longer venue name wraps
   instead of overflowing. */
.page-events .nextshow-detail h2 {
  margin: 0; max-width: 175px;
  font-family: var(--display); font-weight: 500;
  font-size: var(--ev-ns-venue); line-height: var(--ev-lh-ns-venue);
  letter-spacing: .01em; text-transform: uppercase;
}
.page-events .nextshow .showplace { margin-top: 10px; }
.page-events .nextshow .showtime { margin-top: 6px; }
.page-events .nextshow .btn { margin-top: 14px; height: var(--ev-ns-btn-h); }

/* ---------------------------------------------------------------- filter ---
   Mockup band 441-487, hairline above and below, three centred tabs. */
.page-events .showfilter {
  height: var(--ev-filter-h);
  border-top: 1px solid var(--card-line);
  border-bottom: 1px solid var(--card-line);
}
.page-events .showfilter .wrap {
  display: flex; justify-content: center; align-items: center;
  gap: 78px; height: 100%;    /* tab ink x226-608, three items across 382 */
}
.page-events .showfilter-tab {
  padding: 0 0 7px; border: 0; border-bottom: 2px solid transparent;
  background: none; cursor: pointer;
  font-family: var(--display); font-weight: 400;
  font-size: 15px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-dim);
}
.page-events .showfilter-tab:hover { color: var(--ink); }
.page-events .showfilter-tab[aria-pressed="true"] {
  color: var(--pink); border-bottom-color: var(--pink);
}

/* -------------------------------------------------------------- schedule --- */
.page-events .band-schedule { padding: var(--ev-sched-pt) 0 var(--ev-sched-pb); }
.h2-shows {
  margin: 0; text-align: center;
  font-family: var(--display); font-weight: 300;
  font-size: var(--ev-h2); line-height: 1.1;
  letter-spacing: .06em; text-transform: uppercase;
}
.rule-center { justify-content: center; margin-top: 11px; }   /* 554-561 mockup */

/* The list is one bordered box and the row rules are its internal borders, so
   a hidden row takes its rule with it and the frame never gains a double line
   or a gap. Hence border-top on every row but the first, plus a frame on the
   list itself. */
.shows {
  list-style: none; margin: 22px 0 0; padding: 0;
  border: 1px solid var(--card-line);
}
/* THE THUMBNAIL ABSORBS THE SQUEEZE, NOT THE BUTTON. The four measured columns
   are 896 together and the button needs 138 of its own, so this row wants 1034
   of container - which it stops having at a 1106 viewport. Below that the last
   track collapsed to the button's min-content and the button hung out over the
   card's right border: +11 at 1096, +26 at 1081, reported 7 Sep 2026.

   `minmax(186px, 1fr)` on the button track is the fix and 186 is measured: 138
   of button plus the 24 of inset it has on each side at 1200. `minmax(0, 213px)`
   lets the thumbnail give up the difference instead - it is a decorative,
   `object-fit: cover` frame, so a tighter crop costs nothing, and it is already
   the first thing the 1080 block drops. At 1096 it goes 213 -> 155 and every
   other column holds its measured width. */
.show {
  display: grid;
  grid-template-columns:
    var(--ev-col-date) var(--ev-col-venue) var(--ev-col-time)
    minmax(0, var(--ev-col-thumb)) minmax(186px, 1fr);
  align-items: center;
  min-height: var(--ev-row-h);
}
.show + .show { border-top: 1px solid var(--card-line); }
/* The dividers after the date and venue columns run the full row height, as
   the mockup draws them. */
.show > .show-venue, .show > .showtime {
  border-left: 1px solid var(--card-line);
  align-self: stretch; display: flex; justify-content: center;
}
/* Only the venue block stacks. The time is icon-then-text on one line, and
   inheriting the column direction here put the clock above it. */
.show > .show-venue { flex-direction: column; }
.show > .showdate { padding-left: 30px; }
/* Vertical padding is a floor, not a spacing decision: the column is
   `align-self: stretch`, so a billing long enough to fill the row ran its text
   flush against the border above and below it. Rows whose content is shorter
   than the min-height are unmoved, which is all of them but one. */
.show > .show-venue { padding: 10px 20px 10px 34px; }
.show > .showtime { padding-left: 24px; }

.showdate { margin: 0; font-family: var(--display); line-height: 1; }
.showdate-mon {
  display: block; font-weight: 500;
  font-size: var(--ev-mon); letter-spacing: .18em; text-transform: uppercase; color: var(--pink);
}
.showdate-day {
  display: block; margin-top: 6px; font-weight: 300;
  font-size: var(--ev-day); letter-spacing: .01em; color: var(--ink);
}
.show-venue h3 {
  margin: 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--ev-venue); line-height: 1.1;
  letter-spacing: .01em; text-transform: uppercase;
}
.showplace, .showtime {
  display: flex; align-items: center; gap: 7px; margin: 7px 0 0;
  font-family: var(--body); font-size: var(--ev-place); line-height: 1.2; color: var(--ink-mut);
}
.showplace .icon, .showtime .icon { width: 15px; height: 15px; flex: none; }
.showtime { margin: 0; font-size: var(--ev-time); color: var(--ink); }
.showtime .icon { color: var(--pink); width: 17px; height: 17px; }
.showtag {
  margin: 6px 0 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--ev-tag); letter-spacing: .14em; text-transform: uppercase; color: var(--pink);
  /* 1.25, not the inherited body leading. Every tag was one line until the
     House of Blues billing arrived on 9 Sep 2026, so nothing showed that this
     was carrying 23px of leading on a 12px uppercase face - 1.92, which is set
     for running prose and is far too loose for caps. At two lines it cost 46px
     in a 127px row and pushed the text against both borders. Single-line tags
     do not move: the row's min-height governs them. */
  line-height: 1.25;
}
/* The second line of a billing: "With special guest ...". Added 9 Sep 2026 for
   the House of Blues row, the first show whose name and whose support act are
   two different facts. It is a quieter sibling of .showtag rather than a second
   tag - two lines in the same pink caps would read as two tags of equal weight,
   and the tribute is what the row is. No new size token: it takes --ev-tag and
   steps down with it at every breakpoint, so it cannot part company with the
   line above. */
.showguest {
  margin: 3px 0 0;
  font-family: var(--display); font-weight: 400;
  font-size: var(--ev-tag); letter-spacing: .06em; color: var(--ink-mut);
  line-height: 1.35;
}
.show-thumb { height: var(--ev-thumb-h); font-size: 10px; }
.show > .btn { justify-self: center; }
.btn-ghost-sm { height: 43px; padding: 0 22px; font-size: 13px; letter-spacing: .14em; }

.shows-note {
  margin: 24px 0 0; text-align: center;
  font-size: var(--ev-note); line-height: 1.4; color: var(--ink-dim);
}

/* --- venues: rooms Seven plays -------------------------------------------
   NOT MEASURED - no mockup draws this band. Added 2 Sep 2026 with the venues
   section on /events. It borrows the services page's bordered `.strip` box
   wholesale (same border colour, same radius, same 2% wash) so it reads as a
   sibling of "Three ways to book Seven" rather than a new kind of object,
   and takes its type from the tokens already on this page. */
.page-events .band-venues { padding: 0 0 var(--ev-sched-pb); }
.venues {
  padding: 24px var(--gut) 27px; text-align: center;
  border: 1px solid rgba(var(--gold-rgb), .22); border-radius: var(--r-card);
  background: rgba(var(--ink-rgb), .02);
}
.venues .eyebrow-sm { margin-bottom: 9px; }
/* --f-h2 is the measured section-heading size (SPEC.md > Type). Used
   rather than a new number: --ev-h2's 42px is the page's own big
   "UPCOMING PERFORMANCES" and would outrank the schedule above. */
.venues-h2 { font-size: var(--f-h2); }

/* Chips, not a divider-separated row. A `li + li { border-left }` rule looks
   right on one line and wrong the moment the list wraps - the first name on
   every wrapped line keeps a leading rule, and CSS cannot see a line break to
   suppress it. Each name carrying its own border sidesteps that entirely and
   survives any number of names at any width. */
.venue-list {
  list-style: none; margin: 26px 0 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
}
.venue-list li {
  padding: 7px 15px;
  border: 1px solid rgba(var(--gold-rgb), .28); border-radius: 2px;
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-card-title); line-height: 1.2;
  letter-spacing: .10em; text-transform: uppercase; color: var(--ink-mut);
}

.venues-note { margin: 27px 0 0; font-size: var(--ev-note); color: var(--ink-dim); }
.venues-note a { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }

/* Chips wrap on their own, so the phone override the divider version needed
   is gone with it. */

/* ----------------------------------------------------------------- split ---
   Photo runs from the page edge to x405 of 862 native; the copy starts x460.
   47/53, against the shared .split's 62/38. */
/* Hairline at native y1252, matching the one under the filter bar and the
   one above the signup - the mockup rules every band boundary below the hero. */
/* ONE COLUMN, because there is no photograph beside this copy any more. The
   E9 placeholder - "Seven with the crowd" - came out on 7 Sep 2026 at Jacob's
   request; it had already been hidden on a phone the day before for being that
   layout's largest piece of dead space. `.split-live .split-media` and the
   47/1fr split went with it, and so did the hairline: it was there to close
   the split against the picture, and with the picture gone it is a rule across
   the page for no reason.

   Same shape and the same reasoning as `.corp-split` on /services, which lost
   its generated ballroom on 6 Sep - so the copy centres here too rather than
   sitting hard left in half a page it no longer shares. */
.page-events .split-live { grid-template-columns: 1fr; border-top: 0; }
.page-events .split-copy-live {
  padding: 40px var(--gut); text-align: center;
}
/* `margin-inline: auto` AND `justify-content`, because the hairline is a
   `<p class="rule">` and `.split-copy p` caps every paragraph in this block at
   the mockup's 317. Centring its CONTENT while its 317px box stays hard left
   moves nothing - which is exactly what it did until this was measured: the
   computed style read `justify-content: center` and the rule still sat at
   x192 of a column running to 1284. */
.page-events .split-copy-live .rule { justify-content: center; margin-inline: auto; }
.page-events .split-copy-live .explore { justify-content: center; }
.page-events .split-copy-live p:not([class]) {
  margin: 16px auto 0; max-width: 330px;
  font-size: var(--f-body); line-height: 21px; color: var(--ink-mut);
}
.page-events .split-copy-live .explore .icon { width: 17px; height: 17px; }

/* ---------------------------------------------------------------- signup --- */
.page-events .band-signup {
  min-height: var(--ev-signup-h);
  border-top: 1px solid var(--card-line);
  display: flex; align-items: center;
}
.page-events .band-signup .wrap {
  width: 100%;   /* a flex item, so it would otherwise shrink to its content */
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  column-gap: 34px; row-gap: 10px;
}
.h2-signup { text-align: left; font-size: 34px; }   /* 235 mockup -> 327 wide */
.signup-sub {
  margin: 0; font-size: var(--f-small); line-height: 19px; color: var(--ink-mut);
}
.signup-form { display: flex; gap: 16px; }
.signup-form input {
  width: 250px; height: 47px; padding: 0 15px;   /* box 180x34 mockup -> 250x47 */
  border: 1px solid rgba(var(--ink-rgb), .22); border-radius: var(--r-btn);
  background: rgba(var(--ink-rgb), .04); color: var(--ink);
  font-family: var(--body); font-size: var(--f-body);
}
.signup-form input::placeholder { color: var(--ink-dim); }
.signup-form input:focus { outline: none; border-color: var(--gold); }
.signup-form .btn { height: 47px; }                /* button 142x35 -> 198x49 */
/* Under the form, spanning the row, rather than a third grid column. It used
   to be the permanent "not connected" chip and sat beside the button; it is a
   result line now, so it belongs where the reader's eye lands after pressing
   the button, and it can be two words or a sentence. */
.page-events .signup-status { grid-column: 1 / -1; justify-self: start; margin: 10px 0 0; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ------------------------------------------------------------------- cta ---
   Eyebrow above the serif line, both left, button right. `.band-cta p` sets a
   242px width for the home page's paragraph; the eyebrow here is not that. */
.page-events .band-cta-live { border-top: 1px solid var(--pink); padding: 30px 0 32px; }
.page-events .band-cta-live p.eyebrow { width: auto; font-size: var(--f-eyebrow-sm); }
.page-events .band-cta-live .h2-serif { margin-top: 12px; }

/* ------------------------------------------------------------- narrower --- */
/* WHERE THE EVENTS HERO STOPS CLEARING THE HEADER. The header goes into normal
   flow at 940 (see `.site-header { position: relative }` in that block), so
   from here down `--ev-hero-pt`'s job is done and the hero takes 30px of its
   own - the value the 1080 block used to apply 140px too early.

   IT LIVES HERE, NOT IN THE 940 BLOCK WITH THE HEADER RULE IT BELONGS TO, and
   that is not tidiness lost for nothing: `.page-events .hero-live-grid` is
   (0,2,0) either way, so the copy in the 940 block up at line ~900 loses to
   the base rule 2000 lines below it and does nothing at all. Same specificity
   means later wins, and this is later. */
@media (max-width: 940px) {
  .page-events .hero-live-grid { padding-top: 30px; }
}

@media (max-width: 1080px) {
  /* THE HERO KEEPS ITS PLACE HERE. This rule used to drop the copy's top
     padding 172 -> 30 at 1080, and the header does not go into normal flow
     until 940 - so from 1080 down to 941 the eyebrow and the lettering were
     laid straight over the wordmark and the nav. Measured overlap: 82px at
     1080, 75px at 1000. Reported 7 Sep 2026 - "the hero layout totally jumps
     and the hero gets pushed up top... it should stay in its location and
     continue to get smooshed."

     It does now: `--ev-hero-pt` above ramps with the header instead, so the
     copy holds its position and only narrows. The 30 moves to the 940 block,
     where the header IS in flow and no clearance is needed.

     `grid-template-columns` and `gap` went with it, and they had been dead
     since the gold hand came out - this element is a plain block, so neither
     property did anything. They were the giveaway that the whole rule belonged
     to a layout that no longer exists. */
  .page-events .nextshow { max-width: 364px; }
  /* Scoped to this breakpoint on purpose, because it is exactly where the
     <source> swaps to the 5:4 frame. The wide one is 1757x895 in a 1200x611
     box - the same 1.96 either way - so it is not cropped at all and a value
     here would only disturb the 1080-1200 band.

     RE-PICKED 5 Sep with the new frames, and the band it matters in is
     narrower than it looks. The 5:4 crop only gets cropped HORIZONTALLY where
     the hero box is narrower than 1.25, which is roughly 850 down to 641;
     above that the box is wider and the excess is vertical, so this value is
     inert; below 641 the hero restacks and the picture is shown whole. Inside
     that band, 15/30/45 were rendered at 700 and compared: 30 - the old
     frame's value - leaves her face behind "Live", and 15 slides the window
     left far enough that she sits clear of the lettering.

     The rest of the fix is in the CROP, not here: the 5:4 frame is cut centred
     on x800 of 1757 rather than the x910 that centres the arch, which puts her
     right of middle so the lettering has the left of the frame to itself at
     900 where this value can do nothing. */
  .page-events .hero-live .hero-media img { object-position: 15% 50%; }
  /* The thumbnail is the first thing that has to go: it is decorative, and
     the date, venue, time and button are not. */
  /* EVERY TRACK DETERMINISTIC, so the two rows agree. `1fr` is
     `minmax(auto, 1fr)` and `auto` is content-sized - and each `.show` is its
     OWN grid, so both tracks resolved per row off that row's own text. Below
     668 they diverged: the venue track came out 152 on "Tap N' Ash Social Club"
     and 174 on "The Composers Room", so the column rules between the two rows
     stopped lining up and the buttons sat at different x. That is the "grid
     gets all out of wack" at 668, and from 660 the button overflowed too.

     `minmax(0, 1fr)` lets the venue shrink instead of flooring at its longest
     word, and 162px is the button track measured (138 + its 24 of margin), so
     nothing in the row is content-sized any more and the rows cannot disagree.

     AND THE VENUE GETS A FLOOR, because fixing the alignment alone still left
     it starving: `minmax(0, 1fr)` is the only flexible track, so it absorbed
     every pixel the fixed date and time columns did not want, and at 668 that
     was 152 - "Tap N' Ash Social Club" over four lines and "The Composers
     Room" over three. With 230 as its minimum the two date and time columns
     give way first (they hold "OCT 09" and a 92px chip, so they have room to
     spare), and both venue names hold TWO lines at 700, 668 and 641. Floors of
     190, 210 and 230 were rendered and counted before picking it; 230 is the
     first that gets the longer name down to two.

     Every floor is measured, and the ONLY track that is not flexible at both
     ends is the venue's maximum. At 641 - the last pixel before the phone
     block - the row wants 84 + 230 + 108 + 146 = 568 against 569 of container,
     so it fits with one pixel to spare and nothing here is content-sized, so
     the rows still cannot disagree. The button's own minimum is 146 against
     its 138 of width, so it can never clip: what gives is its inset. */
  .show {
    grid-template-columns:
      minmax(72px, 130px) minmax(230px, 1fr) minmax(108px, 150px) 154px;
  }
  /* Three insets come in at the narrow end, and together they are what buys the
     venue its 230 at 641: the date's 30 -> 18, the time's 24 -> 12, and the
     button's margin 24 -> 16. Without them the row wants 84 + 230 + 108 + 162
     = 584 against 569 of container and something has to give; with them it is
     72 + 230 + 108 + 154 = 564, and the five spare pixels are real.

     THE BUTTON'S TRACK IS FIRM AT 154, NOT A minmax, and that is the bug this
     replaces. `justify-self: end` measures from the track's right edge and
     `margin-right` eats into the track, so a track narrower than button +
     margin pushes the button out of its OWN LEFT EDGE and into the time
     column: at 641 the track came out 146 against 138 + 24, and "8:00 PM" ran
     underneath the button. 154 is exactly 138 + 16 and cannot do that. */
  .show > .showdate { padding-left: 18px; }
  .show > .showtime { padding-left: 12px; }
  .show > .show-thumb { display: none; }
  .show > .btn { justify-self: end; margin-right: 16px; }
  .page-events .showfilter .wrap { gap: 40px; }
}

/* --------------------------------------------------- events signup, 1024 ---
   THE SIGNUP BAND STACKS FROM 1024 DOWN, NOT FROM 820, and 1025 is measured
   rather than chosen.

   Its wrap is `grid-template-columns: auto 1fr auto` - heading, sub, form. Both
   outer columns are `auto`, so they hold their content and the `1fr` in the
   middle absorbs every pixel the viewport is short. The sub is written to set
   as two lines and carries its own <br> to say so; its natural width is 147px,
   the heading settles at 295 and the form at 444, which with two 34px gaps
   needs 954px of content width before the middle column can hold that line.

   Below that the middle column does not shrink gracefully, it collapses: 60px
   flat from 821 all the way to 940, where "Get new dates and appearances" sets
   as five lines of about ten characters in a column narrower than the button
   beside it. check:layout reported it as `p.signup-sub 10.6 chars/line over 5
   lines, box 60px, worst at 821`, one pixel above the old breakpoint.

   Measured by walking 990 to 1100 in 5px steps and watching the element's
   height: 57px while it wraps to three lines, 38px once it holds two. It holds
   at 1025 and not at 1020, so the stack ends at 1024.

   The padding is the same 34/30 the 820 block already reasoned out, and for
   the same reason - the one-column content is 142px against a 121px band, so
   the centring that held the desktop row has nothing left to give. That note
   is below, with the block it was written in. */
@media (max-width: 1024px) {
  .page-events .band-signup { padding: 34px 0 30px; }
  .page-events .band-signup .wrap { grid-template-columns: 1fr; }
  .page-events .signup-status { grid-column: 1; }
}

@media (max-width: 820px) {
  /* `.split-live` is one column at every width now, so only the padding is
     this breakpoint's business. */
  .page-events .split-copy-live { padding: 30px var(--gut) 34px; }
  /* THE BAND NEEDS REAL PADDING FROM HERE DOWN, because from here down its
     min-height stops being what holds the copy off the edges.

     `.page-events .band-signup` has NO padding at all: it is a 121px band
     (the mockup's measured 1494-1580) with `align-items: center`, and the
     centring is what produced the 24-42px of air above the heading and 37
     below it. That works while the content is a single 47-95px row. The wrap
     goes one-column here, the content becomes 142 - taller than the 121 - and
     the band grows to fit it with nothing to spare: measured 1px above the
     heading and 0 below the form at 820, 792, 700 and 641. Reported at 792.

     34/30 rather than the phone block's 40/26 because the content here is 142
     against its 231, and rather than the desktop's 24/37 because that number
     was never a padding - it was whatever the centring had left over. Below
     640 the phone block sets its own and this is overridden. */
  /* The signup band's three rules moved UP to max-width: 1024, where they now
     start. They still apply here - 820 is inside 1024 - and the reasoning
     above is theirs. See the block above this one. */
  .page-events .band-cta-live .wrap { flex-direction: column; align-items: flex-start; }
}

@media (max-width: 640px) {
  /* =====================================================================
     THE PHONE EVENTS PAGE, from design/mockup/mobile/events-385.png.

     Supplied 6 Sep 2026 and this page's FIRST phone mockup - the block that
     stood here said so in three places and fitted its values instead. Those
     notes are gone with the guesswork; everything below traces to a reading
     off that image, which is 385px of drawn column and is measured 1:1
     against a 390 build (a 1.3% difference, inside the ink of any of these
     numbers).

     NO DEPARTURES. An earlier pass set the gutter to 20 because Jacob had
     asked for 20-22 before this mockup was measured; he then asked for the
     mockup followed exactly, so it is the mockup's own 16 and every card is
     8px wider than it was. That extra width is not cosmetic - it is what lets
     the next-show venue and the schedule venue names hold one line.

     The page was 3678px tall at 385 before this and the mockup draws 1775.
     Nothing was removed to get there except the crowd placeholder, which the
     mockup does not draw - see the note on .split-media below.

     ---------------------------------------------------------------------
     7 SEP 2026: A SECOND PHONE IMAGE ARRIVED AND IT IS NOT A SECOND SPEC.

     design/mockup/mobile/events-companion-629.png came with a written list of
     changes. It is a COMPANION, and the difference from events-385.png is
     load-bearing enough to write down: it does not measure. Its hero is 1.52
     where the measured one is 1.30, its wordmark scales to 79 where the build
     is 104, and it draws three of the things the list beside it asks to have
     removed - the newsletter's border, the hot-pink hairline over the CTA,
     and the Oct 9 row. It is a picture of the idea, not of the pixels.

     So WHERE THE TWO DISAGREE, THE WRITTEN ASK WINS, and the comment on the
     value says so and quotes him. Everything the words do not cover is still
     read off the image: the "Where you'll find Seven" wording, the socials
     over the copyright, the rule above the legal line, the footer's purple,
     and the CTA button sized to its label. events-385.png is untouched and
     still owns every number that this list did not name.
     ===================================================================== */
  .page-events main { --gut: 16px; }   /* cards x16-368 of 385 */

  .page-events {
    /* Vertical rhythm, all of it ink-to-ink off the mockup. */
    --evm-hero-eyebrow: 12px;   /* hero 350 -> eyebrow ink 362 = 11 */
    --evm-eyebrow-script: 6px;  /* 373 -> 380 */
    --evm-script-lede: 6px;     /* 476 -> 483 */
    --evm-lede-rule: 9px;       /* 517 -> 527 */
    --evm-rule-card: 10px;      /* 534 -> 545 */
    --evm-card-h2: 20px;        /* card 723 -> "UPCOMING" ink 744 */
    --evm-h2-rule: 7px;         /* 760 -> 768 */
    --evm-rule-list: 6px;       /* 775 -> 782 */
    --evm-list-note: 6px;       /* 939 -> 946 */
    --evm-note-venues: 13px;    /* 957 -> 971 */
    /* WAS THE MOCKUP'S 21, AND IS JACOB'S. 7 Sep 2026: "A Night Out,
       Elevated ... gets squeezed between two bordered sections. Give it
       another 20-30px above and below." 21 + 25 above, 15 + 25 below. The
       companion mockup draws 15 here and is not followed - see the block
       header on why its geometry is not the spec this time. */
    --evm-venues-night: 46px;   /* was 21 (venues card 1184 -> "A NIGHT OUT" 1206) */
    --evm-night-rule: 9px;      /* 1225 -> 1235 */
    --evm-rule-body: 8px;       /* 1242 -> 1251 */
    --evm-body-body: 6px;       /* 1281 -> 1288 */
    --evm-body-explore: 9px;    /* 1317 -> 1327 */
    --evm-explore-signup: 40px; /* was 15 - the other half of the +25 above */
    /* Was declared and never referenced - the pink hairline over the CTA band
       was doing this job, so nothing noticed. Taking the hairline off left the
       newsletter card sitting flush on the bokeh, and 12 is the gap the mockup
       drew UNDER A BORDERED card. This one ends in a glow that fades out, so
       it needs the room the border used to save it. */
    --evm-signup-cta: 26px;     /* was 12 (mockup 1498 -> 1511), now applied */
    --evm-cta-footer: 34px;     /* was 17. "Add more space above the logo." */
    --evm-card-pad: 14px;       /* card frame to its own ink, both axes */

    /* --- the 7 Sep newsletter and CTA rebuild ---------------------------
       Jacob's numbers, not the mockup's, and quoted where he gave a range so
       the next person can see which end was taken and why. */
    --evm-signup-pad: 30px;     /* "Increase padding to roughly 28-32px" */
    --evm-field-gap: 11px;      /* "Add 10-12px between them" */
    /* NOT MEASURED AND NOT A RANGE HE GAVE. He asked for the field and the
       button to be the same height; 44 is the height that makes them the same
       AND clears the 44x44 touch target, which 34 did not. Every other gold
       button on the site is 39-47, so it is in the family. */
    --evm-field-h: 44px;
    --evm-cta-pt: 30px;         /* "Increase vertical padding substantially" - */
    --evm-cta-pb: 32px;         /*   from 10/11, and the desktop band's own */
    /* Sampled off the companion mockup at x40/x315/x590, y1810 and y2010: it
       fades #0F011E -> the page background rather than sitting flat, so the
       footer separates at its top edge and closes seamlessly at the bottom. */
    --evm-footer-bg: #0F011E;
  }

  /* --- hero ------------------------------------------------------------
     Photograph as a block above the copy rather than a background behind it,
     copy centred underneath, the primary block running gutter to gutter. The
     reasoning is on `.hero-home` in the home page's phone block; the reason
     it is wanted HERE is the same: the background treatment put "SEE SEVEN
     LIVE" straight across her face, which is why this hero already carried an
     object-position nudge at 1080. That nudge is inert below this width.

     640, not the 620 home and about use, because 640 is this page's own phone
     breakpoint and the shows list already turns over here.

     THE BAND IS THE MOCKUP'S 385x297 = 1.30, not the supplied frame's own
     5:4. That crops the photograph by 4% of its height, centred, which is the
     one place this block cuts a supplied file - and it is here because the
     mockup is being followed exactly rather than approximately. Her head and
     the arch both sit well inside it; checked at 320, 375 and 430. The 30px
     of dead air that used to sit under this band is now the mockup's 11. */
  .page-events .hero-live { display: block; min-height: 0; padding: 0; }
  .page-events .hero-live .hero-media { position: static; }
  .page-events .hero-live .hero-media picture,
  .page-events .hero-live .hero-media img { aspect-ratio: 385 / 297; height: auto; }
  .page-events .hero-live .hero-media::after { content: none; }

  .page-events .hero-live-grid {
    padding: var(--evm-hero-eyebrow) var(--gut) 0; gap: 0;
  }
  .page-events .hero-live-copy { max-width: none; text-align: center; }
  /* Eyebrow down from the desktop's measured 20px: "LIVE VOCALS.
     UNFORGETTABLE ROOMS." is 386 wide at 20 and the column is 350 at a 390
     viewport with this gutter, so it wrapped mid-phrase. The mockup sets it
     on one line at 281 wide, which is 16px here. */
  .page-events { --ev-eyebrow-hero: 16px; }
  /* The lede holds two lines in the mockup (ink y483-517, 341 wide) and was
     taking three at the desktop's measured 21px. */
  .page-events { --ev-hero-lede: 15px; --ev-lh-hero-lede: 19px; }
  .page-events .hero-live .script-art {
    margin: var(--evm-eyebrow-script) auto 0;
    width: min(348px, 100%);          /* ink x19-366 of 385 */
  }
  .page-events .hero-live .lede {
    max-width: none; margin: var(--evm-script-lede) auto 0;
  }
  .page-events .hero-live .rule { width: auto; margin-top: var(--evm-lede-rule); }

  /* --- next show card --------------------------------------------------
     The old block stacked this into one column and said in a comment that
     two would not fit, because it was measuring a row of date + detail +
     button. The mockup answers that: the BUTTON drops to its own line and
     spans the card, so the date and the detail get the full width to share
     and the card comes out 179px instead of 400.

     `display: contents` on the two wrapper divs, because the label belongs
     across the top and the button across the bottom, and in source order
     both are locked inside a column. The divs carry no semantics, so
     dissolving them costs a screen reader nothing.

     The divider moves with them: it was a border on .nextshow-date, which
     has no box any more, so it is the date's own right edge now. */
  .page-events .nextshow {
    display: grid;
    grid-template-columns: auto 1fr;
    width: 100%; max-width: none; margin-top: var(--evm-rule-card);
    /* min-height explicitly cleared. The desktop card is a fixed 225px box
       (--ev-card-h, measured off events-1024.png) and it was pinning this one
       open at exactly that number no matter what the type inside it did -
       which is why four passes of tightening moved every other box on the
       page and never this one. The mockup draws 179 and the content now
       makes it. */
    min-height: 0;
    padding: 16px 14px;               /* card 179 tall */
    text-align: left;
  }
  .page-events .nextshow-date,
  .page-events .nextshow-detail { display: contents; }
  .page-events .nextshow-label {
    grid-column: 1 / -1; grid-row: 1;
    margin: 0 0 6px; text-align: center;
  }
  .page-events .nextshow .showdate {
    grid-column: 1; grid-row: 2 / span 3;
    align-self: center; margin: 0;
    padding-right: 14px;                /* divider x110 -> venue ink x130 */
    border-right: 1px solid rgba(var(--gold-rgb), .45);
    text-align: center;
  }
  .page-events .nextshow-detail h2 {
    grid-column: 2; grid-row: 2;
    max-width: none; padding-left: 14px;
  }
  .page-events .nextshow .showplace { grid-column: 2; grid-row: 3; padding-left: 14px; margin-top: 4px; }
  .page-events .nextshow .showtime  { grid-column: 2; grid-row: 4; padding-left: 14px; margin-top: 3px; }
  .page-events .nextshow .showplace,
  .page-events .nextshow .showtime { justify-content: flex-start; }
  .page-events .nextshow .btn {
    grid-column: 1 / -1; grid-row: 5;
    width: 100%; margin: 12px 0 0;      /* time ink 665 -> button box 677 */
  }
  /* The date numeral is the card's loudest thing and at 93px it was setting
     the card's whole height. The mockup draws "09" 56 tall in a 179 card. */
  .page-events { --ev-ns-day: 50px; --ev-ns-mon: 18px; --ev-ns-label: 12px;
                 --ev-ns-venue: 18px; --ev-lh-ns-venue: 20px; --ev-ns-btn-h: 36px; }
  /* The card is 179 tall in the mockup and was landing 225. The type sizes
     were already right; what was holding it open was LEADING - the label's
     line box was 32px for a 13px label, and the place and time rows were 28
     and 33 for 13px text, because they inherit line-heights measured for the
     desktop card. Set to the ink they actually need. */
  .page-events .nextshow-label { line-height: 1; }
  .page-events .nextshow .showdate-mon,
  .page-events .nextshow .showdate-day { line-height: 1; }
  .page-events .nextshow-detail h2 { line-height: 21px; }
  .page-events .nextshow .showplace,
  .page-events .nextshow .showtime { font-size: 12px; line-height: 1.1; }
  .page-events .nextshow .showplace .icon,
  .page-events .nextshow .showtime .icon { width: 13px; height: 13px; }

  /* --- schedule --------------------------------------------------------
     One row per show, scanned left to right: WHEN, WHERE, the button. The
     old block put the time and the button on a second line under the date,
     which made every row four lines tall and the list the opposite of
     scannable.

     The time goes UNDER THE DATE rather than beside the venue - the mockup
     drops it entirely, but Jacob asked for it kept, and date-over-time is
     one "when" column rather than a fourth thing to find. */
  .page-events .band-schedule { padding: var(--evm-card-h2) 0 0; }
  .h2-shows { font-size: 25px; }        /* "UPCOMING PERFORMANCES" 245 wide */
  .rule-center { margin-top: var(--evm-h2-rule); }
  .shows { margin-top: var(--evm-rule-list); }
  /* THE PHONE LISTS BOTH SHOWS, same as every other width.

     `.page-events .shows .show:first-child { display: none }` stood here from
     earlier on 7 Sep: Oct 9 is the featured card 200px up this page, so the
     first row of "Upcoming performances" repeated it, and the companion mockup
     drops that row. Jacob reversed it the same day - "we get rid of next event
     in upcoming performances when the site is < 640 or so.. lets leave it in."

     So the schedule is the schedule at every width, and this is the one place
     the companion mockup is knowingly not followed. Worth keeping in mind if
     the duplication is ever revisited: a reader who lands mid-page never sees
     the card, and a list that silently omits its soonest date is a worse
     surprise than one that repeats it. */
  .show {
    grid-template-columns: 48px 1fr auto;
    grid-template-rows: auto;
    align-items: center;
    column-gap: 10px;
    padding: 13px 12px;              /* row box 79 tall, ink 46 */
    min-height: 0;
  }
  .show > .showdate   { grid-column: 1; grid-row: 1; padding: 0; text-align: center; }
  /* THE ROW DOES NOT CARRY THE TIME. The mockup draws three things in a row
     - date, venue block, button - and no time in either of its two rows, and
     it is being followed exactly. It costs the list two facts: Oct 9's 8:00
     PM, which the next-show card above still states, and Oct 18's "Time to
     come", which was the page's way of admitting a start time had never been
     supplied and now appears nowhere. That second one is a content loss, not
     a layout one; design/BRIEF.md carries it. */
  .show > .show-venue { grid-column: 2; grid-row: 1; border-left: 0; padding: 0; }
  /* flex-start explicitly: the row above centres this on the desktop grid,
     where the time is its own centred column. Here it is the last line of
     the venue block and has to start where the venue name starts. */
  .show > .showtime   { display: none; }
  .show > .btn        { grid-column: 3; grid-row: 1; justify-self: end; margin: 0; }
  /* "TAP N' ASH SOCIAL CLUB" is 186 wide at 15px against the 165 of venue
     column this row leaves, so it holds one line where 17px broke it in two
     and added 20px to every row. */
  .page-events { --ev-day: 28px; --ev-mon: 12px; --ev-venue: 15px;
                 --ev-place: 12px; --ev-tag: 10px; }
  .show-venue h3 { line-height: 1.15; }
  .showplace { margin-top: 3px; }
  .showtag { margin-top: 3px; line-height: 1.1; }
  .btn-ghost-sm { height: 30px; padding: 0 10px; font-size: 10px; letter-spacing: .10em; }
  .shows-note { margin-top: var(--evm-list-note); font-size: 12px; }

  /* --- rooms Seven plays ----------------------------------------------- */
  .page-events .band-venues { padding: var(--evm-note-venues) 0 0; }
  .venues { padding: 12px var(--evm-card-pad) 11px; }
  .venues .eyebrow-sm { margin-bottom: 4px; }
  .venues-h2 { font-size: 21px; line-height: 1.1; }
  /* Chips: 8 names in 4 rows in the mockup rather than the 7 rows they were
     taking. Smaller box, tighter gap, and the list centres so a short last
     row sits under the middle of the ones above it. */
  /* CALMED DOWN, 7 Sep 2026. "They compete with the performance listings.
     Smaller type, fewer outlines, or slightly more vertical spacing would calm
     that section down." All three, lightly: 11px -> 10, the gold outline
     traded for a flat wash, and 6px of gap -> 9 down / 6 across.

     `border-color: transparent` rather than `border: 0`, so the 1px box the
     desktop pill measures at stays in the layout and the row pitch does not
     jump by two pixels between breakpoints. The wash is what keeps them
     reading as pills with the outline gone - drop both and they are eight
     loose words. They still fit four rows. */
  .venue-list { gap: 9px 6px; margin-top: 12px; }
  .venue-list li {
    padding: 5px 9px; font-size: 10px; letter-spacing: .05em; line-height: 1.1;
    border-color: transparent;
    background: rgba(var(--ink-rgb), .05);
    color: var(--ink-dim);
  }
  .venues-note { margin-top: 12px; font-size: 12px; line-height: 1.2; }

  /* --- a night out, elevated -------------------------------------------
     THE CROWD PLACEHOLDER IS HIDDEN HERE, NOT DELETED. The mockup does not
     draw it, and on a phone it was a 331px box of diagonal hatching between
     the venues and this copy - the single largest piece of dead space on the
     page.

     IT IS GONE EVERYWHERE NOW, 7 Sep 2026 - the box came out of the markup and
     MANIFEST E9 closed with it, so this block no longer has anything to hide
     and the base rule above carries the one-column layout and the centring at
     every width. What is left here is this breakpoint's own spacing. */
  .page-events .split-copy-live {
    padding: var(--evm-venues-night) var(--gut) 0;
  }
  .page-events .split-copy-live .h2-serif-sm { font-size: 20px; }
  .page-events .split-copy-live .rule { margin-top: var(--evm-night-rule); justify-content: center; }
  .page-events .split-copy-live p:not([class]) {
    margin: var(--evm-rule-body) auto 0; max-width: none;
  }
  .page-events .split-copy-live p:not([class]) + p:not([class]) { margin-top: var(--evm-body-body); }
  .page-events .split-copy-live .explore { margin-top: var(--evm-body-explore); justify-content: center; }

  /* --- newsletter ------------------------------------------------------
     One card rather than a full-bleed band: the mockup boxes it the way it
     boxes the next show and the venues, and inside it the field and the
     button are one stacked pair at the same width, which is what makes them
     read as a form rather than as two controls that happen to be adjacent.

     THE STATUS LINE IS NO LONGER HIDDEN HERE. It used to be, and the note that
     stood in this place explained why: it was a permanent "not connected yet"
     chip, a developer's note that broke the form into three objects, and Jacob
     asked for it gone from the phone. That reasoning died with the chip. It is
     a RESULT line now - sending, sent, or why not - and hiding the only
     feedback a phone gets would put this form straight back to the thing that
     made it worth fixing: looking like it worked when it did not. It is
     centred here because the card is. */
  .page-events .signup-status { text-align: center; justify-self: center; }
  .page-events .band-signup {
    min-height: 0; border-top: 0; display: block;
    padding: var(--evm-explore-signup) 0 var(--evm-signup-cta);
  }
  .page-events .band-signup .wrap {
    display: block;
    /* `width: auto` on purpose: the desktop rule above sets width:100% so the
       wrap does not shrink as a flex item, and left alone that ran this card
       from screen edge to screen edge. */
    width: auto; margin-inline: var(--gut);
    padding: var(--evm-signup-pad) var(--evm-signup-pad) calc(var(--evm-signup-pad) - 2px);
    /* NO BORDER. It was a 1px gold hairline and Jacob asked for it gone -
       "so it feels like a designed section, not a generic embedded form."
       A box drawn round a form says "widget"; a glow says the page put it
       there. The radius stays so the wash has a shape to end on. */
    border: 0;
    border-radius: var(--r-card);
    /* THE GLOW, and it is deliberately NOT tokenised. See the note over the
       channel tokens at the top of this file: decorative gradient stops are
       one-off colours that are not the palette's four, and a token per stop
       would claim they were. This is the same purple the CTA band's radial
       already uses, lit from the two corners the card's own content sits
       between - pink over the heading, purple under the button. */
    background:
      radial-gradient(86% 118% at 50% 0%, rgba(var(--pink-rgb), .15), transparent 70%),
      radial-gradient(96% 128% at 50% 100%, rgba(150, 60, 190, .30), transparent 68%),
      rgba(var(--ink-rgb), .025);
  }
  /* CENTRED, both of them - "Center the heading and supporting copy." Not the
     field: its placeholder is where a reader starts typing and centring that
     is a different and worse thing. */
  .h2-signup { font-size: 19px; text-align: center; line-height: 1.1; }
  .signup-sub { margin-top: 6px; line-height: 15px; font-size: 12px; text-align: center; }
  .signup-form { display: block; margin-top: 17px; }
  /* ONE BOX, TWICE. The field was 34 tall against a button that read taller
     for being filled gold, and Jacob asked for them equal; sharing the token
     is what makes them stay equal the next time one is touched. */
  .signup-form input { width: 100%; height: var(--evm-field-h); }
  .signup-form .btn {
    width: 100%; height: var(--evm-field-h); margin-top: var(--evm-field-gap);
    /* `max-width: none` is what makes "equal" true. The 640-and-down rule
       `.btn-gold, .band-cta .btn-gold { width: 100%; max-width: 267px }` was
       capping the button at 267 inside a 298 card, so it sat narrower than the
       field above it and left-aligned under it - two controls that happen to
       be adjacent, which is the thing this card was rebuilt to stop being. */
    max-width: none;
  }

  /* --- closing CTA and footer ------------------------------------------
     Both were set by desktop padding that a phone never needed: 30/32 on the
     CTA and 22/33 on the footer, plus a 23px flex gap between every footer
     item. The mockup gives the CTA 96px and the footer 168; they were 224
     and 267.

     SCOPED TO .page-events, deliberately. The footer is shared, this mockup
     is one page, and a footer that is 168 tall here and 267 everywhere else
     is a bug somebody files later. It should go site-wide - it is the same
     dead space on every page - but that is a decision about five other
     pages and it is not this one's to make. */
  /* REBUILT 7 Sep 2026. It was 10px of padding over 4px of gap over 11 - 96px
     of band carrying three separate things - and it read exactly as Jacob
     described it: "crushed between the newsletter and footer."

     THE HOT-PINK HAIRLINE IS OFF. `.page-events .band-cta-live` sets a 1px
     top border in --pink for the desktop, where it is the only thing dividing
     this band from the signup band above it. On a phone the newsletter is a
     card with air around it, so the line had nothing to divide and read as a
     stray - "it looks accidental", and it was. Both mockups still draw it. */
  .page-events .band-cta-live {
    border-top: 0;
    padding: var(--evm-cta-pt) 0 var(--evm-cta-pb);
  }
  /* THE BOKEH RUNS THE WHOLE BAND HERE. `.band-cta` washes its left 45% out
     to solid --bg so a left-set heading stays readable over the texture; the
     copy is centred at this width, so that gradient was leaving the texture
     stranded in the right third and the band looking half-painted. Replaced
     with an even veil that darkens the middle and keeps both edges lit. */
  .page-events .band-cta-live {
    background:
      radial-gradient(72% 130% at 50% 50%, rgba(var(--bg-rgb), .82), rgba(var(--bg-rgb), .34) 78%),
      /* ZOOMED INTO THE LIT END, not `cover`. The file is 1600x535 and its
         bokeh is all in the right third - sampled by decile, its left 40% is
         within three levels of black. At `cover` a 390 band shows source
         x147-1452, so a third of this band was the dead half of a photograph
         and it looked half-painted whatever the veil did. At 300% the same
         390 shows 533 source px, and 84% puts that window over x897-1430,
         which is the dense part, edge to edge. */
      url("/assets/photos/home-cta-bokeh.jpg") 84% center / 300% auto no-repeat;
  }
  /* THREE THINGS, NOT ONE BLOCK - "Separate the eyebrow, headline, and
     button." The eyebrow and the headline are one pair inside the first div,
     so their gap is the h2's margin; the button is the flex gap. */
  .page-events .band-cta-live .wrap {
    flex-direction: column; align-items: center; text-align: center; gap: 20px;
  }
  /* "Let's bring the show to you." is 327 wide at 19px against 345 of column,
     so it holds the mockup's single line; at 24 it broke in two. */
  .page-events .band-cta-live .h2-serif { margin-top: 9px; font-size: 19px; line-height: 1.15; }
  .page-events .band-cta-live p.eyebrow { line-height: 1.1; }
  /* SIZED TO ITS LABEL - "Make the button slightly narrower." The 640-and-down
     rule gives every gold button `width: 100%; max-width: 267px`, which is a
     stretched bar. Off it, "Contact us" plus .btn-gold's own 28px of side
     padding measures 161, against the 155 the companion mockup draws. The
     narrower number is the mockup's, arrived at by taking the padding off the
     rule rather than by picking one. */
  .page-events .band-cta-live .btn { width: auto; max-width: none; height: 40px; }

  /* --- footer ----------------------------------------------------------
     "Simplify the footer ... their absence is why the footer feels
     unfinished." The marks themselves are shared markup and shared CSS - see
     `.footer-end` up by `.site-footer` - because the footer is one component
     on seven pages. What is scoped here is this page's phone spacing, for the
     reason the block below the CTA already gives. */
  .page-events .site-footer {
    /* Was 17 over 12. The logo now has air above it and the band closes at 22. */
    padding: var(--evm-cta-footer) 0 22px;
    /* THE HAIRLINE COMES OFF BECAUSE THE COLOUR REPLACES IT. A 10%-white rule
       was the only thing separating footer from page; the purple does that
       job now, and keeping both would draw a line across a seam that is
       already visible. */
    border-top: 0;
    /* "Consider a subtle dark-purple footer background so it separates from
       the main page." Sampled off the companion mockup, which fades rather
       than fills: #0F011E at its top edge, the page's own --bg by its bottom.
       That is what makes it separate at the join and not end in a hard band
       above the fold of the browser chrome. */
    background: linear-gradient(180deg, var(--evm-footer-bg) 0%, var(--bg) 100%);
  }
  .page-events .site-footer .wrap { gap: 11px; }
  /* BRIGHTER. --ink-dim is .55 white and the line is the only sentence in the
     footer that says what the business does; at .78 it reads without pulling
     level with the wordmark above it. */
  .page-events .site-footer p { line-height: 18px; font-size: 12px; color: var(--ink-mut); }
  /* "Keep the copyright smaller, but increase its line-height." 12/17 -> 10/18
     - two full points of leading added while the type came down, which is what
     stops two lines of letterspaced caps setting as a solid block. */
  .page-events .site-footer .legal { font-size: 10px; line-height: 18px; }
  /* The short centred rule the companion mockup draws over the copyright, 275
     drawn of its 629 column = 43%, taken as 168 of the 390 build. It is what
     separates the marks from the legal line now that the footer is one
     centred column rather than three across. */
  .page-events .footer-end { gap: 9px; }   /* marks 1952 -> rule 1964 = 8 */
  /* A PSEUDO-ELEMENT, NOT A BORDER ON .legal. Sizing the box to the rule's own
     168px is what a border needs and it broke the copyright into three lines:
     "HEAVEN'S GLITTER" / "ENTERTAINMENT" / "ALL RIGHTS RESERVED", against the
     two the <br /> asks for. The rule draws itself and the text keeps the
     column. */
  .page-events .site-footer .legal::before {
    content: ""; display: block;
    width: 168px; height: 1px; margin: 0 auto 11px;   /* rule 1964 -> legal 1980 */
    background: rgba(var(--ink-rgb), .14);
  }
  .page-events .site-footer .brand img { width: 104px; }   /* logo ink 48 tall */
}

/* ===========================================================================
   Gallery page
   NO MOCKUP EXISTS FOR THIS PAGE. Like /coming-soon (SPEC.md > Assumptions 8)
   it is ours, not the client's design, so nothing here is measured off an
   image. Every value is derived from tokens that were: the hero band copies
   /contact's proportions, the type comes from the shared scale, and the only
   new numbers are the column count and the gutter. Recorded in SPEC.md under
   Assumptions for exactly that reason - do not let these leak back into a
   page that does have a mockup.
   =========================================================================== */

:root {
  --gl-hero-h: 185px;   /* the same band height /contact uses */
  --gl-gap: 20px;       /* column gutter and the drop between figures */
  --gl-cols: 4;
}

/* The header overlays a tall photograph on the pages that have one. This page
   opens on a copy band, so the header sits in normal flow with a hairline
   under it - the same treatment /contact needed, for the same reason. */
.page-gallery .site-header { position: static; padding: 29px 0 22px; }

/* --- the top band, from design/mockup/mobile/gallery-440.png -------------
   THE FIRST MOCKUP THIS PAGE HAS EVER HAD. The block comment at the head of
   this section still holds for the grid below - that is ours - but this band
   is now measured, and SPEC.md > Gallery, phone carries the numbers.

   Three things changed with it: the band centres, it opens on the client's
   own stage-light artwork, and both hairlines came off. The mockup has no
   rule between the header and the wash - sampled across that boundary, it is
   a smooth dark gradient with no gold in it - and the rule that used to close
   the band is now the divider under the lede, which is inside the band and
   is drawn very differently. */
.gl-hero {
  position: relative; overflow: hidden;
  min-height: var(--gl-hero-h);
  display: flex; align-items: center;
  text-align: center;
}
/* The client's artwork, supplied 5 Sep 2026 as a 2162x727 transparent PNG and
   brought in at 1400 wide as WebP - 828 KB to 101 KB, alpha intact. Two
   magenta beams from the top corners; the middle two thirds of the file is
   fully transparent, so it lies over the page rather than painting a panel on
   it, and it fades out top and bottom on its own (mean alpha 7.9 and 0.3 on
   the first and last rows), which is why nothing here masks it.

   A background rather than an <img>: it is decoration with nothing to say to
   a screen reader, and this way the markup gains nothing to caption. */
/* `inset: 0` and a mask, NOT the artwork's own aspect-ratio. At 440 the file
   lands 148 tall inside a 163 band and either would do; at 1200 it lands 404
   tall inside a band of 185, and an aspect-sized box is then cut dead straight
   across by the band's edge - a hard horizontal line through the middle of two
   glows, which is what the first render did. Bound to the band and faded out
   over its last third instead, so the artwork ends where the band does at
   every width and never shows a cut. */
.gl-hero::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: url("/assets/gallery-lights.webp") top center / 100% auto no-repeat;
  -webkit-mask-image: linear-gradient(to bottom, #000 66%, transparent);
          mask-image: linear-gradient(to bottom, #000 66%, transparent);
  pointer-events: none;
}
.gl-hero .wrap { position: relative; z-index: 1; width: 100%; }
.gl-hero h1 {
  margin: 7px 0 0;                      /* eyebrow ink -> H1 ink 10.7 */
  font-family: var(--display); font-weight: 700;
  font-size: 39px; line-height: 1.05;
  letter-spacing: .01em; text-transform: uppercase;
}
.gl-hero .eyebrow-sm { letter-spacing: .16em; }
/* 365 was /contact's measure, chosen when this band was left-aligned and its
   copy was a different sentence. Centred, and with the mockup's longer line,
   it broke after "and" and hung "celebrations across Las Vegas." underneath -
   a short first line over a long second, which is the one shape a centred
   two-line block should not have. `balance` settles it at every width above
   the phone, where the measured 334 takes over. */
.gl-lede {
  margin: 9px auto 0; max-width: 470px;  /* H1 ink -> lede ink 11.7 */
  font-size: var(--f-small); line-height: 24px; color: var(--ink-mut);
  text-wrap: balance;
}

/* The divider under the lede: a gold hairline the width of the container,
   brightest at its centre. Measured along its brightest row, the mockup runs
   221 at x51, 254 dead centre, and falls away at both ends - so it is a rule
   with a glow in it rather than a flat line, and rather than the site's
   `.rule`, which is pink and carries a diamond.

   The centre bloom is 2.8 tall against the line's 1, which a background
   gradient on a 1px box cannot do, so it is a second layer on ::before. */
.gl-rule {
  position: relative;
  margin: 9px 0 0; height: 1px;          /* lede ink -> rule 13.1 */
  background: linear-gradient(90deg,
    rgba(var(--gold-rgb), .50),
    rgba(var(--gold-rgb), .95) 50%,
    rgba(var(--gold-rgb), .50));
}
.gl-rule::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 116px; height: 3px; transform: translate(-50%, -50%);
  /* White, not gold, and that is measured rather than assumed: the peak pixel
     at the centre of the mockup's rule is 254,255,255. It reads as a gold
     spark because the line it sits on is gold. Goes through --ink-rgb so no
     fifth channel token has to exist for one glow. */
  background: radial-gradient(closest-side, rgba(var(--ink-rgb), .92), transparent);
}

.band-gallery { padding: 34px 0 40px; }

/* Multi-column, not grid. See the note in gallery.html: these photographs are
   eleven different aspect ratios and columns is the only layout that takes
   them at their own shape with no JavaScript and nothing to keep in sync. */
.masonry {
  column-count: var(--gl-cols);
  column-gap: var(--gl-gap);
}
.masonry figure {
  /* Without this a figure tears across the column boundary. */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin: 0 0 var(--gl-gap);
}
.masonry img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-img);
  /* A hairline so a dark photograph still has an edge against the dark page. */
  border: 1px solid rgba(var(--ink-rgb), .07);
  transition: filter .2s ease, border-color .2s ease;
}
.masonry img:hover { filter: brightness(1.08); border-color: rgba(var(--gold-rgb), .55); }

@media (prefers-reduced-motion: reduce) {
  .masonry img { transition: none; }
}

/* Column count steps with the width. The floor is two, not one: at one column
   a phone photograph fills the viewport and eleven of them is a very long
   scroll for a page whose job is to be skimmed. */
@media (max-width: 1080px) { :root { --gl-cols: 3; } }
@media (max-width: 760px)  { :root { --gl-cols: 2; --gl-gap: 14px; } }
/* --- the band at the mockup's own width ---------------------------------
   design/mockup/mobile/gallery-440.png, measured at 440. Ink widths, because
   on every one of these three the CAP reading overflows the measure - Oswald
   and Barlow set wider per cap than the face the mockup is lettered in, which
   is the trade SPEC.md records for the home and services H1s:

     eyebrow  ink 289.4 wide, cap 14.0. Cap gives 19.4px -> 374 wide, into a
              368 measure. 15px renders 289.
     H1       ink 305.8 wide, cap 49.6. Cap gives 69px -> 373 wide, same
              overflow. 57px renders 306.
     lede     line 1 "…energy and sparkle" 303.5, line 2 270.7, 18.2 apart.

   The lede's measure is doing the wrapping, not the size: at the old 365 the
   break fell a word late and left a 143px orphan where the mockup has a full
   271px second line. 310 puts it back where it was drawn. */
@media (max-width: 620px) {
  /* This page's header carries `padding: 29px 0 22px` for the desktop band,
     and that is two classes, so it beats the phone header's own 5/2 and left
     /gallery standing 107 tall against the 70.6 this mockup draws - the band
     below it sat 36px lower than everything measured against it. Overridden
     here rather than by loosening the rule above, because this is the only
     page with a mockup that says what its phone header should be.
     9/11 against the mockup's 9/10.6; ours reads ~76 to its 70.6, the extra
     being the taller wordmark SPEC.md records for every other header. */
  .page-gallery .site-header { padding: 9px 0 11px; }
  .page-gallery .gl-hero { min-height: 0; padding: 13px 0 13px; }
  .page-gallery .gl-hero .eyebrow-sm { font-size: 15px; }
  .page-gallery .gl-hero h1 { font-size: 57px; }
  /* `wrap`, undoing the balance the wider band wants. The mockup's own break
     is after "sparkle", giving a long first line over a shorter second; left
     to balance them the browser moves it back to "and" and returns a SHORT
     first line over a longer one - the opposite shape, and not what was
     drawn. Below 620 the measured 334 does the shaping on its own. */
  .page-gallery .gl-lede {
    max-width: 334px; font-size: 15px; line-height: 18px; text-wrap: wrap;
  }
  .band-gallery { padding: 24px 0 30px; }
}

/* ===========================================================================
   404
   No mockup, like /gallery and /coming-soon - SPEC.md > Assumptions 8. Built
   from the shared tokens only; nothing here is a new measurement.
   =========================================================================== */

.page-404 .site-header { position: static; padding: 29px 0 22px; }
.page-404 .site-header .wrap { border-bottom: 1px solid rgba(var(--gold-rgb), .30); padding-bottom: 22px; }

.nf { padding: 92px 0 104px; text-align: center; }
.nf .eyebrow { letter-spacing: .34em; }
.nf h1 {
  margin: 16px 0 0;
  font-family: var(--display); font-weight: 700;
  font-size: 46px; line-height: 1.05;
  letter-spacing: .01em; text-transform: uppercase;
}
.nf-lede {
  margin: 18px auto 0; max-width: 400px;
  font-size: var(--f-body-hero); line-height: var(--lh-body-hero); color: var(--ink-mut);
}
.nf-links {
  list-style: none; display: flex; flex-wrap: wrap; justify-content: center;
  gap: 12px 30px; margin: 34px 0 36px; padding: 0;
}
.nf-links a {
  font-family: var(--display); font-weight: 400;
  font-size: var(--f-nav); line-height: 1;
  letter-spacing: .14em; text-transform: uppercase; color: var(--gold);
  padding-bottom: 5px; border-bottom: 1px solid transparent;
}
.nf-links a:hover { color: var(--pink-light); border-bottom-color: var(--pink); }

@media (max-width: 620px) {
  .nf { padding: 56px 0 64px; }
  .nf h1 { font-size: 32px; }
  .nf-links { gap: 10px 20px; }
}

/* ===========================================================================
   Showreel dialog
   ---------------------------------------------------------------------------
   Added 31 Aug 2026 with the client's showreel link. No mockup draws it, so
   nothing here is a measurement - it is recorded with the other derivations in
   SPEC.md > Assumptions 10. Every value is either a token this file already
   owns or is set by the video itself: the stage is 16/9 because the source is,
   and the width ceiling is --wrap less two gutters, so the player never sits
   wider than the page's own content column.
   =========================================================================== */
.showreel {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%;
  margin: 0; padding: var(--gut); border: 0; background: transparent;
  overflow: hidden;
}
/* Scoped to [open], never to the bare element: a dialog is display:none until
   it opens, so an unscoped `display: flex` here would leave it permanently
   visible. Centring has to be the container's job - `margin: auto` on the
   inner box only centres it horizontally, which put the close button 10px
   above the top of the viewport. */
.showreel[open] { display: flex; align-items: center; justify-content: center; }
.showreel::backdrop { background: rgba(var(--bg-rgb), .88); }
.showreel-inner {
  position: relative;
  width: min(calc(var(--wrap) - var(--gut) * 2), 100%);
}
.showreel-stage {
  position: relative; aspect-ratio: 16 / 9; width: 100%;
  background: #000;
  border: 1px solid var(--pink);
}
.showreel-stage iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}
/* Sits above the stage's top-right corner, clear of the video. Below 620 the
   corner is too tight for that, so it moves inside. */
.showreel-close {
  position: absolute; right: 0; top: -46px;
  width: 39px; height: 39px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 26px; line-height: 1;
  color: var(--pink); background: transparent;
  border: 1px solid var(--pink); border-radius: 50%;
  cursor: pointer;
}
.showreel-close:hover, .showreel-close:focus-visible {
  color: var(--bg); background: var(--pink);
}

@media (max-width: 620px) {
  .showreel { padding: 0; }
  .showreel-inner { width: 100%; }
  .showreel-stage { border-left: 0; border-right: 0; }
  .showreel-close {
    top: auto; bottom: -52px; right: 50%; transform: translateX(50%);
  }
}

/* ===========================================================================
   About / Meet Seven — rebuilt 1 Sep 2026
   ---------------------------------------------------------------------------
   From design/mockup/about-864.png, which replaces the 28 Aug mockup outright
   (kept beside it as about-812-superseded.png). The page reuses the measured
   components it can rather than inventing a parallel set: `.split` for both
   photo/copy bands, `.cards` for the three rooms, `.strip-cols` for the three
   support columns, `.hero-inner` for the hero. What is new here is the ratio
   overrides those components need for THIS mockup, plus the four pieces it
   draws that exist nowhere else - the hero's pink script line, the icon rows,
   the pull quote and the review cards.
   =========================================================================== */

/* Hero -------------------------------------------------------------------- */
/* The About hero line is the client's outlined SVG as of 1 Sep 2026, the same
   move as the home tagline. It replaces live text in Night Elevated Display,
   and it also sidesteps that face's loose punctuation bearings - this phrase
   carries four periods, which is where the gap shows worst. Sized by width;
   the artwork's own 5.85 aspect takes the height. */
.ab-script-art {
  display: block; margin: 15px 0 0;
  width: min(486px, 100%); height: auto;
}

/* This is Seven ----------------------------------------------------------- */
/* Mockup puts the photograph on x0-390 of 864, so 45% rather than the 62%
   `.split` carries for the events page. */
.ab-split { grid-template-columns: 45% 1fr; align-items: center; }
.ab-split-copy { padding: 28px var(--gut) 28px 46px; }
.ab-split-copy > p { margin: 15px 0 0; max-width: 583px;
                     font-size: var(--ab-body-sm); line-height: var(--ab-lh-body-sm);
                     color: var(--ink-mut); }
.ab-pull { margin: 26px 0 0; padding: 0; }
.ab-pull p:first-child {
  margin: 0; max-width: 583px;
  font-family: var(--serif); font-weight: 500; font-style: italic;
  font-size: 30px; line-height: 1.25; color: var(--gold-bright);
}

/* A performance you feel --------------------------------------------------
   A BAND, NOT A SPLIT, since 7 Sep 2026. It was `split split-reverse` with the
   copy in 45% and a photograph in the rest; Jacob moved the photograph into
   the "Live performances" card below, and a 45% column with 55% of nothing
   beside it is not a layout. The section keeps its own class name because the
   rows inside it are still this page's alone. */
.ab-feel { padding: 40px 0; }
.ab-feel-copy { padding-block: 0; }
/* The 940 block centres `.rule` site-wide, which was right while this copy sat
   in a 45% column and is not now that it sits in the full container: the
   heading is left-set and the rule was drifting to the middle of the page on
   its own. Two classes, so it wins on specificity wherever the shared rule
   lands in the file. */
.ab-feel-copy .rule { justify-content: flex-start; }
.ab-h2-pink { color: var(--pink-light); }
/* THREE ACROSS. The rows were written for a 45% column and each paragraph is
   two or three lines there; in the full container they would set ~1128px
   lines. Three columns puts them back at the measure they were written for
   and lines them up with .ab-cards directly below. The 34px gap is the 26 the
   rows carried between them plus the 8 that separated the glyph column from
   the one beside it - the horizontal rhythm this band already had. */
.ab-feel-rows {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 34px;
  margin-top: 30px;
}
.ab-feel-row {
  display: grid; grid-template-columns: 52px 1fr; gap: 17px;
  align-items: start;
}
.ab-feel-row .glyph { width: 52px; height: 52px; object-fit: contain; }
.ab-feel-row h3 {
  margin: 0;
  font-family: var(--display); font-weight: 500;
  font-size: var(--f-label); line-height: 1.2;
  letter-spacing: .06em; text-transform: uppercase; color: var(--gold);
}
.ab-feel-row p { margin: 7px 0 0; font-size: var(--ab-body-sm);
                 line-height: var(--ab-lh-body-sm); color: var(--ink-mut); }

/* The three occasion cards -------------------------------------------------
   Was "One voice. Every kind of room.", which came off 7 Sep 2026 with the
   photograph above it. `.ab-rooms-h2` went with it; the band's own 33px of top
   padding is now what separates the cards from the rows above, and it did not
   need the heading's 30px of margin added back - the .ab-feel band below the
   rows contributes its own 40. */
.ab-rooms { padding: 33px 0 38px; }
.ab-cards { grid-template-columns: repeat(3, 1fr); }
.ab-cards .card { height: 470px; }
.ab-cards .card-photo { height: 384px; }

/* Complete the experience ------------------------------------------------- */
.ab-complete { padding: 33px 0 42px; }
/* THREE CHILDREN, TWO ROWS, AND THE PLACEMENT IS EXPLICIT. The DJ credit moved
   out of `.ab-complete-copy` on 7 Sep 2026 so a phone could stack it under the
   two photographs, which is where Jacob wants it - a grid only reorders its
   own children, and nested inside the copy it was unreachable.

   The DOM order is now the PHONE order (copy, pictures, credit). The desktop
   is put back by hand: copy in 1/1, credit under it in 2/1, photographs down
   the right across both rows. Nothing about that layout moved.

   `row-gap: 0` because there was no second row before this and `.dj-credit`
   already carries its own 26px of margin and 22 of padding over its divider.
   Left at the shared 46 the credit would have sat 72px below the copy. */
.ab-complete-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 583px);
  column-gap: 46px; row-gap: 0; align-items: center;
}
.ab-complete-grid > .ab-complete-copy  { grid-area: 1 / 1; }
.ab-complete-grid > .dj-credit         { grid-area: 2 / 1; }
.ab-complete-grid > .ab-complete-media { grid-area: 1 / 2 / span 2 / auto; }
.ab-complete-copy p { margin: 17px 0 0; max-width: 454px;
                      font-size: var(--ab-body-sm); line-height: var(--ab-lh-body-sm);
                      color: var(--ink-mut); }
.ab-complete-media { display: grid; grid-template-columns: 1fr 1fr; gap: 17px; }
.ab-complete-media img {
  width: 100%; height: 288px; object-fit: cover; border-radius: var(--r-img);
}
/* THREE columns explicitly. .strip-cols is repeat(4, 1fr) because the services
   package ladder gained a fourth rung on 2 Sep 2026; this row has only ever had
   three children, so it inherited a 4-column grid and has been sitting in
   three-quarters of the width with an empty fourth column since that change.
   Setting the count here rather than reverting the shared rule, because the two
   rows genuinely differ in length. */
.ab-complete-cols { grid-template-columns: repeat(3, 1fr); margin-top: 38px; }
/* Full 1200, not the 1128 inside the gutters (Jacob, 3 Sep 2026). The row sits
   in a .wrap, so it was inheriting the container's 36px of padding each side;
   pulling that back by --gut puts the outer edge of the first and last column
   on the container's own edge, level with the photographs above them.

   Only from 1272px up, which is 1200 + two gutters: the narrowest viewport at
   which the wrap has reached its 1200 cap AND still has a gutter's worth of
   page margin outside it. Below that the wrap is viewport-width, so the same
   pull would put the column text hard against the screen edge - 1024 was tried
   and does exactly that between 1024 and 1199.

   THE LAST SENTENCE HERE USED TO SAY "the row stacks to one column at 620".
   IT DOES NOT AND NEVER HAS - `.strip-cols` has no breakpoint of its own, so
   these three run three-across at every width. At 390 that is ~110px a column
   and paragraphs setting three words to a line. Left alone because nobody has
   asked for it; noted because a comment claiming a breakpoint that does not
   exist is worse than no comment. */
@media (min-width: 1272px) {
  .ab-complete-cols { margin-inline: calc(var(--gut) * -1); }
}
.ab-complete-cols .strip-col { text-align: center; }
.ab-complete-cols .strip-col p {
  margin: 7px 0 0; padding: 0 12px;
  font-size: var(--f-small); line-height: var(--lh-small); color: var(--ink-mut);
}

/* What people are saying ---------------------------------------------------
   NOTHING ON THE SITE USES THESE FOUR RULES TODAY and they are kept anyway.
   The band they style was removed from about.html on 10 Sep 2026 because no
   client review has ever been supplied; the markup is preserved, commented,
   at the same spot in that file, so restoring it is a paste rather than a
   rebuild. Do not prune this block as dead - prune it if the decision is ever
   taken that this site carries no reviews at all. .ab-h-small above is held
   for the same reason: this band's heading was its last remaining user. */
.ab-saying { padding: 7px 0 38px; text-align: center; }
.ab-reviews {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 17px;
  margin-top: 26px; text-align: left;
}
.ab-review {
  margin: 0; padding: 17px 20px;
  border: 1px solid rgba(var(--gold-rgb), .30); border-radius: var(--r-card);
  background: rgba(var(--ink-rgb), .03);
}
/* 16/23 from 12/22, 2 Sep 2026: these were set at the site's SMALLEST type,
   smaller than the body copy beside them, which made a client's words read as
   a caption. One step below the home carousel's 19px, which this used to point
   at - that carousel was removed on 3 Sep and its rules with it. */
.ab-review p { margin: 10px 0 0; font-size: var(--f-body);
               line-height: var(--lh-body); color: var(--ink-mut); }
.ab-review cite {
  display: block; margin-top: 15px; font-style: normal;
  font-family: var(--display); font-weight: 400;
  font-size: 12px; letter-spacing: .10em; text-transform: uppercase; color: var(--pink);
}

@media (max-width: 940px) {
  .ab-split { grid-template-columns: 1fr; }
  .ab-split-copy { padding: 33px var(--gut) 0; }
  /* Two columns and three rows leaves an orphan, so they stack straight from
     three. At this width the container is ~880 and one column sets a 828px
     line, which is why the copy takes a measure here rather than running the
     full width the way the rows above it do. */
  .ab-feel { padding: 33px 0 0; }
  .ab-feel-rows { grid-template-columns: 1fr; gap: 26px; max-width: 560px; }
  /* One column, and the explicit placement above comes off with it so the
     three children fall in DOM order: copy, photographs, DJ credit. The row
     gap comes back here because there is no longer a second column for the
     28 to be doing anything else with. */
  .ab-complete-grid { grid-template-columns: 1fr; gap: 28px; }
  .ab-complete-grid > .ab-complete-copy,
  .ab-complete-grid > .dj-credit,
  .ab-complete-grid > .ab-complete-media { grid-area: auto; }
  .ab-cards { grid-template-columns: repeat(2, 1fr); }
  .ab-reviews { grid-template-columns: 1fr; }
}
@media (max-width: 620px) {
  .ab-cards { grid-template-columns: 1fr; }

  .ab-pull p:first-child { font-size: 24px; }
  .ab-complete-media img { height: 160px; }
}

/* Per-slot crops for /about ------------------------------------------------
   Every photograph the client has is a PORTRAIT phone frame, and most of these
   slots are landscape. `object-fit: cover` centre-crops by default, which on a
   tall source means the middle of a body and no face. Each value below was set
   by rendering the page and looking at the box, not by reading the file. */
.ab-hero-img   { object-position: 50% 20%; }   /* keeps her face out of the top crop */
.ab-chair-img  { object-position: 50% 38%; }
/* `.ab-feel-img` retired 7 Sep 2026 with the slot it cropped for. Its 50% 12%
   belonged to about-stage.jpg in a 354-tall landscape box; the same file is
   now in the Live performances card, which is portrait, and the value there
   is a different one - below. A crop outlives the box it was set for unless
   somebody moves it deliberately. */
/* about-stage.jpg, 1254x1254, into 254x384. Cover scales it to 384x384, so the
   VERTICAL IS AN EXACT FIT and the Y here does nothing - it is 50% to say so
   rather than to leave the old 34% reading as a live setting. The crop is all
   horizontal, 130px of it, taking source x212-1042; Seven runs x380-870 and
   sits clear of both edges. Checked in the box at 1440, 940 and 390, not in
   the file. */
.ab-live-img   { object-position: 50% 50%; }
/* Centred again. The hard-left value belonged to gallery-children.jpg, a
   landscape frame with her at the far edge; this source is near-square (1288
   x1221 into 364x367) so cover discards only 24px and centre is correct. A
   crop value outlives the photograph it was set for unless it is re-checked. */
.ab-kids-img   { object-position: 50% 50%; }
.ab-dj-img     { object-position: 50% 16%; }   /* was cutting the DJ's head */
