/* ============================================================
   TRENCHES — shared shell for interior pages (v5)
   Mirrors the v4 homepage design system: light Trenches-blue
   environment, deep navy bands, #033ca4 accent, serif display.
   ============================================================ */

:root{
  --sky:       #9bcdfd;
  --sky-hi:    #c0dffe;
  --gold:      #e9b83f;   /* Trenches Leverage gold. DARK SURFACES ONLY:
                             8.7:1 on the navy modules, but 1.0:1 on the
                             light-blue band - it is an accent for the navy
                             panels, never for type on the environment. */
  --blue:      #033ca4;
  --blue-mid:  #2d63d8;
  --navy:      #061634;
  --navy-2:    #0a1f4a;
  --line-dark: rgba(155,205,253,.16);
  --line-lite: rgba(6,22,52,.22);
  /* Edge of a dark module (.panel, .stat-card). A TOKEN, not a literal,
     because block components render with inline styles and inline styles
     cannot be overridden by a class rule -- but they DO resolve a custom
     property the class rule sets. So theme and hover reach the CMS-rendered
     markup through this and nothing else. Change it here, not in the rules. */
  --card-edge: rgba(6,22,52,.4);
  --ink:       #0a1c40;
  --ink-dim:   #23385c;   /* was #34507e until 2026-10-11: 3.4:1 on the darkest background behind it (the services caption in its image shadow), under AA; this is 4.95:1 */
  --on-dark:   #eef4fd;
  --on-dark-dim:#b4c5e6;
  --on-dark-mute:#7f95c2;
  --gutter:    clamp(24px, 5vw, 72px);
  --radius:    12px;
  /* ⇉ ONE PROSE MEASURE. Before this there were eight unrelated caps on text
     across the site - 20ch, 22ch, 52ch, 60ch, 62ch, 72ch, 560px and uncapped -
     and the homepage intro was the visible symptom: its heading was capped
     NARROWER (22ch) than its own paragraph (60ch), which is backwards and
     happens nowhere else.
     ch, NOT px, on purpose: 1ch is a fraction of the ELEMENT'S font-size, so one
     number gives the same characters per line at 13px and at 17px. A px number
     re-creates the inconsistency it was meant to fix.
     --measure-centred is deliberately tighter: centred text has no fixed return
     edge for the eye, so it wants a shorter line than left-aligned text. */
  --measure:         72ch;
  --measure-centred: 58ch;
  --measure-wide:    92ch;
  --font-sans: "Segoe UI", system-ui, -apple-system, Helvetica, Arial, sans-serif;
  --font-serif: Georgia, "Times New Roman", "Palatino Linotype", serif;
  --speed: 180ms;
}

*{ margin:0; padding:0; box-sizing:border-box; }
html{
  scroll-behavior:smooth;
  /* the 64px topbar is fixed: without this, anchor links land with the
     section heading hidden behind it. 32px extra gives breathing room. */
  scroll-padding-top:96px;
}
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  *, *::before, *::after{ transition-duration:.01ms !important; animation-duration:.01ms !important; }
}

body{
  background:
    radial-gradient(90% 40% at 15% 30%, rgba(255,255,255,.35), transparent 70%),
    radial-gradient(80% 50% at 90% 70%, rgba(255,255,255,.22), transparent 70%),
    linear-gradient(180deg, var(--sky-hi) 0%, var(--sky) 26%, #8ec5fb 100%);
  background-attachment:fixed;
  color:var(--ink);
  font-family:var(--font-sans);
  font-size:calc(16px * var(--type-scale, 1));
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
/* The smallest phones (320 to 340px wide) have no room for larger text anywhere: the Text size choice does not apply there. The
   variable is forced back to 1 (important, so the theme's :root rule, which comes later, cannot win). Everything that scales reads
   this one variable, so every size on those phones is exactly the standard one. */
@media (max-width: 340px){ :root{ --type-scale:1 !important; } }

a{ color:inherit; text-decoration:none; }
img,svg{ display:block; }
button{ font:inherit; color:inherit; background:none; border:none; cursor:pointer; }
::selection{ background:rgba(3,60,164,.85); color:#fff; }
:focus-visible{ outline:2px solid var(--blue); outline-offset:3px; border-radius:4px; }


/* ---------- top bar ---------- */
.topbar{
  position:fixed; inset:0 0 auto 0; z-index:40;
  display:flex; align-items:center; gap:22px;
  padding:0 var(--gutter); height:64px;
  background:linear-gradient(to bottom, rgba(240,248,255,.92), rgba(240,248,255,.5) 75%, transparent);
  transition:background var(--speed) ease;
}
.topbar.is-scrolled{
  background:rgba(244,250,255,.96);
  border-bottom:1px solid rgba(10,28,64,.12);
  backdrop-filter:blur(10px);
}
.brand{ display:flex; align-items:center; }
/* the topbar mark follows the theme: blue on the light page, white on
   dark, where the brand blue would sit at poor contrast on navy */
.brand .logo-dark{ display:none; }
.brand img{ height:32px; width:auto; }

.topnav{ display:flex; gap:14px; margin-left:auto; }
.topnav a{
  font-size:calc(12px * var(--type-scale, 1)); letter-spacing:.07em; text-transform:uppercase;
  color:var(--ink-dim); padding:6px 2px;
  border-bottom:1px solid transparent;
  transition:color var(--speed) ease, border-color var(--speed) ease;
}
.topnav a:hover, .topnav a:focus-visible{ color:var(--blue); border-color:var(--blue); }
.topnav a[aria-current="page"]{ color:var(--blue); border-color:var(--blue-mid); }

/* The Menu button (2026-10-06). Hidden here and shown only by the <=1240px
   query at the bottom of this file, where the bar has no room for the links.
   assets/nav-toggle.js builds it and inserts it immediately before .topnav, so
   the DOM order is button -> the links it opens; the chrome is the theme
   toggle's, so the bar's two controls read as one set. */
.nav-toggle{
  display:none;
  flex:0 0 auto;
  width:38px; height:38px;
  align-items:center; justify-content:center;
  border-radius:50%;
  border:1px solid rgba(10,28,64,.28);
  background:rgba(255,255,255,.4);
  color:var(--blue);
  cursor:pointer;
  transition:background var(--speed) ease, border-color var(--speed) ease;
}
.nav-toggle:hover{ border-color:var(--blue); background:rgba(255,255,255,.7); }
.nav-toggle svg{ width:18px; height:18px; }
.nav-toggle .alt{ display:none; }
.nav-toggle.is-alt .base{ display:none; }
.nav-toggle.is-alt .alt{ display:block; }


/* ---------- buttons ---------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  font-size:calc(13.5px * var(--type-scale, 1)); font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  padding:13px 26px; border-radius:6px;
  transition:transform var(--speed) ease, box-shadow var(--speed) ease,
             background var(--speed) ease, border-color var(--speed) ease, color var(--speed) ease;
}
.btn svg{ width:14px; height:14px; }
.btn:hover, .btn:focus-visible{ transform:translateY(-2px); }
.btn-primary{
  background:linear-gradient(160deg, #0b4ecb, var(--blue)); color:#fff;
  box-shadow:0 8px 28px rgba(2,20,64,.5);
}
.btn-primary:hover, .btn-primary:focus-visible{
  background:linear-gradient(160deg, #1b5fe0, #0645b4);
  --btn-bg:linear-gradient(160deg, #1b5fe0, #0645b4);
}
.btn-ghost{
  border:1px solid rgba(10,28,64,.35); color:var(--ink); background:rgba(255,255,255,.35);
}
.btn-ghost:hover, .btn-ghost:focus-visible{ border-color:var(--blue); background:rgba(3,60,164,.08); }
.btn-small{ padding:9px 18px; font-size:calc(12px * var(--type-scale, 1)); }
.cta-row{ display:flex; gap:14px; flex-wrap:wrap; justify-content:center; }

/* ---------- light/dark switch ----------
   The interior topbar is dark in BOTH themes (it sits over the hero band),
   so the button is styled for a dark bar as its base. */
.theme-toggle{
  flex:0 0 auto;
  width:38px; height:38px; margin-left:10px;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%;
  border:1px solid rgba(10,28,64,.28);
  background:rgba(255,255,255,.4);
  color:var(--blue);
  transition:background var(--speed) ease, border-color var(--speed) ease,
             color var(--speed) ease, transform var(--speed) ease;
}
.theme-toggle svg{ width:17px; height:17px; }
.theme-toggle:hover, .theme-toggle:focus-visible{
  border-color:var(--blue); background:rgba(3,60,164,.1); color:var(--blue);
  transform:translateY(-1px);
}
.theme-toggle .i-sun{ display:none; }
.theme-toggle.is-dark .i-moon{ display:none; }
.theme-toggle.is-dark .i-sun{ display:block; }

/* ---------- dark bands ---------- */
.band{
  position:relative; background:transparent; color:var(--ink);
  overflow:hidden; isolation:isolate;
}
.band::before{
  content:""; position:absolute; inset:0; z-index:-1;
  background:
    radial-gradient(110% 90% at 85% 0%,  rgba(255,255,255,.65), transparent 55%),
    radial-gradient(70% 70% at 100% 60%, rgba(45,99,216,.16),   transparent 65%),
    radial-gradient(60% 60% at 8% 85%,   rgba(255,255,255,.4),  transparent 65%),
    linear-gradient(115deg, #d3e8ff 15%, #a8d3fe 60%, #8ec5fb 100%);
}
.band--fade{ background:transparent; padding-bottom:72px; }
.band--fade::before{
  -webkit-mask-image:linear-gradient(to bottom, #000 0, #000 calc(100% - 72px), rgba(0,0,0,.6) calc(100% - 42px), transparent 100%);
  mask-image:linear-gradient(to bottom, #000 0, #000 calc(100% - 72px), rgba(0,0,0,.6) calc(100% - 42px), transparent 100%);
}
.band--fade-top{ background:transparent; }
.band--fade-top::before{
  -webkit-mask-image:linear-gradient(to bottom, transparent 0, rgba(0,0,0,.5) 50px, #000 110px);
  mask-image:linear-gradient(to bottom, transparent 0, rgba(0,0,0,.5) 50px, #000 110px);
}

/* BLOCK: band--dark ------------------------------------------------------
   The "Dark navy band" the page-hero's Theme control has offered since the
   block was written. Until 2026-09-18 the control was INERT: `.band--dark`
   existed in no stylesheet in this repo, so both values rendered the light
   band and a client could set it, publish, and see no change. Written by the
   studio chat with Bronze's explicit go-ahead on 2026-09-17 ("I design it, you
   review"), under the constraint agreed with it — ADDITIVE ONLY. Nothing above
   is modified; every rule here is new and every one is scoped to
   `.band--dark`, so a page that does not opt in cannot be touched by it.

   WHY IT DOES NOT USE --ink, and this is the trap CtaBand.tsx names: --ink
   flips to near-white under `:root[data-theme="dark"]`, so a band that painted
   its text with --ink would be dark-navy-on-navy in light theme and
   white-on-navy in dark — legible in one and invisible in the other. The band
   sets its own literal on-dark colours instead, because the band is dark in
   BOTH site themes. Same reason the property cards carry their own.

   It composes with `.band--fade`: that modifier masks the ::before, and this
   one only repaints its background, so the masked bottom edge still fades. */
.band--dark::before{
  background:
    radial-gradient(110% 90% at 85% 0%,  rgba(45,99,216,.40),  transparent 58%),
    radial-gradient(70% 70% at 100% 60%, rgba(3,60,164,.45),   transparent 65%),
    radial-gradient(60% 60% at 8% 85%,   rgba(155,205,253,.10), transparent 65%),
    linear-gradient(115deg, #061634 12%, #0a1f4a 58%, #123472 100%);
}
/* On a dark page the band would otherwise sit navy-on-navy and stop reading as
   a band at all. Lifted a step so the edge stays visible against the body. */
:root[data-theme="dark"] .band--dark::before{
  background:
    radial-gradient(110% 90% at 85% 0%,  rgba(155,205,253,.16), transparent 58%),
    radial-gradient(70% 70% at 100% 60%, rgba(45,99,216,.30),   transparent 65%),
    linear-gradient(115deg, #0a1f4a 12%, #123472 58%, #16408f 100%);
}

/* Text, scoped to the band. Specificity is deliberate rather than lucky:
   `.band--dark .page-hero h1` (0,2,1) beats `.page-hero h1` (0,1,1), and
   `.band--dark .page-hero p.lede` (0,3,1) beats `.page-hero p.lede` (0,2,1).
   The `:root[data-theme="dark"]` rules further down are (0,3,1) and already
   set white, so the two agree rather than fight. */
.band--dark .eyebrow{ color:var(--sky); }
.band--dark .page-hero h1, .band--dark .page-hero .sv2-h1{ color:#fff; }
.band--dark .page-hero p.lede{ color:var(--on-dark-dim); }

/* Anything a future block puts in a dark band: links stay legible without
   needing the block to know it is in one. */
.band--dark a:not(.btn){ color:var(--sky); }
.band--dark a:not(.btn):hover{ color:var(--sky-hi); }

/* ---------- footer layout: social on top, blurb left, links right ---------- */
/* Capped to the same content box as .content/.rails (1440px including the
   gutters), so the blurb's left edge and the links' right edge line up with
   the body above instead of running full-bleed on wide screens. */
/* TWO REAL HALVES (2026-09-09). This was a flex row with the blurb at
   flex:1 1 320px and the links pushed over by margin-left:auto, which is not
   a split - measured at 1440 it gave the blurb 44% of the row, the links 25%,
   and left a 396px hole down the middle. A 2-column grid makes each side
   genuinely half, so the two blocks face each other instead of drifting apart. */
.foot-main{
  display:grid; grid-template-columns:1fr 1fr; gap:34px 48px; align-items:start;
  width:100%; max-width:calc(1440px - 2*var(--gutter)); margin:0 auto;
}
.foot-about h2{
  font-family:var(--font-serif); font-weight:700;
  font-size:calc(19px * var(--type-scale, 1)); line-height:1.2; letter-spacing:0; color:var(--ink);
  margin-bottom:10px;
}
.foot-about p{ font-size:calc(13px * var(--type-scale, 1)); line-height:1.65; letter-spacing:.01em; }
/* The right half's text reads LEFT (2026-09-09, Bronze's call after seeing it
   right-aligned). The block still occupies the right half of the grid - it is
   the copy inside it that starts at that half's left edge, so the two columns
   share one reading edge instead of facing away from each other.

   ⇉ AND THE BLOCK ITSELF NOW SITS ON THE RIGHT RAIL (2026-09-25, Bronze:
   "move the right side section back over to the right"). These two sentences
   are not in tension and the distinction is the whole point: `justify-self:end`
   moves the BLOCK, `align-items:flex-start` keeps the copy INSIDE it flush
   left. Without justify-self the block stretched across its whole grid half
   and flex-start packed it against that half's LEFT edge - mid-page, which is
   what he was looking at.

   Do NOT "simplify" this to `align-items:flex-end`: that right-aligns nav,
   contact and legal individually and leaves their left edges ragged, which is
   the exact thing 96e9efa was written to undo. */
/* gap:10px is the WITHIN-A-GROUP step, not the spacing between every child -
   see "the right column's three groups" below for why it is 10 and not 16. */
.foot-links{ display:flex; flex-direction:column; align-items:flex-start; gap:10px; text-align:left; justify-self:end; }
/* The BLOCK hugs the right edge; the ITEMS inside stay start-aligned so each
   column is a flush vertical line. justify-items:end right-aligned every cell
   individually, so a short word sat indented above a long one and all four
   columns read ragged. Row-major flow is still untouched - DOM, reading and
   tab order agree. */
/* FIVE columns, not four (2026-10-06, Bronze: "move it up and put it in line on
   the right side of Success Stories"). The Admin link used to sit in the contact
   line beside the phone number, 14px from it, where it read as a second way to
   contact Trenches rather than as a way in to the editor.
   It is the nav's 5th item in DOM order, so with five columns it lands in row 1
   to the right of Success Stories and the eight page links keep their two rows:
       Home  About  Contact  Success Stories  Admin
       Services  News  Sitemap  Privacy Policy
   Column 5 sizes to "Admin" alone; row 2 simply ends after four.
   ⇉ Fifth in the DOM, NOT placed with `grid-column`. Putting it last in source
   and explicitly at row 1 / column 5 would look identical and break the rule
   this grid is built on - DOM, reading and tab order agree. A keyboard user
   would tab Privacy Policy -> Admin while the eye reads Success Stories ->
   Admin. At <=760px the grid drops to two columns and Admin sits mid-list;
   that is the cost of keeping tab order honest, and it is the right trade. */
.foot-links nav{ display:grid; grid-template-columns:repeat(5, auto); gap:10px 30px; justify-items:start; }
.foot-contact{ display:flex; flex-wrap:wrap; gap:4px 14px; justify-content:flex-start; }
/* the blurb is a full paragraph now, so give it more room */
/* was declared twice - 470px here and 560px 29 lines down, same specificity,
   so the first was dead code. One declaration, on the shared measure. */
.foot-about{ max-width:var(--measure); }
.foot-contact{ align-items:center; }
.foot-contact a{ display:inline-flex; align-items:center; gap:7px; }
.foot-contact svg{ width:13px; height:13px; flex:0 0 auto; }
/* ---- the business details (2026-10-05, Bronze): name, street address, hours.
   The footer is the one place on the site that states who and where Trenches
   is, so it is marked up as a real <address> rather than three spans - which
   also means a search engine reads it as the business's own contact block
   rather than as decoration. <address> renders italic by default; the site has
   no italic footer, hence font-style:normal.
   They inherit the footer's 12.5px/.04em; only the name is lifted, so the eye
   finds the business before it reads the street. */
.foot-nap{ font-style:normal; display:flex; flex-direction:column; gap:3px; }
.foot-name{ font-weight:700; }
.foot-hours{ display:flex; flex-direction:column; gap:3px; }
/* The standing line under the footer blurb. Matches .foot-about p exactly,
   because it IS another paragraph of that blurb, not a caption. */
.foot-since{ font-size:calc(13px * var(--type-scale, 1)); line-height:1.65; letter-spacing:.01em; margin-top:12px; }
/* ---- the right column's three groups (2026-10-06, Bronze: "the spacing in
   the footer is still not quite right").
   It had FIVE children on one flat 16px gap - nav, name/address, hours,
   phone, copyright - so nothing said which of them belong together. Worse,
   16px is only 6px more than the nav's OWN 10px row gap, so the address read
   as a third row of the menu.
   There are three groups, not five blocks: the menu; the business (who, where,
   when, how to reach - one object, and the one a search engine reads as the
   contact block); and the legal line. So the column now steps
   3px (line) / 10px (within a group) / 28px (between groups).
   Both numbers are already in this footer and now mean something: 10px is the
   nav's own row gap, 28px is the footer's own section gap. Flex `gap` and
   margin COMPOUND, so 10 + 18 is the 28. */
.foot-nap, .foot-links .legal{ margin-top:18px; }
/* copyright sits under the contact line in the right column */
.foot-links .legal{ opacity:.8; }
@media (max-width: 760px){
  .foot-links nav{ grid-template-columns:repeat(2, auto); }
}
.foot-contact a{ transition:color var(--speed) ease; }
.foot-contact a:hover, .foot-contact a:focus-visible{ color:var(--blue); }
@media (max-width: 900px){
  /* one column. ⇉ AND THE RIGHT-RAIL PARK HAS TO BE UNDONE HERE, which is
     load-bearing rather than tidy: with one column the grid item is the full
     width, so `justify-self:end` would shrink the links block to its content
     and pin it to the PHONE's right edge while the blurb above it stayed
     left. This line is what keeps the two sharing a reading edge at every
     width. */
  .foot-main{ grid-template-columns:1fr; }
  .foot-links{ justify-self:start; }
}

/* ---------- BLOCK: story card ----------
   The home spotlight's card, shared with the Success Stories grid so the two
   surfaces are the same object. Copied from the homepage's inline <style>,
   which stays as it is. ⇉ NOT because that page "links no stylesheet" - it
   DOES, at trenches-streaming-home.html:51, and this sentence claimed the
   opposite until 2026-09-25. The real reason to keep both copies is that the
   inline block comes AFTER the link, so at equal specificity the inline rule
   wins for every property it declares - which makes duplication a deliberate
   override, not an inability to read this file. The corollary is the useful
   half: a property the inline copy does NOT declare (the footer's
   `justify-self` is the live example) reaches this page from here with no
   second edit. Change both or neither for anything the inline copy declares.

   The home rail wants a fixed-width flex item; the grid here wants the cell
   to decide. .ss-cards .card resets that below rather than forking the card. */
.card{
  border-radius:var(--radius);
  background:linear-gradient(170deg, var(--navy-2), var(--navy));
  border:1px solid var(--card-edge);
  color:var(--on-dark);
  overflow:hidden;
  display:flex; flex-direction:column;
  transition:transform var(--speed) ease, border-color var(--speed) ease;
}
.card:hover, .card:focus-within{ transform:translateY(-4px); --card-edge:var(--blue-mid); }
.card-art{
  position:relative;
  aspect-ratio:16/9;
  overflow:hidden;
  display:flex; align-items:flex-end;
  padding:14px;
}
.card-art > img{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
}
.card-art .glyph{
  position:absolute; right:-8px; top:-26px;
  font-family:var(--font-serif); font-weight:700;
  font-size:calc(130px * min(var(--type-scale, 1), 1.125)); line-height:1; /* a watermark: held at the Large step, Larger pushed it 2px past the card at phone width */
  color:rgba(255,255,255,.055);
  user-select:none; pointer-events:none;
  letter-spacing:-.04em;
}
.card-art .kicker{
  position:relative; z-index:1;
  font-size:calc(11px * var(--type-scale, 1)); font-weight:700; letter-spacing:.2em; text-transform:uppercase;
  color:rgba(255,255,255,.85);
  text-shadow:0 1px 8px rgba(0,0,0,.6);
}
/* These badges always sit on dark card art, so they need the on-dark palette
   in BOTH themes - the light theme's #033ca4 on the near-black panel measured
   2.03:1 and was effectively unreadable. */
.card-art .badge{
  position:absolute; top:12px; left:12px; z-index:1;
  font-size:calc(10px * var(--type-scale, 1)); font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  padding:3px 8px; border-radius:4px;
  background:rgba(4,12,32,.8);
  border:1px solid rgba(155,205,253,.45);
  color:var(--sky);
}
/* abstract gradient art variants (blue family plus muted supports) */
.art-royal { background:radial-gradient(120% 100% at 80% 0%, rgba(45,99,216,.55),  transparent 60%), linear-gradient(135deg, #10265c, #081228 70%); }
.art-ice   { background:radial-gradient(120% 100% at 50% 0%, rgba(155,205,253,.32),transparent 62%), linear-gradient(135deg, #142a4e, #0a1526 70%); }
.art-steel { background:radial-gradient(120% 100% at 20% 0%, rgba(120,145,170,.35),transparent 60%), linear-gradient(135deg, #1a2740, #0e1424 70%); }
.art-navy  { background:radial-gradient(120% 100% at 30% 0%, rgba(3,60,164,.6),    transparent 62%), linear-gradient(135deg, #0b1c46, #080e1e 70%); }
.art-pine  { background:radial-gradient(120% 100% at 75% 10%, rgba(96,150,140,.28),transparent 60%), linear-gradient(135deg, #102536, #0a141e 70%); }
.art-slate { background:radial-gradient(120% 100% at 85% 20%, rgba(160,170,180,.24),transparent 60%), linear-gradient(135deg, #1e2536, #10131e 70%); }
.card-body{ padding:16px 18px 18px; display:flex; flex-direction:column; gap:7px; flex:1; }
.card-meta{
  font-size:calc(11.5px * var(--type-scale, 1)); font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  color:var(--sky);
}
.card-title{
  font-family:var(--font-serif); font-size:calc(19px * var(--type-scale, 1)); font-weight:700; line-height:1.25;
  color:#fff;
}
.card-desc{ font-size:calc(13.8px * var(--type-scale, 1)); color:var(--on-dark-dim); }
.card-impact{
  margin-top:6px; padding-top:10px;
  border-top:1px solid rgba(155,205,253,.18);
  font-size:calc(13px * var(--type-scale, 1)); color:var(--on-dark-dim);
}
.card-impact b{
  display:block; margin-bottom:3px;
  font-size:calc(10.5px * var(--type-scale, 1)); font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  color:var(--gold);
}
.card-link{
  margin-top:auto; padding-top:10px;
  font-size:calc(12px * var(--type-scale, 1)); font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--on-dark-mute);
  display:flex; align-items:center; gap:8px;
  background:none; border:0; cursor:pointer; text-align:left;
  transition:color var(--speed) ease, gap var(--speed) ease;
}
.card-link svg{ width:13px; height:13px; }
.card:hover .card-link, .card:focus-within .card-link{ color:var(--sky); gap:12px; }
/* the grid cell sizes the card here, unlike the home rail's fixed 300px */
.ss-cards .card{ width:auto; }

/* ---------- page hero ---------- */
.page-hero{ text-align:left; }
.page-hero-inner{
  max-width:1440px; margin:0 auto;
  padding:104px var(--gutter) 16px;
}
.eyebrow{
  font-size:calc(12px * var(--type-scale, 1)); font-weight:700; letter-spacing:.32em; text-transform:uppercase;
  color:var(--blue); margin-bottom:14px;
}
/* Matched to the HOME hero's curve (2026-09-09, Bronze). The interior pages
   ran clamp(34px, 4vw, 54px) against the homepage's clamp(38px, 4.4vw, 60px):
   a smaller floor AND a shallower slope, so they trailed it at every width,
   not just at the cap - 54px vs 60px at 1440. A hero is a hero, and the 10%
   gap read as an inconsistency rather than a hierarchy.

   The homepage keeps its own declaration in its inline <style>; it links no
   stylesheet, so this rule cannot reach it. The two must be changed together
   or they drift apart again. line-height was already 1.08 on both. */
/* ⇉ .sv2-h1 RENDERS AS THE h1 WHILE STAYING h2 IN THE OUTLINE: the services
   hero shows one of three interchangeable headings (two lens sides and the
   flip card's mirror), and only the first in document order may be the h1
   (one h1 per page, audit item 16). No margin compensation: the global
   reset zeroes heading margins, so h1 and h2 already sit identically —
   MEASURED, not assumed (a .67em "UA margin pin" here moved the visible
   heading 38px down before it was caught in the browser). */
.page-hero h1, .page-hero .sv2-h1{
  font-family:var(--font-serif); font-weight:700;
  font-size:calc(clamp(38px, 4.4vw, 60px) * var(--type-scale, 1)); line-height:1.08; color:var(--ink);
  margin-bottom:14px; text-wrap:balance;
}
/* On a phone the page title keeps its standard size at every Text size: a long word ("announcements." in the News title) does not fit
   the line at 43px or more. (Letting the word break instead changed the title of News on a 360px phone with nothing chosen: an
   independent review caught that, and the differ, which only looked at 390px and wider, had not.) */
@media (max-width: 480px){
  .page-hero h1, .page-hero .sv2-h1{ font-size:calc(38px * min(var(--type-scale, 1), 1)); }
}
.page-hero p.lede{
  color:var(--ink-dim); font-size:calc(16.5px * var(--type-scale, 1)); max-width:none; /* Bronze 2026-10-10: the lede runs the width of the page body */
}

/* ---------- content area ---------- */
.content{
  max-width:1440px; margin:0 auto;
  padding:24px var(--gutter) 60px;
  display:flex; flex-direction:column; gap:44px;
}
.sec-title{
  font-size:calc(13.5px * var(--type-scale, 1)); font-weight:700; letter-spacing:.26em; text-transform:uppercase;
  color:var(--ink); margin-bottom:16px;
}
.grid{ display:grid; gap:16px; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); }

.panel{
  border-radius:var(--radius);
  background:linear-gradient(170deg, var(--navy-2), var(--navy));
  border:1px solid var(--card-edge);
  color:var(--on-dark);
  padding:24px 24px 22px;
  display:flex; flex-direction:column; gap:8px;
  transition:transform var(--speed) ease, border-color var(--speed) ease;
}
.panel:hover, .panel:focus-within{
  transform:translateY(-4px);
  --card-edge:var(--blue-mid);
}
.panel .p-meta{
  font-size:calc(11.5px * var(--type-scale, 1)); font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  color:var(--sky);
}
.panel h3{
  font-family:var(--font-serif); font-size:calc(19px * var(--type-scale, 1)); font-weight:700; line-height:1.25; color:#fff;
}
/* News tiles link to /news/<slug> (Milestone 2): the link wears the h3's
   look and only reveals itself on hover. */
.panel h3 .panel-link{ color:inherit; text-decoration:none; }
.panel h3 .panel-link:hover{ text-decoration:underline; }
.panel p{ font-size:calc(13.8px * var(--type-scale, 1)); color:var(--on-dark-dim); }
.panel .p-date{
  font-size:calc(11.5px * var(--type-scale, 1)); letter-spacing:.14em; text-transform:uppercase;
  color:var(--on-dark-mute); font-weight:600;
}
.panel a.p-link{
  margin-top:auto; padding-top:8px;
  font-size:calc(12px * var(--type-scale, 1)); font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--on-dark-mute);
  transition:color var(--speed) ease;
}
.panel a.p-link:hover, .panel a.p-link:focus-visible{ color:var(--sky); --card-link:var(--sky); }
/* A panel is navy in both themes, and the page's --blue focus ring is 1.7:1
   on it: a keyboard user could not see where they were. The sky ring, as on
   the homepage's news box (2026-10-14, when the News cards gained a second
   link each). */
.panel a:focus-visible{ outline-color:var(--sky); }
/* A News card's photo (news slice 2, 2026-10-14): the build adds it when the
   post has one. A 16:9 frame the picture fills, so cards line up whatever
   shape each photo is; its corners follow the card's. */
.panel .p-photo{
  display:block; width:100%; aspect-ratio:16 / 9; object-fit:cover;
  border-radius:calc(var(--radius) - 4px); margin-bottom:6px;
}

/* ---------- BLOCK: stat-shelf ----------
   Big-number credibility cards. Same dark-module language as .panel, but
   the serif numeral carries the row, so it gets its own tighter grid. */
.stat-grid{
  display:grid; gap:16px;
  grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));
}
.stat-card{
  border-radius:var(--radius);
  border:1px solid var(--card-edge);
  background:linear-gradient(170deg, var(--navy-2), var(--navy));
  color:var(--on-dark);
  padding:20px 18px 18px;
  display:flex; flex-direction:column; gap:7px;
  transition:transform var(--speed) ease, border-color var(--speed) ease;
}
.stat-card:hover, .stat-card:focus-within{
  transform:translateY(-4px);
  --card-edge:var(--blue-mid);
}
.stat-num{
  font-family:var(--font-serif);
  /* held at its standard size: a card is 200px wide at its narrowest and "millions" at 36px or more does not fit it (the Success
     Stories stat card at 768 to 820px); the labels under the numbers still scale */
  font-size:calc(32px * min(var(--type-scale, 1), 1)); font-weight:700; line-height:1;
  color:var(--gold);
}
.stat-label{
  font-size:calc(12px * var(--type-scale, 1)); font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  color:#fff;
}
.stat-desc{ font-size:calc(13.5px * var(--type-scale, 1)); color:var(--on-dark-dim); }

/* ---------- closing + footer ---------- */
.closing{ position:relative; padding:96px var(--gutter) 72px; text-align:center; }
.closing h2{
  font-family:var(--font-serif); font-size:calc(clamp(28px, 3.2vw, 42px) * var(--type-scale, 1));
  font-weight:700; line-height:1.12; color:var(--ink);
  max-width:20ch; margin:0 auto 14px; text-wrap:balance;
}
.closing p{ color:var(--ink-dim); max-width:var(--measure-centred); margin:0 auto 30px; }
/* ⇉ THE EYEBROW IS A <p>, so the rule above — `.closing p` at (0,1,1) — beats
   `.eyebrow` at (0,1,0) and was overriding its 14px bottom margin with 30px.
   `.closing .eyebrow` is (0,2,0) and takes it back.

   This was latent for as long as `.closing` has existed and nothing could see
   it: the CMS block hard-coded `margin-bottom:14px` inline, and inline beats
   every class, so the one place it would have shown was the one place it was
   masked. Removing those inline styles on 2026-09-22 is what exposed it —
   found by measuring computed styles in a browser, which is the only thing
   that can see this class of bug. The homepage's private stylesheet has
   carried this exact rule all along (trenches-streaming-home.html:666); this
   is site.css catching up to it, and it is why the hand-authored interior
   closings and the CMS-rendered ones now agree. */
.closing .eyebrow{ margin-bottom:14px; }

/* ---------- the Section block: text with a picture or clip beside it -------
   Emitted by packages/ui/src/blocks/CtaBand.tsx. It sits INSIDE `.closing`,
   so everything above — the padding, the type scale, the vertical rhythm,
   the theme colours — already applies and is deliberately not repeated. What
   is here is only the two-column geometry, which `.closing` never had.

   ⇉ THESE RULES ARE THE REASON THE COMPONENT CAN STOP DUPLICATING site.css.
   Inline styles beat class rules, so anything the component hard-codes is a
   value the page can never correct — which is how the old component would
   have shrunk the homepage closing on conversion (its private `.closing` runs
   84px/110px padding and a clamp(30px,3.6vw,46px) heading). The component now
   emits only per-instance alignment; the rest lives here, per page.

   ⇉ AND THE MEASURES HAVE TO BE UNDONE. `.closing h2` is max-width:20ch and
   `.closing p` is max-width:var(--measure-centred) — correct for a centred
   single column, strand-in-the-middle wrong inside a half-width one. */
.sec-split{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:clamp(24px, 4vw, 56px);
  /* The split is the full content column; the measures come off the children
     below, so the copy fills its half instead of the page's reading width. */
  text-align:inherit;
}
.sec-split > .sec-copy,
.sec-split > .sec-media{ flex:1 1 320px; min-width:0; }

/* Inside a split the heading and body fill their column. The horizontal
   margins the component emits for `justify` still apply on top of this — at
   max-width:none they are simply inert, which is the correct result: there is
   no slack left to distribute. */
.sec-split .sec-copy h2,
.sec-split .sec-copy p{ max-width:none; }

.sec-media img,
.sec-media video{
  display:block; width:100%; height:auto;
  border-radius:var(--radius);
}

/* The HEADER's picture or clip (page-hero, 2026-09-24) is capped, so a
   portrait photo cannot push the page's first words and everything under
   them far below the fold (Bronze: "add a height cap for now"). A picture
   fills the capped box and is cropped from its middle — cover, never
   stretched; a clip keeps its whole frame inside the box (a video's own
   default fit), so its controls are never cut off. Landscape pictures in a
   half-width column come in well under the cap and are not cropped at all.
   The Section block's media is deliberately NOT capped. */
.page-hero .sec-media img{
  max-height:min(560px, 70vh);
  object-fit:cover; object-position:center;
}
/* A clip has no stored size (the upload reads image headers only), so its
   box is reserved at 16:9 — or the header, the first thing on the page,
   would grow when the clip's metadata arrives and shove everything below it
   (the 2026-09-24 review). The clip letterboxes inside. */
.page-hero .sec-media video{
  max-height:min(560px, 70vh);
  aspect-ratio:16 / 9; background:#000;
}

/* The chosen-but-unresolvable state (archived, deleted, or not yet picked).
   Visible on purpose — see the note in CtaBand.tsx. */
.sec-media-empty{
  display:flex; align-items:center; justify-content:center;
  min-height:160px; border-radius:var(--radius);
  border:1px dashed var(--card-edge, rgba(6,22,52,.4));
  color:var(--ink-dim); font-size:calc(13px * var(--type-scale, 1));
  letter-spacing:.08em; text-transform:uppercase;
}

/* One column on phones. The component puts media FIRST in the DOM only when
   the client asked for it on the left, so the stacked order needs no rule. */
@media (max-width:760px){
  .sec-split{ gap:24px; }
  .sec-split > .sec-copy,
  .sec-split > .sec-media{ flex-basis:100%; }
}

/* ---------- the media block ------------------------------------------------
   Emitted by packages/ui/src/blocks/Media.tsx, which until now rendered from
   its own inline styles alone — `media-block` appeared zero times in this
   file. That was the hard prerequisite on offering `media` in any region's
   palette: a block whose appearance no stylesheet can reach is a block the
   site cannot theme, and the client's spec item 25 is "the ability to add an
   image section". */
.media-block{ margin:0 0 28px; }
.media-block-el{ display:block; width:100%; height:auto; border-radius:var(--radius); }
.media-block-caption{ margin-top:10px; font-size:calc(13px * var(--type-scale, 1)); color:var(--ink-dim); text-align:center; }
.media-block.is-unresolved > div{
  border:1px dashed var(--card-edge, rgba(6,22,52,.4));
  color:var(--ink-dim);
}

/* ---------- the photo-scroller block --------------------------------------
   Emitted by packages/ui/src/blocks/PhotoScroller.tsx in one of two frames.
   The `slides` frame wears .pp-slides/.pp-slide, whose rules already live
   above (the property pages' slider). The `shelf` frame wears .pho-shelf,
   whose base rules lived ONLY inside about.html's private <style> — promoted
   here verbatim (2026-09-22) so the block renders correctly in the editor
   canvas (which loads only this file) and on any page a region later puts it
   on; about.html keeps its identical private copy, and the marquee MOTION
   (.pho-auto, .pho-track, the keyframes) deliberately stays page-private:
   it is script-applied and never runs in the canvas. */
.pho-shelf{ display:flex; gap:14px; overflow-x:auto; padding:4px var(--gutter) 18px; scrollbar-width:thin; }
.pho-shelf img{ flex:0 0 auto; width:min(320px, 72vw); height:auto; aspect-ratio:16/10; object-fit:cover; border-radius:var(--radius); }

/* The visible empty states — a chosen photo that no longer resolves, or a
   strip with nothing in it yet. Sized like a photo so the strip keeps its
   shape, dashed so nobody mistakes it for content. */
.pho-empty.is-unresolved,
.pp-slide.is-unresolved{
  display:flex; align-items:center; justify-content:center;
  border:1px dashed var(--card-edge, rgba(6,22,52,.4));
  color:var(--ink-dim); font-size:calc(13px * var(--type-scale, 1));
  letter-spacing:.08em; text-transform:uppercase;
}
.pho-empty.is-unresolved{
  flex:0 0 auto; width:min(320px, 72vw); aspect-ratio:16/10;
  border-radius:var(--radius);
}

footer{
  position:relative; isolation:isolate;
  border-top:1px solid rgba(10,28,64,.15);
  padding:52px var(--gutter) 44px;
  display:flex; flex-direction:column; gap:28px;
  font-size:calc(12.5px * var(--type-scale, 1)); color:var(--ink); letter-spacing:.04em;
}
/* Light-theme footer ink is --ink, not --ink-dim, BECAUSE of the crowd plate.
   Measured over the darkest realistic part of the photo (5th percentile):
   --ink-dim gave 3.39:1 and even bare, with no photo at all, it was only
   4.55:1 - no headroom to spend. --ink lands at 6.97:1, and the .8-opacity
   copyright line still clears 5.4:1. Dark theme keeps --ink-dim: a light
   plate on a navy band moves contrast the safe way. */
:root[data-theme="dark"] footer{ color:var(--ink-dim); }
/* ---------- footer crowd ----------
   ONE grayscale plate serves both themes: the tint comes from the band
   showing through, not from the file, so there is no second asset to keep
   in sync. Masked in from the top so there is no seam under the closing
   CTA. Deliberately low and flat - the footer text is --ink-dim, and a hot
   spot behind it costs real contrast, so the opacity is set from a measured
   ratio rather than by eye. */
footer::before{
  content:""; position:absolute; inset:0; z-index:-1;
  background:url("footer-crowd.088178d0.webp") 50% 64%/cover no-repeat;
  opacity:.18;
  -webkit-mask-image:linear-gradient(180deg, transparent 0, #000 30%, #000 100%);
          mask-image:linear-gradient(180deg, transparent 0, #000 30%, #000 100%);
  pointer-events:none;
}
:root[data-theme="dark"] footer::before{ opacity:.24; }
/* social row: sits on its own line at the top of the footer.
   Footers here are dark navy, so the icons use the brand light blue;
   the light homepage footer uses the dark brand blue instead. */
.social{
  flex:1 0 100%;
  display:flex; align-items:center; justify-content:center; gap:18px;
  padding-bottom:20px;
}
.social a{
  display:inline-flex;
  color:var(--blue);
  transition:color var(--speed) ease, transform var(--speed) ease;
}
.social a:hover, .social a:focus-visible{ color:var(--ink); transform:translateY(-2px); }
.social svg{ width:20px; height:20px; }
/* Google Business Profile: the one social icon that is a picture, not an <svg>.
   The picture is a white shape with transparency, and this paints currentColor
   through it, so it follows the same colour, hover and dark-mode rules as the
   four icons beside it (scripts/lib/footer.mjs says why it is not an <img>). */
.social .social-gbp{
  display:block; width:20px; height:20px; background-color:currentColor;
  -webkit-mask:url("google-business-profile.8e155e40.png") center/contain no-repeat;
          mask:url("google-business-profile.8e155e40.png") center/contain no-repeat;
  /* Windows High Contrast repaints every background with the system Canvas
     colour, which would paint this shape Canvas on Canvas: an invisible icon.
     Opting out keeps `currentColor`, which the link around it still gets from
     the system. The four <svg> icons need none of this: they are fills. */
  forced-color-adjust:none;
}

footer nav{ display:flex; flex-wrap:wrap; gap:18px; }
footer nav a{ color:inherit; transition:color var(--speed) ease; }
footer nav a:hover, footer nav a:focus-visible{ color:var(--blue); }
footer nav a[aria-current="page"]{ color:var(--blue); }

/* ---------- responsive ---------- */
/* ⇉ THE PRIMARY MENU BECOMES A DROP-DOWN BELOW 1240px (2026-10-06, Bronze:
   "go with the drop-down panel").
   This query used to be the whole story - `.topnav{ display:none }` and nothing
   put in its place. Below 1240px the menu was GONE: no button, no replacement,
   the footer the only way to navigate. That is every phone, every tablet and
   any laptop window that is not maximised.
   The closed state is still `display:none`, so a visitor without JS is exactly
   where they were and nothing regressed. `.is-open` is scoped INSIDE this query
   deliberately: above 1240px the query does not apply, so a left-over class
   cannot leave the desktop bar in a broken state after a resize. */
@media (max-width: 1240px){
  /* the button anchors the right-hand cluster now that the links have left the
     row: brand ......... [Menu] [Contact] [theme] */
  .nav-toggle{ display:flex; margin-left:auto; }
  .topnav{ display:none; }
  .topnav.is-open{
    position:absolute; top:100%; left:0; right:0;
    display:flex; flex-direction:column; align-items:stretch;
    gap:0; margin:0;
    padding:6px var(--gutter) 10px;
    background:rgba(244,250,255,.98);
    border-bottom:1px solid rgba(10,28,64,.12);
    backdrop-filter:blur(10px);
    box-shadow:0 18px 40px -24px rgba(10,28,64,.55);
  }
  /* 44px rows, not a line of text with a 6px pad (WCAG 2.5.8 target size).
     ⇉ `border:0` AND a box-shadow divider, NOT a border one: `.topnav a` marks
     hover and the current page by setting `border-color`, which would repaint
     this divider on hover and make the current row look like a section break.
     Zeroing the width lets those rules keep working on colour alone. */
  .topnav.is-open a{
    display:flex; align-items:center;
    min-height:44px; padding:12px; border:0;
    box-shadow:inset 0 -1px 0 rgba(10,28,64,.10);
  }
  .topnav.is-open a:last-child{ box-shadow:none; }
  .topnav.is-open a[aria-current="page"]{
    box-shadow:inset 2px 0 0 var(--blue-mid), inset 0 -1px 0 rgba(10,28,64,.10);
  }
}
@media (max-width: 760px){
  .topbar{ gap:14px; }
  .topbar .brand img{ height:22px; }
  .topbar .btn{ margin-left:auto; }
  .page-hero-inner{ padding-top:88px; }
}
/* Without scripts (assets/nav-toggle.js moves the Contact link and the theme toggle into the menu on a phone, below) the bar's four items
   still fill it: below about 490px wide the Contact button is cut off and the theme toggle is off the screen. The button's text keeps its
   standard size at every Text size, so choosing Large or Larger does not push them further out. */
@media (max-width: 520px){
  .topbar .btn{ font-size:calc(12px * min(var(--type-scale, 1), 1)); }
}

/* On a phone (520px and narrower) assets/nav-toggle.js moves the Contact link and the theme toggle into the menu panel (see there), so the
   bar holds only the logo and the Menu button and fits every phone. The moved Contact link is an ordinary row of the panel (the panel's own
   rules above already give every link in it a 44px row); what is left to say is that it is not a button here, and the theme row. */
@media (max-width: 520px){
  .topbar.has-phone-menu .topnav .btn{
    margin:0; border:0; border-radius:0; background:none; box-shadow:inset 0 -1px 0 rgba(10,28,64,.10);
    justify-content:flex-start; transform:none;
    font-size:calc(12px * var(--type-scale, 1)); /* beats the standard-size cap on the bar's button above, which is for the bar */
  }
  .topbar.has-phone-menu .topnav .btn:hover, .topbar.has-phone-menu .topnav .btn:focus-visible{ transform:none; }
  .topbar.has-phone-menu .nav-theme-row{
    display:flex; align-items:center; justify-content:space-between; gap:12px;
    min-height:44px; padding:6px 12px;
  }
  .topbar.has-phone-menu .nav-theme-label{
    font-size:calc(12px * var(--type-scale, 1)); letter-spacing:.07em; text-transform:uppercase; color:var(--ink-dim);
  }
  :root[data-theme="dark"] .topbar.has-phone-menu .topnav .btn{ box-shadow:inset 0 -1px 0 rgba(155,205,253,.14); }
  :root[data-theme="dark"] .topbar.has-phone-menu .nav-theme-label{ color:var(--on-dark-dim); }
}
/* A 320px phone: the logo and the Menu button (and the bar's padding) are 4px too wide for it, so the logo is two pixels shorter there. */
@media (max-width: 340px){
  .topbar.has-phone-menu .brand img{ height:20px; }
}

/* ---------- brand logo grid (success stories page) ----------
   Permanently open since 2026-08-24: the click-to-open trigger and its
   collapsing stage are gone, and their CSS went with them. The tiles now
   burst out from the centre on a scroll reveal, then repel from the
   cursor. Section chrome lives on .ss-brands on the page itself. */
.burst-grid{
  display:grid; grid-template-columns:repeat(6, minmax(0,1fr));
  gap:12px; padding:14px 2px 12px;
}
.burst-tile{
  display:flex; align-items:center; justify-content:center;
  height:92px; padding:10px 18px;
  border:1px solid rgba(6,22,52,.4); border-radius:var(--radius);
  background:linear-gradient(170deg, var(--navy-2), var(--navy));
  will-change:transform;
  transition:transform .45s cubic-bezier(.2,.75,.3,1.12), opacity .35s ease, border-color var(--speed) ease;
}
.burst-tile:hover{ border-color:var(--blue-mid); }
.burst-tile img{
  max-width:100%; max-height:60px; width:auto; height:auto; object-fit:contain;
  /* Rest state is deliberately quiet and monochrome; hover restores full colour.
     The brightness lift is what makes that quiet state legible: greyscale alone
     maps a saturated brand colour to a mid-dark grey, and at .55 opacity over
     navy that lands around 1.3:1 - invisible in practice. Measured across all
     49 marks, grayscale+brightness(1.6)+opacity(.72) puts the dimmest (Chick-fil-A,
     Coca-Cola, TxDOT) at ~2.3:1 while keeping the muted look. Raising opacity
     alone would not do it - the colours have to be lifted before they are faded.
     See assets/logos/SOURCES.md for the per-mark treatment. */
  filter:grayscale(1) brightness(1.6) opacity(.72);
  transition:filter var(--speed) ease;
}
.burst-tile:hover img{ filter:none; }
@media (max-width: 900px){
  .burst-grid{ grid-template-columns:repeat(3, minmax(0,1fr)); }
  .burst-tile{ height:76px; padding:8px 12px; }
  .burst-tile img{ max-height:48px; }
}
@media (prefers-reduced-motion: reduce){
  .burst-tile{ transition:none; }
}

/* ============================================================
   BLOCK: property-detail
   The property page's hero content block (property-*.html). Same
   markup renders two chrome variants, switched by [data-chrome] on
   <html> — set by a small inline script reading ?v= before first
   paint (same slot/pattern as the theme bootstrap):
     popup (default, ?v=1) — tan card floating on the light-blue band
     flat  (?v=2)          — edge-to-edge, no card, follows site theme
   The tan card is theme-invariant by design (like .panel/.card stay
   dark-navy in both themes, the tan surface stays tan in both) — only
   flat chrome follows light/dark. ============================== */
.pp-section{ padding:0; }
.pp-wrap{
  max-width:1320px; margin:0 auto;
  padding:96px var(--gutter) 40px;
  position:relative;
}
[data-chrome="popup"] .pp-wrap{ max-width:1680px; }  /* widened 2026-08-18 for the slider layout */
/* The property page's content area (S4): a direct child of .pp-wrap after the
   card. ONLY the column and the gap .content gives About's area; width and
   gutter come from .pp-wrap. Never nest a div.content here instead — its own
   padding would apply the gutter twice and make the area narrower than the card.
   ⇉ NOT `.pp-body`: that name belongs to the dormant two-column card CSS below
   (padding 44px 42px 46px, gap 16px under popup chrome), and borrowing it made
   the EMPTY area 90px tall on the first measurement. */
.pp-sections{ display:flex; flex-direction:column; gap:44px; }
/* 44px above the FIRST section (Bronze, 2026-09-28), so it does not sit flush
   against the card — and only once there IS a section. `:has()` keeps the
   EMPTY area 0px tall, so an unfilled page is unchanged; the padding is the
   container's, so no block's own computed style moves. */
.pp-sections:has(> [data-block]){ padding-top:44px; }

.pp-card{
  position:relative;
  display:block;   /* 2026-08-18: rows (tile rail, stage) instead of two columns */
  border-radius:20px;
}
[data-chrome="flat"] .pp-card{ background:transparent; box-shadow:none; border-radius:0; }

/* Three background options for Bronze to compare on Home I's popup
   chrome (default tan, ?bg=navy, ?bg=sky — see the bootstrap script in
   <head>).

   SOLID surfaces (changed 2026-08-14). These were full-strength
   gradients; sky ran #d9edff -> #9bcdfd, and #9bcdfd is exactly the
   page's own band colour, so the card dissolved into the background at
   its lower edge. Each variant is now one flat colour with a whisper of
   shading (a ~4% top highlight) so the surface still has some form
   without reading as a gradient.

   --pp-surface is the card; --pp-panel is the shaded inner surface the
   copy sits on; --pp-edge is the hairline between them. Declaring them
   as variables per variant is what keeps the child rules below free of
   per-variant duplication.

   Every colour here is a FIXED value, never --ink/--ink-dim/--line-lite.
   Those tokens flip to near-white in dark theme; these surfaces do not
   flip, so using them put near-white text on the light sky card — see
   the note above .pp-title. */
[data-chrome="popup"][data-bg="tan"] .pp-card{
  --pp-surface:#f7f1e3;
  --pp-panel:#fcf9f3;  /* opaque: slider passes beneath; = old rgba over the surface */
  --pp-edge:rgba(120,90,40,.16);
  --pp-title:#241a0d; --pp-copy:#5b4b34; --pp-strong:#332714; --pp-accent:#8a5a1e;
  --pp-logo-plate:transparent; --pp-logo-pad:0;
  background:var(--pp-surface);
  border:1px solid rgba(120,90,40,.14);
  box-shadow:0 30px 70px rgba(20,30,50,.28);
}
[data-chrome="popup"][data-bg="navy"] .pp-card{
  --pp-surface:#0a1f4a;
  --pp-panel:#172f5a;  /* opaque: slider passes beneath; = old rgba over the surface */
  --pp-edge:rgba(155,205,253,.22);
  --pp-title:#eef4fd; --pp-copy:#b4c5e6; --pp-strong:#eef4fd; --pp-accent:#9bcdfd;
  /* dark card: logos need a light plate to read */
  --pp-logo-plate:rgba(255,255,255,.9); --pp-logo-pad:14px 20px;
  background:var(--pp-surface);
  border:1px solid rgba(155,205,253,.32);
  box-shadow:0 30px 80px rgba(2,10,32,.65), 0 0 70px rgba(3,60,164,.4);
}
[data-chrome="popup"][data-bg="sky"] .pp-card{
  /* lighter than the #9bcdfd band it sits on, so the card edge reads */
  --pp-surface:#e4f1ff;
  --pp-panel:#f3f6ff;  /* opaque: slider passes beneath; = old rgba over the surface */
  --pp-edge:rgba(3,60,164,.16);
  --pp-title:#0a1c40; --pp-copy:#33507f; --pp-strong:#0a1c40; --pp-accent:#033ca4;
  --pp-logo-plate:transparent; --pp-logo-pad:0;
  background:var(--pp-surface);
  border:1px solid rgba(3,60,164,.22);
  box-shadow:0 30px 80px rgba(2,10,32,.45), 0 0 70px rgba(3,60,164,.28);
}
/* the "light shading": a single soft highlight off the top edge, sitting
   under the content. Subtle enough to keep each surface reading as solid. */
[data-chrome="popup"] .pp-card::before{
  content:""; position:absolute; inset:0; border-radius:inherit;
  background:linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,0) 42%);
  pointer-events:none; z-index:0;
}
[data-chrome="popup"] .pp-body,
[data-chrome="popup"] .pp-media{ position:relative; z-index:1; }

/* sits to the right of the card, out on the page's own backdrop, not on
   .pp-media's dark/photo panel — that panel is unpredictable-colored
   (photo or navy gradient) and made a same-toned close icon nearly
   invisible on most pages. Centered in the wrap's own gutter so it never
   pushes past the viewport edge at any width. Card is full-width and
   stacked below 860px, so there's no room beside it there — floats above
   instead (see the mobile override below). */
.pp-close{
  position:absolute; top:16px; right:calc(-19px - var(--gutter) / 2); z-index:5;
  width:38px; height:38px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:#fff; color:var(--blue);
  box-shadow:0 8px 20px rgba(10,20,40,.22);
  transition:background var(--speed) ease, transform var(--speed) ease, color var(--speed) ease;
}
:root[data-theme="dark"] .pp-close{ background:#0b1c3f; color:var(--sky); }
[data-chrome="popup"] .pp-close{ background:#fff; color:#5b4326; }
.pp-close:hover, .pp-close:focus-visible{ background:var(--blue); color:#fff; transform:rotate(90deg); }
.pp-close svg{ width:16px; height:16px; }

.pp-body{ padding:44px 40px 40px; display:flex; flex-direction:column; gap:14px; min-width:0; }
[data-chrome="flat"] .pp-body{ padding:6px 0 0; gap:16px; }
[data-chrome="popup"] .pp-body{ padding:44px 42px 46px; gap:16px; }

.pp-pills{ display:flex; gap:8px; flex-wrap:wrap; }
/* its own shaded chip, sitting above the copy panel rather than on it */
[data-chrome="popup"] .pp-pills{
  align-self:flex-start;
  background:var(--pp-panel); border:1px solid var(--pp-edge);
  border-radius:14px; padding:10px 14px;
}
.pp-pill{
  font-size:calc(10.5px * var(--type-scale, 1)); font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  padding:5px 12px; border-radius:999px;
  border:1px solid rgba(3,60,164,.35); color:var(--blue);
}
[data-chrome="popup"] .pp-pill{ border-color:var(--pp-edge); color:var(--pp-accent); }

/* the shaded surface the copy sits on (wraps title -> CTA) */
[data-chrome="popup"] .pp-panel{
  background:var(--pp-panel);
  border:1px solid var(--pp-edge);
  border-radius:18px;
  padding:26px 28px 28px;
  display:flex; flex-direction:column; gap:14px;
  flex:1 1 auto;
}
/* Flat's surface tokens follow the theme, unlike the popup variants whose
   card colour is fixed. Same token names, so the shared child rules apply. */
[data-chrome="flat"] .pp-card{
  --pp-panel:rgba(255,255,255,.5);
  --pp-edge:rgba(3,60,164,.16);
}
:root[data-theme="dark"][data-chrome="flat"] .pp-card{
  --pp-panel:rgba(155,205,253,.09);
  --pp-edge:rgba(155,205,253,.22);
}
[data-chrome="flat"] .pp-panel{
  display:flex; flex-direction:column; gap:14px; flex:1 1 auto;
  background:var(--pp-panel); border:1px solid var(--pp-edge);
  border-radius:18px; padding:26px 28px 28px;
}
[data-chrome="flat"] .pp-pills{
  align-self:flex-start;
  background:var(--pp-panel); border:1px solid var(--pp-edge);
  border-radius:14px; padding:10px 14px;
}

.pp-logo{ display:block; max-width:180px; max-height:52px; width:auto; height:auto; object-fit:contain; }
.pp-mono{
  width:52px; height:52px; border-radius:12px;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--font-serif); font-weight:700; font-size:calc(22px * var(--type-scale, 1)); color:#fff;
  background:linear-gradient(160deg, var(--navy-2), var(--navy));
}

/* NOTE: --ink/--ink-dim/--line-lite are theme-FLIPPING tokens (redefined
   inside the dark-theme block to near-white, for pages whose background
   also flips). The tan popup card never flips, so its text/border colors
   are fixed values here, not those tokens; only flat chrome — whose
   background genuinely follows the theme — uses them. */
.pp-title{
  font-family:var(--font-serif); font-weight:700;
  font-size:calc(clamp(26px, 3vw, 38px) * var(--type-scale, 1)); line-height:1.07;
}
[data-chrome="popup"] .pp-title{ font-size:calc(clamp(30px, 3.3vw, 44px) * var(--type-scale, 1)); }
[data-chrome="popup"] .pp-title{ color:var(--pp-title); }
[data-chrome="flat"] .pp-title{ color:var(--ink); }

.pp-soon{ font-size:calc(12.5px * var(--type-scale, 1)); font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
[data-chrome="popup"] .pp-soon{ color:var(--pp-accent); }
[data-chrome="flat"] .pp-soon{ color:var(--blue); }

.pp-copy{ display:flex; flex-direction:column; gap:9px; }
.pp-copy p{ font-size:calc(13.8px * var(--type-scale, 1)); line-height:1.52; max-width:62ch; }
/* In the popup card the copy fills the panel (same width as the bullets box
   below it), which also shortens it enough to clear the panel's scrollbar.
   The flat chrome keeps the 62ch readability cap above. */
[data-chrome="popup"] .pp-copy p{ color:var(--pp-copy); max-width:none; }
[data-chrome="flat"] .pp-copy p{ color:var(--ink-dim); }

/* rich-text inside a property card. Three of the block's decisions are INLINE
   styles (RichText.tsx:141-147) and one of OUR rules stops working, so this is
   what keeps the converted region rendering exactly like the hand-authored
   markup it replaced.
   - padding: the block hard-codes `0 0 56px`. Inside .pp-stage's clamped
     height that is 56px of dead space above the bullets. An inline LENGTH can
     only be beaten by an author !important.
   - the gap: .pp-copy above is `display:flex; gap:9px`, and with `*{margin:0}`
     at the top of this file that gap is the ONLY thing separating the two
     paragraphs. The block renders ONE <section>, so .pp-copy is left with a
     single flex child and the gap separates nothing - the paragraphs would run
     flush. Restored on the block's inner div, which needs no !important
     because the block sets no display or gap there.
   - --ink: the inner div's `color:var(--ink)` is a custom-property reference,
     so redefining the property reaches it (see the note at the top of this
     file). The <p> rules above already colour the paragraphs; this covers a
     list or a heading the client adds later.
   Scoped to .pp-copy on purpose: privacy-policy ships the same block root and
   must not move. */
[data-chrome="popup"] .pp-copy{ --ink:var(--pp-copy); }
[data-chrome="flat"] .pp-copy{ --ink:var(--ink-dim); }
.pp-copy [data-block="rich-text"]{ padding:0 !important; }
.pp-copy [data-block="rich-text"] > div{ display:flex; flex-direction:column; gap:9px; }
.pp-copy [data-block="rich-text"] ul,
.pp-copy [data-block="rich-text"] ol{ padding-left:1.1em; }

/* ⇉ TEXT BLOCKS SPAN THE BODY (Bronze, 2026-09-24: "Paragraph blocks with
   just text should be the width of the body."). The block caps itself with an
   INLINE max-width:var(--measure, 72ch) (or --measure-wide), and an inline
   style beats any class rule, but it resolves the custom property, so
   redefining it here lifts the cap with no !important. The same redefinition
   reaches page rules that cap the block's own h2/p with var(--measure)
   (text blocks in the homepage body). A wrapper that caps a block from OUTSIDE has to
   go too (services.html .sv2-who). A block inside a card or a column
   (.pp-copy, .abt-bio-body) is still bounded by its card.
   This includes the Privacy Policy. */
[data-block="rich-text"]{ --measure:none; --measure-wide:none; }

.pp-bullets{
  display:grid; grid-template-columns:1fr 1fr; gap:9px 18px;
  padding:12px 16px; border-radius:12px;
}
[data-chrome="popup"] .pp-bullets{ border:1px solid var(--pp-edge); }
[data-chrome="flat"] .pp-bullets{ border:1px solid var(--line-lite); }
.pp-bullet{ display:flex; align-items:flex-start; gap:9px; }
.pp-bullet svg{ flex:0 0 auto; width:20px; height:20px; color:var(--blue); margin-top:1px; }
[data-chrome="popup"] .pp-bullet svg{ color:var(--pp-accent); }
.pp-bullet span{ font-size:calc(12px * var(--type-scale, 1)); font-weight:600; line-height:1.32; }
[data-chrome="popup"] .pp-bullet span{ color:var(--pp-strong); }
[data-chrome="flat"] .pp-bullet span{ color:var(--ink); }

.pp-cta{ margin-top:2px; align-self:flex-start; }

.pp-media{
  position:relative; min-height:260px;
  background:linear-gradient(160deg, var(--navy-2), var(--navy));
  display:flex; align-items:center; justify-content:center; overflow:hidden;
  border-radius:0 20px 20px 0;
}
/* Flat chrome (Home II, ?v=2) gets the SAME layout as popup: one uniform
   surface with the logo above the photo grid. It used to keep a dark navy
   media panel, which split the slide into a light left half and a dark right
   half and left the logo sitting beside the photos rather than above them. */
[data-chrome="flat"] .pp-media{
  border-radius:0; background:transparent;
  flex-direction:column; justify-content:center; gap:26px;
  padding:0; overflow:visible;
}
/* Popup chrome: the panel no longer paints its own dark fill, so the brand
   mark and the deck photo grid sit straight on the card surface. Stacked as
   a column (logo above gallery) rather than a single centred child. */
[data-chrome="popup"] .pp-media{
  min-height:320px; background:transparent;
  flex-direction:column; justify-content:center; gap:26px;
  padding:52px 46px 46px; overflow:visible;
}

/* The brand mark, now at the top of the media column.
   MUST reset position/inset/size: `.pp-media img` below sets
   absolute/inset:0/cover for the full-bleed photo case, and without this
   reset the logo inherits it and collapses to 0x0 — the same trap
   documented on .pp-logo-lg. */
/* Qualified with `img` on purpose: `.pp-media img` is (0,1,1) and a bare
   `.pp-media-logo` is only (0,1,0), so the reset would lose the specificity
   contest and the logo would stay absolutely positioned in the corner.
   Same shape as the `.pp-media img.pp-gallery-slide` rule below. */
.pp-media img.pp-media-logo{
  position:relative; inset:auto;
  /* A DEFINITE height, not max-height: `height:auto` refuses to scale an
     asset up, and metro-lagoons.svg carries explicit 181x27 attributes, so
     it rendered a third the height of every other mark. All three SVGs here
     are pure vector (no embedded rasters) and the rasters are 960px wide or
     more, so a 92px box downscales everything except that one lossless SVG.
     object-fit:contain keeps the aspect ratio when max-width clamps a wide
     mark, so nothing is ever stretched. */
  width:auto; height:92px;
  max-width:min(320px, 74%);
  object-fit:contain; flex:0 0 auto;
}
/* A logo only needs a plate where the surface behind it is dark; on the tan
   and sky cards it sits directly on the surface. --pp-logo-plate is set per
   variant with the other surface tokens. */
[data-chrome="popup"] .pp-media-logo{
  background:var(--pp-logo-plate, transparent);
  border-radius:14px;
  padding:var(--pp-logo-pad, 0);
}
/* No plate in flat any more: the surface behind the mark is the page itself,
   which is light in light theme and the site's navy environment in dark, and
   every logo in the set reads on both. */
[data-chrome="flat"] .pp-media-logo{ background:transparent; padding:0; }
.pp-media img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
/* client-deck photo grid (transparent-background PNG, content fills the
   canvas edge-to-edge) — contained and centered rather than cropped to
   fill, so the whole grid stays intact on the dark panel. Same
   position-reset trap as .pp-logo-lg: this inherits .pp-media img's
   absolute/inset:0/cover above unless explicitly undone here. */
.pp-media img.pp-gallery-slide{
  position:relative; inset:auto;
  width:auto; height:auto;
  max-width:92%; max-height:90%;
  object-fit:contain;
  filter:drop-shadow(0 14px 28px rgba(10,28,64,.22));
  z-index:1;
}
.pp-media .pp-mono-lg{
  font-family:var(--font-serif); font-weight:700; font-size:calc(92px * var(--type-scale, 1));
  color:rgba(255,255,255,.92); z-index:1;
}
.pp-media .pp-logo-lg{
  position:relative; inset:auto;
  max-width:60%; max-height:42%; width:auto; height:auto;
  object-fit:contain; z-index:1;
}
/* pp-media--plate: for the one white-on-transparent mark (World Music
   Sessions). Every other logo reads on the card surface directly; this one
   would vanish, so it keeps a dark plate of its own. */
/* Applies in BOTH chromes: flat's media panel is no longer dark either, so
   this mark would be white-on-light there too. */
.pp-media--plate .pp-logo-lg{
  background:linear-gradient(160deg, var(--navy-2), var(--navy));
  border-radius:18px; padding:46px 54px;
  max-width:78%; max-height:none;
}

.pp-nav{ display:flex; gap:10px; justify-content:flex-end; padding-top:14px; }
.pp-nav a{
  width:40px; height:40px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line-lite); color:var(--ink-dim);
  transition:all var(--speed) ease;
}
.pp-nav a:hover, .pp-nav a:focus-visible{ border-color:var(--blue); color:var(--blue); transform:translateY(-2px); }
.pp-nav a svg{ width:16px; height:16px; }

@media (max-width:860px){
  .pp-card{ grid-template-columns:1fr; }
  .pp-media{ min-height:220px; order:-1; border-radius:20px 20px 0 0; }
  [data-chrome="flat"] .pp-media{ border-radius:0; }
  /* stacked layout: no room beside the card, and .pp-media (now on top)
     would swallow a side-positioned button anyway — float above instead,
     same as the desktop fix used to before it moved to the side. */
  .pp-wrap{ padding-top:118px; }
  .pp-close{ top:-46px; right:8px; }
}

/* ---------- dark theme ----------
   Mirrors the homepage: the chrome (topbar, bands, footer) is light by
   default and flips here, so an interior page looks like the same site as
   the homepage in both themes. Panels stay dark in both, which is the
   established language - dark modules on a light-blue environment. */
:root[data-theme="dark"]{
  --line-lite: rgba(155,205,253,.24);
  --ink:       #eef4fd;
  --ink-dim:   #b4c5e6;
}
:root[data-theme="dark"] body{
  background:
    radial-gradient(90% 40% at 15% 30%, rgba(45,99,216,.18), transparent 70%),
    radial-gradient(80% 50% at 90% 70%, rgba(3,60,164,.28), transparent 70%),
    linear-gradient(180deg, #050f26 0%, #061634 40%, #072050 100%);
  background-attachment:fixed;
}
:root[data-theme="dark"] :focus-visible{ outline-color:var(--sky); }

/* top bar */
:root[data-theme="dark"] .topbar{
  background:linear-gradient(to bottom, rgba(4,12,30,.9), rgba(4,12,30,.45) 75%, transparent);
}
:root[data-theme="dark"] .topbar.is-scrolled{
  background:rgba(5,15,38,.97);
  border-bottom:1px solid var(--line-dark);
}
:root[data-theme="dark"] .brand .logo-light{ display:none; }
:root[data-theme="dark"] .brand .logo-dark{ display:block; }
:root[data-theme="dark"] .topnav a{ color:var(--on-dark-dim); }
:root[data-theme="dark"] .topnav a:hover,
:root[data-theme="dark"] .topnav a:focus-visible{ color:#fff; border-color:var(--sky); }
:root[data-theme="dark"] .topnav a[aria-current="page"]{ color:var(--sky); border-color:var(--gold); }   /* gold current-page rule on the dark bar; on the light bar it stays blue, where gold would be 1.0:1 */
/* the Menu button and its panel on the dark bar: a light plate on navy, the
   same move .topbar.is-scrolled makes, so the panel reads as the bar growing */
:root[data-theme="dark"] .nav-toggle{
  border-color:rgba(155,205,253,.3);
  background:rgba(6,22,52,.4);
  color:var(--sky);
}
:root[data-theme="dark"] .nav-toggle:hover{ border-color:var(--sky); background:rgba(6,22,52,.7); }
:root[data-theme="dark"] .topnav.is-open{
  background:rgba(5,15,38,.98);
  border-bottom-color:var(--line-dark);
}
:root[data-theme="dark"] .topnav.is-open a{ box-shadow:inset 0 -1px 0 rgba(155,205,253,.14); }
:root[data-theme="dark"] .topnav.is-open a:last-child{ box-shadow:none; }
:root[data-theme="dark"] .topnav.is-open a[aria-current="page"]{
  box-shadow:inset 2px 0 0 var(--gold), inset 0 -1px 0 rgba(155,205,253,.14);
}
:root[data-theme="dark"] .theme-toggle{
  border-color:rgba(155,205,253,.3);
  background:rgba(6,22,52,.4);
  color:var(--sky);
}
:root[data-theme="dark"] .theme-toggle:hover,
:root[data-theme="dark"] .theme-toggle:focus-visible{
  border-color:var(--sky); background:rgba(155,205,253,.14); color:#fff;
}

/* buttons */
:root[data-theme="dark"] .btn-ghost{
  border:1px solid rgba(238,244,253,.4);
  color:var(--on-dark);
  background:rgba(6,22,52,.3);
}
:root[data-theme="dark"] .btn-ghost:hover,
:root[data-theme="dark"] .btn-ghost:focus-visible{
  border-color:var(--sky); background:rgba(155,205,253,.12);
}

/* bands + page hero */
:root[data-theme="dark"] .band{ color:var(--on-dark); }
:root[data-theme="dark"] .band::before{
  background:
    radial-gradient(110% 90% at 85% 0%,  rgba(45,99,216,.4),   transparent 55%),
    radial-gradient(70% 70% at 100% 60%, rgba(3,60,164,.5),    transparent 65%),
    radial-gradient(60% 60% at 8% 85%,   rgba(155,205,253,.1), transparent 65%),
    linear-gradient(115deg, #050f26 15%, #071a42 60%, #072055 100%);
}
:root[data-theme="dark"] .eyebrow{ color:var(--sky); }
:root[data-theme="dark"] .page-hero h1, :root[data-theme="dark"] .page-hero .sv2-h1{ color:#fff; }
:root[data-theme="dark"] .page-hero p.lede{ color:var(--on-dark-dim); }
:root[data-theme="dark"] .closing h2{ color:#fff; }
:root[data-theme="dark"] .closing p{ color:var(--on-dark-dim); }
:root[data-theme="dark"] .sec-title{ color:var(--ink); }

/* cards: the dark hairline vanishes against a dark page.
   This sets the TOKEN, not border-color. As a border-color declaration it was
   (0,3,0) and out-ranked .panel:hover at (0,2,0), so the hover edge never
   appeared in dark theme at all -- a bug that predates the CMS blocks. */
:root[data-theme="dark"]{
  --card-edge:var(--line-dark);
}

/* footer */
:root[data-theme="dark"] footer{
  border-top:1px solid var(--line-dark);
  color:var(--on-dark-mute);
}
:root[data-theme="dark"] .social a{ color:var(--sky); }
:root[data-theme="dark"] .social a:hover,
:root[data-theme="dark"] .social a:focus-visible{ color:#fff; }
:root[data-theme="dark"] footer nav a{ color:var(--on-dark-mute); }
:root[data-theme="dark"] footer nav a:hover,
:root[data-theme="dark"] footer nav a:focus-visible{ color:var(--sky); }
:root[data-theme="dark"] footer nav a[aria-current="page"]{ color:var(--sky); }
:root[data-theme="dark"] .foot-about h2{ color:#fff; }
:root[data-theme="dark"] .foot-contact a:hover,
:root[data-theme="dark"] .foot-contact a:focus-visible{ color:var(--sky); }
:root[data-theme="dark"] .band :focus-visible,
:root[data-theme="dark"] footer :focus-visible{ outline-color:var(--sky); }

/* property-detail: flat chrome follows theme (--ink/--ink-dim already
   auto-flip, so only the non-token values need an explicit override
   here); popup's tan card stays tan regardless, styled directly above,
   not with these theme-flipping tokens. */
/* NOTE the missing space: data-theme and data-chrome are BOTH on <html>, so
   `:root[data-theme="dark"] [data-chrome="flat"]` (with a descendant
   combinator) asks for two different elements and matches NOTHING. These five
   rules were silently dead until 2026-08-14 — dark-theme flat chrome kept the
   light-theme pill colour at 1.87:1. Keep them compound. */
:root[data-theme="dark"][data-chrome="flat"] .pp-pill{ border-color:rgba(155,205,253,.35); color:var(--sky); }
:root[data-theme="dark"][data-chrome="flat"] .pp-soon{ color:var(--sky); }
:root[data-theme="dark"][data-chrome="flat"] .pp-bullets{ border-color:var(--line-dark); }
:root[data-theme="dark"][data-chrome="flat"] .pp-bullet svg{ color:var(--sky); }
:root[data-theme="dark"][data-chrome="flat"] .pp-close{ background:rgba(155,205,253,.14); color:var(--on-dark); }
:root[data-theme="dark"] .pp-close:hover, :root[data-theme="dark"] .pp-close:focus-visible{ color:var(--sky); }
:root[data-theme="dark"] .pp-nav a{ border-color:var(--line-dark); color:var(--on-dark-dim); }
:root[data-theme="dark"] .pp-nav a:hover, :root[data-theme="dark"] .pp-nav a:focus-visible{ color:var(--sky); border-color:var(--sky); }

/* ---- slide layout v2 (2026-08-18, Bronze's sketch) ----
   Card is rows now: a related-property tile rail on top, then a "stage" of
   text panel (left) + auto-advancing image slider (right). The old
   .pp-body/.pp-media two-column CSS above is dormant (nothing renders it);
   reviving the flat chrome would need markup work anyway.

   The slider is the reason the --pp-panel tokens went OPAQUE: its viewport
   tucks 26px underneath the panel's right edge (negative margin, lower
   z-index), so images glide in from the card's right edge and vanish under
   the text box. A translucent panel would let them show through the copy. */
.pp-tiles{
  position:relative; z-index:1;
  display:flex; gap:14px; align-items:flex-start;
  padding:18px 26px 2px; overflow-x:auto;
  scrollbar-width:none;
}
.pp-tiles::-webkit-scrollbar{ display:none; }
.pp-tile{
  flex:0 0 72px; display:flex; flex-direction:column; gap:5px;
  text-align:center;
}
.pp-tile b{
  /* CAPPED AT THE LARGE STEP: the tile is 72px wide and clamps its label to two lines, so at Larger a name like "Stadium Naming
     Rights" overflows it. min() keeps Large (1.125) and stops Larger (1.25) from growing this one label further. */
  font-size:calc(10px * min(var(--type-scale, 1), 1.125)); font-weight:700; letter-spacing:.02em;
  color:var(--pp-copy); line-height:1.25;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.pp-tile:hover b, .pp-tile:focus-visible b{ color:var(--pp-strong); }
/* ONE PLAIN TILE (Bronze, 2026-10-14: the homepage's Featured tile and this one "should be the same, just make the popup page
   version smaller"). This is the homepage's .ptile-art (132px, inline in trenches-streaming-home.html and not available here,
   block rule 8) drawn at 72px: everything that has a size is scaled by 72/132 (radius 12px to 6.5px, the shadow 0 8px 22px to
   0 4px 12px, the hover lift -4px to -2px); the border, plate, scale-on-hover and the 1px ring are the same. Only the colours
   are the popup card's own fixed tokens (--pp-edge, --pp-accent), because the card is navy, tan or sky whatever the theme.
   The hover lift is on the ART, as on the homepage, not the whole tile. The picture covers the square; with none, or one that
   fails to load (property-rail.mjs: onerror removes it), the navy plate stays. */
.pp-tile-art{
  display:block; width:100%; aspect-ratio:1/1;
  border-radius:6.5px; overflow:hidden; position:relative;
  border:1px solid var(--pp-edge);
  background:linear-gradient(160deg, var(--navy-2), var(--navy));
  box-shadow:0 4px 12px rgba(7,25,70,.18);
  transition:transform var(--speed) ease, border-color var(--speed) ease, box-shadow var(--speed) ease;
}
.pp-tile:hover .pp-tile-art, .pp-tile:focus-visible .pp-tile-art{
  transform:translateY(-2px) scale(1.04);
  border-color:var(--pp-accent);
  box-shadow:0 0 0 1px var(--pp-accent);
}
.pp-tile-art img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
/* ⇉ THE RAIL FOLLOWS THE CARD'S CATEGORY SWITCHES (Bronze, 2026-10-16; assets/property-filter.js). A tile outside the
   switched-on categories carries [hidden], which .pp-tile's display:flex would override without this rule (an author
   rule beats the browser's own [hidden]); and when no tile is left, the rail's one line says so. */
.pp-tile[hidden], .pp-tiles-none[hidden]{ display:none; }
/* As tall as a row of tiles (102px measured: the 72px picture, the gap and two lines of words), so switching between
   tiles and this line does not move the card below. */
.pp-tiles-none{
  margin:0; padding:0; min-height:102px; display:flex; align-items:center;
  font-size:calc(11.5px * var(--type-scale, 1)); color:var(--pp-copy); opacity:.8;
}
/* The pills as switches: only once the script has made them buttons, so without JavaScript they stay plain labels. */
.pp-pill[role="button"]{ cursor:pointer; user-select:none; transition:opacity var(--speed) ease, border-color var(--speed) ease; }
.pp-pill[role="button"]:hover{ border-color:var(--pp-accent); }
.pp-pill[role="button"].is-off:hover{ opacity:.62; }
.pp-pill[role="button"]:focus-visible{ outline:2px solid var(--pp-accent); outline-offset:2px; }

.pp-stage{
  position:relative; z-index:1;
  display:grid; grid-template-columns:minmax(0, 1.08fr) minmax(0, .92fr);
  align-items:stretch;
  padding:12px 0 26px 28px;
  /* the uniform-height knob: fixed per viewport, so every property slide is
     the same height and fits above the fold on common screens. The panel
     scrolls internally as the safety valve on short viewports.
     ⇉ THE TEXT SIZE GROWS IT: the panel's copy, bullets and Contact Us button grow with the Text size, but this height did not, so at
     Large and Larger the button was pushed under the panel's own scroll (72 to 235px of the panel out of sight on a 1440x900 screen,
     the button below the fold on all six pages). The extra is 1000px per whole step of the multiplier on a wide screen (125px at Large,
     250px at Larger) and up to 1500px on a narrow one (the panel is narrower there, so its text wraps into more lines): measured, that
     keeps what is out of sight at the standard amount or close to it. With no size chosen it is 0. */
  height:calc(clamp(500px, calc(100vh - 330px), 700px) + (var(--type-scale, 1) - 1) * clamp(1000px, 2400px - 97vw, 1500px));
}
.pp-panel-scroll{ overflow-y:auto; scrollbar-width:thin; min-height:0; }
[data-chrome="popup"] .pp-stage .pp-panel{
  position:relative; z-index:2;
  overflow-y:auto; scrollbar-width:thin;
  height:100%; min-width:0;
  padding:16px 26px 18px; gap:10px;
}
.pp-head{
  display:flex; justify-content:space-between; align-items:flex-start; gap:16px;
}
.pp-head .pp-pills{ background:none; border:none; padding:2px 0 0; }
.pp-pill.is-off{
  opacity:.34; border-color:var(--pp-edge); color:var(--pp-copy);
}
.pp-panel-logo{
  flex:0 0 auto; height:54px; width:auto; max-width:180px;
  object-fit:contain;
  background:var(--pp-logo-plate, transparent);
  border-radius:12px; padding:7px 12px;
}
.pp-panel-logo.is-bare{ background:transparent; padding:0; }
/* The ghost button INSIDE the popup panel: the Property card's "View the full presentation" (slice A2). .btn-ghost takes its
   colours from the THEME tokens (--ink, --on-dark), which flip with the page, but the popup card's surface does not: it is navy
   (or tan, or sky, under ?bg=) in BOTH themes, so the stock button was illegible on it (about 3.7:1 measured on navy). Like the
   pills, tiles and bullets around it, this one reads the card's own FIXED tokens, on a transparent ground so the panel shows
   through. It ties the dark-theme .btn-ghost rules on weight, so it stays BELOW them; the hover pair needs the same place.
   Scoped to the panel: the ghost button anywhere else is unchanged. */
[data-chrome="popup"] .pp-panel .btn-ghost{
  background:transparent; border-color:var(--pp-accent); color:var(--pp-strong);
}
[data-chrome="popup"] .pp-panel .btn-ghost:hover,
[data-chrome="popup"] .pp-panel .btn-ghost:focus-visible{
  background:var(--pp-edge); border-color:var(--pp-accent); color:var(--pp-strong);
}
/* The Property card's description: a list or a heading the client types there (slice A2). The card block puts the converted words
   straight into div.pp-copy (PropertyCard.tsx), with none of the Text block's wrapper, so only `.pp-copy p` is coloured and sized
   (above). A <ul>, <ol>, <li>, <h2> or <h3> inherited the colour of the .band the whole page sits in, which is --ink: near-black
   in light theme, about 1.3:1 on the navy panel; set 16px against the paragraphs' 13.8px; and (`*{padding:0}`) hung its markers
   outside the panel's edge. The `--ink` redefinition on .pp-copy (above) cannot fix the first: a custom property reaches only a
   `color:var(--ink)` declared INSIDE .pp-copy, and `color` itself is inherited as an already-computed value. So .pp-copy takes
   the colour here, reading that same redefined --ink (the card's --pp-copy in popup chrome, --ink-dim in flat), and everything
   inside inherits it. No chrome prefix: nothing else sets a colour or size on .pp-copy, so there is no tie to win, and the one
   redefinition already decides the colour per chrome. Scoped to the card block: the hand-authored .pp-copy markup is unchanged. */
[data-block="property-card"] .pp-copy{ color:var(--ink); font-size:calc(13.8px * var(--type-scale, 1)); line-height:1.52; }
[data-block="property-card"] .pp-copy :is(ul, ol){ padding-left:1.1em; }

.pp-slider{
  position:relative; z-index:1; min-width:0;
  /* ⇉ min-height:0 (2026-10-09). A grid item's automatic minimum height is its CONTENT height, and this column's
     content is the slide, whose `height:100%` cannot resolve while the row is being sized, so it fell back to the
     picture's own aspect ratio. A 16:9 picture fit inside the stage; a 3:2 one (the property slides, once their
     transparent padding is trimmed) made the row 96px taller than the stage's fixed height and the cards came out
     square. With the minimum at 0 the stage's height decides the row, as the `height` knob above intends. */
  min-height:0;
  display:flex; flex-direction:column;
  /* tuck under the panel: this is where the images "disappear" */
  margin-left:-26px;
}
.pp-viewport{ flex:1; min-height:0; overflow:hidden; }
.pp-slides{
  display:flex; gap:18px; height:100%;
  padding-left:44px;    /* breathing room so a resting slide clears the panel edge */
  transition:transform .6s cubic-bezier(.25,.7,.3,1);
  will-change:transform;
}
.pp-slide{
  flex:0 0 auto;
  width:calc(100% - 62px); height:100%;
  object-fit:cover; border-radius:14px;
  border:1px solid var(--pp-edge);
}
.pp-slider-nav{ display:flex; gap:8px; justify-content:flex-end; padding:12px 24px 0 0; }
.pp-slider-nav button{
  width:32px; height:32px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--pp-edge); color:var(--pp-accent);
  transition:border-color var(--speed) ease, color var(--speed) ease, transform var(--speed) ease;
}
.pp-slider-nav button:hover, .pp-slider-nav button:focus-visible{
  border-color:var(--pp-accent); color:var(--pp-strong); transform:translateY(-1px);
}
.pp-slider-nav svg{ width:14px; height:14px; }

@media (prefers-reduced-motion: reduce){
  .pp-slides{ transition:none; }
  .pp-tile-art{ transition:none; }
  .pp-tile:hover .pp-tile-art, .pp-tile:focus-visible .pp-tile-art{ transform:none; }
}
@media (max-width: 980px){
  .pp-stage{ grid-template-columns:1fr; height:auto; padding:14px 24px 26px; gap:18px; }
  [data-chrome="popup"] .pp-stage .pp-panel{ height:auto; overflow:visible; }
  .pp-slider{ margin-left:0; }
  .pp-viewport{ height:240px; min-height:240px; flex:0 0 auto; }
  .pp-slides{ padding-left:0; }
  .pp-slide{ width:calc(100% - 40px); }
  .pp-panel-logo{ height:44px; max-width:140px; }
}
/* On a phone the property title keeps its standard size at every Text size: "Championship" at 34px or more does not fit the card's line
   (26px is the flat chrome's phone size, 30px the popup chrome's). Placed after the rules it overrides, which have the same weight.
   (Changing the bullet columns or letting the header row wrap would have changed the standard layout at 390px too: tried, and the
   differ caught it.) */
@media (max-width: 480px){
  .pp-title{ font-size:calc(26px * min(var(--type-scale, 1), 1)); }
  [data-chrome="popup"] .pp-title{ font-size:calc(30px * min(var(--type-scale, 1), 1)); }
}

/* ---------- Pause/Play for the auto-moving strips (2026-09-28) ----------
   WCAG 2.2.2, the audit's M-a11y-1. assets/motion-toggle.js builds the button
   and says where it may and may not go. The look came from the homepage hero
   reel's own play/pause button, so every pause control on the site is the same
   control. That reel's controls were deleted with the old header on 2026-10-07
   and these rules are what is left of the look; the homepage still has the
   cluster they sat in (.vid-ctrls, in that page's own <style>), because the
   header's slideshow puts its Pause button there.
   `button.motion-btn`, not `.motion-btn`: that outranks the global
   :focus-visible rule's 4px radius outright, and TIES with `.pp-slider-nav
   button` above (written in c6269cc for Previous/Next photo buttons that no
   longer ship), where source order decides. Keep this block below that one. */
button.motion-btn{
  flex:0 0 auto;
  width:40px; height:40px; border-radius:50%;
  background:rgba(4,12,32,.74);
  border:1px solid rgba(155,205,253,.38);
  color:#fff;
  display:flex; align-items:center; justify-content:center;
  transition:background var(--speed) ease, border-color var(--speed) ease, transform var(--speed) ease;
}
button.motion-btn:hover, button.motion-btn:focus-visible{
  background:rgba(3,60,164,.85);
  border-color:var(--sky);
  transform:scale(1.06);
}
/* display:flex above would beat the hidden attribute, and the homepage
   spotlight hides its button whenever it stops rotating (under 900px). */
button.motion-btn[hidden]{ display:none; }
.motion-btn svg{ width:16px; height:16px; }
.motion-btn .alt{ display:none; }
.motion-btn.is-alt .base{ display:none; }
.motion-btn.is-alt .alt{ display:block; }
/* A row of its own, under a strip whose page has no control row to borrow. */
.motion-row{ display:flex; justify-content:flex-end; }
