/* ==========================================================================
   Luxury Listing Media, v6
   Conversion-first structure, futuristic shell. Zero dependencies. Mobile-first.
   Brand tokens carried forward unchanged. DESIGN.md governs every value here.
   ========================================================================== */

/* DM Sans, self-hosted. SIL Open Font License 1.1, text beside the files in
   assets/fonts/OFL.txt; provenance in docs/launch/fonts.md. These replace the
   Google Fonts stylesheet, so no page asks a third party for type before a
   visitor has chosen anything. font-display:swap keeps text visible on the
   system stack while the file loads. */
@font-face{ font-family:'DM Sans'; font-style:normal; font-weight:300; font-display:swap; src:url(../fonts/dm-sans-300.ttf) format('truetype'); }
@font-face{ font-family:'DM Sans'; font-style:normal; font-weight:400; font-display:swap; src:url(../fonts/dm-sans-400.ttf) format('truetype'); }
@font-face{ font-family:'DM Sans'; font-style:normal; font-weight:500; font-display:swap; src:url(../fonts/dm-sans-500.ttf) format('truetype'); }
@font-face{ font-family:'DM Sans'; font-style:normal; font-weight:700; font-display:swap; src:url(../fonts/dm-sans-700.ttf) format('truetype'); }

:root{
  /* ── BRAND (confirmed, do not drift) ── */
  --gold:#dfbe6f; --gold-light:#ead08a; --gold-dim:#a8834a;
  --gold-14:rgba(223,190,111,.14); --gold-28:rgba(223,190,111,.28);

  --bg:#0a0908; --bg-1:#0f0e0b; --bg-2:#14120e; --bg-3:#1b1811;
  --line:#26221a; --line-2:#332d22;

  /* --muted-2 was #6f6a5e through 2026-09-06: 3.69:1 on --bg, under the 4.5:1
     AA floor at every size it is used (fine print, footer, contact labels).
     Raised to #878073, 5.08:1, the lighter of the two same-hue steps that
     clear it. audit.mjs fails any text token under 4.5:1 on --bg. */
  --cream:#f3ede1; --cream-dim:#e8e2d6; --muted:#9a9284; --muted-2:#878073;

  /* ── TYPE ──
     ONE typeface. DM Sans, the LLM brand font. Hierarchy comes from weight,
     size and letter-spacing only, never from a second family. The old
     Space Grotesk display face and the ui-monospace label face are both gone;
     --f-display and --f-mono are kept as aliases so no rule silently falls
     back to a system font, but they all resolve to the same stack. */
  --f-sans:'DM Sans',ui-sans-serif,system-ui,-apple-system,sans-serif;
  --f-display:var(--f-sans);
  --f-body:var(--f-sans);
  --f-mono:var(--f-sans);

  --pad:clamp(18px,5vw,64px);

  /* ── TWO RHYTHMS, TWO TOKENS. THEY ARE NOT THE SAME NUMBER. ──
     v9.3 SPLIT THESE APART. Read this before merging them back.

     Through v9.2 there was one token and .group was derived from it, on the
     theory that one number could not drift. It could not, but it also could not
     be steered, and the two gaps it fed want to move in OPPOSITE directions:

       --rhythm  BETWEEN sections (01 -> 02 -> 03 ...). A boundary between two
                 numbered sections is a change of subject and should read as one,
                 so on mobile this wants to be LARGER.
       --group   BETWEEN groups INSIDE section 01 (horizontal films -> vertical
                 videos). Those two groups are one subject shown two ways and
                 BELONG together, so this wants to stay TIGHT.

     Raising --rhythm in v9.2 dragged the group gap from 38px to 60px on mobile
     as a side effect and pushed the two film groups visibly apart. That is the
     bug this split fixes. One token could not express "further apart between
     sections, closer together within one", so there are two.

     Derived, still:
       .sec padding-block    = --rhythm / 2   (two adjacent sections => --rhythm)
       .strip padding-bottom = --sec          (its half of strip -> section 01)
       .group margin-bottom  = --group

     Uniformity is now enforced WITHIN each category, never across them, the
     audit asserts every section gap equals every other section gap and every
     group gap equals every other group gap, and pins each category to its own
     token. Two rhythms, each internally uniform. Never re-tie them, and never
     hard-code a gap on an individual section or group. */

  /* Section-to-section. v9.3: mobile 60px -> 68px, a touch more separation at a
     change of subject. DESKTOP IS UNCHANGED at exactly 80px, 5.56vw is picked so
     the ramp lands on 80.0px at 1440px, not near it. 68px flat below 1223px,
     ramping to 80px at 1440px, flat above. */
  --rhythm:clamp(68px,5.56vw,80px);
  --sec:calc(var(--rhythm) / 2);

  /* Between groups inside a section. RESTORED in v9.3 to its pre-v9.2 value at
     both viewports, 38px mobile / 72px desktop. This is the gap that was
     reviewed and signed off; v9.2 inflated it to 60/80 only because it was
     derived from --rhythm. It is not, any more. Do not "harmonise" it with
     --rhythm: being smaller than the section gap is the whole point, because it
     says these groups belong to each other. */
  --group:clamp(38px,5vw,72px);
  --r:3px; --r-lg:6px;
  --ease:cubic-bezier(.16,1,.3,1);
  --hdr-h:68px;
}

*,*::before,*::after{ box-sizing:border-box; }
html{ background:var(--bg); -webkit-text-size-adjust:100%; scroll-behavior:smooth; scroll-padding-top:var(--hdr-h); }
html.lock{ overflow:hidden; }
body{
  margin:0; background:var(--bg); color:var(--cream);
  font-family:var(--f-body); font-size:16px; line-height:1.6;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
img,video{ display:block; max-width:100%; }
a{ color:inherit; text-decoration:none; }
button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
h1,h2,h3{ margin:0; font-family:var(--f-display); font-weight:400; }
ul,ol,dl,dd{ margin:0; padding:0; list-style:none; }
::selection{ background:var(--gold); color:#000; }
:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }

.skip{ position:fixed; top:-100px; left:16px; z-index:300; background:var(--gold); color:#000; padding:12px 18px; border-radius:var(--r); }
.skip:focus{ top:14px; }

.grain{
  position:absolute; inset:-50%; pointer-events:none; z-index:3; opacity:.10;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.5'/></svg>");
  animation:grain 1.2s steps(3) infinite;
}
@keyframes grain{0%{transform:translate(0,0)}33%{transform:translate(-3%,2%)}66%{transform:translate(2%,-2%)}100%{transform:translate(0,0)}}

/* ══════════ BUTTONS ══════════ */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:14px 26px; border:1px solid var(--gold); border-radius:var(--r);
  background:var(--gold); color:#000;
  font-family:var(--f-display); font-size:12.5px; font-weight:500;
  letter-spacing:.14em; text-transform:uppercase; white-space:nowrap;
  transition:background .35s var(--ease), color .35s var(--ease), transform .35s var(--ease);
}
.btn:hover{ background:transparent; color:var(--gold); transform:translateY(-2px); }
.btn--ghost{ background:transparent; color:var(--gold); border-color:var(--gold-28); }
.btn--ghost:hover{ background:var(--gold); color:#000; }
.btn--sm{ padding:11px 18px; font-size:11px; }
.btn--full{ width:100%; }

.eyebrow{
  display:block; margin-bottom:12px;
  font-family:var(--f-mono); font-size:11px; letter-spacing:.22em;
  text-transform:uppercase; color:var(--gold-dim);
}

/* ══════════ HEADER ══════════ */
.hdr{
  position:fixed; inset:0 0 auto 0; z-index:200; height:var(--hdr-h);
  display:flex; align-items:center; gap:22px; padding:0 var(--pad);
  background:rgba(10,9,8,.55); backdrop-filter:blur(16px);
  border-bottom:1px solid transparent;
  transition:background .4s, border-color .4s, transform .5s var(--ease);
}
.hdr.is-solid{ background:rgba(10,9,8,.9); border-bottom-color:var(--line); }
.hdr.is-hidden{ transform:translateY(-100%); }
/* Keyboard focus inside the header brings it back. Measured 2026-09-06: with
   the page scrolled and the header slid away, Tab put focus on the logo at
   y -52px, an invisible focus stop. Same specificity as .is-hidden, later in
   source, so it wins only while something in the header has focus. Mouse and
   touch never trigger it; the scroll behaviour is unchanged. */
.hdr:focus-within{ transform:none; }

/* Header logo, the supplied file, unmodified.
   The delivered artwork carries transparent padding baked into its canvas:
   measured ink is 2968x371 inside a 3163x549 canvas, so the mark occupies
   only 67.6% of the file's height. Element height is therefore set to
   ~1.48x the intended optical size (41px -> ~28px of visible mark) rather
   than cropping or re-exporting the asset. margin-left cancels the 113
   canvas-px of dead space on the left edge so the mark's ink aligns with
   the content column below. Both numbers are optical corrections applied
   to the element; the file itself is untouched. */
.hdr__logo{ display:flex; align-items:center; }
.hdr__logo-img{ display:block; height:41px; width:auto; margin-left:-8px; }
@media (max-width:620px){
  .hdr__logo-img{ height:35px; margin-left:-7px; }
}


.hdr__nav{ display:none; margin-left:auto; gap:28px; }
.hdr__nav a{ font-size:12.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--cream-dim); position:relative; padding-block:6px; }
.hdr__nav a::after{ content:''; position:absolute; left:0; bottom:0; height:1px; width:100%; background:var(--gold); transform:scaleX(0); transform-origin:right; transition:transform .45s var(--ease); }
.hdr__nav a:hover::after{ transform:scaleX(1); transform-origin:left; }

.hdr__act{ display:none; align-items:center; gap:18px; }
.hdr__contact{ font-size:12.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--cream-dim); padding-block:6px; }
.hdr__contact:hover{ color:var(--gold); }

@media (min-width:1000px){
  .hdr__nav{ display:flex; }
  .hdr__act{ display:flex; }
}

.burger{ margin-left:auto; width:40px; height:40px; display:grid; place-content:center; gap:6px; }
.burger span{ display:block; width:22px; height:1.5px; background:var(--cream); transition:transform .35s var(--ease), opacity .35s; }
.burger[aria-expanded="true"] span:first-child{ transform:translateY(3.75px) rotate(45deg); }
.burger[aria-expanded="true"] span:last-child{ transform:translateY(-3.75px) rotate(-45deg); }
/* Hidden on desktop. This rule MUST come after the .burger base rule above:
   same specificity, so source order decides, and through v9.3 the hide rule
   sat in the media block before the base rule and lost. The burger rendered
   beside "Book a shoot" on every desktop header. Fixed 2026-09-05. */
@media (min-width:1000px){ .burger{ display:none; } }

.mobnav{
  position:fixed; inset:var(--hdr-h) 0 auto 0; z-index:190;
  display:grid; gap:4px; padding:22px var(--pad) 30px;
  background:rgba(10,9,8,.97); backdrop-filter:blur(18px); border-bottom:1px solid var(--line);
}
.mobnav[hidden]{ display:none; }
.mobnav a{ padding:13px 0; font-family:var(--f-display); font-size:19px; border-bottom:1px solid var(--line); }
.mobnav .btn{ margin-top:16px; border-bottom:0; }

/* ══════════ HERO ══════════ */
.hero{ position:relative; min-height:100svh; display:flex; align-items:flex-end; overflow:hidden; }
.hero__media{ position:absolute; inset:0; z-index:0; }
.hero__poster,.hero__video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.hero__video{ opacity:0; transition:opacity 1.2s var(--ease); }
.hero__video.is-ready{ opacity:1; }
.hero__scrim{
  position:absolute; inset:0; z-index:2;
  background:
    linear-gradient(to top, rgba(10,9,8,.94) 0%, rgba(10,9,8,.55) 26%, rgba(10,9,8,.10) 52%, rgba(10,9,8,.45) 100%),
    linear-gradient(to right, rgba(10,9,8,.80) 0%, rgba(10,9,8,.25) 46%, transparent 72%);
}

/* lower third, left, the footage stays readable */
.hero__lower{
  position:relative; z-index:5;
  width:100%; max-width:660px;
  padding:0 var(--pad) clamp(74px,11vh,120px);
}
.badge{
  display:inline-flex; align-items:center; gap:9px; margin-bottom:18px;
  padding:7px 14px; border:1px solid var(--gold-28); border-radius:100px;
  font-family:var(--f-mono); font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(--gold-light);
  background:rgba(10,9,8,.5);
}
.dot-live{ width:6px; height:6px; border-radius:50%; background:var(--gold); box-shadow:0 0 0 0 var(--gold-28); animation:pulse 2.4s infinite; }
@keyframes pulse{ 0%{box-shadow:0 0 0 0 rgba(223,190,111,.5)} 70%{box-shadow:0 0 0 9px rgba(223,190,111,0)} 100%{box-shadow:0 0 0 0 rgba(223,190,111,0)} }

/* ── THE HERO HEADLINE, the one bold voice on the page ──
   v9.2. Was 400 weight, clamp(1.7rem,3.5vw,2.9rem), line-height 1.14, -.02em,
   and read as a quiet sentence rather than a claim.

   Four changes, and the weight is only one of them, a bolder weight alone would
   have read as heavier text, not as a statement:
     WEIGHT   400 -> 700. The page's entire display scale is 300, so the H1 is now
              the single heaviest thing on it, by a wide margin. That contrast is
              the confidence; it only works BECAUSE everything around it stays
              light, so do not bolden .sec__title to match.
     SIZE     up ~26% at both ends. The headline, not the video, is now the first
              thing the eye resolves.
     LEADING  1.14 -> 1.02. At two lines and this weight the old leading left a
              visible trough between them; near-solid setting locks the two lines
              into one block.
     TRACKING -.02em -> -.038em. Bold DM Sans at display size sets loose by
              default; pulling it in gives the dense, cut-from-one-piece look and
              is what stops "bold" from reading as "shouty".

   STILL SENTENCE CASE, deliberately. DESIGN.md §1 sets every display role in
   uppercase "except the hero", that exception is the whole reason the H1 reads
   as a voice and the six .sec__titles read as labels. Uppercase here would have
   been the obvious move and would have flattened the hero into another section
   header, so the confidence comes from weight, scale and tracking instead. */
.hero__h1{
  font-size:clamp(2.05rem,4.6vw,3.6rem);
  line-height:1.02; letter-spacing:-.038em; font-weight:700;
  text-wrap:balance;             /* only a safety net, the spans do the breaking */
}
/* Each line is its own block, so the break is ours and identical at every width.
   Nothing here may become `display:inline`, that hands the break back to the
   browser and re-orphans "listings." on mobile. */
.hero__h1 span{ display:block; }
/* 16px -> 22px in v9.2. Not a spacing whim: the H1 above went to 700 weight and
   1.02 leading, and against that much heavier block the old 16px read as the sub
   being crowded by it. The gap has to grow when the thing above it gets denser. */
.hero__sub{ margin:22px 0 0; max-width:46ch; color:var(--cream-dim); font-size:clamp(.95rem,1.4vw,1.08rem); }
.hero__cta{ display:flex; flex-wrap:wrap; gap:12px; margin-top:28px; }

.sound{
  position:absolute; z-index:6; right:var(--pad); bottom:clamp(74px,11vh,120px);
  display:inline-flex; align-items:center; gap:9px;
  padding:10px 16px; border:1px solid var(--gold-28); border-radius:100px;
  background:rgba(10,9,8,.55); backdrop-filter:blur(8px);
  font-family:var(--f-mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--cream-dim);
  transition:border-color .3s, color .3s;
}
.sound:hover{ border-color:var(--gold); color:var(--gold); }
.sound b{ font-weight:400; }
.sound__ico{ width:11px; height:11px; border-radius:2px; border:1.5px solid currentColor; }
.sound.is-audible .sound__ico{ background:var(--gold); border-color:var(--gold); }
/* The button must not change width when its label flips between "Turn Sound On"
   and "Turn Sound Off". Both labels are laid out, invisibly and zero-height,
   under the visible one, so the box is always as wide as the wider of the two.
   Same trick on the film player's sound button below. */
.sound b, .cinema__sound b{ display:inline-block; text-align:center; }
.sound b::before, .sound b::after, .cinema__sound b::before, .cinema__sound b::after{
  display:block; height:0; overflow:hidden; visibility:hidden; pointer-events:none; }
.sound b::before, .cinema__sound b::before{ content:"Turn Sound On"; }
.sound b::after,  .cinema__sound b::after{ content:"Turn Sound Off"; }
@media (max-width:620px){ .sound{ bottom:auto; top:calc(var(--hdr-h) + 14px); } }

/* ══════════ CAPABILITY STRIP ══════════ */
/* True black, no tinted band. Confirmed by pixel sample, not just by the token:
   the whole band renders rgb(10,9,8), see DESIGN.md §2 "The strip is true
   black" before suspecting this element of being lighter.

   v9.2, MOBILE RESTRUCTURE. This was ONE flex row with wrap and
   `flex:1 1 auto`, which at 390px produced rows of 1/2/2/1: single items
   stretched to the full width while paired items split unevenly (224px vs
   166px), so nothing lined up and the last row orphaned one item. Measured
   ragged at 390, 500, 768, 900 and 1024, the fault was never mobile-only.

   The fix is a grid whose COLUMN COUNT ALWAYS DIVIDES THE SIX ITEMS, so every
   row is full at every width and no item can ever be orphaned (DESIGN.md §4,
   the same rule the card grids follow):

       2 cols  ->  3 full rows   (base, up to 619px)
       3 cols  ->  2 full rows   (620px - 1099px)
       6 cols  ->  1 full row    (1100px+, the flex row below, unchanged)

   6-across needs 1077px of natural item width (measured), so 1100px is the
   narrowest breakpoint at which the desktop row genuinely fits. Desktop at 1440
   is byte-for-byte what it was.

   Separators are the 1px hairline-grid idiom already used by .deliv and .steps:
   --line on the list, --bg on each cell, so the rules appear only BETWEEN cells
   and never on the outer edge. This is a component-internal separator, not a
   section divider, the distinction DESIGN.md §3 draws. */
/* padding-bottom, derived from the same token, so the strip -> section 01
   boundary matches every other boundary on the page. The strip is not a .sec, so
   it contributed NOTHING to that gap: section 01's own padding-top was the whole
   of it, making this the tightest boundary on the page at exactly half a rhythm
   (30px mobile / 40px desktop) while every other one was a full 60/80. That is
   the "service list -> 01" boundary in Michael's list. --sec here + --sec on
   .sec = one --rhythm, same as any two adjacent sections. This is not a
   special case; it is the one non-conforming boundary being brought INTO the
   uniform system, and it stays derived so it cannot drift.
   No padding-top by design: the strip reads as a caption band attached to the
   footage above it, so it stays snug to the hero's bottom edge. */
.strip{ background:var(--bg); overflow:hidden; padding-bottom:var(--sec); }
.strip ul{
  display:grid; grid-template-columns:repeat(2,1fr);
  gap:1px; background:var(--line);
  /* 1fr rows, so a cell whose label wraps to two lines does not leave its
     row-mate short, every row in the grid is the same height. */
  grid-auto-rows:1fr;
}
.strip li{
  display:flex; align-items:center; justify-content:center;
  padding:17px 10px; text-align:center; background:var(--bg);
  font-family:var(--f-mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);
}

/* 620px+, three across, two full rows. */
@media (min-width:620px){
  .strip ul{ grid-template-columns:repeat(3,1fr); }
  .strip li{ padding:17px 14px; }
}

/* 1100px+, the original desktop row, restored exactly: one flex line, natural
   widths, nowrap labels, a vertical --line rule between items and none after the
   last. The grid above never applies at this width. */
@media (min-width:1100px){
  .strip ul{ display:flex; flex-wrap:wrap; justify-content:center; gap:0; background:none; }
  .strip li{
    display:block; flex:1 1 auto; padding:18px 22px; white-space:nowrap;
    letter-spacing:.18em; border-right:1px solid var(--line);
  }
  .strip li:last-child{ border-right:0; }
}

/* ══════════ SECTION FURNITURE ══════════ */
.sec{ padding:var(--sec) var(--pad); }
.sec__head{
  display:grid; gap:18px; margin-bottom:clamp(30px,4.5vw,58px);
  padding-bottom:26px; border-bottom:1px solid var(--line);
}
@media (min-width:900px){ .sec__head{ grid-template-columns:1fr 1fr; align-items:end; gap:40px; } }
.sec__title{ font-size:clamp(2rem,5.2vw,3.6rem); line-height:1; letter-spacing:-.03em; text-transform:uppercase; font-weight:300; }
.sec__lede{ margin:0; color:var(--muted); max-width:52ch; }

/* ══════════ VIDEO GROUPS ══════════ */
/* --group, NOT --rhythm. These are two different rhythms on purpose (see the
   token block at the top of this file): the section gap is larger because it
   separates subjects, this one is smaller because it joins them. Never hard-code
   the value here, the audit pins every group gap to this token. */
.group{ margin-bottom:var(--group); }
.group:last-child{ margin-bottom:0; }
.group__head{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:8px 16px; margin-bottom:18px;
}
.group__head h3{ font-size:clamp(1.05rem,2vw,1.35rem); letter-spacing:-.01em; }
.group__meta{ font-family:var(--f-mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--gold-dim); }

.vgrid{ display:grid; gap:clamp(12px,1.6vw,20px); }
.vgrid--h{ grid-template-columns:1fr; }
@media (min-width:820px){ .vgrid--h{ grid-template-columns:1fr 1fr; } }
.vgrid--v{ grid-template-columns:1fr 1fr; }
@media (min-width:820px){ .vgrid--v{ grid-template-columns:repeat(4,1fr); } }

.vcard{
  position:relative; overflow:hidden; border-radius:var(--r-lg);
  border:1px solid var(--line); background:var(--bg);
  opacity:0; transform:translateY(24px);
  transition:opacity .8s var(--ease), transform .8s var(--ease), border-color .4s;
}
.vcard.is-in{ opacity:1; transform:none; }
.vcard:hover{ border-color:var(--gold-28); }
.vcard__media{ position:relative; width:100%; }
.vgrid--h .vcard__media{ aspect-ratio:16/9; }
.vgrid--v .vcard__media{ aspect-ratio:9/16; }
/* fill the frame, no letterboxing, no gaps */
.vcard__media img,.vcard__media video{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
}
.vcard__media picture{ position:absolute; inset:0; display:block; }
.vcard__loop{ opacity:0; transition:opacity .55s var(--ease); }
.vcard.is-playing .vcard__loop{ opacity:1; }
/* v9: NO RESTING FILTER, same rule as .svc__img img and .mosaic figure img.
   Was saturate(.8) brightness(.86) at rest. A dulled poster on a real estate
   media site implies a dulled deliverable, and the poster is what a scroller
   judges the film by. Hover is a TRANSFORM ONLY, and no `filter` transition is
   declared, so nothing can animate one back in. */
.vcard__poster{ transition:transform 1.4s var(--ease); }
.vcard:hover .vcard__poster{ transform:scale(1.03); }

.vcard__bar{
  position:absolute; z-index:4; inset:auto 0 0 0;
  display:flex; align-items:flex-end; justify-content:space-between; gap:12px;
  padding:clamp(13px,1.6vw,20px);
  background:linear-gradient(to top, rgba(10,9,8,.92), rgba(10,9,8,.35) 60%, transparent);
}
.vcard__t{ font-family:var(--f-display); font-size:clamp(.9rem,1.5vw,1.05rem); line-height:1.25; }
.vcard__k{ display:block; margin-top:4px; font-family:var(--f-mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:var(--gold); }
.vcard__play{
  flex:none; width:38px; height:38px; border:1px solid var(--gold-28); border-radius:50%;
  display:grid; place-items:center; background:rgba(10,9,8,.45); transition:background .35s var(--ease);
}
.vcard__play svg{ width:10px; height:12px; fill:var(--gold); transition:fill .35s; }
.vcard:hover .vcard__play{ background:var(--gold); }
.vcard:hover .vcard__play svg{ fill:#000; }
/* marks a duplicate slot until real clips land */
.vcard__ph{
  position:absolute; z-index:5; top:10px; left:10px;
  padding:4px 9px; border-radius:100px;
  background:rgba(10,9,8,.72); border:1px solid var(--line-2);
  font-family:var(--f-mono); font-size:9px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--muted);
}

/* ══════════ PHOTO MOSAIC ══════════
   Packed tight: a repeating 6-col x 4-row unit that tiles exactly, so there is
   never a leftover cell. Six photos per unit, mirrored each time.              */
.mosaic{
  display:grid; gap:clamp(8px,1.1vw,14px);
  grid-template-columns:repeat(2,1fr);
  grid-auto-rows:30vw;
}
.mosaic figure{
  position:relative; margin:0; overflow:hidden; border-radius:var(--r);
  background:var(--bg); cursor:zoom-in;
  opacity:0; transform:scale(.98);
  transition:opacity .7s var(--ease), transform .7s var(--ease);
}
.mosaic figure.is-in{ opacity:1; transform:none; }
.mosaic picture{ display:block; width:100%; height:100%; }
/* v9: NO RESTING FILTER, same rule as .svc__img img and .vcard__poster.
   Was saturate(.82) brightness(.88) at rest. This is the photography grid; it is
   the single most literal sample of the product on the page, and it displays at
   full richness. Hover is a TRANSFORM ONLY, and no `filter` transition is
   declared, so nothing can animate one back in. */
.mosaic img{
  width:100%; height:100%; object-fit:cover;
  transition:transform 1.5s var(--ease);
}
.mosaic figure:hover img{ transform:scale(1.06); }

/* mobile, unit of 6 across 2 cols = 8 cells = 4 rows exactly:
   [ full-width ] [ half ][ half ] [ half ][ half ] [ full-width ]            */
.mosaic figure:nth-child(6n+1),
.mosaic figure:nth-child(6n+0){ grid-column:span 2; }

@media (min-width:900px){
  /* desktop, unit of 6 across 6 cols = 24 cells = 4 rows exactly:
     row 1-2: [ 4x2 big ][ 2x1 ]        mixed sizes, packed tight
              [        ][ 2x1 ]
     row 3-4: [ 2x2 ][ 2x2 ][ 2x2 ]                                          */
  .mosaic{ grid-template-columns:repeat(6,1fr); grid-auto-rows:12vw; }
  .mosaic figure:nth-child(6n+1){ grid-column:span 4; grid-row:span 2; }
  .mosaic figure:nth-child(6n+2){ grid-column:span 2; grid-row:span 1; }
  .mosaic figure:nth-child(6n+3){ grid-column:span 2; grid-row:span 1; }
  .mosaic figure:nth-child(6n+4){ grid-column:span 2; grid-row:span 2; }
  .mosaic figure:nth-child(6n+5){ grid-column:span 2; grid-row:span 2; }
  .mosaic figure:nth-child(6n+0){ grid-column:span 2; grid-row:span 2; }
}

/* ══════════ SERVICES ══════════ */
.svcs{ display:grid; gap:clamp(12px,1.6vw,20px); grid-template-columns:1fr; }
@media (min-width:680px){ .svcs{ grid-template-columns:repeat(2,1fr); } }
@media (min-width:1080px){ .svcs{ grid-template-columns:repeat(3,1fr); } }
.svc{
  position:relative; overflow:hidden; border-radius:var(--r-lg);
  border:1px solid var(--line); background:var(--bg);
  display:flex; flex-direction:column;
  opacity:0; transform:translateY(22px);
  transition:opacity .8s var(--ease), transform .8s var(--ease), border-color .4s, background .4s;
}
.svc.is-in{ opacity:1; transform:none; }
.svc:hover{ border-color:var(--gold-28); background:var(--bg-2); }
.svc__img{ position:relative; aspect-ratio:16/10; overflow:hidden; }
.svc__img picture{ position:absolute; inset:0; display:block; }
/* v9: NO RESTING FILTER. These tiles used to sit at saturate(.72) brightness(.72)
   and lift to saturate(1) brightness(.9) on hover, so the photography was dulled
   at rest and STILL never reached full brightness even on hover. This is a real
   estate media company: dulled photos on the page imply dulled deliverables.
   Hover is a ZOOM ONLY. Do not reintroduce a filter here, and note that no
   `filter` transition is declared either, so nothing can animate one back in. */
.svc__img img{ width:100%; height:100%; object-fit:cover; transition:transform 1.4s var(--ease); }
.svc:hover .svc__img img{ transform:scale(1.05); }
.svc__body{ padding:clamp(18px,2vw,26px); display:flex; flex-direction:column; gap:9px; flex:1; }
.svc__body h3{ font-size:1.12rem; }
.svc__body p{ margin:0; color:var(--muted); font-size:14.5px; }
.svc__link{ margin-top:auto; padding-top:12px; font-family:var(--f-mono); font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(--gold); }
.svc__link::after{ content:' →'; }

/* ══════════ WHAT YOU GET ══════════
   .sec--alt is deliberately GONE. Every section is true black from hero to
   footer, no alternating band, no border-block divider. Do not reintroduce. */
/* SEPARATORS ARE BORDERS ON THE CELLS, NOT A --line BACKGROUND SHOWING THROUGH
   A 1px GAP. Changed 2026-09-06, DESIGN.md §11 item 15, which carries the
   measurements. The short version: a gap is a hole, and the --line rect filling
   it sat BEHIND the cards. .deliv__card is composited (it has an opacity and
   transform transition for the reveal), so the fill and the thing covering it
   were rasterised on different pixel grids. At fractional device pixel ratios,
   which is every browser zoom step, individual boundaries dropped to the card
   fill while their neighbours stayed at full strength: swept 1430 to 1450px at
   1.35x, 6 of 21 widths put a .deliv boundary under the floor, low point 13.7
   against a card fill of 9.0. Borders are painted by the card itself, on one
   grid: 0 of 21, low point 22.0. That is the reported "the divider between
   blocks 2 and 3 is missing while the others have it".
   A border is also what every other divider on this page already is:
   .sec__head, .tier__price, .strip li, .mobnav a.
   The column counts must still divide the item count, see DESIGN.md §4. */
.deliv{
  display:grid; grid-template-columns:1fr; background:var(--bg);
  border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
}
/* CASCADE ORDER IS LOAD-BEARING, and the burger bug of 2026-09-05 is why this
   says so. Every selector below is exactly one class plus one :nth-child, so
   they all carry the SAME specificity and the LAST match wins. Base rule first,
   then the breakpoints in ascending order, each opening with an
   :nth-child(n+1) that clears both sides before re-applying the ones its own
   column count needs. Reordering these blocks, or reaching for a shorter
   selector in one of them, silently drops a divider. */
.deliv__card:nth-child(n+2){ border-top:1px solid var(--line); }
@media (min-width:680px){
  .deliv{ grid-template-columns:repeat(2,1fr); }
  .deliv__card:nth-child(n+1){ border-top:0; border-left:0; }
  .deliv__card:nth-child(n+3){ border-top:1px solid var(--line); }
  .deliv__card:nth-child(2n){ border-left:1px solid var(--line); }
}
@media (min-width:1100px){
  .deliv{ grid-template-columns:repeat(4,1fr); }
  .deliv__card:nth-child(n+1){ border-top:0; border-left:0; }
  .deliv__card:nth-child(n+2){ border-left:1px solid var(--line); }
}
.deliv__card{
  background:var(--bg); padding:clamp(24px,2.6vw,38px);
  display:flex; flex-direction:column; gap:10px;
  opacity:0; transform:translateY(20px);
  transition:opacity .8s var(--ease), transform .8s var(--ease), background .5s var(--ease);
}
.deliv__card.is-in{ opacity:1; transform:none; }
.deliv__card:hover{ background:var(--bg-2); }
.deliv__n{ font-family:var(--f-mono); font-size:10px; letter-spacing:.2em; text-transform:uppercase; color:var(--gold-dim); }
.deliv__card h3{ font-size:1.15rem; letter-spacing:-.01em; }
.deliv__card p{ margin:0; color:var(--muted); font-size:14.5px; }
.deliv__tag{
  margin:clamp(24px,3vw,40px) 0 0; text-align:center;
  font-family:var(--f-display); font-weight:300;
  font-size:clamp(1.05rem,2.4vw,1.7rem); letter-spacing:-.01em; color:var(--gold-light);
}

/* ══════════ TIERS ══════════ */
/* align-items:stretch (the default) is what makes all four cards equal height.
   The old align-items:start sized each card to its own content, which is why
   Social, the shortest list, came out visibly shorter than the rest. */
.tiers{ display:grid; gap:clamp(14px,1.8vw,22px); grid-template-columns:1fr; align-items:stretch; }
@media (min-width:680px){ .tiers{ grid-template-columns:repeat(2,1fr); } }
@media (min-width:1150px){ .tiers{ grid-template-columns:repeat(4,1fr); } }
.tier{
  display:flex; flex-direction:column; gap:14px; height:100%;
  padding:clamp(24px,2.6vw,36px);
  border:1px solid var(--line); border-radius:var(--r-lg); background:var(--bg);
  transition:border-color .4s, background .4s, transform .4s var(--ease);
}
.tier:hover{ border-color:var(--gold-28); transform:translateY(-3px); }
.tier--feat{ border-color:var(--gold-28); background:var(--bg); position:relative; }
.tier__tag{
  position:absolute; top:-1px; right:20px; padding:6px 12px;
  background:var(--gold); color:#000; border-radius:0 0 var(--r) var(--r);
  font-family:var(--f-mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
}
.tier h3{ font-size:1.35rem; letter-spacing:-.01em; }
.tier__for{
  margin:0; color:var(--muted); font-size:14.5px;
  /* Reserves TWO lines (14.5px x 1.6 x 2 = 46.4px = 3.2em). At 2.8em a
     two-line description ("Photos plus the vertical cut agents post.") pushed
     Social's price row ~6px below the other three cards. Keep >= 3.2em. */
  min-height:3.2em;
}
/* "Starting at" sits above the figure so the number can never be read as a
   flat rate. --sqft-tier line deleted: it was redundant with the lede and ate
   the room the real package contents now use. */
.tier__price{ display:grid; gap:2px; padding-block:10px; border-block:1px solid var(--line); }
.tier__from{
  font-family:var(--f-sans); font-size:10px; font-weight:500; letter-spacing:.18em;
  text-transform:uppercase; color:var(--gold-dim);
}
.tier__fig{ display:flex; align-items:baseline; gap:5px; }
.tier__fig span{ color:var(--gold-dim); font-size:15px; font-weight:400; }
.tier__fig b{ font-size:2.4rem; font-weight:300; letter-spacing:-.02em; line-height:1; }
/* .tier__list flexes so every Book button lands on the same baseline across
   all four equal-height cards. */
.tier__list{ display:grid; gap:9px; align-content:start; flex:1; }
.tier__list > li{ position:relative; padding-left:20px; font-size:14.5px; color:var(--cream-dim); }
.tier__list > li::before{ content:''; position:absolute; left:0; top:.62em; width:7px; height:1px; background:var(--gold); }
/* Indented secondary lines under a line item. No new size and no new spacing
   value: same 14.5px as the parent line, same 20px indent as the parent's
   padding-left, same 2px gap as .tier__price. The hierarchy is carried by the
   indent and by --muted against the parent's --cream-dim, and these carry no
   gold dash, the dash marks top-level deliverables only. */
.tier__sub{ margin-top:2px; padding-left:20px; display:grid; gap:2px; }
.tier__sub li{ font-size:14.5px; color:var(--muted); }
/* Small-print footnote, rendered with a leading asterisk by site.js. Reuses
   .note's treatment exactly, left-aligned instead of centred. */
.tier__fn{ margin:2px 0 0; font-size:11.5px; font-weight:400; letter-spacing:.08em; color:var(--muted-2); }
.tier .btn{ margin-top:6px; }
.note{ margin:22px 0 0; font-size:11.5px; font-weight:400; letter-spacing:.08em; color:var(--muted-2); text-align:center; }
.note--price{ margin-top:12px; font-size:14px; letter-spacing:.01em; line-height:1.55; color:var(--cream-dim); }

/* ══════════ STEPS ══════════
   Identical treatment to .deliv, borders and all, and the column counts are
   exact divisors of the 4 items (1 / 2 / 4). The old repeat(3,1fr) left the 4th
   step alone on a second row and the empty 3-of-4 cell rendered as a hole in
   the grid. Borders mean that hole is now plain --bg rather than an exposed
   --line box, which is quieter and therefore easier to miss: the rule is
   unchanged and matters as much as it did. Never use a column count that does
   not divide 4. */
.steps{
  display:grid; grid-template-columns:1fr; background:var(--bg);
  border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
}
/* Same cascade rule as .deliv above. Read the note there before editing. */
.steps li:nth-child(n+2){ border-top:1px solid var(--line); }
@media (min-width:680px){
  .steps{ grid-template-columns:repeat(2,1fr); }
  .steps li:nth-child(n+1){ border-top:0; border-left:0; }
  .steps li:nth-child(n+3){ border-top:1px solid var(--line); }
  .steps li:nth-child(2n){ border-left:1px solid var(--line); }
}
@media (min-width:1100px){
  .steps{ grid-template-columns:repeat(4,1fr); }
  .steps li:nth-child(n+1){ border-top:0; border-left:0; }
  .steps li:nth-child(n+2){ border-left:1px solid var(--line); }
}
.steps li{
  background:var(--bg); padding:clamp(24px,2.6vw,38px);
  display:flex; flex-direction:column; gap:10px;
  transition:background .5s var(--ease);
}
.steps li:hover{ background:var(--bg-2); }
.steps__n{ font-family:var(--f-sans); font-size:10px; font-weight:500; letter-spacing:.2em; text-transform:uppercase; color:var(--gold-dim); }
.steps li h3{ margin:0; font-size:1.15rem; letter-spacing:-.01em; }
.steps li p{ margin:0; color:var(--muted); font-size:14.5px; }

/* ══════════ CONTACT ══════════ */
/* No border-top, no tinted background, true black continues into the footer. */
.sec--contact{ background:var(--bg); }
.cta{ display:grid; gap:clamp(26px,3.5vw,52px); }
/* Even halves, not the old 1.05fr/.95fr. The right column carries the two call
   buttons and they are plain .btn, full size, white-space:nowrap, so the pair
   needs 549px side by side. .95fr left only 529px of content width and they
   wrapped to two rows at 1440. Even halves give 561px and they sit across.
   Nothing on the left needs the extra 31px it gives up. */
@media (min-width:960px){ .cta{ grid-template-columns:1fr 1fr; align-items:center; } }
.cta__title{ font-size:clamp(2rem,5vw,3.4rem); line-height:1.02; letter-spacing:-.03em; text-transform:uppercase; font-weight:300; }
.cta__copy p{ margin:16px 0 0; color:var(--muted); max-width:44ch; }
.cta__acts{ display:flex; flex-wrap:wrap; gap:12px; margin-top:28px; }
.cta__meta{ display:grid; gap:14px; margin-top:34px; padding-top:26px; border-top:1px solid var(--line); }
@media (min-width:560px){ .cta__meta{ grid-template-columns:repeat(3,auto); justify-content:start; gap:38px; } }
.cta__meta dt{ font-family:var(--f-mono); font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:var(--muted-2); margin-bottom:5px; }
.cta__meta dd{ font-size:14.5px; color:var(--cream); }
/* The other-work panel. Back to the v4/v5 shape in v8: a short horizontal panel
   beside the contact details, sized by its own content. The v7 calendar iframe
   made it a tall widget block and threw the section out of proportion; it is
   gone for good, see DESIGN.md §4. */
.cta__more{
  display:flex; flex-direction:column; align-items:flex-start; gap:16px;
  padding:clamp(24px,2.6vw,34px);
  border:1px solid var(--line); border-radius:var(--r-lg); background:var(--bg);
}
.cta__more h2,.cta__more h3{ font-size:1.15rem; letter-spacing:-.01em; }
.cta__more p{ margin:0; color:var(--muted); font-size:14.5px; max-width:38ch; }
/* The two call buttons that replaced the calendar widget. Same flex row and the
   same 12px gap as .cta__acts, no new spacing value, but without its
   margin-top, because .cta__more is already a 16px-gap flex column.

   The buttons are plain .btn: identical padding, height, fill, border and type
   to every "Book a shoot" on the page, deliberately, because they carry the same
   weight. .btn is white-space:nowrap, so a phone number can never break
   mid-digit; flex-wrap stacks the pair when the column cannot hold two.

   align-self:stretch, then flex:1 0 auto on the buttons. Both are load-bearing
   and this is the whole reason there is no breakpoint here:
     - .cta__more is align-items:flex-start, so without stretch this row would
       shrink to its content and grow would have nothing to grow into.
     - flex-basis auto keeps each button's base size at its own label width, so
       wrapping is decided by the real text; shrink 0 means a squeezed column can
       never clip a nowrap phone number.
     - grow 1 then does the rest: two on a row end flush with the panel, and a
       wrapped button is alone on its line and fills it. So the stacked pair is
       always equal-width instead of ragged, at EVERY width.
   That last point is why grow beats a media query. The pair needs 549px and the
   panel only clears that above ~1415px, so a 1366px laptop stacks, and a
   breakpoint tuned to 390px would have left it ragged there. */
.cta__more-acts{ align-self:stretch; display:flex; flex-wrap:wrap; gap:12px; }
.cta__more-acts .btn{ flex:1 0 auto; }

/* ══════════ SUBPAGES: /login and the 404 ══════════
   Pages without a hero. The fixed header would otherwise sit on the first
   section, so main clears it by the header token; min-height keeps the footer
   off the middle of a short page. Every value is an existing token or an
   existing hero value; the sections inside use the ordinary .sec pattern. */
.page main{ min-height:100svh; padding-top:var(--hdr-h); display:flex; flex-direction:column; justify-content:center; }

/* ══════════ LEGAL PAGES: /privacy and /terms ══════════
   Added 2026-09-06, DESIGN.md §15. Long-form prose in the shell, built from
   values that already exist: the hero's 660px column, the card heading size
   (1.15rem / -.01em), the .cta__meta margin (34px), the .cta__copy paragraph
   margin (16px), the package-list indent (20px), the .group__meta label
   treatment for the date line, and --cream-dim for body text. No new token.
   Links are underlined here and nowhere else: in running prose gold against
   --cream-dim is under the 3:1 a link needs to be told apart by colour alone.
   .legal__todo is the visible marker for a fact the repository could not
   supply. The pages gate counts them; the count must be zero before launch. */
.legal{ max-width:660px; }
.legal h2{ margin:34px 0 0; font-size:1.15rem; letter-spacing:-.01em; }
.legal p{ margin:16px 0 0; color:var(--cream-dim); }
.legal a{ color:var(--gold); text-decoration:underline; overflow-wrap:anywhere; }  /* a long URL must wrap at 320px (WCAG 1.4.10) */
/* Scoped under .legal so they outrank `.legal p` above; a bare class loses to it. */
.legal .legal__date{ margin:0; font-family:var(--f-mono); font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--gold-dim); }
.legal .legal__todo{ padding-left:20px; border-left:1px solid var(--gold-28); font-size:14.5px; color:var(--muted); }
.legal .legal__todo span{ margin-right:8px; font-weight:500; letter-spacing:.14em; text-transform:uppercase; color:var(--gold-dim); }
.legal .cta__meta dd a{ text-decoration:none; }
.legal dl.legal__list{ margin:16px 0 0; }
.legal .legal__list > div{ padding:14px 0; border-top:1px solid var(--line-2); }
.legal .legal__list dt{ color:var(--cream); font-weight:500; }
.legal .legal__list dd{ margin:4px 0 0; color:var(--cream-dim); }
.legal .legal__ul{ margin:16px 0 0; padding-left:22px; color:var(--cream-dim); }
.legal .legal__ul li{ margin-top:10px; }
.legal code{ font-family:var(--f-sans); background:var(--bg-3); padding:1px 6px; border-radius:var(--r); color:var(--cream); }

/* ══════════ THE CONTACT FORM: /contact ══════════
   Added 2026-09-09 for the A2P consent page. NO NEW TOKEN, and no new value
   that was not already somewhere in this file. Everything here is borrowed:

     the panel        .cta__more's 1px --line border, --r-lg radius, --bg fill
                      and clamp(24px,2.6vw,34px) padding
     the column       .legal's 660px, so the form reads at prose width
     the field label  the .legal__date label treatment, 11px / .14em / upper
     the field box    --bg-2 behind a --line-2 hairline at --r, the same pair
                      the lightbox chrome uses
     the focus ring   --gold, the one focus colour on the site
     the prose        .cta__more p's 14.5px, in --cream-dim for body and
                      --muted for fine print
     the TODO         the exact .legal__todo treatment, restated under .form
                      because that rule is scoped to .legal and this page has
                      no .legal block. Same four declarations, no drift.

   THE FIELD BOX IS --bg-2, NOT --bg. An input has to read as something you can
   type into before it is focused, and on this palette the only honest way to
   say that without a new token is to lift the fill one step off the page and
   put the hairline at --line-2 rather than --line. */
.form{
  max-width:660px;
  padding:clamp(24px,2.6vw,34px);
  border:1px solid var(--line); border-radius:var(--r-lg); background:var(--bg);
}
/* The business name as TEXT. A2P reviewers read the page, and the header logo
   is an <img alt=""> that says nothing to them. Card heading size, --cream. */
.form__who{ margin:0; font-size:1.15rem; letter-spacing:-.01em; color:var(--cream); }
.form__where{ margin:6px 0 0; font-size:14.5px; color:var(--muted); }

.form__fields{ display:flex; flex-direction:column; gap:18px; margin-top:26px; }
.field{ display:flex; flex-direction:column; gap:8px; }
.field label{
  font-size:11px; font-weight:500; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted-2);
}
.field input[type=text],.field input[type=email],.field input[type=tel]{
  width:100%; padding:13px 14px;
  border:1px solid var(--line-2); border-radius:var(--r);
  background:var(--bg-2); color:var(--cream);
  font:inherit; font-size:15px;
  transition:border-color .3s var(--ease);
}
.field input:focus-visible{ outline:2px solid var(--gold); outline-offset:2px; border-color:var(--gold); }  /* 2026-10-13: a border colour change alone is a weak focus indicator; keep it and add the ring (WCAG 2.4.7, 2.4.13 target) */

/* The consent row is prose, not a label: it carries the rate, frequency, STOP
   and HELP sentences the carriers require, so it is sentence case at reading
   size and never the uppercase field-label treatment above. */
.field--check{ flex-direction:row; align-items:flex-start; gap:12px; }
.field--check input{ flex:none; width:24px; height:24px; margin-top:0; accent-color:var(--gold); }
.field--check label{
  font-size:14.5px; font-weight:400; letter-spacing:normal;
  text-transform:none; color:var(--cream-dim); line-height:1.55;
}

/* THE HONEYPOT. Off the screen, out of the tab order, out of the accessibility
   tree (aria-hidden in the markup), and 1px so it can never push the layout.
   NOT display:none, which is the one hiding technique a scraper can cheaply
   detect and skip: the field has to look fillable to a script and be
   unreachable for a person. api/contact.js drops anything that arrives in it. */
.hp{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* THE OUTCOME, SAID OUT LOUD, EITHER WAY. The panel is revealed on submit and
   holds two states, one visible at a time: the submission landed, or it did
   not. Same box for both, because the person needs to read the same place
   whichever happened, and because a form that looks like it worked when it did
   not is the failure this whole page is built to avoid. */
.form__status{
  padding:16px 18px;
  border:1px solid var(--gold-28); border-radius:var(--r);
  background:var(--bg-2); color:var(--cream-dim); font-size:14.5px;
}
.form__status[hidden]{ display:none; }
.form__state[hidden]{ display:none; }
.form__status strong{ display:block; margin-bottom:6px; color:var(--gold); font-weight:500; }
.form__status a{ color:var(--gold); text-decoration:underline; }

.form__legal{ margin:0; font-size:14.5px; color:var(--muted); }
.form__legal a{ color:var(--gold); text-decoration:underline; }

/* The .legal__todo treatment, restated: the rule above is scoped to .legal and
   /contact has no .legal block. Same values, deliberately duplicated rather
   than unscoped, so the legal pages keep sole ownership of that selector. */
.form .legal__todo{
  margin:26px 0 0; padding-left:20px; border-left:1px solid var(--gold-28);
  font-size:14.5px; color:var(--muted);
}
.form .legal__todo span{
  margin-right:8px; font-weight:500; letter-spacing:.14em;
  text-transform:uppercase; color:var(--gold-dim);
}

/* ══════════ THE PRE-SHOOT CHECKLIST: /pre-shoot-checklist ══════════
   Added 2026-09-23, DESIGN.md §19. The /privacy shell with two additions, and
   NO NEW TOKEN and no new value in either:

     the list        .tier__list > li restated: 9px gap, the 20px package-list
                     indent, 14.5px in --cream-dim behind the 7px --gold dash.
                     Restated rather than reusing .tier__list, so the package
                     cards keep sole ownership of that selector
     its margin      .legal p's 16px, so a list sits under its h2 exactly as a
                     paragraph does
     the download    .cta__more as it is, capped at .legal's 660px and spaced
                     from the prose by .legal h2's 34px. It sits outside
                     .legal on purpose: `.legal a` would underline the button */
.legal__list{ display:grid; gap:9px; margin:16px 0 0; }
.legal__list li{ position:relative; padding-left:20px; font-size:14.5px; color:var(--cream-dim); }
.legal__list li::before{ content:''; position:absolute; left:0; top:.62em; width:7px; height:1px; background:var(--gold); }
.check__dl{ max-width:660px; margin-top:34px; }

/* ══════════ REBUILT FUNNEL PAGES: 2026-09-25 ══════════
   The public and client pages brought over from the old funnel (DESIGN.md §20).
   No new token and no new value: every number below already exists above.

     .tiers--3 / --2  the package grid for three and two cards. Column counts
                      must divide the item count (§4), so three cards go 1 then 3
                      and two cards go 1 then 2, never the 2 / 4 of the base grid.
     .tier--plan      a card with several labelled lists: the lists stop
                      flexing, so the labels sit where the content puts them.
     .zones           the travel-fee list, a .tier__price row per area.
     .zmap            the service-area map, drawn inline from the old page's
                      own polygons. No tiles, no Leaflet, no request.
     .pairs / .pair   side-by-side photo comparisons on the client pages, the
                      .vgrid--h grid and the .group__meta label.
     .clip            a client page's film, the .vgrid--h grid again.
   Order is load-bearing: these come after .tiers so the same-specificity
   overrides win. */
.tiers--3,.tiers--2{ grid-template-columns:1fr; }
@media (min-width:680px){ .tiers--2{ grid-template-columns:repeat(2,1fr); } }
@media (min-width:1000px){ .tiers--3{ grid-template-columns:repeat(3,1fr); } }
/* On a standalone page the card title is an h2 under the page h1, so it takes
   the .tier h3 and .deliv__card h3 rules as they are. */
.tier h2{ font-size:1.35rem; letter-spacing:-.01em; }
.deliv__card h2{ font-size:1.15rem; letter-spacing:-.01em; }
.tier--plan .tier__list{ flex:none; }
.tier--plan .tier__from{ margin-top:6px; }
.zones{ max-width:660px; margin-top:34px; border-bottom:1px solid var(--line); }
.zones li{ display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:14px; padding-block:14px; border-top:1px solid var(--line); font-size:14.5px; color:var(--cream-dim); }
.zones i{ width:14px; height:14px; border-radius:var(--r); background:var(--gold); }
.zones b{ font-weight:400; color:var(--cream); }
.zmap{ max-width:660px; margin:34px 0 0; }
.zmap svg{ display:block; width:100%; height:auto; border:1px solid var(--line); border-radius:var(--r-lg); background:var(--bg-1); }
.zmap figcaption{ margin-top:14px; }
.pairs{ display:grid; gap:var(--group); }
.pair{ margin:0; display:grid; gap:clamp(12px,1.6vw,20px); grid-template-columns:1fr; }
@media (min-width:820px){ .pair{ grid-template-columns:1fr 1fr; } }
.pair figure{ margin:0; display:grid; gap:14px; }
.pair img,.clip video{ display:block; width:100%; height:auto; border-radius:var(--r-lg); background:var(--bg-2); }
.pair > figcaption{ grid-column:1 / -1; }
.clip{ display:grid; gap:clamp(12px,1.6vw,20px); grid-template-columns:1fr; }
@media (min-width:820px){ .clip{ grid-template-columns:1fr 1fr; } }
.clip figure{ margin:0; display:grid; gap:14px; }
/* Five images (the twilight options) cannot sit in a 2-column grid without a
   hole, so the first one spans the row: 1 + 2 + 2. Same move as the mosaic. */
@media (min-width:820px){ .pair--lead > figure:first-child{ grid-column:1 / -1; } }
/* A price ladder: the .zones row without the swatch. */
.zones--plain li{ grid-template-columns:1fr auto; }
/* A sub-heading inside long-form prose on a client page. The .sec__head
   26px and the .group__head h3 1.05rem, both existing values. */
.legal h3{ margin:26px 0 0; font-size:1.05rem; letter-spacing:-.01em; }

/* ══════════ FOOTER ══════════ */
.foot{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:18px;
  padding:30px var(--pad);
  font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2);
}
.foot nav{ display:flex; flex-wrap:wrap; gap:0 22px; }
/* 2026-10-13: footer targets were 18px tall (WCAG 2.5.8 wants 24). Padding gives
   36px with the same rhythm as the old 22px row gap. */
.foot nav a, .foot__btn{ padding-block:9px; }
.foot nav a:hover{ color:var(--gold); }
/* Footer logo, the SAME FILE as the header since v8 (the colour/gold variant),
   and the same rules: never regenerated, recoloured, re-cut, upscaled or
   filtered. Horizontal lockup because this slot is a horizontal bar; the
   vertical lockup would force the bar taller. Sized smaller than the header
   (34px -> ~23px of visible ink) and pulled left by 7px to cancel the file's
   baked-in canvas padding. Both numbers are unchanged by the colour swap: the
   gold artwork's geometry is byte-identical to the white variant's.
   See .hdr__logo-img. */
.foot__logo{ display:flex; align-items:center; }
.foot__logo-img{ display:block; height:34px; width:auto; margin-left:-7px; }

/* ══════════ VIDEO PLAYER ══════════
   REBUILT ON FLEXBOX. Read this before changing any value here.

   The previous version used `display:grid; place-items:center` on the stage
   with `max-height:min(100%, calc(100svh - 150px))` on the video. A 1080x1920
   clip in a 1440x900 viewport was measured at height 750px but positioned at
   y=632.6, running to 1382.6, so only the top slice rendered, low on the
   screen, with blank space around it. The percentage max-height resolved
   against a track that grid had already sized from the video's *intrinsic*
   2311px height, so the item overflowed its own centred track. Mobile passed
   by luck, which is why this read as fixed twice.

   The flex version cannot reproduce it:
     - .cinema is a column flexbox with an explicit height, so the stage's
       flex-basis is definite.
     - min-height:0 on the stage lets it shrink below its content, which is the
       whole reason a percentage cap on the child now resolves correctly.
     - The video is capped in BOTH axes and keeps its ratio via width/height
       auto, so it can never exceed the stage in either direction.
     - Vertical clips align to the START of the stage, not the centre, so the
       frame sits high, up near the close X, as specified.
   Verified by measuring the OPEN state at 390px and 1440px for both vertical
   clips and both horizontal clips. Do not reintroduce place-items:center.   */
.cinema{
  position:fixed; inset:0; z-index:250;
  height:100svh; max-height:100svh;
  background:rgba(4,4,3,.97);
  display:flex; flex-direction:column;
  opacity:0; transition:opacity .4s var(--ease);
}
.cinema.is-open{ opacity:1; }
.cinema[hidden]{ display:none; }
/* The padding is an ARROW GUTTER, and it is the lightbox's, not --pad. Measured
   2026-09-08: with --pad the carousel arrows sat ON the footage at every width
   that matters, over the left and right edges of a 9:16 clip at 390px and of a
   16:9 clip at 1440px. Covering the work on a page whose whole argument is the
   quality of the work is the wrong trade, so the video gives up the width
   instead: 351 -> 286px at 390px, 1312 -> 1248px at 1440px. .lb__stage reserves
   the same gutter for the same reason. Change this and change the video's
   max-width below with it. */
.cinema__stage{
  flex:1 1 auto; min-height:0;            /* min-height:0 is load-bearing */
  position:relative;
  display:flex; justify-content:center; align-items:center;
  overflow:hidden; padding:0 clamp(52px,7vw,96px);
}

/* THE FRAME, AND WHY THE VIDEO IS NO LONGER CAPPED IN PERCENTAGES. 2026-09-08.

   The close control now sits on the VIDEO's top right rather than the screen's,
   so something in the DOM has to be exactly the size of the video for it to
   position against. .cinema__frame is that box: a flex container holding only
   the video, so it shrink-wraps it in both axes.

   That is only safe because the CAP MOVED OFF PERCENTAGES. The video used to be
   max-width/max-height:100%, resolving against the stage, and the long note
   above is the story of how fragile that chain is. Put a shrink-wrapping frame
   in the middle of it and the chain breaks outright: the frame's height is
   content-based, so a percentage max-height on the video inside it resolves
   against an INDEFINITE height, is treated as `none`, and the video renders at
   its intrinsic 1920px and overflows. That is the identical failure the grid
   version had, one layer down.

   So the video is capped in DEFINITE VIEWPORT LENGTHS instead, and nothing in
   this block resolves a percentage against an indefinite track any more:
     - width  is the viewport less the stage's own padding, both sides
     - height is the viewport less the meta row, which is why .cinema__meta now
       has a FIXED height. If that height ever changes, change it here too; the
       audit's "video overlaps the meta row" check is what catches a mismatch.
   The close row itself is gone from the column, which is where the extra
   height comes from.                                                          */
.cinema__frame{
  position:relative;
  display:flex;
  border-radius:var(--r);
}

/* WIDTH IS SET, NOT CAPPED, AND --ar IS WHY. 2026-09-09, DESIGN.md reversal 18.

   `width:auto; height:auto` plus max-* was the rule here until 2026-09-09, and
   it made the player THE SIZE OF THE HLS RENDITION currently decoding. A video
   element with auto in both axes renders at its intrinsic size, and a video's
   intrinsic size is the frames coming off the wire, not the file on Cloudflare.
   Measured at 1440x900 against this same stylesheet, four rungs of the ladder:

       426x240   ->  427x240    29.7% of the viewport width
       852x480   ->  853x480    59.2%
       1280x720  -> 1248x702    86.7%
       1920x1080 -> 1248x702    86.7%

   The two horizontal films then opened at DIFFERENT sizes, every time, because
   Cloudflare lists their rungs in a different order and hls.js opens on the
   first one: premium-listing-h leads with 1280x720 and harris-ridge with
   852x480, so one filled 86.7% of the width and the other 59.2%. Both local
   -preview.mp4 encodes are 1920x1080, so the local path never showed it and no
   gate had ever seen it.

   The fix is to SET the width to the smallest of three definite lengths and let
   height follow the ratio, so nothing about the decoded frame reaches layout:
     - the viewport less the stage's arrow gutter, both sides
     - the viewport less the meta row, converted to a width through --ar
     - 80vw, the comfort ceiling, signed off 2026-09-09
   --ar is the film's own width/height. site.js seeds it per orientation at
   render time and rewrites it from videoWidth/videoHeight on loadedmetadata, so
   it is the video's own opinion of its shape, never a second one. The defaults
   below keep a sane size if that block ever throws inside safe().

   max-height stays as a NET, not as the cap. It cannot bind while --ar is right,
   because the second term already holds the height under it. If --ar is ever
   wrong (a film that is not 16:9 or 9:16, of which there are none today) it
   stops the video overflowing the meta row, and the box ratio then disagrees
   with the natural ratio, which is exactly what audit.mjs section J reports. */
.cinema--h{ --ar:1.7778; }
.cinema--v{ --ar:.5625; }
.cinema__frame video{
  display:block;
  width:min( calc(100vw - clamp(52px,7vw,96px) * 2),   /* the stage's arrow gutter */
             calc((100svh - 56px) * var(--ar,1.7778)), /* the viewport less .cinema__meta */
             80vw );                                   /* the comfort ceiling */
  height:auto;
  max-height:calc(100svh - 56px);                      /* the net, see the note above */
  object-fit:contain; border-radius:var(--r); background:#000;
}
/* Portrait clips ride high, tight to the close control. */
.cinema--v .cinema__stage{ align-items:flex-start; }
.cinema--h .cinema__stage{ align-items:center; }

/* ON the video's top right corner, not the screen's. Offsets are in em so the
   inset tracks the control's own type size rather than the viewport, which is
   what keeps it reading as part of the player at 390px and at 1440px. The scrim
   is not decoration: this sits over footage that can be white sky. */
.cinema__close{
  position:absolute; z-index:3; top:.9em; right:.9em;
  padding:.55em .8em; border-radius:var(--r);
  background:rgba(10,9,8,.72); border:1px solid var(--line-2);
  font-size:11px; font-weight:500; letter-spacing:.18em; text-transform:uppercase; color:var(--cream);
  transition:color .3s, background .3s, border-color .3s;
}
.cinema__close:hover{ color:var(--gold); border-color:var(--gold-28); background:rgba(10,9,8,.9); }

/* CONTROLS ON THE PLAYER. Revised 2026-10-14 on Michael's review.
   The arrows used to sit at the far edges of the viewport. They are now children
   of the FRAME, which is exactly the size of the video, so they sit a fixed gap
   outside the video's own left and right edges, for horizontal and vertical
   films alike. The stage's side padding (clamp(52px,7vw,96px)) is the room they
   need: the largest arrow (54px) plus the largest gap (22px) is 76px, inside
   the 96px that applies at the widths where the arrow is that large.

   Above 700px .cinema__ctl is display:contents, so the buttons position
   against the frame directly: arrows beside the video, the sound button on its
   top-left corner (the Close control mirrors it on the right).
   At 700px and below the three become one compact row UNDER the video
   (arrow, sound, arrow), so nothing sits on the footage or on the browser's
   control bar, and the video gives up the height and width it needs. */
.cinema__nav{
  z-index:3;
  width:clamp(40px,5vw,54px); height:clamp(40px,5vw,54px);
  display:grid; place-items:center;
  border:1px solid var(--line-2); border-radius:50%;
  background:rgba(10,9,8,.6); color:var(--cream);
  font-size:26px; line-height:1;
  transition:background .3s, border-color .3s, color .3s;
}
.cinema__nav:hover{ background:var(--gold); border-color:var(--gold); color:#000; }
.cinema__nav[hidden], .cinema__sound[hidden], .cinema__play[hidden]{ display:none; }
.cinema__sound{
  z-index:3; padding:.55em .8em; border-radius:var(--r);
  background:rgba(10,9,8,.72); border:1px solid var(--line-2);
  font-size:11px; font-weight:500; letter-spacing:.18em; text-transform:uppercase; color:var(--cream);
  transition:color .3s, background .3s, border-color .3s;
}
.cinema__sound:hover{ color:var(--gold); border-color:var(--gold-28); background:rgba(10,9,8,.9); }
.cinema__play{ position:absolute; z-index:4; left:50%; top:50%; transform:translate(-50%,-50%); white-space:nowrap; }
.cinema__ctl{ display:contents; }
@media (min-width:701px){
  .cinema__nav{ position:absolute; top:50%; transform:translateY(-50%); }
  .cinema__nav--prev{ right:calc(100% + clamp(12px,1.6vw,22px)); }
  .cinema__nav--next{ left:calc(100% + clamp(12px,1.6vw,22px)); }
  .cinema__sound{ position:absolute; top:.9em; left:.9em; }
}
@media (max-width:700px){
  .cinema__stage{ padding:0 12px; }
  .cinema__ctl{
    position:absolute; top:100%; left:50%; transform:translateX(-50%); margin-top:10px;
    display:flex; align-items:center; gap:8px; z-index:3;
    width:max-content; max-width:calc(100vw - 24px);
  }
  .cinema__nav{ width:44px; height:44px; flex:none; }
  .cinema__sound{ min-height:44px; padding:0 14px; white-space:nowrap; letter-spacing:.14em; }
  .cinema__frame video{
    width:min( calc(100vw - 24px), calc((100svh - 120px) * var(--ar,1.7778)) );   /* 56px meta row + 64px control row */
    max-height:calc(100svh - 120px);
  }
}

.cinema__meta{
  flex:none; height:56px;                 /* fixed: .cinema__frame video reads it */
  display:flex; flex-wrap:wrap; gap:14px; align-items:center; justify-content:center; padding:0 var(--pad);
  font-size:11px; font-weight:500; letter-spacing:.16em; text-transform:uppercase;
}
#cinemaKind{ color:var(--gold); }

/* ══════════ PHOTO LIGHTBOX ══════════ */
/* Flexbox for the same reason as .cinema, see that block. A portrait photo in
   a short viewport hits the identical overflow trap under grid centring.

   dvh HERE, svh IN .cinema, AND THE DIFFERENCE IS DELIBERATE. 2026-09-09.
   On a phone this overlay is the whole experience: iOS Safari gives a web page
   no way to hide its own chrome for anything but a video, so the lightbox has
   to earn its size from the viewport it is actually given. svh is the SMALLEST
   viewport height, which on a phone in landscape throws away the strip Safari
   has already collapsed. dvh tracks what is really there. .cinema keeps svh
   because its height feeds the `calc(100svh - 56px)` term that sizes the video,
   and a cap that changes size while a film plays is a resize, not a fix; on a
   phone .cinema goes to the browser's own fullscreen anyway (site.js). */
.lb{
  position:fixed; inset:0; z-index:260;
  height:100dvh; max-height:100dvh;
  background:rgba(4,4,3,.985);
  display:flex; flex-direction:column;
  opacity:0; transition:opacity .3s var(--ease);
}
.lb.is-open{ opacity:1; }
.lb[hidden]{ display:none; }
.lb__bar{
  flex:none;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:14px var(--pad);
  font-size:11px; font-weight:500; letter-spacing:.18em; text-transform:uppercase;
  transition:opacity .25s var(--ease);          /* for .is-bare, see the phone viewer block */
}
.lb__count{ color:var(--gold); }
.lb__close{ color:var(--muted); font:inherit; }
.lb__close:hover{ color:var(--gold); }
.lb__stage{
  position:relative; flex:1 1 auto; min-height:0;
  display:flex; align-items:center; justify-content:center;
  overflow:hidden; padding:0 clamp(52px,7vw,96px);
}
/* height/width:100%, NOT max-height/max-width:100%.
   The bug this fixes: with `max-height:100%` the figure's own `height` stays
   `auto`, so the percentage `max-height:100%` on the <img> inside resolved
   against an INDEFINITE height and was therefore treated as `none`. Landscape
   photos never showed it, `max-width:100%` binds first and the resulting
   height already fits. The one portrait photo in the set (5:7) is bound by
   height, so at 1440x900 it rendered 1248x1747 inside an 800.8px stage,
   y[-425.6..1321.6], and .lb__stage's overflow:hidden cropped ~473px off the
   top and bottom, cutting the subject's face in half.
   Giving the figure a DEFINITE height makes the img's percentage resolve, so
   object-fit:contain letterboxes instead of overflowing. Same class of bug as
   the .cinema grid-centring trap, see the note on that block.

   THE 3:2 aspect-ratio IS THE WIDTH CEILING FOR THE WHOLE SET. 2026-09-09.
   The photos are three shapes: 25 landscape frames at 3:2, four at 16:9 (06
   community-water-dusk, 07 aerial-townhomes-twilight, 13 streetscape-sunset,
   24 exterior-modern-twilight) and one portrait at 5:7 (30 portrait-agent).
   With the figure at width:100% every photo was fitted to the STAGE, so a 3:2
   frame ran out of height first and a 16:9 frame ran out of width first, and
   the four 16:9 ones came out visibly wider than everything else: measured at
   1440x900, 1248x702.4 against 1200.4x800.8, 86.7% of the viewport width
   against 83.4%. Shaping the figure 3:2 and sizing it off the stage HEIGHT
   makes the widest 3:2 photo the ceiling for all of them, so nothing in the
   set can be wider than any other. Measured after: 16:9 lands 1201.2x676.1,
   83.4%, and the 5:7 photo is untouched at 572x800.8.

   height:100% is still what it was and is still load-bearing, per the note
   above. width is auto and aspect-ratio derives it FROM that definite height,
   so both of the image's percentage caps still resolve and the 5:7 crop of
   2026-09-08 cannot come back. max-width:100% is the guard for a viewport too
   short to give the 3:2 box its width, measured at 1440x620. */
.lb__fig{
  margin:0; min-width:0; min-height:0;
  width:auto; max-width:100%; height:100%; aspect-ratio:3/2;
  display:flex; align-items:center; justify-content:center;
}
.lb__fig img{
  display:block; width:auto; height:auto;
  max-width:100%; max-height:100%;
  object-fit:contain; border-radius:var(--r);
}
.lb__nav{
  position:absolute; top:50%; transform:translateY(-50%); z-index:3;
  width:clamp(40px,5vw,54px); height:clamp(40px,5vw,54px);
  display:grid; place-items:center;
  border:1px solid var(--line-2); border-radius:50%;
  background:rgba(10,9,8,.6); color:var(--cream);
  font-size:26px; line-height:1;
  transition:background .3s, border-color .3s, color .3s;
}
.lb__nav:hover{ background:var(--gold); border-color:var(--gold); color:#000; }
.lb__nav--prev{ left:clamp(8px,1.6vw,22px); }
.lb__nav--next{ right:clamp(8px,1.6vw,22px); }
.lb__cap{
  flex:none;
  padding:14px var(--pad) 20px; text-align:center;
  font-size:11px; font-weight:500; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted);
  transition:opacity .25s var(--ease);          /* for .is-bare, see the phone viewer block */
}

/* THE PHONE VIEWER. 2026-09-09, DESIGN.md section 4 "the landscape clause".

   STATE THE LIMIT FIRST, because it decides the shape of everything below.
   iPhone Safari implements the Fullscreen API for a VIDEO ELEMENT ONLY. There
   is no Element.requestFullscreen and no element-level webkitRequestFullscreen
   on iPhone, so a photo CANNOT be given a true full screen there and the URL
   bar and the tab bar stay. That is the platform, not this stylesheet. Android
   Chrome and iPad Safari do have element fullscreen and site.js asks for it;
   these rules are what iPhone gets instead, and what everyone gets underneath
   the fullscreen element anyway.

   Two things were eating the photo in phone landscape: the bar and the caption
   are flex:none ROWS, which took 99 of 390 available pixels, and the stage
   reserves an arrow gutter it does not need when swipe is the real control.
   Measured at 844x390, before and after:

       3:2  photo   436x291 -> 585x390     51.7% -> 69.3% of the width
       16:9 photo   517x291 -> 693x390     61.2% -> 82.1%
       5:7  photo   208x291 -> 279x390     24.6% -> 33.0%

   The rows become overlays on their own scrim rather than disappearing: the
   count and the caption are the only things telling a person where they are in
   a set of 30, and a caption is alt text made visible, which DESIGN.md section
   6 treats as content. .is-bare, toggled by a tap on the photo, is the standard
   viewer gesture for getting them out of the way, and it works at every size. */
@media (orientation:landscape) and (max-height:500px){
  .lb__bar, .lb__cap{
    position:absolute; left:0; right:0; z-index:4;
    padding-left:max(var(--pad), env(safe-area-inset-left));
    padding-right:max(var(--pad), env(safe-area-inset-right));
  }
  .lb__bar{ top:0; background:linear-gradient(rgba(4,4,3,.82), transparent); }
  .lb__cap{ bottom:0; padding-bottom:max(20px, env(safe-area-inset-bottom)); background:linear-gradient(transparent, rgba(4,4,3,.82)); }
  /* Swipe is the control here and .lb__nav would sit on the photo, not beside
     it, once the gutter is gone. The gutter is what the arrows were clearing. */
  .lb__stage{ padding:0; }
  .lb__nav{ display:none; }
}
/* Tap the photo to get the chrome out of the way. Not display:none, because these
   fade, so a second tap brings them back without a layout jump. */
.lb.is-bare .lb__bar, .lb.is-bare .lb__cap{ opacity:0; pointer-events:none; }

/* ══════════ REDUCED MOTION ══════════ */
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; }
  .grain{ display:none; }
  .vcard,.svc,.mosaic figure{ opacity:1 !important; transform:none !important; }
}


/* ══════════ CONSENT: banner, settings panel, placeholders (DESIGN.md §19) ══════════
   Built from existing tokens only. The three banner actions share one class so
   no choice is visually favoured over another. */
.foot__btn{
  background:none; border:0; padding:0; margin:0; cursor:pointer;
  font:inherit; color:inherit; text-decoration:none;
  letter-spacing:inherit; text-transform:inherit;   /* a button does not inherit these, which made it the one mixed-case item in the footer */
}
.foot__btn:hover{ color:var(--gold); }
.foot__btn[hidden]{ display:none; }

.consent{
  position:fixed; z-index:90; left:var(--pad); right:var(--pad); bottom:16px;
  max-width:720px; margin-inline:auto;
  background:var(--bg-2); border:1px solid var(--gold-28); border-radius:var(--r-lg);
  padding:22px; color:var(--cream-dim); box-shadow:0 12px 40px rgba(0,0,0,.6);
  max-height:calc(100dvh - 32px); overflow:auto;
}
.consent[hidden]{ display:none; }
.consent h2{ font-size:1.15rem; letter-spacing:-.01em; margin:0 0 8px; color:var(--cream); }
.consent p{ margin:0 0 14px; font-size:15px; line-height:1.55; }
.consent a{ color:var(--cream); text-decoration:underline; }
.consent__row{ display:flex; flex-wrap:wrap; gap:10px; }
.consent__row .btn{ flex:1 1 150px; justify-content:center; text-align:center; }
.consent fieldset{ border:0; margin:0 0 14px; padding:0; }
.consent legend{ padding:0; font-size:15px; color:var(--cream); margin-bottom:6px; }
.consent__cat{ display:flex; gap:12px; align-items:flex-start; padding:12px 0; border-top:1px solid var(--line-2); }
.consent__cat input{ flex:none; width:24px; height:24px; margin:0; accent-color:var(--gold); }
.consent__cat label{ font-size:15px; line-height:1.5; color:var(--cream); }
.consent__cat small{ display:block; color:var(--muted); font-size:14px; margin-top:2px; }
.consent__off{ font-size:14px; color:var(--muted); margin:0 0 14px; }
.consent__warn{ color:var(--gold-light); }

.optph{
  border:1px solid var(--gold-28); border-radius:var(--r-lg); background:var(--bg-2);
  padding:22px; max-width:460px; color:var(--cream-dim); font-size:15px; line-height:1.55;
}
.optph h3{ font-size:1.15rem; letter-spacing:-.01em; color:var(--cream); margin:0 0 8px; }
.optph p{ margin:0 0 12px; }
.optph a{ color:var(--cream); text-decoration:underline; }
.optph .btn{ margin-right:8px; margin-bottom:8px; }
.optph--video{ position:relative; z-index:2; width:min(460px,calc(100vw - 32px)); padding-top:60px; /* clears the Close control, which sits on the frame corner */ }
.cinema__frame.is-gated{ max-height:calc(100dvh - 56px); overflow:auto; }
.cinema__frame.is-gated video{ display:none; }
.optph--video[hidden], .optph[hidden]{ display:none; }
.optph--chat{ position:fixed; z-index:80; left:var(--pad); bottom:16px; }
@media (prefers-reduced-motion:reduce){ .consent, .optph{ transition:none; animation:none; } }
.consent__live{ position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
.consent__cat label{ min-height:24px; }
/* 24px minimum target for every link in the consent UI (WCAG 2.2, 2.5.8), not
   only the ones the inline-text exception could excuse. */
.consent a, .optph a{ display:inline-block; min-height:24px; padding-block:2px; }

/* A phone number never breaks across lines at its hyphens (it did at 320 and 390). */
a[href^="tel:"], .nb{ white-space:nowrap; }
/* later rules above reset the button's padding; restate the footer target size here, and give the contact block's stacked links a 24px target */
.foot nav .foot__btn{ padding-block:9px; }
.cta__meta dd a{ display:inline-block; padding-block:3px; }

/* The film is loading: a status line over the poster, announced politely. */
.cinema__status{ position:absolute; z-index:2; left:50%; bottom:18px; transform:translateX(-50%); margin:0; max-width:calc(100% - 28px); padding:9px 14px;
  border:1px solid var(--gold-28); border-radius:var(--r); background:rgba(10,9,8,.82); color:var(--cream); font-size:13px; letter-spacing:.04em; text-align:center; }
.cinema__status[hidden]{ display:none; }

.field__hint{ display:block; margin-top:6px; font-size:13.5px; line-height:1.45; color:var(--muted); text-transform:none; letter-spacing:normal; }
.form__fields[aria-busy="true"] input[readonly]{ opacity:.7; }

/* Consent choices are comfortable touch targets on narrow screens (2026-10-14). */
@media (max-width:700px){ .consent__row .btn{ min-height:44px; } }
