   /* Viewport Taxonomy
 * Mobile, ≤ 767px
 * Tablet, 768–999px
 * Tablet Plus, 1000–1329px
 * Desktop, ≥ 1330px
 * Superwide (Ultra-wide Desktop), ≥ 1920px
 *
 * Desktop, type can scale modestly with viewport up to 1920px
 * Superwide, typography is locked, layout breathes via gutters only
 */

:root{
  /* TODO(csg-usa): accent colour value - blocked on the US brand palette being chosen.
     The hex below is inherited from the source theme and is provisional. The token NAMES
     are deliberately unchanged: --csg-gold is also defined independently in
     inc/admin/admin-acf.css, and renaming here would split the vocabulary across the
     theme and churn every reference site for no rendered benefit. Change the value in
     one place when the palette lands; the names are theme-scoped and render nothing. */
  --csg-brass:#AFA287;
  --csg-gold:var(--csg-brass);  /* alias - identical hex, kept for existing usages */
  --charcoal:#1C1F22;
  --stone:#EAE8E4;
  --grey:#7B7F85;
  --copper:#B67352;
  --forest:#2F4A3B;

  --bg:#FFFFFF;
  --surface:#FFFFFF;
  --surface-editorial:#F7F6F3;
  --surface-card:#FFFFFF;
  --border:rgba(28,31,34,.14);
  --shadow:0 16px 50px rgba(28,31,34,.10);
  --radius:18px;
  --btn-radius: 6px;

  --max:1180px;
  --pad:24px;
  --hero-rail: min(1180px, calc(100% - 48px));
  --hero-fullbleed-top-gap: clamp(28px, 3.2vw, 64px);

  --h1:clamp(34px, 4vw, 54px);
  --h2:clamp(22px, 2.6vw, 34px);
  --h3:20px;
  --body:17px;
  --small:13px;

  --lh:1.55;

  --header-height: 132px;
  --header-offset: var(--header-height);
  --header-blur: 10px;

  /* header surfaces */
  --surface-header-light: var(--bg);
  --surface-header-dark: #000000;

  /* header opacity/blur (tunable) */
  /* 1, not 0.84. Duncan, 2026-09-01: "use white background".
     A translucent header over a full-bleed hero and then over white sections does not hold one
     colour - it takes on whatever scrolls beneath it, which reads as the header changing colour
     even when its own theme is fixed. Solid white removes the second cause of the switching he
     reported; pinning the theme in header-scroll.js removed the first. */
  --header-light-alpha: 1;

  /* wide gutters for header and footer on ultra-wide screens */
  /* R02 (review, f790570): this was clamp(24px, calc((100vw - 1920px)/2 + 150px), 150px).
     The calc is designed to hold the wide rail at a constant 1620px above 1920 - gutter grows
     by exactly half the extra viewport - but the 150px UPPER BOUND stopped it doing so, so the
     gutter froze at 150 and .wrap--wide (max-width:none) grew without limit: measured 1620 at
     1920, 2260 at 2560, 3140 at 3440, with the portfolio image reaching 1575px while its title
     stayed at 160px and visually detached from it.
     max() instead of clamp() keeps the lower bound and drops the bound that was the bug. The
     calc only exceeds 150px once the viewport passes 1920, so EVERY width below 1920 computes
     identically to before and the change is confined to the superwide range it fixes. */
  --gutter-wide: max(24px, calc((100vw - 1920px) / 2 + 150px));

  --matte-inner-ratio: 0.63636;
  --matte-radius: 22px;
  --print-radius: 6px;

  /* Golden ratio spacing */
  --phi: 1.618;
  --space-1: 12px;
  --space-2: calc(var(--space-1) * var(--phi));
  --space-3: calc(var(--space-2) * var(--phi));
  --space-4: calc(var(--space-3) * var(--phi));

  --panel-black: #000;
  --panel-white: transparent;

  --callout-bg: transparent;
  --callout-border: rgba(255,255,255,.08);  /* on dark sections */
  --callout-border-light: rgba(28,31,34,.14); /* if used on light sections */
  --callout-radius: 6px;   /* near-square, print-like */
  --callout-pad: 34px;
  --callout-max: 560px;

  --cta-btn-bg: var(--csg-gold);
  --cta-btn-fg: #111;
  --cta-btn-radius: 999px;
  --cta-btn-h: 46px;
}

/* Typography scaling, Desktop to 1920, then lock on superwide */
@media (min-width: 1330px){
  :root{
    --body: clamp(17px, 1.35vw, 21px);
    --small: clamp(13px, 0.85vw, 14px);
    --h3:   clamp(17px, 1.35vw, 21px);
    --h4:   clamp(17px, 1.35vw, 21px);
    --h2: clamp(28px, 2.2vw, 42px);
    --h1: clamp(48px, 3.40vw, 60px);
  }
}

@media (min-width: 1920px){
  :root{
    --body: 21px;
    --small: 14px;
    --h3:   21px;
    --h4:   21px;
    --h2: 42px;
    --h1: 60px;
  }
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  background:var(--bg);
  color:var(--charcoal);
  padding-top: var(--header-height);
  font-family:"Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size:var(--body);
  /* Archivo is a dense grotesque, and 300 is the weight Duncan chose off the ladder on
     2026-09-17 after "this font feels heavy throughout". This is the DOCUMENT DEFAULT, so
     anything that must not thin out with it - buttons, the nav CTA, headings - has to carry
     its own font-weight rather than inherit. */
  font-weight:300;
  line-height:var(--lh);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* Enforce serif on all headings by default */
h1, h2, h3, h4, h5, h6{
  font-family: "Libre Bodoni", Didot, "Bodoni MT", Georgia, "Times New Roman", serif;
}

/* Explicitly lock body + UI elements to Archivo */
p,
li,
span,
a,
button,
input,
textarea,
label,
nav{
  font-family: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.serif{font-family:"Libre Bodoni", Didot, "Bodoni MT", Georgia, "Times New Roman", serif}
a{color:inherit; text-decoration:none}
button{font:inherit}
img{max-width:100%; display:block}

.visually-hidden{
  position:absolute !important;
  width:1px;
  height:1px;
  padding:0;
  margin:-1px;
  overflow:hidden;
  clip:rect(0,0,0,0);
  white-space:nowrap;
  border:0;
}

.is-protected{
  position:relative;
}

.is-protected .image-shield{
  position:absolute;
  inset:0;
  z-index:2;
  background:transparent;
  cursor:default;
  pointer-events:auto;
}

.is-protected img{
  pointer-events:none;
  user-select:none;
  -webkit-user-drag:none;
}

.wrap{max-width:var(--max); margin:0 auto; padding:0 var(--pad)}

/* Wide rail modifier - aligns with header/footer gutters */
.wrap--wide{
  max-width: none;
  width: 100%;
}

@media (min-width: 768px){
  .wrap--wide{
    padding-inline: 75px;
  }
}

@media (min-width: 1330px){
  .wrap--wide{
    padding-inline: max(75px, var(--gutter-wide));
  }
}

.section{padding:64px 0}

.section--editorialWide{
  margin-top: clamp(48px, 7vw, 120px);
}

.section--editorialWide .editorialWide-inner{
  max-width: 72ch;
}

.section--editorialWide h2{
  margin-top: 0;
}

/* Intro section – dark background */
.section-intro--dark{
  background:#000;
  color:#fff;
}

.section-intro--dark h2,
.section-intro--dark h3{
  color:#fff;
}

.section-intro--dark p,
.section-intro--dark li,
.section-intro--dark span{
  color:rgba(255,255,255,.9);
}

.section-intro--dark .muted{
  color:rgba(255,255,255,.7);
}

.section-intro--dark .card{
  background:#111;
  border-color:rgba(255,255,255,.12);
}

.section-intro--dark .tick{
  background-color:#fff;
}

/* Intro section – remove the default section divider seam */
#section-intro.section-intro--dark{
  border-top: 0;
  margin-top: 0;
}

/* If there is still a 1px seam due to sub-pixel rendering, cover it */
#section-intro.section-intro--dark{
  margin-top: -1px;
}

/* Intro editorial points (no bullets) */
#section-intro .intro-points{
  margin-top: 16px;
}

/* Intro editorial points – Condé-style hierarchy */
#section-intro .intro-points p{
  margin: 0 0 18px 0;
}

#section-intro .point-lead{
  display: block;
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: #fff;
  margin-bottom: 4px;
}

#section-intro .point-body{
  display: block;
  /* MATCHES THE ABOUT PAGE. Duncan, 2026-09-14: "font size on about page ... is good but on
     the home page is too small". Measured: the About body ran at 19.44px/30.13px and this at
     15.2px/22.04px, because .point-body was built as the small supporting line UNDER a bold
     lead in the old fact-bullet layout. The leads are empty now and each span carries a whole
     paragraph, so the reason for the step down went with the bullets.

     inherit rather than a number, so it tracks the paragraph it sits in and cannot drift away
     from the About page the next time the body scale moves. */
  font-size: inherit;
  line-height: inherit;
  color: rgba(255,255,255,.82);
}

.kicker{
  font-size:var(--small);
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(28,31,34,.70);
}

/* Utilities (Phase 1 cleanup) */
.mt-6{ margin-top:6px; }
.mt-18{ margin-top:18px; }
.m-0{ margin:0; }

.grid-gap-sm{
  display:grid;
  gap:10px;
}

.wrap-narrow{
  max-width:860px;
}

/* Header */
.header{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  will-change: transform;
  transition: transform 160ms ease;
  border-bottom: 0;
  box-shadow: none;

  --header-surface: var(--surface-header-light);
  --header-alpha: var(--header-light-alpha);

  background: color-mix(in srgb, var(--header-surface) calc(var(--header-alpha) * 100%), transparent);
  -webkit-backdrop-filter: blur(var(--header-blur));
  backdrop-filter: blur(var(--header-blur));
}
.header-rail {
  width: 100%;
}

.header-inner{
  display:flex;
  align-items:center;
  /* flex-start plus margin-left:auto on .nav, NOT space-between.
   *
   * Duncan, 2026-08-31: "still acres of padding between get in touch and burger". Measured at
   * 1920 it was 398px. space-between distributes THREE children across the rail, so it pushed
   * the burger to the far edge and left a hole between it and the nav - the gap was not
   * padding anyone had set, it was the algorithm doing what it is for.
   *
   * Pushing only .nav with margin-left:auto groups the nav and the burger as one cluster on
   * the right, separated by the 16px gap below and nothing else. */
  justify-content:flex-start;
  gap:16px;
  padding-block: 14px;
  padding-inline: var(--header-gutter, var(--pad));
}
.header.is-hidden{
  transform: translateY(-110%);
}

/* Defensive: hero-top never hides header even if .is-hidden is applied */
body.is-hero-top .header.is-hidden{
  transform: translateY(0);
}

/* Header theme classes */
.header.is-theme-dark {
  --header-surface: var(--surface-header-dark);
  --header-alpha: 1;
  background: var(--header-surface);
  color: #fff;
  border-bottom: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.header.is-theme-dark .nav a,
.header.is-theme-dark .menu a,
.header.is-theme-dark .nav-toggle,
.header.is-theme-dark .menu-toggle {
  color: #fff;
}

.header.is-theme-dark .nav a:hover,
.header.is-theme-dark .nav button:hover {
  border-color: rgba(255,255,255,.25);
  background: rgba(255,255,255,.12);
}

.header.is-theme-light {
  --header-surface: var(--surface-header-light);
  --header-alpha: var(--header-light-alpha);
  background: color-mix(in srgb, var(--header-surface) calc(var(--header-alpha) * 100%), transparent);
  color: var(--charcoal);
  border-bottom: 0;
  box-shadow: none;
  -webkit-backdrop-filter: blur(var(--header-blur));
  backdrop-filter: blur(var(--header-blur));
}

.header.is-theme-light .nav a,
.header.is-theme-light .menu a,
.header.is-theme-light .nav-toggle,
.header.is-theme-light .menu-toggle {
  color: rgba(28,31,34,.86);
}

.header.is-theme-light .nav a:hover,
.header.is-theme-light .nav button:hover {
  border-color: var(--border);
  background: rgba(255,255,255,.55);
}

.brandmark{
  display:flex;
  align-items:center;
  gap:12px;
}
/* 132px. Duncan, 2026-08-31: "logo needs to be 3x the size".
   I had it at 44px, which is the conventional height for a horizontal mark in a sticky
   header. His call overrides that, and it is a brand-presence decision rather than a
   technical one.
   The consequence is real and is handled rather than absorbed silently: at a flat 132px the
   linear 636x255 lockup renders ~330px wide, taking 220px of rail width the ten-item nav
   needs - the rail would then require a 1401px viewport and start CLIPPING items below that,
   which is exactly the defect review caught last time.
   So the height is a clamp, not a constant. It reaches the full 132px at 1886px and above,
   which is the screen this was asked for, and scales down below that so the nav keeps its
   room. The drawer breakpoint is recomputed to 1349px against the widest logo the rail has
   to coexist with. If the ten-item top-level menu were trimmed, both numbers would relax -
   but the menu is Duncan's. */
.brandmark img{height:clamp(72px, 7vw, 132px); width:auto; max-width:100%}

.brandmark-img--mobile{ display:none; }
.nav{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:nowrap;          /* stop header height changing */
  justify-content:flex-end;
  white-space:nowrap;        /* belt and braces */
  margin-left:auto;          /* pushes nav AND the burger after it to the right as one group */
}

@media (max-width: 1349px){
  .header .nav{ display:none; }
  .nav{
    flex-wrap:wrap;          /* allow wrap only on small screens */
    white-space:normal;
  }
}

/* The rail has to hold ten top-level items, and it did not.
 *
 * FOUND BY REVIEW, 2026-08-31: with the burger hidden above 767px, the header showed 4 of
 * 10 links at 768px, 7 at 1024 and 8 at 1280 - all ten only at 1330 and up. The items were
 * not wrapping or scrolling, they were being CLIPPED, and body overflow masking meant the
 * page looked intentional at every one of those widths. Planners, About and Contact simply
 * were not there.
 *
 * This predates the depth-2 dropdown, which only made it matter: a nav that silently drops
 * a third of itself is bad, and one that also holds the only route to 18 sub-pages is worse.
 *
 * Two changes together. This one buys room by tightening the rail between the drawer
 * breakpoint and 1400px; the drawer breakpoint below moves up to cover what is
 * still too narrow. Tightening alone would not have been enough, and moving the breakpoint
 * alone would have put a burger on a 1280px laptop that has room for the real thing. */
@media (min-width: 1350px) and (max-width: 1560px){
  .nav .nav-list{ gap:2px }
  .nav a, .nav button{
    padding:10px 7px;
    font-size:13.5px;
  }
}

/* Primary CTA inside cards */
.btn.alt{
  background:var(--csg-gold);
  border-color:var(--csg-gold);
  color:#1C1F22; /* charcoal for legibility */
  font-weight:500;
}

.btn.alt:hover{
  background:#9E956F;
  border-color:#9E956F;
  color:#1C1F22;
}

.nav a, .nav button{
  /* 17px, up from 14px. Duncan, 2026-08-31: "the menu font can now afford to be larger" - and
     it can: the rail carries four items instead of ten, and beside a 132px brandmark a 14px
     label looked undersized rather than restrained. Measured at 1920 the four items occupy
     507px against roughly 1275px of usable rail, so this is spending room that exists. */
  padding:10px 14px;
  border-radius: var(--btn-radius);
  border:1px solid transparent;
  background:transparent;
  cursor:pointer;
  font-size:17px;
  /* Same reason as .btn: this declared no weight, so the 300 document default thinned every
     navigation label. 400 restores what the browser default was already giving it. The CTA
     keeps its own 540 further down, and is unaffected either way. */
  font-weight:400;
  color:rgba(28,31,34,.86);
  transition:all .18s ease;
}
.nav a:hover, .nav button:hover{
  border-color:var(--border);
  background:rgba(255,255,255,.55);
}

/* Mobile menu */
.nav-toggle{
  appearance:none;
  background:transparent;
  border:0;
  padding:10px;
  margin:0;
  cursor:pointer;
  /* Visible at EVERY width, beside "Get in Touch". It is the only route to the 26 items that
     are not on the four-item rail, so hiding it on desktop stranded most of the site. */
  display:inline-flex;
}

.nav-toggle-lines{
  width:22px;
  height:14px;
  display:inline-flex;
  flex-direction:column;
  justify-content:space-between;
}
.nav-toggle-lines span{
  display:block;
  height:1px;
  background:currentColor;
  opacity:.9;
}

/* Overlay – visibility via opacity/pointer-events, no display toggle */

/* THE BURGER IS ALWAYS VISIBLE, AND THE DRAWER WORKS AT EVERY WIDTH.
 *
 * Duncan, 2026-08-31: "no burger menu visible on the page top the right of Get in Touch on the
 * menu". A real gap in what I built: the rail was cut to four items and the other 26 moved
 * into the drawer, but the drawer only existed below the breakpoint - so a desktop visitor had
 * no route to Service Area, the nine metros, the channel sub-pages, Stretch Tents or Planners
 * and Venues at all. Four items and no way to reach the rest is worse than the ten-item rail
 * it replaced.
 *
 * So the drawer panel, overlay, open state and typography are now UNCONDITIONAL, and the
 * trigger renders at every width beside "Get in Touch". Only ONE thing stays
 * breakpoint-dependent - whether the four-item RAIL is shown - because that is the only part
 * that was ever about available width.
 *
 * The earlier reasoning still holds and is why the split is where it is: the drawer and its
 * trigger must share a breakpoint, or the trigger opens nothing. Making both unconditional
 * satisfies that rule rather than breaking it.
 */

/* Mobile closed: transform/opacity/pointer-events only, no display toggle */

/* A PANEL FROM THE RIGHT, not a full-screen takeover.
 *
 * Duncan, 2026-08-31: "burger menu opens full screen rather than from the rhs and the actual
 * menu is horrible". It was inset:0 at 100% x 100%, sliding down from the top - which on a
 * 1920px desktop meant one tap replaced the entire page with a list, giving no sense of
 * having opened something over the site rather than navigated away from it.
 *
 * Now a fixed-width panel anchored right, sliding in on translateX. It stays full-height and
 * scrolls internally, and goes full width only on a phone where 420px IS the screen.
 *
 * display:none is gone deliberately - a transform cannot animate from display:none, which is
 * why the old one snapped. visibility plus a delayed transition keeps it out of the tab order
 * while closed and still lets it slide. */
/* Open state: explicit transform + pointer-events so overlay/close/links are clickable */

/* Inner spacing */

/* Close button */

/* Typography */

@media (max-width: 767px){
  /* On mobile, avoid viewport-width gutters to prevent layout viewport from inflating scrollWidth */
  :root{
    --gutter-wide: 24px;
  }

  /* On mobile, contain horizontal overflow at document level */
  html,
  body{
    overflow-x: hidden;
  }

  /* On mobile, header participates in normal flow; remove global header offset padding */
  body{
    padding-top: 0;
  }

  /* Mobile header: size to document width and stick to top (no fixed overflow) */
  .header{
    position: sticky;
    top: 0;
    left: auto;
    right: auto;
  }

  /* Neutralise scroll-hide behaviour on mobile */
  .header.is-hidden{
    transform: none;
  }

  /* Mobile header: solid black background, always black, no theme switching */
  .header,
  .header.is-theme-light,
  .header.is-theme-dark {
    background: #000 !important;
    background-image: none !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    color: #fff !important;
  }

  /* Burger icon must always be white on mobile */
  .nav-toggle,
  .nav-toggle-lines span{
    color: #fff !important;
  }

  /* Show brandmark on mobile */
  .brandmark {
    display: inline-flex !important;
    align-items: center;
    gap: 0;
    min-width: auto;
  }

  /* Hide desktop logo, show mobile logo */
  #csgHeaderLogo {
    display: none !important;
  }

  .brandmark-img--mobile {
    display: block !important;
    height: 26px;
    width: auto;
    max-width: 180px;
  }

  /* Ensure header-inner aligns like a clean rail */
  .header-inner{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:16px;
  }

}

/* Body lock when menu open */
body.nav-open{
  overflow:hidden;
}

/* Hero */
.hero{
  position:relative;
  z-index:0;
  min-height:72vh;
  display:grid;
  align-items:end;
  padding:72px 0 40px;
  overflow:hidden;
  border-bottom:1px solid var(--border);
  background:var(--charcoal);
  color:#fff;
}

/* Remove hero border-bottom on home page to eliminate seam with intro section */
body.home .hero{
  border-bottom: 0;
}

/* Hero-top mode: inset hero image */
/* Hero-top: header becomes nav-only, so reduce reserved height */
/* Hero-top: compact nav-only header */
body.is-hero-top{
  --header-height: 72px; /* compact target */
}

/* Ensure header actually matches the compact target */
body.is-hero-top .header-inner{
  padding-block: 10px;           /* reduce vertical padding */
  min-height: var(--header-height);
}

/* NO SCROLL CHOREOGRAPHY. The hero starts in what used to be its scrolled state.
 *
 * Duncan, 2026-08-31: "drop the hero animation, just start full width".
 *
 * What this replaced: the hero opened with the image confined to a centred 1180px rail and a
 * compact nav-only header with the brandmark hidden, then on first scroll the image animated
 * out to 100vw over 2000ms while the brandmark faded in and the header grew. Four things
 * moving at once, all keyed off a .has-scrolled class.
 *
 * The rules below are the former .has-scrolled values, now unconditional. Every
 * :not(.has-scrolled) branch is gone rather than neutralised, so there is ONE state and no
 * second definition to drift out of step with it - which is what a leftover "off" branch
 * always becomes.
 *
 * .is-hero-top is KEPT. It identifies the front page for the type and layout rules further
 * down; it is only the scroll-dependent branching that goes. header-scroll.js still adds
 * .has-scrolled and nothing now reads it for the hero, which is harmless and left alone
 * because it still drives the header's hide-on-scroll behaviour. */
body.is-hero-top .brandmark{
  display: flex;
}

body.is-hero-top .header-inner{
  justify-content: space-between;
  padding-block: 14px;
  min-height: 0;
}

body.is-hero-top .hero{
  min-height: 80vh;
  padding-top: calc(var(--header-height) + var(--hero-fullbleed-top-gap));
  padding-bottom: 40px;
  overflow: hidden;
  background: var(--bg);
}

body.is-hero-top .hero-media{
  width: 100vw;
}

.hero-title-spacer{
  height:clamp(56px, 7vw, 88px); /* roughly the headline height */
}

.hero-media{
  position:absolute;
  top:0;
  left:0;
  right:0;
  bottom:-1px;
  z-index:0;
  pointer-events:none;
  overflow:hidden;
  background: transparent;
  filter: none;
  transform: none;
}

/* Hero media: centred rail with width animation (desktop/tablet only) */
@media (min-width: 768px){
  .hero .hero-media{
    position: absolute;
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    /* 100vw from the start, and no transition. The rail width and the 2000ms ease were the
       animation itself - keeping either would reintroduce it on any page load that toggles
       .has-scrolled before paint. */
    width: 100vw;
  }

  .hero-media-img{
    width:100%;
    height:100%;
    object-fit:cover;
    object-position:center;
    display:block;
  }
}

/* Was object-position:50% 18%, which framed the top of the picture. On a 72-80vh hero a
   3:2 landscape is cropped hard, and at 18% the structure fell below the visible band -
   Duncan, 2026-09-08: "the entire tent is cropped so you only see the sea". 18% -> 50% was
   still too high - the structure sits low in this frame - so 72%. A LARGER Y shows more of
   the BOTTOM of the picture, which is the counterintuitive bit: to move the image up in the
   window you push the focal point down the image. */
.hero .hero-media-img{
  object-position: 50% 72%;
  transition: none;
}

/* Tablet/tablet_plus: fixed 75px gutters pre-scroll, preserve smooth full-width transition */
@media (min-width: 768px) and (max-width: 1329px){
  .hero{
    --hero-gutter: 75px;
  }

  /* Full width here too. The gutter value was the animation's opening frame, so with the
     animation gone it would just be a narrower hero on tablets for no reason. */
  .hero .hero-media{
    width: 100vw;
  }

  /* CTA aligns to the image edge, not viewport edge */
  .hero-cta{
    right: var(--hero-gutter);
  }
}

.hero-title{
  position:absolute;
  left:var(--pad);
  right:var(--pad);
  /* Anchored to the TOP, Duncan 2026-09-14: "redefining needs to be higher up the image,
     towards the top". It was bottom-anchored, which was right while the text was white over
     the dark foot of the frame.

     THE OFFSET IS A SUBTRACTION, which is the part that was wrong first time round.
     body.is-hero-top already pads .hero by calc(--header-height + --hero-fullbleed-top-gap),
     and this box is positioned inside that padding - so adding --header-height again counted
     the header twice and put the headline 213px down a 720px photograph. Measured: .hero at
     y=104, .hero-inner at y=222, .hero-title at y=317.

     Backing both values out lands the box on the top edge of the photograph; the clamp is
     then the gap actually wanted below it. Expressed with the same two variables the padding
     uses, so it follows them rather than drifting when the header resizes - which is what the
     hand-tuned negative top this rule originally replaced could not do. */
  /* Duncan walked this in: bottom-anchored, then 213px down the photograph (the header
     counted twice), then 43px down, then "lower - split the difference". 9vw is 130px at
     1440, the midpoint of the last two - then "nudge up a little - split the difference of
     the last move", which is 6vw, 86px at 1440. */
  top: calc(clamp(48px, 6vw, 100px) - var(--header-height) - var(--hero-fullbleed-top-gap));
  bottom:auto;
  max-width:var(--max);
  margin:0 auto;
  z-index:3;

  /* A HALO, not a drop shadow. This was 0 10px 30px rgba(0,0,0,.55), which held WHITE text
     off a bright sky. The text is black now, so a black shadow behind it only smears the
     edges. A soft white spread does the same job in the other direction. */
  text-shadow: 0 0 18px rgba(255,255,255,.55), 0 1px 2px rgba(255,255,255,.7);
}

/* Scrim removed 2026-09-08 at Duncan's request: this rule carried
   background:rgba(28,31,34,.45) plus a 6px backdrop blur behind the hero headline.
   The panel itself only renders when a headline exists (see parts/home/hero.php). */
.hero-title-bg{
  display:inline-block;
  padding:22px 32px;
  border-radius:18px;
  /* Black, Duncan 2026-09-14. --charcoal is this site's near-black and is what every other
     heading uses; over a photograph it is indistinguishable from #000 and it keeps the hero
     on the same palette as the page below it. */
  color:var(--charcoal);

  /* THE SIZE, added 2026-09-14 with the headline itself.
     There was no font-size here, so the words came back at inherited body size - measured at
     19.44px, which over a photograph reads as a caption rather than as the heading of the
     page. --h1 is the token every other leading line on the site uses, clamp(34px, 4vw, 54px)
     and up to 60px at the widest breakpoint, so this tracks the scale instead of pinning a
     value that will drift away from it.

     Tight leading because a display line at 54px inherits body line-height and falls apart
     across two lines on a phone. */
  font-size:var(--h1);
  line-height:1.05;
  letter-spacing:-.01em;
  /* The panel is bottom-anchored and the photograph is busy at the foot of the frame, so the
     line is held to a readable measure rather than running the full width of the hero. */
  max-width:18ch;
}

.hero-copy{
  max-width:68ch;
  background:rgba(28,31,34,.42);
  padding:18px 22px;
  border-radius:14px;
}

.hero-copy p{
  margin:0;
  color:rgba(255,255,255,.92);
  font-size:18px;
  backdrop-filter: blur(2px);
}

.hero-inner{
  position:relative;
  z-index:2;
  padding:0 var(--pad);
  padding-bottom:clamp(18px, 3vw, 36px);
  max-width:var(--max);
  margin:0 auto;
  width:100%;
  display:grid;
  gap:18px;
  min-height:100%;
}
h1{
  margin:0;
  margin-top:-24px;
  font-size:var(--h1);
  letter-spacing:-.02em;
  line-height:1.05;
  font-weight:400;
}
.hero p{
  margin:0;
  max-width:70ch;
  font-size:18px;
  color:rgba(255,255,255,.82);
}

.hero-cta{
  position:absolute;
  right:0;
  bottom:0;
  margin:0;
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap;

  background:rgba(28,31,34,.35);
  backdrop-filter:blur(4px);
  -webkit-backdrop-filter:blur(4px);
  padding:12px 14px;
  border-radius: var(--btn-radius);
  width:max-content;
}

/* base */
.hero-cta .btn{
  color:#fff;
}

/* primary */
.hero-cta .btn.primary{
  background:rgba(28,31,34,.92);
  border:1px solid rgba(28,31,34,.92);
}
.hero-cta .btn.primary:hover{
  background:rgba(28,31,34,1);
  border-color:rgba(28,31,34,1);
}

/* secondary */
.hero-cta .btn.secondary{
  background:rgba(255,255,255,.14);
  border:1px solid rgba(255,255,255,.35);
}
.hero-cta .btn.secondary:hover{
  background:rgba(255,255,255,.22);
  border-color:rgba(255,255,255,.45);
}

/* Mobile hero: height comes from image, no forced tall container */
@media (max-width: 767px){
  .hero{
    position: relative; /* anchor for absolute CTA */
    background: #000 !important;
    color: #fff;
    min-height: 0 !important;
    height: auto !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    overflow: visible !important;
    transition: none !important;
  }

  .hero-inner{
    min-height: 0 !important;
    height: auto !important;
    padding: 0 !important;
    gap: 0 !important;
  }

  /* keep media in-flow */
  .hero-media{
    position: relative !important;
    inset: auto !important;
    width: 100% !important;
    height: auto !important;
    overflow: visible !important;
    background: #000 !important;
  }

  .hero-media-img{
    display: block;
    width: 100% !important;
    height: auto !important;
    object-fit: contain !important;
    object-position: center center !important;
  }

  /* CTA overlays image */
  .hero-cta{
    position: absolute !important;
    right: 12px;
    bottom: 12px;
    margin: 0 !important;
    width: auto;
    z-index: 5; /* above image */
  }
}

/* THE HERO HEADLINE ON A PHONE. Must come after the block above, which is what collapses
   .hero-inner.

   .hero-inner is position:relative and, once .hero-media goes back in flow, it is a
   ZERO-HEIGHT box sitting BELOW the photograph. That makes it the containing block for the
   absolutely positioned headline, so top:Npx measured N pixels DOWN FROM THE FOOT of the
   image and the headline landed on the white section underneath. Measured: .hero ended at
   y=405 and .hero-title sat at y=500.

   Taking .hero-inner out of the positioning context hands the job to .hero, which is the
   element that actually has the image's height. .hero-cta does not move: it is bottom-
   anchored, and the bottom edge of a zero-height box at the foot of the image is the same
   line as the bottom edge of .hero.

   The header does not overlay the hero at this width - the hero starts below it - so the
   offset here is a plain gap rather than --header-height plus one. */
@media (max-width: 767px){
  .hero-inner{
    position:static;
  }

  .hero-title{
    top: clamp(10px, 3vw, 20px);
  }

  /* The band is 259px tall at 390px wide, so the headline lives inside a strip rather than
     owning a section. 7.5vw is 29px on a 390px screen, which is two lines instead of the
     three --h1's 34px floor produced, and it rejoins --h1 at 34px by 453px wide - so this
     only ever takes the size DOWN on the smallest screens. The 18ch measure comes off: it
     exists to stop a 54px line running the width of a 1440px hero, and here it only forces
     another wrap. */
  .hero-title-bg{
    font-size:clamp(26px, 7.5vw, 34px);
    max-width:none;
    /* The 32px side padding is left over from the scrim that used to sit behind this. On a
       390px screen it pushes the first letter 56px in from the edge, which is most of a word.
       Half it, and only here. */
    padding:10px 16px;
  }
}

.btn{
  display:inline-flex;
  align-items:center;
  gap:10px;
  padding:12px 14px;
  border-radius: var(--btn-radius);
  border:1px solid rgba(255,255,255,.18);
  background:rgba(255,255,255,.08);
  color:#fff;
  cursor:pointer;
  transition:all .18s ease;
  font-size:14px;
  /* Explicit, because body is now font-weight:300 and this declared none - so it inherited the
     new document default and every button quietly went thin. 400 is what it already rendered at
     before that change, via the browser default; this restores it rather than restyling it. */
  font-weight:400;
  transform: translateY(0);
  box-shadow: none;
}
.btn:hover{
  background:rgba(255,255,255,.12);
  transform: translateY(-1px);
  box-shadow: 0 10px 24px rgba(0,0,0,.18);
}
.btn:active{
  transform: translateY(0);
  box-shadow: 0 6px 14px rgba(0,0,0,.16);
}
.btn:focus-visible{
  outline: 2px solid color-mix(in srgb, var(--csg-brass) 55%, transparent); /* csg-brass @ 55% */
  outline-offset: 3px;
}
@media (hover: none){
  .btn:hover{
    transform: none;
    box-shadow: none;
  }
}

.btn--ghost{
  background: transparent;
  border: 1px solid rgba(28,31,34,.18);
  color: var(--charcoal);
}

.btn--ghost:hover{
  background: rgba(28,31,34,.04);
  border-color: rgba(28,31,34,.28);
}

.btn--dark{
  background: var(--charcoal);
  border: 1px solid var(--charcoal);
  color: #fff;
}

.btn--dark:hover{
  background: rgba(28,31,34,.92);
  border-color: rgba(28,31,34,.92);
}

.link-underline{
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.scrollhint{font-size:13px; color:rgba(255,255,255,.70)}

/* Site modal */
.site-modal{
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
}

.site-modal.is-open{
  display: block;
}

.site-modal__backdrop{
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.35);
}

.site-modal__panel{
  position: relative;
  max-width: 640px;
  max-height: 85vh;
  margin: 12vh auto 0;
  background: #fff;
  padding: 56px 56px 48px;
  border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.site-modal__panel--schedule{
  /* R01 (review, f790570): was a flat max-width:1136px with a max-width:1100px media query
     dropping it to calc(100vw - 48px). Between those two rules the panel had no gutter at all:
     measured x=24 width=1052 at 1100, then x=0 width=1101 at 1101 - edge to edge - before
     centring again once the viewport passed 1136. min() keeps the 24px gutter at every width
     and still caps at 1136, so the jump cannot recur. */
  max-width: min(1136px, calc(100vw - 48px)); /* 1024px content + 56px padding each side */
  height: 1024px;
  padding-left: 0;
  padding-right: 0;
  position: relative;
  background: #f4f4f4;
}

.site-modal__panel--schedule::before{
  content: '';
  position: absolute;
  top: 24px;
  left: 0;
  right: 0;
  height: 104px;
  background: #f4f4f4;
  z-index: 0;
}

.site-modal__title{
  margin: 0 0 12px;
}

.site-modal__copy{
  font-size: 14px;
  line-height: 1.6;
  color: rgba(0,0,0,0.7);
}

.site-modal__close{
  position: absolute;
  top: 18px;
  right: 18px;
  background: transparent;
  border: 0;
  font-size: 22px;
  cursor: pointer;
  color: rgba(0,0,0,0.6);
  z-index: 10;
}

.site-modal__logo-wrapper{
  position: absolute;
  top: 24px;
  left: 24px;
  height: 104px;
  z-index: 1;
  background: #f4f4f4;
  padding: 8px 16px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
}

.site-modal__logo{
  height: 100%;
  width: auto;
}

@media (max-width: 767px){
  .site-modal__panel--schedule::before{
    height: 10vw;
    min-height: 60px;
    max-height: 104px;
    top: 12px;
  }

  .site-modal__logo-wrapper{
    height: 10vw;
    min-height: 60px;
    max-height: 104px;
    top: 12px;
    left: 12px;
    padding: 4px 8px;
  }

  .site-modal__logo{
    height: 100%;
    width: auto;
  }
}

.booking-embed {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
  padding-top: 25px;
}

.booking-embed__iframe {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  border: 0;
  min-height: 0;
}

.booking-embed__fallback {
  margin: 0;
  padding: 10px 16px;
  font-size: 14px;
  text-align: center;
  color: rgba(0,0,0,0.7);
}

.booking-embed__fallback a {
  color: var(--csg-gold);
  text-decoration: underline;
}

@media (min-width: 768px) and (max-width: 1068px){
  .booking-embed {
    padding-top: 50px;
  }
}

/* =====================================================
   TELL MODAL - "Tell us about your event"

   Built to Duncan's mockup, 2026-09-18 (_claude/references/REF-tell-modal-mockup-2026-09-18.png):
   a narrow centred card; the brand mark, heading and intro centred; fields in pairs; a full-width
   brass submit; then a centred phone line between two rules and a small letterspaced strapline.

   Gravity Forms' own stylesheets are switched off for this form (functions.php: gform_disable_css
   plus the legacy theme slug), so this block is everything the form looks like. Nothing here is
   overriding a framework, which is why there is no !important below except on the hidden rows.

   ONE SCROLL CONTAINER, AND IT IS THE PANEL. Earlier versions scrolled the body, then the field
   list, inside a fixed-height panel, and each of them ended up clipping the submit button at some
   viewport height - the last one hid it completely. Here the panel is never taller than the
   viewport and scrolls as a whole, so every part of it, the button included, is reachable at any
   size and nothing can be clipped away. At 1440x1000 the whole card fits without scrolling.
   ===================================================== */
#tellModal.is-open{
  display: flex;
  align-items: center;
  justify-content: center;
  /* 24 -> 14 vertically. This is the gap between the dialog and the window edge, so every pixel
     taken off it is a pixel the panel can use before it has to scroll. Horizontal padding is
     unchanged - the panel is capped at 640px wide and never comes near the edges. */
  padding: 14px 16px;
  box-sizing: border-box;
}

#tellModal .site-modal__panel--tell{
  width: 100%;
  max-width: 640px;
  max-height: 100%;
  margin: 0;
  /* Compressed 44/40 -> 30/26 so the whole dialog clears a laptop viewport without scrolling.
     Duncan, 2026-09-22: "a little vertical compression and we will fit on the screen, the
     Extraordinary spaces. Unforgettable occasions. is being clipped".

     THE BOTTOM IS 100px, NOT 26px, AND THAT IS THE CALENDAR'S ROOM. The date picker is absolutely
     positioned, so it adds nothing to this panel's in-flow height - but the panel is a scroll
     container, and the overhang extends its scrollable area, which is what put a scrollbar on the
     dialog the moment the picker opened. The date field sits about two-thirds down, so a ~355px
     calendar hangs roughly 75px below the foot.

     Reserved PERMANENTLY rather than only while the calendar is in the DOM. A :has() rule was
     written first and Duncan chose against it - "the modal can be taller all the time it does not
     need to grow", 2026-09-23. One layout beats two, and a dialog that changes size as you
     interact with it is its own kind of wrong. So the blank space below the strapline when the
     picker is closed is deliberate, and the compression above is what paid for it.

     Kept as ONE declaration on purpose: an earlier version set this as a separate padding-bottom
     rule further down the file, which would have silently out-ranked anything edited here. */
  padding: 30px 40px 100px;
  box-sizing: border-box;
  display: block;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #FAF9F7;
  border-radius: 10px;
  color: #24272F;
}

#tellModal .site-modal__close{
  top: 14px;
  right: 14px;
  width: 40px;
  height: 40px;
  padding: 0;
  font-size: 30px;
  font-weight: 300;
  line-height: 1;
  color: #545559;
}

#tellModal .site-modal__close:hover{
  color: var(--charcoal);
}

/* Header: mark, heading and intro stacked and centred, no box behind the mark. */
#tellModal .site-modal__header--tell{
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
  padding: 0;
  text-align: center;
}

/* Reset the shared .site-modal__logo-wrapper, which the schedule dialog positions absolutely
   over a grey band. Scoped here so the schedule dialog is untouched. */
#tellModal .site-modal__logo-wrapper--tell{
  position: static;
  height: auto;
  /* 18 -> 10 in the compression pass. The logo itself is left at 90px - Duncan asked for it
     50% bigger on 2026-09-18 and shrinking it back would undo that. */
  margin: 0 0 10px;
  padding: 0;
  background: none;
  display: block;
}

#tellModal .site-modal__logo-wrapper--tell .site-modal__logo{
  display: block;
  height: 90px;  /* Duncan, 2026-09-18: "logo needs to be 50% bigger" (was 60px) */
  width: auto;
}

#tellModal .site-modal__title--tell{
  margin: 0;
  font-size: clamp(28px, 4.2vw, 40px);
  font-weight: 400;
  line-height: 1.15;
  color: #000;
  text-align: center;
}

#tellModal .site-modal__copy{
  /* 10/30 -> 6/18 in the compression pass. */
  margin: 6px 0 18px;
  font-size: 16px;
  line-height: 1.5;
  color: #605F6A;
  text-align: center;
}

/* SHORT VIEWPORTS: buy back the ~45px that makes the dialog scroll on an 800px-high laptop.
   Duncan, 2026-10-07, choosing "trim it to fit" over living with the scrollbar.

   WHAT IS NOT TOUCHED, because both are his explicit decisions and neither is mine to undo:
   the 90px logo ("logo needs to be 50% bigger", 2026-09-18), and the fact that the calendar's
   room is reserved PERMANENTLY rather than appearing when the picker opens ("the modal can be
   taller all the time it does not need to grow", 2026-09-23). So every pixel here comes out of
   padding and margins, never out of content.

   THIS IS NOT THE :has() PATTERN HE REJECTED. That one changed size as you interacted with the
   dialog, which is the part he objected to. A viewport-height query gives one fixed layout per
   screen size and never moves while you use it.

   The bottom reserve drops 100 -> 76, but the content above it also loses 22px, so the picker
   moves up by almost exactly what the reserve loses: measured clearance below the calendar goes
   from 22px to ~20px, i.e. unchanged in practice. The reserve is not being raided.

   The breakpoint is 860px, not 800: .site-modal adds 14px of padding top and bottom, so an
   800px viewport only offers 772px to the panel, and the headroom above that is deliberate. */
@media (max-height: 860px){
  #tellModal .site-modal__panel--tell{
    padding: 20px 40px 76px;
  }

  #tellModal .site-modal__logo-wrapper--tell{
    margin-bottom: 6px;
  }

  #tellModal .site-modal__copy{
    margin: 4px 0 12px;
  }
}
#tellModal .site-modal__body{
  padding: 0;
  overflow: visible;
}

#tellModal .gform_wrapper,
#tellModal .gform_wrapper form{
  margin: 0;
  padding: 0;
}

/* The mockup carries no "* indicates required fields" line. Each required label shows its own
   asterisk and each required input carries aria-required, so nothing is lost by dropping it. */
#tellModal .gform_required_legend{
  display: none;
}

/* Fields */
#tellModal .gform_fields{
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Row gap 22 -> 18 when the Notes field landed, then -> 14 in the compression pass. Column
     gap is unchanged throughout; it is the vertical budget that is tight, never the horizontal. */
  gap: 14px 20px;
  margin: 0;
  padding: 0;
  list-style: none;
}

#tellModal .gfield{
  margin: 0;
  padding: 0;
  min-width: 0;
}

#tellModal .gfield_label{
  display: block;
  /* 8 -> 6: five visible rows, so every 2px here is 10px off the dialog. */
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  color: #24272F;
}

/* "Full name *", as in the mockup. GF nests one .gfield_required span inside another, so the gap
   goes on the outer one only (the label's direct child) - on both, it was doubled. */
#tellModal .gfield_label > .gfield_required{
  margin-left: .25em;
}

#tellModal .gfield_required{
  color: inherit;
}

#tellModal input[type="text"],
#tellModal input[type="email"],
#tellModal input[type="tel"],
#tellModal input[type="number"],
#tellModal select,
#tellModal textarea{
  display: block;
  width: 100%;
  /* 46 -> 44 in the compression pass. Still a comfortable touch target; below about 40 it
     would not be, so this is close to the floor and should not be trimmed further. */
  height: 44px;
  margin: 0;
  padding: 0 14px;
  box-sizing: border-box;
  font: inherit;
  font-size: 14px;
  color: #24272F;
  background-color: #FFF;
  border: 1px solid #DCDCDC;
  border-radius: 6px;
  outline: none;
}

/* 72px, and it has to be an explicit HEIGHT rather than min-height.
   Gravity Forms renders a textarea with a rows attribute, and `height: auto` honours it - the
   field came out 243px tall, which is most of the dialog. min-height alone cannot pull it back
   down; only a real height can. Measured, not assumed.

   72px is about two lines, and resize:vertical means anyone with more to say can drag it taller.
   The old value here was 120px, set when nothing on this form used a textarea. */
#tellModal textarea{
  height: 72px;
  min-height: 72px;
  padding: 12px 14px;
  resize: vertical;
}

/* The hint sits on the label's line, right-aligned, as the mockup has it.

   Gravity Forms emits the description as a SIBLING of the label, not a child, so it cannot be
   aligned by styling the label alone. Making the field a flex row puts them on one line and
   drops the input onto the next, with no floats and no negative margins to break when the copy
   changes length. descriptionPlacement is set to 'above' in create-tell-form.php, which is what
   produces this order: label, description, input. */
#tellModal .gfield--type-textarea{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 12px;
}

#tellModal .gfield--type-textarea > .gfield_label{
  flex: 0 1 auto;
}

#tellModal .gfield--type-textarea > .gfield_description{
  flex: 0 1 auto;
  margin-left: auto;
  padding: 0;
  font-size: 12.5px;
  font-weight: 400;
  letter-spacing: 0;
  /* 4.85:1 on the panel. #8A8D93, which this carried when the field shipped, was 3.16 - below
     AA for 12.5px text, and mine to fix. */
  color: #6B6E75;
  text-align: right;
}

#tellModal .gfield--type-textarea > .ginput_container{
  flex: 0 0 100%;
}

#tellModal input::placeholder,
#tellModal textarea::placeholder{
  color: #9B9DA3;
  opacity: 1;
}

#tellModal input:focus,
#tellModal select:focus,
/* FOCUS INDICATOR. Every control here has outline:none, so THIS rule is the focus state -
   there is nothing underneath it to fall back to, and WCAG 2.2 SC 2.4.11 wants 3:1.

   Flat brass is 2.52:1 on a white field and fails. color-mix toward TRANSPARENT does not help:
   it composites against whatever is behind, so it only ever gets lighter. Mixing toward the
   charcoal token keeps it opaque - 80% reaches 3.51:1 - and still reads as brass rather than
   brown. The translucent halo is kept as a second, outer ring so it looks as it did.
   (Raised by the csg-corporate session, 2026-09-23; measured here before changing anything.) */
#tellModal textarea:focus{
  border-color: color-mix(in srgb, var(--csg-brass) 80%, #1C1F22);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--csg-brass) 80%, #1C1F22),
              0 0 0 5px color-mix(in srgb, var(--csg-brass) 22%, transparent);
}

/* The select draws its own chevron, as in the mockup, rather than the platform's.

   ITS HEIGHT MUST TRACK THE SHARED CONTROL HEIGHT ABOVE. This rule carries its own value, so it
   does not follow when that one changes - it was 50 against inputs of 46, and the compression
   pass on 2026-09-22 took the inputs to 44 and widened the gap to 6px before it was caught by
   measuring the rendered heights rather than reading the CSS. Change one, change both. */
#tellModal select{
  height: 44px;
  padding-right: 44px;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1.5l6 6 6-6' stroke='%2324272F' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 14px 9px;
  cursor: pointer;
}

/* Guest count: the people icon from the mockup, drawn in the input, not in the markup. */
#tellModal .gfield--type-number input{
  padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='16' viewBox='0 0 18 16' fill='none' stroke='%239B9DA3' stroke-width='1.4' stroke-linecap='round'%3E%3Ccircle cx='6.5' cy='4.5' r='3'/%3E%3Cpath d='M1 15c0-3 2.5-5 5.5-5s5.5 2 5.5 5'/%3E%3Cpath d='M12 1.7a3 3 0 010 5.6M14 10.4c1.8.6 3 2.2 3 4.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 18px 16px;
  -moz-appearance: textfield;
}

#tellModal .gfield--type-number input::-webkit-outer-spin-button,
#tellModal .gfield--type-number input::-webkit-inner-spin-button{
  -webkit-appearance: none;
  margin: 0;
}

/* The "1 to 5000" range hint is Gravity Forms' own, not the mockup's. It stays in the DOM, so
   aria-describedby still reads it, and it reappears with the error it explains. */
#tellModal .gfield:not(.gfield_error) .instruction{
  display: none;
}

/* =====================================================
   DATE FIELD AND ITS CALENDAR

   With Gravity Forms' CSS off, its calendar toggle rendered as an empty pill (its icon is a
   Dashicons glyph, and Dashicons is not loaded on the front end) and the calendar it opens has no
   styling at all. Both are styled here, following the rules GF ships for legacy markup
   (legacy/css/formsmain.css) but in this site's colours.
   ===================================================== */
#tellModal .ginput_container_date{
  position: relative;
}

#tellModal input.datepicker{
  padding-right: 46px;
}

/* Square, and the same height as the input it sits in - see the note on the select above; this
   is the second rule that has to track the shared control height by hand. */
#tellModal button.gform-datepicker-toggle{
  position: absolute;
  top: 0;
  right: 0;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%239B9DA3' stroke-width='1.4'%3E%3Crect x='1.5' y='2.5' width='13' height='12' rx='1.5'/%3E%3Cpath d='M1.5 6.5h13M5 1v3M11 1v3'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  cursor: pointer;
}

#tellModal button.gform-datepicker-toggle .gform-datepicker-toggle-icon{
  display: none;
}

#tellModal .gform-datepicker-calendar{
  position: absolute;
  z-index: 20;
  margin-top: 6px;
  padding: 14px;
  width: auto;
  max-width: 340px;
  background: #FFF;
  border: 1px solid #DCDCDC;
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(28,31,34,.14);
  color: #24272F;
  font-size: 13px;
}

#tellModal .gform-datepicker-calendar table{
  border-collapse: collapse;
}

#tellModal .gform-datepicker-calendar th,
#tellModal .gform-datepicker-calendar td{
  padding: 2px;
  text-align: center;
}

#tellModal .gform-datepicker-calendar .nav.prev,
#tellModal .gform-datepicker-calendar .nav.next{
  width: 30px;
  font-size: 16px;
  line-height: 1;
  text-align: center;
  color: #605F6A;
  cursor: pointer;
}

#tellModal .gform-datepicker-calendar .nav.prev:hover,
#tellModal .gform-datepicker-calendar .nav.next:hover{
  color: var(--charcoal);
}

/* MONTH AND YEAR SIDE BY SIDE, not stacked.
   Duncan, 2026-09-23: "datepicker is opening calendar but month year are not side by side".

   Gravity Forms stacks .monthSelector above .yearSelector, each a full-width row carrying its
   own prev/next arrows. That costs a row of vertical height in a calendar measured at 400px
   inside a 749px dialog - and the dialog is a scroll container, so the excess is CLIPPED rather
   than spilling out. Measured with a real click: 101px cut off the bottom.

   One line reclaims roughly 36px. Worth having, and NOT the whole fix - 101 less 36 still cuts
   the last week. The real fix is opening the calendar upward when there is no room below, which
   is a separate change and deliberately not in this rule. */
/* =====================================================
   CALENDAR HEAD: MONTH AND YEAR ON ONE ROW

   Duncan, 2026-09-23: "datepicker is opening calendar but month year are not side by side", and
   then "please replicate how they have done theirs" - so this is the csg-corporate session's
   implementation (their 9227f16), not a parallel one. Their reasoning is reproduced because it is
   the part worth keeping, and three details in it are ones I had got wrong on my own attempts.

   THE SELECTORS ARE TABLE ROWS. GF builds the whole calendar as one table and the two selectors
   are <tr class="yearSelector"> then <tr class="monthSelector">, each 1 + colspan-5 + 1 cells so
   they line up with the 7 day columns. That rules out both obvious fixes: they cannot merge into
   one real row, because two colspan-5 centre cells would ask for 14 columns against the grid's 7;
   and they cannot be flexed in place, because a non-row child of a tbody is wrapped in an
   anonymous row AND cell, which blows column 1 out to ~340px.

   WHAT I HAD WRONG, for anyone comparing against the history here:
     - display:grid on the calendar DIV cannot reach them at all; they are children of the table.
     - float:left works but is width-sensitive - the two share a row at 55px each and stack at
       152px - because a floated <tr> is blockified and its containing block is not the obvious one.
     - anchoring to the calendar div rather than the TABLE is a level too high. The table is what
       the rows belong to, and putting position:relative there is what makes the offsets stable.

   So both rows come out of flow into a strip reserved above the table with margin-top. The
   reservation and the offset are the SAME custom property, so there is one number to tune. padding
   would not work, because the table sets border-collapse:collapse and the collapsing model ignores
   the table's own padding. The day grid's box tree is untouched, which matters: GF's rerenderTable
   does container.innerHTML = content on every month change, so only a pure CSS fix survives.

   SPECIFICITY IS DELIBERATE. These carry four classes to out-rank two existing rules in this file
   without reaching for !important: the .nav.prev/.next rule (1,3,0) and table.calendar td span
   (1,2,3). Do not simplify the selectors.
   ===================================================== */
#tellModal .gform-datepicker-calendar table.calendar{
  --cal-head-h: 34px;
  position: relative;
  margin-top: var(--cal-head-h);
}

#tellModal .gform-datepicker-calendar table.calendar tr.monthSelector,
#tellModal .gform-datepicker-calendar table.calendar tr.yearSelector{
  position: absolute;
  top: calc(var(--cal-head-h) * -1);
  display: flex;
  align-items: center;
  gap: 2px;
  height: var(--cal-head-h);
}

/* EACH ROW ANCHORS TO ONE EDGE AND SIZES TO ITS OWN CONTENT.

   Their first version set BOTH left:0 and right:0 and separated the two with justify-content. An
   absolutely positioned box with both insets set STRETCHES, so both rows spanned the full strip
   and sat exactly on top of each other; justify-content moved each <select> within its box but
   never moved the box. The template builds yearSelector first, so month painted last, covered
   year, and swallowed every click aimed at the year dropdown - Duncan found it as "year dropdown
   is not working".

   Setting one inset and leaving the other auto makes each box shrink to fit, so the two occupy
   different space rather than merely appearing to. Nothing here changes what is DRAWN; it changes
   what can be HIT - which is why a screenshot could never have caught it. */
#tellModal .gform-datepicker-calendar table.calendar tr.monthSelector{
  left: 0;
  right: auto;
}

#tellModal .gform-datepicker-calendar table.calendar tr.yearSelector{
  left: auto;
  right: 0;
}

/* NO ARROWS. Always a duplicate route: the chunk passes forceSelect:!0, which is what makes GF
   render both <select> elements visible and hide its own .nav.current span, so the dropdowns are
   the intended control. Removing them also ends the stray glyph pair where month's "next" met
   year's "previous" nose to nose, and gives back 64px of the strip.

   td.nav rather than .nav.prev/.nav.next, so every nav cell in these two rows goes - including
   the .nav.current button GF keeps as a fallback. */
#tellModal .gform-datepicker-calendar table.calendar tr.monthSelector td.nav,
#tellModal .gform-datepicker-calendar table.calendar tr.yearSelector td.nav{
  display: none;
}

#tellModal .gform-datepicker-calendar table.calendar tr.monthSelector td.month,
#tellModal .gform-datepicker-calendar table.calendar tr.yearSelector td.year{
  flex: 0 1 auto;
  min-width: 0;
}

/* Sized to their own widest option rather than filling the row, and shrinkable so a long month
   name trims instead of pushing the year half off the end. */
#tellModal .gform-datepicker-calendar table.calendar tr.monthSelector select,
#tellModal .gform-datepicker-calendar table.calendar tr.yearSelector select{
  width: auto;
  max-width: 100%;
  min-width: 0;
}

#tellModal .gform-datepicker-calendar .yearSelector .year span.current,
#tellModal .gform-datepicker-calendar .monthSelector .month span.current{
  display: none;
}

#tellModal .gform-datepicker-calendar select{
  display: inline-block;
  width: 100%;
  height: 34px;
  padding: 0 28px 0 10px;
  font-size: 13px;
  background-position: right 10px center;
}

#tellModal .gform-datepicker-calendar table.calendar th span,
#tellModal .gform-datepicker-calendar table.calendar td span{
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid transparent;
  border-radius: 50%;
}

#tellModal .gform-datepicker-calendar table.calendar th span{
  font-weight: 500;
  color: #605F6A;
}

#tellModal .gform-datepicker-calendar table.calendar td span{
  cursor: pointer;
}

#tellModal .gform-datepicker-calendar table.calendar td span:hover,
#tellModal .gform-datepicker-calendar table.calendar td span:focus{
  border-color: var(--csg-brass);
  outline: none;
}

#tellModal .gform-datepicker-calendar table.calendar .dayToday span{
  background: var(--csg-brass);
  border-color: var(--csg-brass);
  color: #FFF;
}

#tellModal .gform-datepicker-calendar table.calendar .disabled span,
#tellModal .gform-datepicker-calendar table.calendar .empty span{
  color: #B5B5B8;
  cursor: default;
}

/* "Press F1 for help" moves into the accessibility tree instead of onto the screen.
   csg-corporate's dae87f7, replicated here - Duncan on their side: "is the Press F1 for help
   really necessary ... never seen it on any other site, can it live in the meta?"

   IT CLIPS RATHER THAN DELETES, and that is the point. The string is GF's helpTextShort, and F1
   reveals helpText: "Press the arrow keys to navigate by day, PageUp and PageDown to navigate by
   month, Alt+PageUp and Alt+PageDown to navigate by year, or Escape to cancel." That is the only
   pointer a keyboard user gets to any of it, so deleting it would take a real affordance from the
   people most likely to need it. Clipped, it stays announced and F1 still works.

   NOT VIA data-options, WHICH LOOKS RIGHT AND SILENTLY FAILS. The obvious route is the one that
   fixed the year range, but the chunk reads

       var o = (e = e||{}).helpTextShort || "Press F1 for help."

   so an empty string is FALSY and GF falls straight back to its own default. helpTextShort:""
   would have looked like a clean fix and changed nothing.

   clip rather than display:none even though nothing currently points at this by id: clip survives
   someone later adding an aria-describedby to it, which display:none would break in some screen
   readers.

   SCOPE: .monthMessage is also where GF puts a month-specific message when a date range is
   configured. This form sets none, so the container only ever holds the help text. If date
   restrictions are ever added, check this still hides only what it should.

   The text-align:center this replaces is redundant now - nothing here is on screen to centre. */
/* The legend's asterisk, a shade bigger than its sentence.
   csg-corporate's 9227f16. The legend is 11px and the asterisk inherits it, so once the speech
   marks come off (functions.php, gform_required_legend_N) there is nothing giving it presence.
   13px against 11px reads as deliberate without changing the strip's height - line-height:1 keeps
   the taller glyph inside the box the <p> already reserves, so nothing below it shifts. Colour is
   inherited from the brass rule elsewhere; this sets size only.

   The second selector carries .gform_wrapper because GF's own rules reach these elements through
   it, and a single-class version loses on specificity. */
#tellModal .gform_required_legend .gfield_required,
#tellModal .gform_wrapper .gform_required_legend .gfield_required{
  font-size: 13px;
  line-height: 1;
  vertical-align: -1px;
}

#tellModal .gform-datepicker-calendar .monthMessage{
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
}

/* Submit: full width, brass, serif, with the arrow from the mockup. The <button> takes an
   ::after, so the arrow needs no markup and no change to the form. */
#tellModal .gform_footer{
  position: relative;
  margin: 28px 0 0;
  padding: 0;
}

#tellModal .gform_button{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  width: 100%;
  height: 54px;
  margin: 0;
  padding: 0 24px;
  border: 0;
  border-radius: 6px;
  /* A deeper brass than --csg-brass, sampled from the mockup's own button. White on the brand
     brass measured about 2.5:1, below every WCAG AA threshold. On #8F7F5D it is about 3.9:1, and
     19px bold is "large text" (14pt bold and up), whose AA minimum is 3:1.
     Duncan, 2026-09-18: "Darker brass, bolder text". */
  background: #8F7F5D;
  color: #FFF;
  /* Archivo, as every button on the site is. Duncan, 2026-09-18: "buttons should be using
     archivo". Weight is explicit because body is 300. */
  font-family: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 19px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: background-color .2s ease;
}

#tellModal .gform_button::after{
  content: "";
  width: 18px;
  height: 12px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='12' viewBox='0 0 18 12' fill='none' stroke='%23FFF' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 6h16M12 1l5 5-5 5'/%3E%3C/svg%3E") center / 18px 12px no-repeat;
}

#tellModal .gform_button:hover{
  background: #7A6C4E;
}

#tellModal .gform_button:focus-visible{
  outline: 2px solid var(--charcoal);
  outline-offset: 3px;
}

/* Gravity Forms appends its spinner after the button while the AJAX submit runs. */
#tellModal .gform_ajax_spinner{
  position: absolute;
  top: 50%;
  right: 18px;
  width: 18px;
  height: 18px;
  margin-top: -9px;
}

/* Validation */
#tellModal .gfield_error input,
#tellModal .gfield_error select,
#tellModal .gfield_error textarea{
  border-color: #B3261E;
}

#tellModal .validation_message,
#tellModal .gfield_validation_message{
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.4;
  color: #B3261E;
}

#tellModal .gform_validation_errors{
  margin: 0 0 22px;
  padding: 12px 14px;
  border: 1px solid rgba(179,38,30,.3);
  border-radius: 6px;
  background: rgba(179,38,30,.05);
}

#tellModal .gform_validation_errors > h2{
  margin: 0;
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  color: #B3261E;
}

/* GF's error icon is an icon-font glyph from the stylesheet that is switched off. */
#tellModal .gform_validation_errors .gform-icon{
  display: none;
}

#tellModal .gform_validation_errors ol{
  margin: 6px 0 0;
  padding-left: 18px;
  font-size: 13px;
  line-height: 1.6;
  color: #B3261E;
}

#tellModal .gform_validation_error_link{
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The confirmation that replaces the form after a successful send. */
#tellModal .gform_confirmation_message{
  padding: 8px 0 4px;
  font-size: 16px;
  line-height: 1.6;
  text-align: center;
  color: #24272F;
}

/* "Prefer to talk?" centred between two short rules; the number in brass. */
#tellModal .site-modal__phone{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* 26 -> 16 in the compression pass. */
  margin: 16px 0 0;
  font-size: 15px;
  line-height: 1.4;
  /* 4.85:1. #928F8F was 3.05 - below AA for 15px text. */
  color: #6B6E75;
}

#tellModal .site-modal__phone::before,
#tellModal .site-modal__phone::after{
  content: "";
  flex: 0 0 44px;
  height: 1px;
  margin: 0 14px;
  background: #E3E1DE;
}

/* 4.82:1, and underlined. The previous #8F7F5D measured 3.72 - short of AA for 15px text -
   and an unadorned coloured link also needs 3:1 against the text AROUND it to mark itself as a
   link by colour alone (WCAG 1.4.1). It had neither, so the underline is the non-colour cue and
   is no longer hover-only. */
#tellModal .site-modal__phone-link{
  color: color-mix(in srgb, var(--csg-brass) 60%, #1C1F22);
  text-decoration: underline;
  text-underline-offset: 2px;
}

#tellModal .site-modal__phone-link:hover{
  text-decoration: underline;
  text-underline-offset: 3px;
}

#tellModal .site-modal__strapline{
  margin: 10px 0 0;
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: .28em;
  text-transform: uppercase;
  text-align: center;
  /* 4.85:1. #A3A2A3 was 2.42 - the worst offender in the dialog. */
  color: #6B6E75;
}

/* =====================================================
   THE ATTRIBUTION FIELDS MUST NOT OCCUPY THE FORM

   The form carries 19 hidden fields - gclid, the UTM set, landing page, GA and Clarity
   identifiers - so an enquiry reaches wp-admin and the Team@ inbox with its origin attached
   (inc/attribution.php). Gravity Forms still renders each as a field row with a label.

   display:none on the ROW: the input inside is still posted, because a display:none input submits
   like any other. Both selectors, so a GF update in either direction cannot bring nineteen empty
   rows back.
   ===================================================== */
#tellModal .gfield--type-hidden,
#tellModal .gfield.gform_hidden,
#tellModal .gform_fields > .gfield--type-hidden{
  display: none !important;
}

/* GRAVITY FORMS' HONEYPOT MUST BE HIDDEN HERE, BECAUSE ITS OWN STYLESHEET IS OFF.
   The honeypot is a decoy text field (a randomised label - "Email", "Instagram") that humans
   never see and bots fill in; a filled one marks the entry as spam. GF hides it with CSS in the
   stylesheet functions.php switches off, so with the honeypot enabled on 2026-09-18 it rendered
   as a real, labelled field at the top of the form - measured on production - where a visitor
   could fill it and be filed as spam. display:none keeps it out of sight and out of the tab
   order; it still posts empty, which is what marks a submission as human. */
#tellModal .gform_validation_container,
#tellModal .gfield--type-honeypot{
  display: none !important;
}

/* The "this form is not loading properly" notice, inserted into .gform_wrapper by the network
   mu-plugin csg-form-resilience (the theme-side copy that used to draw it was removed 2026-09-21).

   EACH BRAND DRESSES THIS IN ITS OWN FLAVOUR - Duncan, 2026-09-21. The plugin supplies the
   behaviour and a plain default; the theme supplies the look, so the notice belongs to the site it
   appears on rather than looking like a system message.

   ONLY the properties the plugin does NOT set inline can be styled from here: it sets padding,
   margin, border and font-weight inline, and inline always beats a stylesheet. Do not add those
   four back expecting them to apply. Deliberately calm either way: it is shown to someone whose
   browser is at fault, and its job is to hand them the phone number, not to look like an error. */
#tellModal .csg-form-fallback{
  border-radius: 6px;
  background: #FFF;
  font-size: 14px;
  line-height: 1.5;
  color: #24272F;
}

/* Gravity Forms' live character counter ("0 of 60 max characters"). GF adds it to any field with
   a maxLength, which Full name and Event location now carry to stop the auto-reply being used to
   deliver someone else's text. With GF's stylesheet off it rendered as large unstyled text under
   both fields, 60px of height the mockup does not have. The input's own maxlength attribute
   still stops typing at the limit, and GF re-checks the length server-side. */
#tellModal .ginput_counter{
  display: none;
}

/* The Cloudflare Turnstile row. The widget is INVISIBLE (set in Cloudflare), so the row renders
   empty (0x0, measured in a real Chrome) - but any in-flow grid item creates a track, and the
   grid gap above that track pushed the button 22px further from Event type (50px, not 28px).
   A negative margin does NOT fix it: under grid stretch the item simply grows back to 22px and
   the gap between tracks remains - measured. Taking the row out of grid flow is the only thing
   that removes the track. Not display:none: the widget must stay in the DOM to run.
   A failed check adds .gfield_error and writes its message into this row, which then returns to
   normal flow so the message is seen.
   IF THE WIDGET IS EVER SWITCHED TO "MANAGED" IN CLOUDFLARE, delete this rule: a visible
   checkbox positioned this way would sit over the last field. */
#tellModal .gform_fields{
  position: relative;
}

#tellModal .gfield--type-turnstile:not(.gfield_error){
  position: absolute;
  left: 0;
  bottom: 0;
  margin: 0;
}

/* =====================================================
   SIX COLUMNS, CARRYING BOTH HALVES AND THIRDS

   Name beside email, phone beside location, then date + guest count + event type sharing ONE
   line, then Notes across the full width. The placement comes from a cssClass on the FIELD
   (csg-half, csg-third), set when the form was built in _claude/tools/create-tell-form.php -
   not nth-child, which would silently re-pair every field the moment one is added, removed or
   reordered in wp-admin.

   WHY SIX. Duncan, 2026-09-22: "those could all comfortably live on one line and create space
   for the notes below". Event type used to sit alone on a full-width row with its right half
   empty; folding those three onto one line released that row for Notes. Six is the smallest
   count divisible by 2 and 3, so a half is span 3 and a third is span 2 - no field has to know
   how many columns there are, and adding a quarter later would only mean going to twelve.

   Measured after the change: 829px against 833px before, so the dialog is marginally SMALLER
   than it was, which was the requirement - "I do not want to increase the size of the modal".
   ===================================================== */
@media (min-width: 768px){
  #tellModal .gform_fields{
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  #tellModal .gform_fields > .gfield{
    grid-column: 1 / -1;
  }

  #tellModal .gform_fields > .gfield.csg-half{
    grid-column: span 3;
  }

  #tellModal .gform_fields > .gfield.csg-third{
    grid-column: span 2;
  }
}

@media (max-width: 767px){
  #tellModal.is-open{
    padding: 12px;
  }

  #tellModal .site-modal__panel--tell{
    padding: 40px 20px 28px;
  }

  #tellModal .site-modal__logo-wrapper--tell .site-modal__logo{
    height: 72px;  /* 50% up from 48px, in step with desktop */
  }

  #tellModal .site-modal__copy{
    margin-bottom: 24px;
    font-size: 15px;
  }

  #tellModal .gform_fields{
    gap: 18px;
  }

  #tellModal .site-modal__phone::before,
  #tellModal .site-modal__phone::after{
    flex-basis: 24px;
    margin: 0 4px;
  }
}

/* Layout */
.grid{display:grid; gap:18px}
.grid-2{grid-template-columns:repeat(2, minmax(0, 1fr))}
.grid-3{grid-template-columns:repeat(3, minmax(0, 1fr))}
.grid-4{grid-template-columns:repeat(4, minmax(0, 1fr))}
@media (max-width:980px){
  .grid-4{grid-template-columns:repeat(2, minmax(0, 1fr))}
}
@media (max-width: 767px){
  .grid-3,.grid-2{grid-template-columns:1fr}
  .header-inner{gap:10px}
  .brandmark{min-width:auto}
}

.card{
  background:var(--surface-card);
  border:1px solid var(--border);
  border-radius:var(--radius);
  overflow:hidden;
  box-shadow:0 1px 0 rgba(28,31,34,.04);
}
.card-pad{padding:18px}

/* Editorial callout container */
.cta-callout{
  background: var(--callout-bg) !important;
  border: none !important;
  border-radius: var(--callout-radius) !important;
  padding: var(--callout-pad) !important;
  box-shadow: none !important;
  background-image: none !important;
  backdrop-filter: none !important;
  max-width: var(--callout-max);
  overflow: visible;
}

/* If the callout appears on light sections anywhere */
.section:not(.is-dark) .cta-callout,
body .cta-callout.is-on-light{
  border-color: var(--callout-border-light);
}

/* Typographic hierarchy */
.cta-callout-title{
  font-size: 17px;
  line-height: 1.2;
  letter-spacing: .01em;
  font-weight: 500;
  margin: 0 0 12px 0;
}

.cta-callout-copy{
  font-size: 15px;
  line-height: 1.6;
  margin: 0 0 12px 0;
  opacity: .92; /* keep calm, not greyed out */
}

/* Single decisive CTA - minimal editorial outline */
.cta-callout-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 38px;
  padding: 8px 16px;
  border-radius: var(--btn-radius);
  background: transparent !important;
  border: 1px solid rgba(255,255,255,0.24) !important;
  color: #fff !important;
  box-shadow: none !important;
  text-decoration: none;
  text-transform: none;
  font-weight: 500;
  letter-spacing: 0.06em;
  margin-top: 0;
  transition: border-color 120ms ease;
}

/* Hover/focus: very subtle */
.cta-callout-btn:hover{
  border-color: rgba(255,255,255,0.45) !important;
  background: transparent !important;
  filter: none;
  transform: none;
}

.cta-callout-btn:focus-visible{
  outline: 2px solid rgba(255,255,255,.65);
  outline-offset: 3px;
  border-color: rgba(255,255,255,0.45) !important;
  background: transparent !important;
}

/* Mobile: full width button and slightly tighter pad */
@media (max-width: 767px){
  .cta-callout{
    padding: 24px;
    max-width: 100%;
  }
  .cta-callout-btn{
    width: 100%;
  }
}

.muted{color:rgba(28,31,34,.70)}
h2{
  margin:8px 0 0;
  font-size:var(--h2);
  letter-spacing:-.01em;
  line-height:1.15;
  font-weight:400;
}
h3{margin:0 0 6px; font-size:var(--h3); font-weight:500; letter-spacing:-.01em}

/* Value props */
.bullets{margin:14px 0 0; padding:0; list-style:none; display:grid; gap:10px}
.bullets li{
  display:flex;
  gap:10px;
  align-items:flex-start;
  color:rgba(28,31,34,.82);
}
.tick{
  width:18px;
  height:18px;
  border-radius:50%;
  background:var(--csg-gold);
  position:relative;
  margin-top:2px;
  flex:0 0 auto;
}

.tick::after{
  content:"";
  position:absolute;
  left:5px;
  top:3px;
  width:6px;
  height:10px;
  border-right:2px solid #fff;
  border-bottom:2px solid #fff;
  transform:rotate(45deg);
}

/* Footer */
footer{
  padding:16px 0 34px;
  border-top: none;
  background: var(--bg);
  margin-top:20px;
}

.footer-grid{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:22px;
}

.footer-rail{
  width: 100%;
}

/* Desktop: expand footer beyond standard container */
@media (min-width: 1330px){
  footer .wrap{
    max-width: none;
    padding-left: 0;
    padding-right: 0;
  }

  footer .footer-grid{
    /* Full-bleed row, controlled gutters */
    max-width: none;
    width: 100%;
    margin-left: 0;
    margin-right: 0;
  }

  footer .footer-rail{
    /*
      Gutters:
      - stay at 24px up to ~1568px
      - then ramp linearly
      - hit 200px at 1920px
      Uses shared --gutter-wide token
    */
    padding-left: var(--gutter-wide);
    padding-right: var(--gutter-wide);
  }
}

/* Desktop: expand header rail beyond standard container */
@media (min-width: 1330px){
  .header-inner {
    --header-gutter: var(--gutter-wide);
    max-width: none;
    width: 100%;
    margin-left: 0;
    margin-right: 0;
  }

  /* Header inner no longer controls width */
  .header .wrap,
  .site-header .wrap,
  header .wrap {
    max-width: none;
    margin-left: 0;
    margin-right: 0;
  }

  /* Ensure header container spans full width */
  .header,
  .site-header,
  header {
    left: 0;
    right: 0;
    width: 100%;
  }
}

/* Six icons on ONE row. Duncan, 2026-09-01: "social logos are wrapping to two lines".
   Measured: the row is a flex ITEM inside .footer-grid and was being shrunk to 222px while its
   six 32px icons plus 14px gaps need 262px, so flex-wrap sent two of them to a second line.
   flex-shrink:0 stops the parent squeezing it below its content, and nowrap makes the
   intention explicit rather than leaving a wrap that only shows up at certain widths. */
.footer-social{
  flex-shrink:0;
  display:flex;
  gap:14px;
  /* nowrap, once. It was declared here and then overridden by flex-wrap:wrap two lines
     later, so the fix the comment above describes was not actually in effect - the last
     declaration wins. Found 2026-09-14 while sizing the tap targets. */
  flex-wrap:nowrap;
  justify-content:flex-start;
  font-size:14px;
  color:rgba(28,31,34,.72);
}

.footer-social a{
  position: relative;
  display: inline-block;
  text-decoration: none;
  border-bottom: none;
  padding-bottom: 2px;
}

/* WCAG 2.2 AA 2.5.8 Target Size (Minimum): 24 x 24 CSS px.
   Measured 2026-09-14 across 19 viewports from 320px to 2560px: these anchors rendered
   20 x 20 on every page at every width, because nothing sized them and the SVG contributes
   its own intrinsic 20px. Six of them, on every page, and worst on the 320px phone where a
   thumb is least accurate.

   The TARGET grows, the ICON does not. inline-flex centres the existing 20px artwork inside
   a 24px box, so nothing looks different and only the hit area changes - which is the whole
   point of 2.5.8.

   Width is not free here: the comment above records that this row is a flex item squeezed to
   222px and wrapped to two lines when its contents needed more. Six 24px targets plus five
   14px gaps is 214px, so this stays inside that budget. Do not raise it further without
   re-measuring the wrap. */
.footer-social a.social-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
}

.footer-social a::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: rgba(28,31,34,.65);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}

.footer-social a:hover::after{
  transform: scaleX(1);
}

.footer-logo:hover{
  background:rgba(255,255,255,.55);
  border-color:var(--border);
}

/* Dark UI: remove grey hover box, only opacity lift */

/* THE FOOTER HAS TWO BACKGROUNDS, so these two follow the theme instead of picking a side.
 *
 * The history is the point. First report, Duncan: "you seem to have lost the self updating
 * copyright" then "it is black text on lack background" - it was rgba(0,0,0,.55) on black,
 * contrast 1.00. I made it white. Second report, minutes later: "it is now white text on a
 * white background... make it black text on white bg".
 *
 * BOTH were true, and I had missed why: `footer` is var(--bg) by default and, at the time,
 * body[data-ui="dark"] footer WAS #000 (that rule has since been removed - the theme is
 * pinned light, so it could never match). The band flipped with the UI theme, which is why my
 * measurement said black and his screen said white. Swapping one literal for the other could
 * only ever fix one of the two states - so the second report was the predictable consequence
 * of the first fix, not a new fault.
 *
 * FIFTH instance of this shape today: white H1 on a white hero, drawer links under the AA
 * floor, black-on-black here, an H1 hidden with display:none, and now this. Every one a colour
 * written against an ASSUMED background. The fix that generalises is not a better literal, it
 * is following the theme the way the .footer-social and .contact-footer-rule rules beside this
 * one already do.
 *
 * Light: rgba(0,0,0,.55) on white = 5.92:1. Dark: rgba(255,255,255,.55) on black = 6.25:1.
 * Both clear the 4.5:1 that 12px text needs. */
.footer-divider{
  width: 100%;
  height: 1px;
  background: rgba(28,31,34,.24);
  margin: 1px 0;
}

.footer-copyright{
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(0,0,0,.55);
}

/* Dark UI: white logo variants should be subdued until hover (match light-mode behaviour) */

/* Hover brings them up to full strength */

/* Footer theme sync with header via global UI state */

body[data-ui="light"] footer{
  background: var(--bg);
  color: var(--charcoal);
}

body[data-ui="light"] .footer-social{
  color: rgba(28,31,34,.72);
}

body[data-ui="light"] .footer-social a::after{
  background: rgba(28,31,34,.65);
}

/* Contact section - footer-style three-column block */
.contact-footer{
  padding: 0; /* let the internal wrap handle vertical rhythm if needed */
}

.contact-footer-rule{
  height: 1px;
  background: rgba(28,31,34,.24);
  opacity: 1;
}

.contact-footer-rule--bottom{
  margin-top: 0;
}

.contact-footer-grid{
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(220px, 1fr) minmax(220px, 1fr);
  align-items: start;
  gap: 48px;
  padding: 40px 0;
}

.contact-footer-policies{
  display: grid;
  gap: 10px;
  justify-items: start;
  padding-top: 28px; /* matches title height so policy lines align with 'Creative House' */
}

.contact-footer-link{
  position: relative;
  display: inline-block;
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  color: inherit;
  opacity: .92;
  padding-bottom: 1px;
}

.contact-footer-link::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}

.contact-footer-link:hover{
  opacity: 1;
}

.contact-footer-link:hover::after{
  transform: scaleX(1);
}

.contact-footer-addressTitle{
  font-size: 18px;
  line-height: 1.15;
  font-weight: 600;
  margin: -6px 0 10px; /* lifts title slightly higher on the page */
}

.contact-footer-addressLine{
  font-size: 14px;
  line-height: 1.6;
  opacity: .92;
}

.contact-footer-addressLine a[href^="tel:"]{
  display: inline-block;
  margin-top: 14px;
  font-weight: 600;
}

.contact-footer-cta{
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-items: flex-start;
}

/* .contact-footer-phone joins this on 2026-09-17. It had NO styling at all - a tel: link with
   no hover affordance, which is the one link on the page a visitor is most likely to want to
   press. .header-phone-link is handled separately below because it must stay inline-flex. */
.contact-footer-tell-more,
.contact-footer-phone{
  position: relative;
  display: inline-block;
  text-decoration: none;
  color: inherit;
  padding-bottom: 1px;
}

.contact-footer-tell-more::after,
.contact-footer-phone::after,
.header-phone-link::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}

.contact-footer-tell-more:hover::after,
.contact-footer-phone:hover::after,
.contact-footer-phone:focus-visible::after,
.header-phone-link:hover::after,
.header-phone-link:focus-visible::after{
  transform: scaleX(1);
}

/* Intro section: Tell us more button (inverted colors for dark background) */
.intro-tell-more{
  position: relative;
  display: inline-block;
  text-decoration: none;
  color: #fff;
  /* Explicit, for the same reason as .btn and the nav links: this declared no weight, so the
     new body default of 300 thinned it. It is the "Party Tent Rental" call to action, which is
     the last thing on the page that should read faint. 400 is what it rendered at before. */
  font-weight: 400;
  padding-bottom: 1px;
}

.intro-tell-more::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.6s cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}

.intro-tell-more:hover{
  color: #fff;
}

.intro-tell-more:hover::after{
  transform: scaleX(1);
}

.contact-footer-headline{
  margin: 0 0 0 0;
  /* One step down the scale, not a literal size: --h2 and --h3 are both redefined at several
     breakpoints, so a px value would hold at one width and break the rhythm at the rest.
     Duncan, 2026-09-14. */
  font-size: var(--h3);
}

.contact-footer-cta .btn.alt{
  padding: 8px 16px;
  border-radius: 6px;
  border: 1px solid rgba(28, 31, 34, 0.24);
  background: transparent;
  color: rgba(28, 31, 34, 0.92);
  letter-spacing: 0.84px;
  transition: border-color 0.12s;
  box-shadow: 0 2px 8px rgba(28, 31, 34, 0.08);
}

.contact-footer-cta .btn.alt:hover{
  border-color: rgba(28, 31, 34, 0.40);
  background: transparent;
  color: rgba(28, 31, 34, 0.92);
}

@media (max-width: 920px){
  .contact-footer-grid{
    grid-template-columns: 1fr;
    gap: 28px;
  }
}

/* =====================================================
   Phase 3 CSS discipline
   - Primitives only (reusable across 2+ sections)
   - Exceptions must be justified
   - No per-section selectors (#section-* .foo)
   ===================================================== */

/* =====================================================
   Editorial layout scaffold (Phase 3)
   Structure only, no section-specific styling
   ===================================================== */

.editorial-section{
  position:relative;
  padding:clamp(64px, 8vw, 120px) 0;
}

.editorial-section .wrap{
  width:min(1200px, calc(100% - 48px));
  margin:0 auto;
}

.editorial-grid{
  display:grid;
  align-items:center;
  gap:clamp(24px, 4vw, 48px);
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
}

/* Alternating rhythm */
.editorial-section--image-left .editorial-media{ order:0; }
.editorial-section--image-left .editorial-body{ order:1; }

.editorial-section--image-right .editorial-media{ order:1; }
.editorial-section--image-right .editorial-body{ order:0; }

/* Text discipline */
.editorial-body{
  max-width:56ch;
}

/* Media abstraction */
.editorial-media{
  position:relative;
}

.editorial-mediaFrame{
  position:relative;
  overflow:hidden;
  border-radius:18px;
  background:rgba(0,0,0,.04);
}

.editorial-mediaFrame > *{
  display:block;
  width:100%;
  height:auto;
}

/* Responsive collapse */
@media (max-width: 920px){
  .editorial-grid{
    grid-template-columns:1fr;
  }

  .editorial-section--image-left .editorial-media,
  .editorial-section--image-right .editorial-media{
    order:0;
  }

  .editorial-section--image-left .editorial-body,
  .editorial-section--image-right .editorial-body{
    order:1;
  }
}

.editorial-sectionHeader{
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-top: clamp(72px, 10vw, 140px);
  padding-bottom: clamp(18px, 3.2vw, 42px);
  padding-inline: 0;
  text-align: center;
  box-sizing: border-box;
  position: relative;
  z-index: 3;
  overflow: visible;
}

.editorial-sectionHeader-inner{
  width: 100%;
  box-sizing: border-box;
}

.editorial-sectionHeader-rail{
  position: relative;
}

.editorial-sectionHeader h2{
  margin: 0;
  position: relative;
  z-index: 1;
  white-space: nowrap;
  text-wrap: nowrap;
  font-size: clamp(16px, 2.2vw, 56px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: var(--charcoal);
  text-shadow:
    0 14px 28px rgba(0,0,0,.22),
    0 40px 110px rgba(0,0,0,.26),
    0 72px 180px rgba(0,0,0,.18);
}

.editorial-sectionHeader h2::before,
.editorial-sectionHeader h2::after{
  content: none !important;
  display: none !important;
}

/* Grid-quotes modifier: disable pseudo-element quotes, use grid-based quotes instead */
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes::before,
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes::after{
  content: none;
  display: none;
}

/* Grid-quotes modifier: allow wrapping for long headlines */
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes h2,
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes h1{
  white-space: normal;
  text-wrap: balance;
}

/* Grid-quotes modifier: grid-based quote layout */
/* Mobile base (≤767px): stretching middle column, wrapping headline */
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid{
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  justify-content: stretch;
  width: 100%;
  padding-inline: 0;
  column-gap: 8px;
}

.editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1{
  margin: 0;
  width: 100%;
  max-width: none;
  white-space: normal;
  text-align: center;
  text-wrap: balance;
  min-width: 0;
  font-size: clamp(15px, 2.9vw, 30px);
}

/* Tablet+ (768px+): tight lockup with max-content */
@media (min-width: 768px){
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid{
    grid-template-columns: auto max-content auto;
    justify-content: center;
    column-gap: 8px;
    padding-inline: 0;
  }

  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1{
    width: auto;
    max-width: none;
  }
}

/* Tablet Plus+ (1000px+): enforce single line */
@media (min-width: 1000px){
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1{
    white-space: nowrap;
  }
}

@media (max-width: 767px){
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid{
    grid-template-columns: auto minmax(0, clamp(28ch, 86vw, 44ch)) auto;
    justify-content: center;
    align-items: center;
    column-gap: 12px;
  }

  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1{
    width: 100%;
    max-width: none;
    margin: 0;
    font-size: 18px;
    line-height: 1.14;
    letter-spacing: -0.01em;
    text-align: center;
    text-wrap: balance;
  }
}

@media (min-width: 768px) and (max-width: 999px) {
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1 {
    font-size: clamp(18px, 2.2vw, 26px);
  }
}

@media (min-width: 1000px) and (max-width: 1329px) {
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1 {
    font-size: clamp(22px, 2.2vw, 27px);
  }
}

@media (min-width: 1330px) and (max-width: 1919px) {
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1 {
    font-size: clamp(28px, 2.2vw, 42px);
  }
}

@media (min-width: 1920px) {
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h2,
  .editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-sectionHeader-grid h1 {
    font-size: clamp(28px, 2.2vw, 42px);
  }
}

/* Grid-quotes modifier: quote glyph styling */
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes .editorial-quote{
  font-family: "Libre Bodoni", Didot, "Bodoni MT", Georgia, serif;
  font-weight: 400;
  line-height: 1;
  align-self: center;
  color: rgba(28,31,34,.20);
  letter-spacing: -0.05em;
  font-size: clamp(40px, 15vw, 160px);
  transform: translateY(-0.08em);
  user-select: none;
  pointer-events: none;
  display: block;
  white-space: nowrap;
}

/* No quote glyphs: collapse the three-column lockup to one centred column.
   Duncan, 2026-09-17: "get rid of all of the giant speech marks on the titles on all pages".

   The headline is auto-placed, so with the two spans gone it would otherwise drop into column 1
   and sit off centre with the stretching track empty beside it. This has to win at EVERY
   breakpoint, and it does so on specificity rather than source order: four classes (0,4,0)
   against the three (0,3,0) used by each media-query variant above, so no !important and no
   duplicating the rule inside each query. */
.editorial-sectionHeader.editorial-sectionHeader--gridQuotes.editorial-sectionHeader--noQuotes .editorial-sectionHeader-grid{
  grid-template-columns: minmax(0, 1fr);
  justify-content: center;
  column-gap: 0;
}

/* =====================================================
   Editorial cover hero (Phase 3)
   ===================================================== */

.editorial-hero{
  position:relative;
  min-height:clamp(480px, 65vh, 720px);
  overflow:hidden;
  /* The dark ground lives HERE, not on .editorial-heroMedia.
   *
   * FOUND BY REVIEW, 2026-08-31 (codex, finding 2). .editorial-heroMedia only renders when
   * the page has a hero image, and it carried the charcoal background - so all 18 channel
   * pages, which have hero copy and no image yet, painted a WHITE title on a WHITE band.
   * 1:1 contrast: the headline was not dim, it was invisible, above 585px of blank page.
   *
   * Putting the ground on the section makes the hero correct with an image and without
   * one, which is the state every page is in until the photography lands. The media layer
   * still sits on top and covers it completely when present. */
  background:var(--charcoal);
}

.editorial-heroMedia{
  position:absolute;
  inset:0;
  background:var(--charcoal);
  min-height:clamp(480px, 65vh, 720px);
}

.editorial-heroMedia img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.editorial-heroImg{
  width:100%;
  height:100%;
  object-fit:cover;
  /* Duncan, 2026-09-14: "hero gallery photo needs moving up the page as the tent is not in
     site". The hero band is 1440x533 against a 1536x1022 source, so cover discards roughly
     half the image height. The default 50% 50% keeps the MIDDLE of the source, which on
     these shots is sky and horizon; the structure sits lower in frame and is cropped away.

     Moving the focal point DOWN the source (72%) brings the lower band into view, which
     moves the tent UP the rendered hero. Same value and the same reason as the homepage
     hero, which Duncan corrected the same way on 2026-09-08. */
  object-position:50% 72%;
  display:block;
}

/* PER-PAGE HERO CROP. The 72% above is not a universal truth, it is a fix for source frames
   where the structure sits LOW, and it inverts on a frame where the structure sits high.

   /corporate-events/ is that case. Duncan, 2026-09-17: "The main photo on this page is great
   but it's cropping the tent out, are we able to move it up slightly?" In
   corporate-golf-beige-joined-w1600.jpg (1600x941) the marquee runs across roughly 22-30% of
   the source height, above the bunker and the golfer. At 1440 wide the image scales to 847px
   tall in a ~617px band, so cover discards 230px; 72% of that is a 166px offset, which opens
   the visible window at about 20% of the source - landing the tent line directly behind the H1
   and under the darkest part of the ::after scrim. The tent is present and unreadable, which
   is what "cropping the tent out" describes.

   SO THIS VALUE GOES DOWN, NOT UP, and that is the counterintuitive part worth stating plainly
   before someone "corrects" it back: a SMALLER Y opens the window higher in the source, which
   pushes the subject DOWN the rendered hero and out from behind the headline. It is the exact
   inverse of the 2026-09-14 change above, for the exact opposite reason.

   Keyed on the slug class from the body_class filter in functions.php, never on page-id-N,
   because post IDs differ between this install and production. */
.page--corporate-events .editorial-heroImg{
  object-position:50% 18%;
}

/* Festivals. Duncan, 2026-09-22: "tent needs to move up the screen".
   Same mechanism and the same counter-intuitive direction as the note above: moving the focal
   point DOWN the source moves the subject UP the rendered band, because it is the crop window
   that travels. The shared 72% was tuned for the old paving-slab shot; the replacement sits
   much lower in frame - sky occupies roughly the top 60% - so 72% left the tent in the bottom
   half of the band. 100% pins the crop to the foot of the source, which lifts the roofline
   from about 56% to 38% down the band and keeps the lounge furniture in shot.

   Desktop only, by construction. On a phone the band is PORTRAIT (400x520 against a 3:2
   source), so cover fills the height exactly, there is no vertical slack, and this value
   cannot move anything. Verified rather than assumed. */
.page--festival-tent-rental .editorial-heroImg{
  object-position:50% 100%;
}

.editorial-heroOverlay{
  position:absolute;
  inset:0;
  z-index:3;
}

.editorial-heroOverlay .wrap{
  position:relative;
  height:100%;
  padding-top:clamp(14px, 6vw, 62px);
}

/* ALL overlay copy is light, not only the title.
 *
 * FOUND BY REVIEW, 2026-08-31, as a regression in the hero fix an hour earlier. Moving the
 * charcoal ground onto .editorial-hero made the white H1 readable and left the standfirst
 * inheriting the body colour - so it computed to rgb(28,31,34) on an rgb(28,31,34) ground.
 * Exactly invisible, and a fix that had introduced the same defect it was fixing, one
 * element down.
 *
 * The lesson is in the selector: colouring the title alone assumed the title was the only
 * thing in the overlay. This rule covers whatever the overlay contains, so adding a third
 * element to it later cannot reopen the same hole. */
.editorial-heroOverlay,
.editorial-heroOverlay p,
.editorial-heroOverlay li,
.channel-hero-standfirst{
  color:rgba(255,255,255,.88);
}

.editorial-heroTitle{
  margin:0;
  font-size:var(--h1);
  letter-spacing:-.02em;
  line-height:1.05;
  font-weight:400;
  color:#fff;
  text-shadow:0 10px 30px rgba(0,0,0,.55);
}

.editorial-heroCta{
  position:absolute;
  right:var(--pad);
  bottom:var(--pad);
  margin:0;
}

/* =====================================================
   Hero-top: disable legacy hero overlays
   ===================================================== */

body.is-hero-top .hero-inner{
  display: grid !important;  /* restore container so CTA can render */
}

/* The headline is BACK on the hero, and the H1 is back in the accessibility tree.
 *
 * This rule hid three things, and it was written for the removed animation: the hero opened
 * as a bare image and the headline arrived with the scroll. With the animation gone the front
 * page had a full-bleed photograph, a CTA, and no words at all - on the page whose whole job
 * is the proposition.
 *
 * The h1.visually-hidden entry was a real bug regardless of the animation. .visually-hidden
 * uses the clip technique precisely so the text STAYS in the accessibility tree while being
 * off-screen; overriding it with display:none removes it from that tree as well, so the
 * homepage had no H1 for a screen reader at all. A hidden H1 and a deleted H1 are not the
 * same thing, and display:none makes it the second one.
 *
 * .kicker stays suppressed - it is a separate decorative element with no content set. */
body.is-hero-top .kicker{
  display: none !important;
}

/* Hero CTA must remain visible even in hero-top mode */
body.is-hero-top .hero-cta{
  display: flex !important;
}

/* Page shells: banner + body.
   Kept as a brand-neutral template. The pages that used it (About, Careers and the UK
   legal set) are deleted; no surviving template renders these classes yet.
   TODO(csg-usa): the US legal page set that will reuse this shell - blocked on those
   pages being written and their slugs being decided (see parts/shared/contact-footer.php). */
.section--pageBanner{
  padding-top: clamp(60px, 8vw, 120px);
  padding-bottom: clamp(14px, 2vw, 28px);
}

/* Avoid double vertical gap: content section follows banner with its own padding */
.section--pageBanner + .section{
  padding-top: 0;
}

/* Tighter gap between policy banner and body content */
.section--pageBanner + .section .pageBody{
  padding-top: clamp(12px, 1.5vw, 22px);
}

.pageBanner{
  border-top: 1px solid rgba(0,0,0,.14);
  padding-top: clamp(18px, 2.5vw, 28px);
}

.pageBanner__kicker{
  font-family: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(28,31,34,.55);
  margin: 0 0 14px 0;
}

.pageBanner__title{
  margin: 0 0 18px 0;
}

.pageBanner__sub{
  max-width: 62ch;
  color: rgba(28,31,34,.78);
  margin: 0;
}

.pageBody{
  border-top: 1px solid rgba(0,0,0,.14);
  padding-top: clamp(22px, 3vw, 38px);
}

.pageBody :is(p, ul, ol){
  max-width: 70ch;
}

/* =====================================================
   Two-column editor content

   Duncan, 2026-09-17, on the Parties page: "can [the occasions block and Get in Touch To
   Explore] sit on the right in a second column to [What our clients say]".

   Opt-in by markup: these rules do nothing until a page's content carries .pageBody-cols, so no
   other page is touched. The wrapper is added by _claude/tools/wrap-parties-body-columns.php,
   which splits the stored content rather than retyping it.
   ===================================================== */
.pageBody-cols{
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
}

/* THE MEASURE HAS TO BE RELEASED INSIDE THE COLUMNS. .pageBody :is(p, ul, ol) caps at 70ch,
   which is right for a single running column and wrong here: neither column is anywhere near
   70 characters wide, so the cap never binds but the rule still sits between the column width
   and the text. Setting none makes the column itself the measure, which is what it should be. */
.pageBody-cols :is(p, ul, ol){
  max-width: none;
}

/* The first heading in each column sits on the same line as its neighbour. Without this the
   left column's h2 carries the section's top margin and the right column's does not, so the two
   headings start a stride apart. */
.pageBody-col > :first-child{
  margin-top: 0;
}

/* One column below this. 860px rather than the usual 767px breakpoint: two columns of running
   text plus a 28px gutter stop being readable well before the layout is technically too narrow,
   and the testimonial is the longest block on the page. Source order already reads correctly -
   testimonial, then the lists - so nothing needs reordering when they stack. */
@media (max-width: 860px){
  .pageBody-cols{
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(22px, 5vw, 36px);
  }

  /* Stacked, it is a single running column again, so the reading measure comes back. */
  .pageBody-cols :is(p, ul, ol){
    max-width: 70ch;
  }
}

/* Policy pages: allow content to fill container width */
.section--pageBanner + .section .pageBody :is(p, ul, ol){
  max-width: none;
}

/* Login gate - minimal standalone page */
body.login-page{
  margin: 0;
  padding: 0 !important;
  padding-top: 0 !important;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

.loginGate-wrapper{
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.loginGate{
  max-width: 400px;
  width: 100%;
  text-align: center;
}

.loginGate__title{
  font-family: "Libre Bodoni", Didot, "Bodoni MT", Georgia, serif;
  font-size: clamp(32px, 4vw, 48px);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0 0 12px 0;
  color: var(--charcoal);
}

.loginGate__subtitle{
  font-family: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 15px;
  color: rgba(28,31,34,.65);
  margin: 0 0 32px 0;
}

.loginGate__form{
  display: grid;
  gap: 16px;
  margin-top: 0;
}

.loginGate__input{
  width: 100%;
  padding: 14px 16px;
  border-radius: 8px;
  border: 1px solid rgba(28,31,34,.18);
  font-family: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 15px;
  background: var(--bg);
  color: var(--charcoal);
  box-sizing: border-box;
}

.loginGate__input:focus{
  outline: 2px solid color-mix(in srgb, var(--csg-brass) 55%, transparent);
  outline-offset: 2px;
  border-color: rgba(28,31,34,.28);
}

.loginGate__input::placeholder{
  color: rgba(28,31,34,.45);
}

.loginGate__error{
  color: rgba(120,0,0,.82);
  margin: 0 0 16px 0;
  font-size: 14px;
}

/* =====================================================
   RECOVERED COMPONENT STYLES - restored 2026-08-30
   -----------------------------------------------------
   These rules were stripped during the csg-corporate sanitisation on the premise that
   their markup had been deleted. Duncan then restored parts/home/clients.php (the logo
   marquee behind the Proud to Supply wall) and parts/home/portfolio.php (the tile grid
   the launch /gallery/ needs), so that premise no longer holds and the styles belong
   with the markup they serve. Recovered verbatim from f349265 by selector match.

   They style STRUCTURE only. Both components render nothing until they are given
   content, and the colours here still reference the UK brand tokens, which are
   TODO(csg-usa) pending the US palette.
   ===================================================== */

/* =====================================================
   Clients marquee section
   ===================================================== */

.section-clients {
  padding: 64px 0;
  background: #fff;
}

.clients-title {
  text-align: center;
  margin: 0 0 48px 0;
  font-size: var(--h2);
}

.clients-marquee {
  /* FULL BLEED, breaking out of the 1180px .wrap.
   *
   * Duncan, 2026-08-31: "Proud to Supply section is all messed up, not full width and logos
   * are miniscule, check how it was done on the csg main site". Measured the LIVE
   * creativestructuresgroup.com rather than trusting the csg-corporate repo - and they differ.
   * The repo still has width:100%, 36px marks and a 42px gap; the live site runs this
   * break-out with 46px marks and a 70px gap. Newer CSS was deployed there than was ever
   * committed, which is why copying the repo reproduced the broken version.
   *
   * The break-out: left:50% moves the box to the centre of its container, -50vw pulls it back
   * to the viewport edge, 100vw makes it the full viewport regardless of the wrap around it.
   * Verify by effect - document.scrollWidth must not exceed the viewport, because 100vw
   * includes the scrollbar in some browsers and that is the classic way this technique
   * introduces a horizontal scroll. */
  position: relative;
  overflow: hidden;
  width: 100vw;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  max-width: none;
  mask-image: linear-gradient(to right, transparent 0, #000 55px, #000 calc(100% - 55px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 55px, #000 calc(100% - 55px), transparent 100%);
}

.clients-track {
  display: flex;
  align-items: center;
  /* 70px and 160s, both from the live site. The wider gap is what stops the wall reading as a
     dense strip of thumbnails; the slower cycle suits the longer track. */
  gap: 70px;
  width: max-content;
  animation: clients-marquee 160s linear infinite;
}

.clients-marquee:hover .clients-track {
  animation-play-state: paused;
}

/* =====================================================
   BRAND NAME ON HOVER

   Duncan, 2026-09-18: "when we hover the animation stops, can we also pop up the brand name as
   some of the logos are too small to read". The pause already existed; this is the label.

   WHY THE MARQUEE GETS PADDING AND A NEGATIVE MARGIN. The label has to be drawn INSIDE
   .clients-marquee, because that element carries a mask-image - and a mask clips its subtree
   regardless of overflow, so `overflow: visible` cannot rescue a tooltip drawn above the strip.
   The mask gradient runs left-to-right and is vertically opaque, so there is nothing stopping a
   label from being painted above the logos PROVIDED the box is tall enough to contain it.

   padding-top makes that room; the equal negative margin-top pulls the box back up so the strip
   sits exactly where it did before and the section's height is unchanged. Verify by effect: the
   logo row must not move down the page when this block is added.

   GATED ON (hover: hover). A touch device cannot hover, cannot dismiss a label once shown, and
   on mobile .clients-marquee additionally carries `contain: layout paint`, which clips the
   subtree independently of the mask. A label that appears on tap and then sticks is worse than
   no label, so pointer devices only.
   ===================================================== */

@media (hover: hover) {
  .clients-marquee {
    padding-top: 58px;
    margin-top: -58px;
  }

  .client-logo {
    position: relative;
  }

  /* The hovered mark sits above its neighbours, or a wide label is overlapped by the next logo. */
  .client-logo:hover {
    z-index: 3;
  }

  /* The label. content comes from data-brand, which both marquee passes carry. */
  .client-logo::after {
    content: attr(data-brand);
    position: absolute;
    bottom: calc(100% + 16px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    z-index: 3;

    padding: 7px 14px;
    border-radius: 8px;
    background: #fff;
    box-shadow: 0 2px 12px rgba(0, 0, 0, .14);

    font-size: 13px;
    line-height: 1.3;
    color: var(--charcoal);
    white-space: nowrap;

    opacity: 0;
    pointer-events: none;
    transition: opacity .16s ease, transform .16s ease;
  }

  /* The caret: a rotated square tucked under the label so its two visible edges read as a point. */
  .client-logo::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 11px);
    left: 50%;
    transform: translateX(-50%) rotate(45deg) translateY(4px);
    z-index: 2;

    width: 10px;
    height: 10px;
    background: #fff;
    /*
     * The caret needs an edge or it does not exist. It is a white square on a white section, so
     * with no shadow it renders as nothing at all - measured: the label appeared correctly in
     * the hover capture and the point below it was simply invisible.
     *
     * Only the two OUTWARD faces are shadowed. The square is rotated 45 degrees, so its
     * bottom-right and bottom-left edges are the ones pointing at the logo; a symmetrical shadow
     * would darken the seam where it meets the label above it.
     */
    box-shadow: 2px 2px 3px rgba(0, 0, 0, .07);

    opacity: 0;
    pointer-events: none;
    transition: opacity .16s ease, transform .16s ease;
  }

  .client-logo:hover::after {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }

  .client-logo:hover::before {
    opacity: 1;
    transform: translateX(-50%) rotate(45deg) translateY(0);
  }

  /* A row with no brand name recorded shows nothing at all, rather than an empty white box. */
  .client-logo[data-brand=""]::after,
  .client-logo[data-brand=""]::before,
  .client-logo:not([data-brand])::after,
  .client-logo:not([data-brand])::before {
    content: none;
  }

  /* The underline from the mockup. A border rather than a third pseudo-element, both of which
     are already spent on the label and its caret. The small dot on the mockup's rule is omitted
     for the same reason - it would need an extra wrapper in the markup. */
  .client-logo {
    border-bottom: 1px solid transparent;
    padding-bottom: 6px;
    transition: border-color .16s ease;
  }

  .client-logo:hover {
    border-bottom-color: rgba(0, 0, 0, .18);
  }
}

@keyframes clients-marquee{
  0%{
    transform: translateX(0);
  }
  100%{
    transform: translateX(-50%);
  }
}

.client-logo {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 46px, not 36px. At 36px a wordmark like "Historic Royal Palaces" renders as unreadable
     grey texture - the "miniscule" complaint. The wall's whole job is the NAMES on it. */
  height: 46px;
}

.client-logo img {
  height: 100%;
  width: auto;
  max-width: none;
  object-fit: contain;
  filter: none;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  /* A STATIC WALL, not a frozen one.
   *
   * FOUND BY REVIEW, 2026-08-31 (codex, addendum to finding 8). Stopping the animation
   * alone left .clients-marquee at overflow:hidden and .clients-track at width:max-content,
   * so the track simply stood still with most of itself outside the clip. Measured at
   * 1440px: 10 of 47 logos visible, and the other 37 unreachable by any means - no scroll,
   * no hover, nothing. A reader who asks for reduced motion is not asking to be shown less
   * of the content, and this is a credibility wall whose entire job is the NAMES on it.
   *
   * So under reduce it becomes a wrapping grid that shows every logo at once. The gradient
   * mask is removed too: it exists to fade items entering and leaving a moving strip, and
   * over a static grid it just dims the outer columns for no reason. */
  .clients-track {
    animation: none;
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
    gap: 30px 42px;
  }

  .clients-marquee {
    overflow: visible;
    mask-image: none;
    -webkit-mask-image: none;
  }

  /* The second pass is a visual duplicate for the seamless loop and is already
     aria-hidden. With the track wrapped there is no loop, so it would just print every
     logo twice. */
  .clients-track .client-logo[aria-hidden="true"] {
    display: none;
  }
}

@media (max-width: 767px) {
  /* On mobile, contain the clients marquee so its wide track does not affect document scrollWidth */
  .clients-marquee {
    overflow-x: hidden;           /* clip horizontal overflow of .clients-track */
    contain: layout paint;        /* prevent track from expanding the page’s scrollable area */
  }
  body.nav-open   /* Portfolio logos rail at bottom of mobile menu */
  /* 0–430px: scale up to fill rail */
}

/* Portfolio tiles */
.portfolio-tile {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.portfolio-tile .img {
  height: 190px;
  background: #ddd;
  flex: 0 0 auto;
  overflow: hidden;
}

.portfolio-tile .img .tile-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.portfolio-tile .meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1 1 auto;
}

.portfolio-grid {
  margin-top: 18px;
}

.portfolio-desc {
  margin: 0;
}

/* Portfolio outbound links */
.portfolio-url {
  display:inline-block;
  width:max-content;
  margin-top:8px;
  font-size:13px;
  font-weight:500;
  color:rgba(28,31,34,.86);
  text-decoration:none;
  border-bottom:1px solid rgba(28,31,34,.25);
  padding-bottom:1px;
  transition:color .18s ease, border-color .18s ease;
}

.portfolio-url:hover {
  color:var(--charcoal);
  border-bottom-color:rgba(28,31,34,.55);
}

/* Hide portfolio logos in footer on mobile (use burger menu rail instead) */
@media (max-width: 767px) {
  footer .footer-logos{
    display: none !important;
  }
}

/* =====================================================
   Portfolio 3-column grid layout
   ===================================================== */

.section-portfolio {
  overflow-x: clip;
  overflow-y: visible;
  position: relative;
  --portfolio-img-shadow:
    0 0 0 1px rgba(0,0,0,.14),
    0 10px 22px rgba(0,0,0,.26),
    0 28px 60px rgba(0,0,0,.18);
  --portfolio-logo-h: 64px;
}

.portfolio-grid {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  margin-top: clamp(-18px, -2vw, -36px);
  padding: var(--space-4) 0;
  display: grid;
  gap: 0;
  row-gap: 0;
  column-gap: 0;
  box-sizing: border-box;
  position: relative;
  z-index: 1;
  background: transparent;
}

.section-portfolio .portfolio-rail {
  width: 100%;
}

.portfolio-row {
  display: grid;
  grid-template-columns: 2fr 1fr;
  align-items: center;
  gap: 0;
  position: relative;
  width: 100%;
}

.portfolio-row.is-right {
  grid-template-columns: 1fr 2fr;
}

.portfolio-row.is-right .portfolio-media {
  order: 2;
}

.portfolio-row.is-right .portfolio-content {
  order: 1;
}

.portfolio-media {
  background: transparent;
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: visible;
  z-index: 1;
}

.portfolio-row.is-left .portfolio-media {
  margin-right: 0;
}

.portfolio-row.is-right .portfolio-media {
  margin-left: 0;
}

.portfolio-row + .portfolio-row .portfolio-media {
  margin-top: 0;
  position: relative;
  z-index: 1;
}

.portfolio-media-inner {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(48px, 6.2vw, 120px);
  box-sizing: border-box;
  background: transparent;
  border-radius: 0;
}

.portfolio-media-img {
  max-width: 85%;
  max-height: 85%;
  width: auto;
  height: auto;
  object-fit: contain;
  object-position: center center;
  display: block;
  border-radius: 0;
  overflow: hidden;
  box-shadow: var(--portfolio-img-shadow);
  /* "Wrapped print" depth */
  outline: 0;
  background: transparent;
}

.portfolio-content {
  background: transparent;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(24px, 2.4vw, 56px);
  gap: var(--space-2);
  position: relative;
  z-index: 3;

  /* Centre the copy block within its grid cell, while keeping text left-aligned */
  max-width: 56ch;
  margin-inline: auto;
  text-align: left;
}

/* Anchor the copy column towards the image seam (keeps pairing tight) */
.portfolio-row.is-right .portfolio-content {
  margin-left: auto;
  margin-right: 0;
  padding-left: clamp(28px, 3vw, 64px);
  padding-right: clamp(18px, 2vw, 48px);
}

.portfolio-row.is-left .portfolio-content {
  margin-left: 0;
  margin-right: auto;
  padding-left: clamp(18px, 2vw, 48px);
  padding-right: clamp(28px, 3vw, 64px);
}

.portfolio-logo {
  background: none;
  border: none;
  box-shadow: none;
  height: var(--portfolio-logo-h);
  display: flex;
  align-items: center;
}

.portfolio-logo img {
  height: 100%;
  width: auto;
  max-width: 100%;
  display: block;
  object-fit: contain;
}

/* Portfolio: per-logo scale (presentation only, no asset changes) */
#section-portfolio .portfolio-logo {
  --logo-scale: 1;
  overflow: visible;
  justify-content: center;
}

#section-portfolio .portfolio-logo[data-logo-scale="0.9"] {
  --logo-scale: 0.9;
}

#section-portfolio .portfolio-logo[data-logo-scale="1.4"] {
  --logo-scale: 1.4;
}

#section-portfolio .portfolio-logo[data-logo-scale="1.6"] {
  --logo-scale: 1.6;
}

#section-portfolio .portfolio-logo img {
  display: block;
  transform: scale(var(--logo-scale));
  transform-origin: center center;
}

.portfolio-title {
  margin: 0;
}

.portfolio-desc {
  margin: 0;
  max-width: 48ch;
}

/* Portfolio CTA overlay positioning baseline */
#section-portfolio.section-portfolio .portfolio-media-inner {
  position: relative;
}

/* Portfolio overlay CTA – neutralized container, solid button */

/* =====================================================
   About page layout (forked from Portfolio)
   ===================================================== */

.section-about .about-grid {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  margin-top: clamp(-18px, -2vw, -36px);
  padding: var(--space-4) 0;
  display: grid;
  gap: 0;
  /* row-gap removed - using margin-bottom on rows instead */
  column-gap: 0;
  box-sizing: border-box;
  position: relative;
  z-index: 1;
  background: transparent;
}

@media (min-width: 1000px) and (max-width: 1329px) {
  /* Match desktop shadow in tablet_plus */
  #section-portfolio.section-portfolio {
    --portfolio-img-shadow:
      0 0 0 1px rgba(0,0,0,.14),
      0 10px 22px rgba(0,0,0,.26),
      0 28px 60px rgba(0,0,0,.18);
    --portfolio-logo-h: 64px;
  }
  /* Portfolio tablet_plus: keep 2-column pairing but constrain to a centred rail */
  #section-portfolio.section-portfolio .portfolio-row {
    width: min(1180px, calc(100% - 48px)); /* centred rail with 24px gutters */
    margin-left: auto;
    margin-right: auto;
    column-gap: clamp(24px, 3vw, 56px);
  }
  /* Remove "shrink + centre" behaviour that causes drift at these widths */
  #section-portfolio.section-portfolio .portfolio-content {
    max-width: none;
    margin-inline: 0;
    top: 0; /* no vertical nudging on this breakpoint */
  }
  /* Keep the seam-anchoring idea, but make it deterministic */
  #section-portfolio.section-portfolio .portfolio-row.is-left .portfolio-content {
    margin-left: 0;
    margin-right: auto;
  }
  #section-portfolio.section-portfolio .portfolio-row.is-right .portfolio-content {
    margin-left: auto;
    margin-right: 0;
  }
  /* Ensure the image wrapper doesn't inflate the perceived alignment */
  #section-portfolio.section-portfolio .portfolio-media-inner {
    padding: clamp(28px, 3.2vw, 64px);
  }
  /* Logos: keep consistent left edge without forcing full-width scaling */
  #section-portfolio.section-portfolio .portfolio-logo img {
    width: auto;
    max-width: min(420px, 100%);
    height: auto;
  }
  /* Text should align consistently, avoid a second "max-width rail inside rail" */
  #section-portfolio.section-portfolio .portfolio-desc {
    max-width: 56ch;
  }
}

@media (max-width: 999px) {
  /* Portfolio tablet, one stable centred rail */
  #section-portfolio.section-portfolio {
    --portfolio-tablet-rail: min(960px, calc(100% - 48px)); /* 24px gutter each side */
    --portfolio-img-shadow:
      0 0 0 1px rgba(0,0,0,.14),
      0 10px 22px rgba(0,0,0,.26),
      0 28px 60px rgba(0,0,0,.18);
    --portfolio-logo-h: 56px;
  }
  /* Stack every row to one column, make layout deterministic */
  #section-portfolio.section-portfolio .portfolio-row {
    display: grid !important;
    grid-template-columns: 1fr !important;
    row-gap: 18px;
    justify-items: stretch !important;
    align-items: start !important;
  }
  /* Force copy ABOVE image on tablet, regardless of DOM order */
  #section-portfolio.section-portfolio .portfolio-content {
    order: 1 !important;
  }
  #section-portfolio.section-portfolio .portfolio-media {
    order: 2 !important;
  }
  /* Both blocks share the exact same centred rail and same left edge */
  #section-portfolio.section-portfolio .portfolio-media,
  #section-portfolio.section-portfolio .portfolio-content {
    width: var(--portfolio-tablet-rail) !important;
    max-width: var(--portfolio-tablet-rail) !important;
    margin-left: auto !important;
    margin-right: auto !important;
  }
  /* Remove any vertical nudging that causes overlaps */
  #section-portfolio.section-portfolio .portfolio-content {
    top: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    display: flex !important;
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
  }
  /* Media wrapper must not add padding or centring bias */
  #section-portfolio.section-portfolio .portfolio-media {
    aspect-ratio: auto !important;
  }
  #section-portfolio.section-portfolio .portfolio-media-inner {
    padding: 0 !important;
    margin: 0 !important;
    width: 100% !important;
    height: auto !important;
    display: block !important;
    background: transparent !important;
    position: relative !important;
    overflow: hidden !important;
  }
  /* Image renders at full rail width, so edges match content rail */
  #section-portfolio.section-portfolio .portfolio-media-img {
    display: block !important;
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;
    margin: 0 !important;
  }
  /* Keep logo sane without affecting rail alignment */
  #section-portfolio.section-portfolio .portfolio-logo {
    width: 100%;
  }
  /* Allow readable line length while keeping the left edge aligned to the rail */
  #section-portfolio.section-portfolio .portfolio-title,
#section-portfolio.section-portfolio .portfolio-desc
{
    width: 100%;
    max-width: 72ch;
    text-align: left !important;
  }
}

@media (max-width: 1329px) {
  .portfolio-grid {
    background: transparent;
  }
  .portfolio-media,
  .portfolio-media-inner,
  .portfolio-content {
    background: transparent;
  }
}

/* =====================================================
   Portfolio breakpoint correction
   - Existing @media (max-width: 999px) is too broad.
   - Override tablet explicitly (768–999)
   - Reset mobile explicitly (<=767) to avoid tablet rules bleeding down
   ===================================================== */

@media (min-width: 768px) and (max-width: 999px) {

  /* Tablet rail + tunable shadow */
  #section-portfolio.section-portfolio{
    --portfolio-tablet-rail: min(960px, calc(100% - 48px)); /* 24px gutters */
    /* Start identical to desktop/tablet_plus, tune in DevTools later */
    --portfolio-img-shadow:
      0 0 0 1px rgba(0,0,0,.14),
      0 10px 22px rgba(0,0,0,.26),
      0 28px 60px rgba(0,0,0,.18);
    --portfolio-logo-h: 112px; /* was 56px */
    --portfolio-media-pad: clamp(48px, 6.2vw, 120px);
  }

  /* Deterministic single-column stack */
  #section-portfolio.section-portfolio .portfolio-row{
    grid-template-columns: 1fr !important;
    row-gap: 18px;
    align-items: start !important;
    justify-items: stretch !important;
  }

  /* Ensure content above media for tablet */
  #section-portfolio.section-portfolio .portfolio-content{ order: 1 !important; }
  #section-portfolio.section-portfolio .portfolio-media{ order: 2 !important; }

  /* One shared centred rail for BOTH blocks */
  #section-portfolio.section-portfolio .portfolio-media,
  #section-portfolio.section-portfolio .portfolio-content{
    width: var(--portfolio-tablet-rail) !important;
    max-width: var(--portfolio-tablet-rail) !important;
    margin-left: auto !important;
    margin-right: auto !important;
  }

  /* Remove any inner padding/centering bias so edges match exactly */
  #section-portfolio.section-portfolio .portfolio-content{
    top: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-inline: auto !important; /* keep centred rail */
    max-width: none !important;     /* avoid "rail inside rail" */
    align-items: flex-start !important;
    text-align: left !important;
  }

  #section-portfolio.section-portfolio .portfolio-media{
    aspect-ratio: auto !important;
  }

  #section-portfolio.section-portfolio .portfolio-media-inner{
    padding: 0 !important;
    margin: 0 !important;
    width: 100% !important;
    height: auto !important;
    display: block !important;
    background: transparent !important;
  }

  /* Image must define the rail width */
  #section-portfolio.section-portfolio .portfolio-media-img{
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;
    margin: 0 !important;
    /* do not hardcode shadow here, keep variable-driven */
  }

  /* Logo normalisation: height-driven, no width-based scaling games */
  #section-portfolio.section-portfolio .portfolio-logo{
    height: var(--portfolio-logo-h) !important;
    width: 100% !important;
    display: flex !important;
    align-items: center !important;
  }

  #section-portfolio.section-portfolio .portfolio-logo img{
    height: 100% !important;
    width: auto !important;
    max-width: 100% !important;
    object-fit: contain !important;
    transform: scale(var(--logo-scale)) !important;
    transform-origin: center center;
  }

  /* Copy stays aligned to rail edge, readable line length */
  #section-portfolio.section-portfolio .portfolio-title,
#section-portfolio.section-portfolio .portfolio-desc
{
    width: 100% !important;
    max-width: 72ch;
    text-align: left !important;
  }

  /* Compensate CTA positioning for media padding */

}

@media (max-width: 767px) {
  /* Mobile must not be forced into the tablet rail constraints */
  #section-portfolio.section-portfolio {
    /* Keep shadow variable-driven, start identical, tune later if desired */
    --portfolio-img-shadow:
      0 0 0 1px rgba(0,0,0,.14),
      0 10px 22px rgba(0,0,0,.26),
      0 28px 60px rgba(0,0,0,.18);
    /* Allow a slightly smaller logo on phones by default */
    --portfolio-logo-h: 96px; /* was 48px */
  }
  /* Undo tablet rail forcing from the broad <=999 block */
  #section-portfolio.section-portfolio .portfolio-media,
  #section-portfolio.section-portfolio .portfolio-content {
    width: auto !important;
    max-width: none !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
  /* Let existing mobile styles control spacing, don't pin to a desktop-like rail */
  #section-portfolio.section-portfolio .portfolio-content {
    padding-left: clamp(18px, 4vw, 24px) !important;
    padding-right: clamp(18px, 4vw, 24px) !important;
  }
  #section-portfolio.section-portfolio .portfolio-media-inner {
    padding: 0 !important;
  }
  #section-portfolio.section-portfolio .portfolio-media-img {
    width: 100% !important;
    max-width: 100% !important;
  }
  #section-portfolio.section-portfolio .portfolio-logo {
    height: var(--portfolio-logo-h) !important;
  }
  #section-portfolio.section-portfolio .portfolio-logo img {
    height: 100% !important;
    width: auto !important;
    transform: scale(var(--logo-scale)) !important;
    transform-origin: center center;
  }
  /* Show overlay CTA, hide inline link */

  /* Mobile CTA sizing – match tablet behaviour */

  /* Slightly tighter positioning on small screens */

}

/* =====================================================
   Portfolio tablet cohesion, 768–999px
   - Keep seam alignment
   - Add quiet editorial stitching (rule + frame context)
   ===================================================== */

@media (min-width: 768px) and (max-width: 999px) {

  #section-portfolio.section-portfolio{
    --portfolio-tablet-inset: clamp(32px, 5vw, 72px);
    --portfolio-tablet-gap: 10px;
    --portfolio-tablet-rule: rgba(28,31,34,.14);
    --portfolio-tablet-frame: rgba(28,31,34,.03);
  }

  /* Keep seam alignment */
  #section-portfolio.section-portfolio .portfolio-media-inner{
    padding-left: var(--portfolio-tablet-inset) !important;
    padding-right: var(--portfolio-tablet-inset) !important;
    background: var(--portfolio-tablet-frame) !important;
  }

  #section-portfolio.section-portfolio .portfolio-content{
    padding-left: var(--portfolio-tablet-inset) !important;
    padding-right: var(--portfolio-tablet-inset) !important;
  }

  /* Caption ↔ image proximity */
  #section-portfolio.section-portfolio .portfolio-row{
    row-gap: var(--portfolio-tablet-gap) !important;
  }

  /* Editorial "production rule": fades out so it doesn't look like UI */
  #section-portfolio.section-portfolio .portfolio-media{
    position: relative;
  }

  /* Hairline rule tying image to caption */
  #section-portfolio.section-portfolio .portfolio-media::after{
    content: "";
    display: block;
    height: 1px;
    margin-top: 10px;
    background: linear-gradient(
      90deg,
      transparent,
      var(--portfolio-tablet-rule),
      transparent
    );
  }

  /* Tighten caption cadence slightly */
  #section-portfolio.section-portfolio .portfolio-logo{
    margin-bottom: 6px;
  }

  /* Portfolio CTA overlay: show overlay button, hide inline link */

}

/* =====================================================
   Portfolio tablet text should wrap to photo edge, 768–999px
   - Remove inner max-width caps that force early line breaks
   ===================================================== */

@media (min-width: 768px) and (max-width: 999px) {

  /* Make the content column truly match the image rail width */
  #section-portfolio.section-portfolio .portfolio-content{
    max-width: none !important;
  }

  /* Remove the "48ch" limiter on the paragraph (and any 72ch from earlier overrides) */
  #section-portfolio.section-portfolio .portfolio-title,
#section-portfolio.section-portfolio .portfolio-desc
{
    max-width: none !important;
  }

  /* Optional: keep link from looking stranded on very wide rails */

}

/* =====================================================
   Portfolio desktop CTA hover affordance, ≥1000px
   - Editorial underline on hover for clear clickability
   - No layout shift, no button styling
   ===================================================== */

@media (min-width: 1330px) {
  #section-portfolio.section-portfolio {
    /* scale logo height with viewport, but keep sane bounds */
    --portfolio-logo-h: clamp(72px, 6.2vw, 160px);
  }
}

/* Fix: ensure portfolio overlay CTA renders above image on mobile/tablet */
#section-portfolio.section-portfolio .portfolio-media-inner {
  position: relative; /* ensure containing block */
}

#section-portfolio.section-portfolio .portfolio-media-img {
  position: relative;
  z-index: 1; /* keep image below overlay stack */
}

/* --- Primary navigation: menu-driven markup ------------------------------------ *
 * header.php renders the 'primary_menu' location through wp_nav_menu(), which emits
 * <ul class="nav-list"><li><a>. The existing `.nav a` and `.mobile-nav-inner a` rules above
 * are DESCENDANT selectors, so they still match the anchors and no type, colour or hover rule
 * needed changing. This block does one job: strip the list chrome and put the flex layout back
 * on the element that now holds the items, so the header rail lays out as it did when .nav's
 * children were bare anchors.
 *
 * RESTORED 2026-08-31 after I destroyed it. Cleaning up the dead .mobile-nav rules, one of my
 * deletion patterns matched from inside THIS comment - the selector list it was aiming at
 * mentioned .mobile-nav-inner - and ate the comment's closing marker along with the whole
 * .nav .nav-list rule. The unterminated comment then swallowed everything after it until the
 * next */ /*, so the desktop nav lost display:flex AND list-style:none and rendered as a
 * vertical bulleted list. One greedy regex, two rules gone, and the damage sat several hundred
 * lines away from where it was caused.
 * ------------------------------------------------------------------------------- */

.nav .nav-list{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:nowrap;       /* mirrors .nav - stop header height changing */
  list-style:none;
  margin:0;
  padding:0;
}

.nav .nav-list > li{
  margin:0;
  padding:0;
}

/* =====================================================
   Default page and index templates

   Added 2026-08-31 with the page.php / index.php rewrite (codex finding 4). Both
   templates previously printed "Theme is active" and never ran the loop, so no
   styling had ever been needed for them. These are the legal and utility pages
   plus the search / archive / not-found fallback: a narrow, readable measure and
   nothing decorative. They should look like part of the site without competing
   with the channel pages for attention.
   ===================================================== */

.page-header{
  padding-bottom:0;
  border-bottom:1px solid var(--border);
  margin-bottom:clamp(28px, 4vw, 56px);
}

.page-header .page-title{
  margin:0 0 clamp(20px, 3vw, 36px);
  font-size:var(--h1);
  letter-spacing:-.02em;
  line-height:1.08;
  font-weight:400;
  color:var(--charcoal);
}

.page-body{ padding-top:0 }

/* A measure, not the full rail. Legal copy is read line by line, and 1180px of
   running text is genuinely harder to read than 68 characters of it. */
.page-content{
  max-width:68ch;
  font-size:var(--body);
  line-height:1.65;
  color:var(--charcoal);
}

.page-content > * + *{ margin-top:1.1em }

.page-content h2{
  font-size:var(--h2);
  letter-spacing:-.01em;
  line-height:1.2;
  margin-top:2em;
  font-weight:400;
}

.page-content h3{
  font-size:var(--h3);
  margin-top:1.7em;
  font-weight:600;
}

.page-content ul,
.page-content ol{ padding-left:1.3em }

.page-content li + li{ margin-top:.5em }

.page-content a{
  color:var(--charcoal);
  text-decoration:underline;
  text-underline-offset:.18em;
  text-decoration-thickness:1px;
  text-decoration-color:var(--csg-brass);
}

.page-content a:hover{ text-decoration-thickness:2px }

.page-content blockquote{
  margin-left:0;
  padding-left:1.2em;
  border-left:2px solid var(--csg-brass);
  color:var(--grey);
}

.page-content table{
  width:100%;
  border-collapse:collapse;
  font-size:var(--small);
}

.page-content th,
.page-content td{
  padding:10px 12px;
  border-bottom:1px solid var(--border);
  text-align:left;
  vertical-align:top;
}

/* Wide content must scroll inside its own box rather than pushing the page
   sideways - an editor pasting a wide table should not break the layout. */
.page-content > table{ display:block; overflow-x:auto }

.index-item-title{
  margin:0 0 8px;
  font-size:var(--h3);
  line-height:1.25;
}

.index-item-title a{
  color:var(--charcoal);
  text-decoration:none;
}

.index-item-title a:hover{ text-decoration:underline }

.page-links,
.pagination{
  margin-top:clamp(28px, 4vw, 48px);
  font-size:var(--small);
}

.page-links a,
.pagination a{
  color:var(--charcoal);
  padding:0 .4em;
}

/* --- Desktop sub-navigation ---------------------------------------------------- *
 * Added 2026-08-31 (codex finding 5). The primary menu was rendered at depth 1, so
 * 18 of its 28 items - every channel sub-page and every metro - existed only in the
 * mobile drawer and were unreachable on desktop.
 *
 * CSS-only, on :hover and :focus-within. No JavaScript, no aria-expanded to keep in
 * sync with a state nobody is updating, and if this stylesheet fails to load the
 * markup degrades to a plain nested list that still works. Touch devices use the
 * drawer, which is why this needed no interaction design.
 *
 * The panel is deliberately its own light surface rather than inheriting the
 * header's dark/light theme classes: the header sits over photography and flips
 * theme per page, and a dropdown that changed colour underneath the pointer would
 * read as a rendering fault.
 * ------------------------------------------------------------------------------- */

.nav .nav-list > li{ position:relative }

.nav .nav-list .sub-menu{
  position:absolute;
  top:100%;
  left:50%;
  transform:translateX(-50%) translateY(-4px);
  min-width:230px;
  margin:0;
  padding:10px 0;
  list-style:none;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:12px;
  box-shadow:var(--shadow);
  text-align:left;
  white-space:nowrap;

  /* Hidden but still laid out, so the transform animates. visibility (not display)
     keeps it out of the tab order while closed. */
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  transition:opacity .16s ease, transform .16s ease, visibility 0s linear .16s;
  z-index:60;
}

/* A hover bridge. Without it the pointer crosses a 4px dead gap between the parent
   item and the panel, the panel closes mid-travel, and the menu feels broken. */
.nav .nav-list > li.menu-item-has-children::after{
  content:"";
  position:absolute;
  top:100%;
  left:0;
  right:0;
  height:10px;
}

.nav .nav-list > li:hover > .sub-menu,
.nav .nav-list > li:focus-within > .sub-menu{
  opacity:1;
  visibility:visible;
  pointer-events:auto;
  transform:translateX(-50%) translateY(0);
  transition-delay:0s;
}

.nav .nav-list .sub-menu li{ margin:0; padding:0 }

/* Scoped tightly so it beats the .header.is-theme-dark .nav a colour rule - the
   panel is always light, whatever theme the header is wearing. */
.nav .nav-list .sub-menu a,
.header.is-theme-dark .nav .nav-list .sub-menu a,
.header.is-theme-light .nav .nav-list .sub-menu a{
  display:block;
  padding:10px 22px;
  font-size:16px;
  line-height:1.35;
  color:var(--charcoal);
  opacity:.82;
  text-decoration:none;
  white-space:nowrap;
}

.nav .nav-list .sub-menu a:hover,
.nav .nav-list .sub-menu a:focus-visible,
.header.is-theme-dark .nav .nav-list .sub-menu a:hover,
.header.is-theme-light .nav .nav-list .sub-menu a:hover{
  opacity:1;
  background:var(--surface-editorial);
}

/* A caret, so a parent item looks like it opens something. */
.nav .nav-list > li.menu-item-has-children > a::after{
  content:"";
  display:inline-block;
  width:.32em;
  height:.32em;
  margin-left:.45em;
  vertical-align:.12em;
  border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translateY(-.06em);
  opacity:.55;
}

@media (prefers-reduced-motion: reduce){
  .nav .nav-list .sub-menu{ transition:none }
}

/* Below the drawer breakpoint the desktop nav is hidden entirely, so the panel must
   never be able to appear - it would otherwise open off-screen on a narrow window. */
@media (max-width: 1349px){
  .nav .nav-list .sub-menu{ display:none }
}

/* A hero with no photograph does not reserve space for one.
 *
 * The cover hero holds clamp(480px, 65vh, 720px) for an image. With no image that is up to
 * 720px of empty charcoal beneath two lines of type - and since no channel page has hero art
 * yet, that is what the whole site currently looks like. It reads as a failed load rather
 * than a pending one. Sized to its own content instead, with enough padding to stay a
 * deliberate band. Reverts on its own the moment an image is set: the modifier is only
 * emitted when there is no attachment. */
.editorial-hero--noMedia{
  min-height:0;
}

.editorial-hero--noMedia .editorial-heroOverlay{
  position:static;
  padding-block:clamp(64px, 9vw, 132px);
}

.editorial-hero--noMedia .editorial-heroOverlay .wrap{
  height:auto;
  padding-top:0;
}

/* Holding values in the footer address block, marked as pending rather than styled to look
   settled. Duncan asked for holding content for the warehouse address and phone; the point of
   a distinct treatment is that nobody reviewing the site mistakes a placeholder for a decision
   already taken, and nobody has to grep the theme to find what is still outstanding. */
.contact-footer-addressLine--pending{
  opacity:.55;
  font-style:italic;
}

/* =====================================================
   Footer group brand row

   Duncan, 2026-08-31: "same comment on the footer, it is totally broken at the moment".
   It was: .footer-logos computed to 0x0 and every .footer-brand-logo to height 0. The markup
   was reinstated earlier today but NO CSS came back with it - those rules went out with the
   csg-corporate strip and nothing replaced them, so six anchors wrapped six zero-height
   images. A logo row with no height is invisible rather than obviously broken, which is why
   it survived a render check that only counted elements.

   Values taken from the live creativestructuresgroup.com, measured directly: flex, 18px gap,
   wrap, right-aligned, marks at clamp(72px, 6.2vw, 130px) above 1025px.
   ===================================================== */

.footer-logos{
  display:flex;
  gap:18px;
  flex-wrap:wrap;
  align-items:center;
  /* Right-aligned, on Duncan's instruction and matching creativestructuresgroup.com. I had
     centred it, reasoning that a hugging cluster in an otherwise empty band reads as a fault;
     his call, and it keeps the two sites consistent. */
  justify-content:flex-end;
  width:100%;
  margin-top:clamp(28px, 3vw, 48px);
}

.footer-logo{
  display:block;
  padding:6px 10px;
  border:1px solid transparent;
  border-radius:var(--btn-radius);
  transition:background .18s ease, border-color .18s ease;
}

.footer-logo:hover{
  background:rgba(255,255,255,.55);
  border-color:var(--border);
}

.footer-logos img{
  height:72px;
  width:auto;
  display:block;
}

@media (min-width: 1025px){
  .footer-logos img{
    height:clamp(72px, 6.2vw, 130px);
    width:auto;
  }
}

/* Kreate's stacked lockup is far wider than the other five marks, so an equal HEIGHT gives it
   an unequal visual weight. Halving it is what the group site does and it is the right call -
   optical balance, not arithmetic balance. */
.footer-logos img[src*="kreate"]{ height:36px; }

@media (min-width: 1025px){
  .footer-logos img[src*="kreate"]{ height:clamp(36px, 3.1vw, 65px); }
}

/* The group mark stays full colour; the five portfolio brands sit back until hovered, so the
   row reads as "Creative Structures Group, and these are its companies" rather than as six
   competing logos. */
.logo-colour{
  filter:none;
  opacity:1;
}

.logo-dim{
  filter:grayscale(100%);
  opacity:.55;
  transition:filter .18s ease, opacity .18s ease;
}

.footer-logo:hover .logo-dim{
  filter:grayscale(0%);
  opacity:1;
}

/* On a phone the row would be six wrapped marks stacked above the legal links, pushing the
   real footer content off the first screen. The drawer already carries the group links. */
@media (max-width: 767px){
  .footer-logos{ display:none; }
}

/* -----------------------------------------------------------------
   UK flag on hover, one per group mark.

   Duncan, 2026-09-14: "on mouseover the logos in the footer can we show a UK flag around a
   quarter of the size of the logo". All six brands are British, so all six carry it. This is
   the one place on the site that says so, and it says it quietly - a mark of where the group
   comes from, shown only to someone who has reached for the logo.

   SIZED AS A PERCENTAGE, not a pixel value. The marks render at 72px, at
   clamp(72px, 6.2vw, 130px) above 1025px, and Kreate at half of each of those - four sizes
   across two breakpoints. Height of 25% of the anchor's own box tracks all four from one
   declaration, so the flag stays a quarter of whatever the logo is currently doing and there
   is no second copy of those clamps here to fall out of step later. aspect-ratio supplies the
   width, so the 1:2 proportion cannot be distorted by any of it.

   Anchored to the top-right corner of the tile, which is inside the tile's 10px right padding
   and so clear of both the artwork and the 18px gap to the next mark. It lifts rather than
   slides, on the same .18s ease the tile background and the grayscale lift already use.

   IT IS A PSEUDO-ELEMENT, which is the accessibility decision and not an implementation
   detail: it carries no accessible name, so a screen reader is told nothing new - correct,
   because the anchor is already named for the brand and the flag only restates the .co.uk in
   its own href. :focus-visible is included so a keyboard gets what a mouse gets. There is no
   touch equivalent and there cannot be one; a hover affordance that carries no information is
   the right kind to leave out on a phone, where the row is hidden anyway.
   ----------------------------------------------------------------- */
.footer-logo{
  position:relative;
}

.footer-logo::after{
  content:"";
  position:absolute;
  top:0;
  right:0;
  height:25%;
  aspect-ratio:2 / 1;
  background:url("../img/flags/union-flag.svg") center / contain no-repeat;
  border-radius:1px;
  /* Reads on both footer backgrounds. The footer follows the header theme, and a flag that is
     part white needs an edge on the light one; a soft shadow does that without a border, which
     at 21px would be a quarter of the flag's height. */
  box-shadow:0 1px 3px rgba(0,0,0,.28);
  opacity:0;
  transform:translateY(-15%);
  transition:opacity .18s ease, transform .18s ease;
  /* The tile is the link. Nothing about the badge should be able to take a click or interrupt
     the hover it depends on. */
  pointer-events:none;
}

.footer-logo:hover::after,
.footer-logo:focus-visible::after{
  opacity:1;
  transform:translateY(-40%);
}

/* Honour a reduced-motion preference: still appear, without the travel. */
@media (prefers-reduced-motion: reduce){
  .footer-logo::after{
    transform:translateY(-40%);
    transition:opacity .18s ease;
  }
  .footer-logo:hover::after,
  .footer-logo:focus-visible::after{
    transform:translateY(-40%);
  }
}

/* Schedule dialog, holding state. Shown until $csg_booking_url is set in footer.php; sized to
   sit in the same panel the booking iframe will occupy so the dialog does not change shape
   when the real calendar arrives. */
.booking-embed--holding{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  text-align:center;
  gap:10px;
  min-height:260px;
  padding:32px 28px;
}

.booking-embed__holding-title{
  margin:0;
  font-size:var(--h3);
  color:var(--charcoal);
}

.booking-embed__holding-copy{
  margin:0;
  max-width:38ch;
  color:var(--grey);
  font-size:var(--small);
  line-height:1.55;
}

/* =====================================================
   Drawer: collapsible sections

   Duncan, 2026-08-31: "the actual menu is horrible, can we use any drop downs in the burger
   menu so we collapse the tent rental section down to Weddings/Parties/Corporate/Festivals".

   Structure first, detail on demand. The drawer opens showing five top-level entries; each
   branch expands where asked. main.js initDrawerAccordion() injects the buttons and owns the
   hidden state, so a JS failure leaves every section OPEN and reachable rather than a menu of
   unopenable headings.
   ===================================================== */

.csg-oc__disclosure{
  flex:0 0 auto;
  appearance:none;
  background:transparent;
  border:0;
  padding:12px 4px 12px 16px;
  margin:0;
  cursor:pointer;
  color:inherit;
  line-height:0;
}

.csg-oc__caret{
  display:inline-block;
  width:.5em;
  height:.5em;
  border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translateY(-.1em);
  opacity:.5;
  transition:transform .22s ease, opacity .22s ease;
}

.csg-oc__disclosure[aria-expanded="true"] .csg-oc__caret{
  transform:rotate(-135deg) translateY(-.15em);
  opacity:.9;
}

.csg-oc__disclosure:focus-visible{
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:3px;
  border-radius:4px;
}

/* The nested list takes the full row under its parent. */

/* A third level exists under Tent Rental (channel -> setting pages); step it down again so
   the hierarchy is readable rather than a flat indent. */

/* =====================================================
   Off-canvas panel

   Modelled on the stunning-tents theme Duncan pointed at: "this theme is rendering the burger
   menu well with social also in the same popout panel". Its shape is an OUTER container that
   owns the scrim and the stacking, and an INNER panel anchored right that slides. Separating
   those two is what makes it behave - one element trying to be both a full-screen scrim and a
   sliding panel is what produced the previous version's immovable transform.

   Deliberately a NEW namespace. The theme carried two competing .mobile-nav definitions and
   the resulting transform could not be overridden even by an inline style. Those rules are
   deleted rather than layered over.
   ===================================================== */

.csg-oc{
  position:fixed;
  inset:0;
  z-index:-1;                 /* out of the stacking order entirely while closed */
  overflow:hidden;
  pointer-events:none;
  visibility:hidden;
}

body.nav-open .csg-oc{
  z-index:1200;
  pointer-events:auto;
  visibility:visible;
}

.csg-oc__overlay{
  position:absolute;
  inset:0;
  background:rgba(0,0,0,.45);
  opacity:0;
  transition:opacity .3s ease;
}

body.nav-open .csg-oc__overlay{ opacity:1 }

.csg-oc__panel{
  position:absolute;
  top:0;
  right:0;
  bottom:0;
  width:min(400px, 100vw);
  display:flex;
  flex-direction:column;
  background:var(--bg);
  box-shadow:-24px 0 60px rgba(0,0,0,.24);
  overflow-y:auto;
  overscroll-behavior:contain;
  padding:26px 28px 40px;
  transform:translateX(100%);
  transition:transform .38s cubic-bezier(.2,.8,.2,1);
}

body.nav-open .csg-oc__panel{ transform:translateX(0) }

.csg-oc__panel:focus{ outline:none }

.csg-oc__top{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  margin-bottom:26px;
}

.csg-oc__logo img{ height:34px; width:auto; display:block }

.csg-oc__close{
  appearance:none;
  background:transparent;
  border:0;
  padding:6px 10px;
  margin:0;
  cursor:pointer;
  font-size:30px;
  line-height:1;
  color:var(--charcoal);
  opacity:.6;
  transition:opacity .18s ease;
}

.csg-oc__close:hover{ opacity:1 }

.csg-oc__close:focus-visible{
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:3px;
  border-radius:4px;
}

.csg-oc__nav{ flex:1 1 auto }

.csg-oc__list,
.csg-oc__list .sub-menu{
  list-style:none;
  margin:0;
  padding:0;
}

.csg-oc__list a{
  display:block;
  padding:11px 0;
  font-size:19px;
  line-height:1.3;
  letter-spacing:-.01em;
  font-weight:500;
  color:var(--charcoal);
  text-decoration:none;
  opacity:.82;
  font-family:"Libre Bodoni", Didot, "Bodoni MT", Georgia, "Times New Roman", serif;
  transition:opacity .2s ease;
}

.csg-oc__list a:hover{ opacity:1 }

.csg-oc__list a:focus-visible{
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:3px;
}

/* Second and third levels: smaller and lighter, so the five top-level entries stay the
   readable structure rather than being lost in a flat list of thirty. */
.csg-oc__list .sub-menu a{
  font-size:16px;
  padding:8px 0;
  opacity:.7;
  font-family:inherit;
  font-weight:400;
}

.csg-oc__list .sub-menu .sub-menu a{
  font-size:15px;
  opacity:.62;
}

.csg-oc__list .sub-menu a:hover,
.csg-oc__list .sub-menu .sub-menu a:hover{ opacity:1 }

.csg-oc__bottom{
  margin-top:32px;
  padding-top:24px;
  border-top:1px solid var(--border);
}

.csg-oc__social{
  display:flex;
  align-items:center;
  gap:18px;
}

.csg-oc__social img{ height:20px; width:auto; display:block; opacity:.65 }
.csg-oc__social a:hover img{ opacity:1 }

@media (prefers-reduced-motion: reduce){
  .csg-oc__panel,
  .csg-oc__overlay{ transition:none }
}

/* Accordion rows in the off-canvas panel.

   The parent link and its caret share a row; the nested list takes the full width beneath.
   These rules were lost when the legacy .mobile-nav blocks were deleted - the caret ended up
   on its own line under each label, which read as a broken layout rather than a control. */
.csg-oc__list li.has-sub{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
}

.csg-oc__list li.has-sub > a{
  flex:1 1 auto;
  min-width:0;
}

.csg-oc__list li.has-sub > .csg-oc__disclosure{
  flex:0 0 auto;
}

/* The nested list drops to its own full-width row under the pair above it. */
.csg-oc__list li.has-sub > .sub-menu{
  flex:1 0 100%;
  padding-left:14px;
  border-left:1px solid var(--border);
  margin:2px 0 8px;
}

/* =====================================================
   No JavaScript: the navigation must still work

   codex, 2026-09-01 (finding 1): with JS disabled at 390px the desktop rail is display:none
   and the off-canvas panel is visibility:hidden, z-index:-1 and translated off-screen, with a
   burger that has no handler. Every link present, none reachable.

   Here the panel stops being a panel. It becomes a plain block at the end of the header, in
   normal flow, with every section expanded - which is what I had wrongly claimed the accordion
   already did. The burger is hidden because without JS it does nothing, and a control that
   cannot act is worse than no control.
   ===================================================== */

.no-js .csg-oc{
  position:static;
  z-index:auto;
  visibility:visible;
  pointer-events:auto;
  overflow:visible;
}

.no-js .csg-oc__overlay,
.no-js .csg-oc__close,
.no-js .nav-toggle{
  display:none;
}

.no-js .csg-oc__panel{
  position:static;
  width:auto;
  max-width:none;
  transform:none;
  box-shadow:none;
  overflow:visible;
  border-top:1px solid var(--border);
  padding:20px var(--pad) 28px;
}

/* Every branch open: the disclosure buttons are injected by JS, so without it the sub-menus
   have nothing to reveal them. */
.no-js .csg-oc__list .sub-menu{
  display:block !important;
}

.no-js .csg-oc__list li.has-sub{
  display:block;
}
/* =====================================================
   Portfolio tiles: one anchor over the whole row
   -----------------------------------------------------
   R03 (review, f790570). The previous version had two anchors - an aria-hidden image link and
   a heading link - which left a pointer-interactive, programmatically focusable element
   outside the accessibility tree, and made row hover advertise more than the row delivered.

   Now a single anchor is laid over the row. The clickable area and the hover affordance are
   the same rectangle by construction, so they cannot disagree again.

   It is a direct child of .portfolio-row rather than a ::after on the heading. I originally
   justified that by .portfolio-content needing position:relative for `top:
   var(--about-content-offset-y)` - which was WRONG: that custom property was never defined
   anywhere, so `top` resolved to auto and the declaration did nothing. The dead declaration
   has since been removed. The row-level anchor is kept because review verified it and because
   it makes the clickable area and the hover affordance the same rectangle by construction,
   not because the alternative was impossible.

   Trade: an overlay across the row means the description underneath is not mouse-selectable.
   That is inherent to a whole-tile-clickable card.
   ===================================================== */
.portfolio-rowLink{
  position: absolute;
  inset: 0;
  z-index: 7;             /* above .portfolio-content (3) and .portfolio-media (1) */
  border-radius: inherit;
}

/* The ring is drawn inside the row so it is not clipped by neighbouring rows. */
.portfolio-rowLink:focus-visible{
  outline: 2px solid var(--csg-brass, #AFA287);
  outline-offset: -4px;
  border-radius: 2px;
}

/* The heading is plain text now, so the interactive signal comes from the row. Underline on
   hover and on keyboard focus alike, so the affordance is not mouse-only. */
.portfolio-row:hover .portfolio-title,
.portfolio-row:focus-within .portfolio-title{
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
}
/* =====================================================
   Review fixes F01, F02, F03
   ===================================================== */

/* F01 - hero scrim.
   Channel hero copy is white over uncontrolled photography with no protection, and review
   measured substantial below-threshold areas at mobile on Party, Festival and Gallery. A
   gradient rather than a flat overlay so the photograph stays a photograph where there is no
   text over it; weighted to the TOP because the copy is top-aligned in .editorial-heroOverlay
   .wrap. It sits at z-index 2 - above .editorial-heroMedia, below the overlay at 3.
   Excluded from --noMedia, which already paints a solid charcoal ground and would otherwise
   be darkened twice. */
.editorial-hero:not(.editorial-hero--noMedia)::after{
  content:"";
  position:absolute;
  inset:0;
  z-index:2;
  pointer-events:none;
  background:linear-gradient(
    to bottom,
    rgba(17,19,21,.74) 0%,
    rgba(17,19,21,.56) 38%,
    rgba(17,19,21,.20) 70%,
    rgba(17,19,21,.06) 100%
  );
}

/* Mobile stacks the copy taller against proportionally less image, so it needs more. */
@media (max-width: 767px){
  .editorial-hero:not(.editorial-hero--noMedia)::after{
    background:linear-gradient(
      to bottom,
      rgba(17,19,21,.82) 0%,
      rgba(17,19,21,.66) 55%,
      rgba(17,19,21,.30) 100%
    );
  }
}

/* FESTIVALS: NO SCRIM AT ALL, AND DARK COPY INSTEAD OF WHITE.
   Duncan, 2026-09-22: "go F", after seeing the alternatives side by side.

   This is the only treatment that leaves the photograph completely untouched, and it is also
   the most readable one. The two facts are the same fact: the scrim above exists to rescue
   WHITE text, and white text is the wrong colour for this frame.

   MEASURED, because I got this wrong twice before getting it right. The background where the
   copy sits is a LIGHT-to-mid grey - relative luminance 0.29 at 1440, 0.34 at 1920, 0.53 on a
   phone. Against that:

     white #fff  -> about 2.4:1   FAILS, which is why a heavy scrim was needed
     ink #111315 -> 6.08 at 1440, 6.87 at 1920, 10.28 on a phone   PASSES AA outright

   So removing the scrim and flipping the copy to ink is BETTER than the .52 scrim it replaces
   (6.08 against 4.61) while darkening nothing. Every intermediate idea was measured and
   rejected: scrim confined to the top third 2.95, to the top half 3.52, lighter but still
   covering 4.04, a gradient baked into the JPEG 3.38, a pale wash under ink text 2.66.

   HOW I GOT IT WRONG, so the next person does not repeat it. A probe that reads the darkest
   pixels inside the text box as the text colour works for white-on-dark, where the glyphs are
   the brightest thing present. It is badly biased for DARK text: 19px weight-300 glyphs cover
   roughly a tenth of the box, so a 25th-percentile reads background, not ink, and the ratio
   collapses to a meaningless 2.41. Compare the DECLARED text colour against the background
   measured with the copy hidden - which is what WCAG actually specifies.

   THE ONE REAL RISK, and it is a design dependency rather than a compliance one: ink works
   because the copy sits over SKY at every width. It would become unreadable over the dark
   tent. So if the crop, the band height or the copy length changes on this page, re-measure -
   do not assume this still holds. The values above are the record to check against. */
.page--festival-tent-rental .editorial-hero:not(.editorial-hero--noMedia)::after{
  background:none;
}

.page--festival-tent-rental .editorial-heroTitle{
  /* --charcoal, the theme's own darkest token, not a stray hex: measured against this
     frame's background it gives 6.08:1 at 1440. */
  color:var(--charcoal);
  /* The shared rule carries a heavy drop shadow to lift white type off photography. Under
     dark type it does the opposite - it muddies the glyph edges against a light sky. */
  text-shadow:none;
}

.page--festival-tent-rental .channel-hero-standfirst{
  /* Full opacity, not the shared .88 white: transparency costs contrast, and having measured
     this to 6.08 there is no reason to give any of it back. */
  color:var(--charcoal);
  opacity:1;
}

/* FESTIVALS, PHONE ONLY: a shorter band, to get more of the tent WIDTH on screen.

   The constraint, because it is not obvious and someone will otherwise "fix" this back. A
   phone hero is a PORTRAIT window against a LANDSCAPE photograph, so cover scales the image
   to fill the height and throws the width away. How much width survives is set purely by how
   tall the band is - nothing else, and no amount of object-position changes it:

     band 400x520 (the shared value)   51% of the tent width visible
     band 400x470                      57%
     band 400x420  <- this             64%

   Duncan, 2026-09-22, choosing 420 from rendered comparisons: "that works".

   THE SAME ARITHMETIC IS WHY THIS PAGE MUST NOT USE A SKY-CROPPED SOURCE. A tighter frame
   improves desktop and RUINS this: cropping to 3:1 drops mobile to 26%. Desktop and phone
   want opposite things from one file, which is the whole reason this page carries its own
   band height.

   Scoped to festivals because 420px is tight - the copy here runs about 370px, so there is
   roughly 50px of headroom, and a longer headline on another channel would collide. Do not
   promote this to the shared rule without re-measuring each page's copy. */
@media (max-width: 767px){
  .page--festival-tent-rental .editorial-hero,
  .page--festival-tent-rental .editorial-heroMedia{
    min-height:420px;
  }
}

/* F02 - skip link. Off-screen until focused, then a solid brass-on-ink chip at the top left.
   It is the first focusable element in the document on every route. */
.skip-link{
  position:absolute;
  left:-9999px;
  top:0;
  z-index:10000;
  padding:12px 18px;
  background:var(--csg-ink, #1C1F22);
  color:#fff;
  font-size:15px;
  text-decoration:none;
  border-radius:0 0 6px 0;
  box-shadow:0 6px 20px rgba(0,0,0,.35);
}

.skip-link:focus{
  left:0;
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:-4px;
}

/* The target must not draw a focus ring of its own when script moves focus to it. */
.site-main:focus{
  outline:none;
}

/* F03 - the pause button becomes visible the moment it is focused. .visually-hidden clips it
   the rest of the time, so the section looks unchanged until someone tabs to it. */
.clients-pause:focus,
.clients-pause:focus-visible{
  position:static;
  width:auto;
  height:auto;
  margin:0 0 14px;
  clip:auto;
  clip-path:none;
  overflow:visible;
  white-space:normal;
  display:inline-block;
  padding:8px 14px;
  border:0;
  border-radius:999px;
  background:var(--csg-ink, #1C1F22);
  color:#fff;
  font:500 13px/1.2 inherit;
  cursor:pointer;
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:3px;
}

.clients-marquee.is-paused .clients-track{
  animation-play-state:paused;
}
/* =====================================================
   Document pages: their own measure and type scale
   -----------------------------------------------------
   Duncan on the privacy policy at superwide, 2026-09-08: the rail is not centred, it could be
   a lot wider, and the type "feels like a childs book from the 1980's". All three, one cause.

   .page-content had max-width:68ch and NO margin-inline, so it sat hard left inside a .wrap
   that is itself centred - the rail looked off-centre because it was.

   And these pages inherited the MARKETING type scale. Above 1920 the theme sets --body:21px
   and --h2:42px, which is right for an editorial hero page and much too big for a legal
   document that is read by scanning. A privacy notice is not a landing page.

   So document pages get their own scale, capped well below the marketing one, and a wider
   measure - legal copy is scanned for the clause you need rather than read line by line, so
   it tolerates a longer line than body prose does.
   ===================================================== */
.page-article .page-content,
.site-main--index .page-content{
  margin-inline: auto;                       /* the centring that was missing */
  max-width: min(92ch, 1040px);              /* was 68ch; wider, but still capped */
  font-size: clamp(16px, 0.95vw, 18px);      /* was var(--body), which hits 21px above 1920 */
  line-height: 1.7;
}

.page-article .page-content h2,
.site-main--index .page-content h2{
  font-size: clamp(20px, 1.35vw, 25px);      /* was var(--h2) - 42px at superwide */
  margin-top: 2.2em;
  line-height: 1.25;
}

.page-article .page-content h3,
.site-main--index .page-content h3{
  font-size: clamp(16px, 1.02vw, 18px);
  margin-top: 1.8em;
}

.page-article .page-content li + li,
.site-main--index .page-content li + li{
  margin-top: .4em;
}

/* The title band follows the same logic: --h1 reaches 60px above 1920, which shouts on a page
   headed "Privacy Policy". Aligned to the same rail so the heading sits over its own text. */
/* The 404 and search-empty states live in index.php and are NOT inside .page-article,
   so the title kept the marketing scale and sat on a different rail from its own copy. */
.page-article .page-header .page-title,
.site-main--index .page-header .page-title{
  font-size: clamp(30px, 2.2vw, 42px);
  max-width: min(92ch, 1040px);
  margin-inline: auto;
}

.page-article .page-header,
.page-article .page-body,
.site-main--index .page-header,
.site-main--index .page-body{
  padding-inline: 0;
}
/* =====================================================
   Channel gallery
   -----------------------------------------------------
   There were NO .channel-gallery rules in this stylesheet at all. The markup has existed since
   the build and was never styled, so each <img> fell back to its own width and height
   ATTRIBUTES - 1024x768 on most of them - while the grid column set a different width. The
   browser honoured both and stretched the picture to fit. Duncan, 2026-09-08: "the gallery is
   horrific, all of the images are distorted, even a simple grid would look better".

   So: a simple grid. One aspect ratio for every cell, object-fit:cover to fill it without
   distortion, and object-position:center so the crop takes the middle rather than the top.
   That matters here because the gallery deliberately mixes orientations - the Westminster
   frames are 2448x3264 portrait, the marina shots 2048x1536 landscape - and cover is what
   lets both sit in the same grid without either being squashed.
   ===================================================== */
.channel-gallery-strip{
  gap: 16px;
}

.channel-gallery-item{
  overflow: hidden;              /* clip the cover crop to the card's rounded corner */
  background: #1d2026;           /* shows while the image decodes, instead of white flash */
  padding: 0;
}

.channel-gallery-img{
  display: block;
  width: 100%;
  height: auto;                  /* neutralises the height ATTRIBUTE, which caused the stretch */
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: center;
}

.channel-gallery-item .card-pad{
  padding: 12px 14px;
}

/* Two columns on tablet, one on phone - three portrait crops side by side at 390px is a
   row of postage stamps. */
@media (max-width: 999px){
  .channel-gallery-strip{
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px){
  .channel-gallery-strip{
    grid-template-columns: 1fr;
  }
  .channel-gallery-img{
    aspect-ratio: 3 / 2;
  }
}

/* =====================================================
   Overlay grid - "What it looks like"

   Duncan, 2026-09-14: "use this loyout for the what it looks like section, you can pull the
   format from the 10x15 theme which is where this element came from ... keep csg styling add
   scrim behind titles so the words are ledgible".

   THE STRUCTURE IS 10x15's, THE TREATMENT IS NOT. Their version is flex with 5px item
   padding, an uppercase letter-spaced sans title, the whole photograph dropped to
   opacity .7 over #130d0d, and a hover that inserts a 2px white border and greyscales the
   image. What is kept here is the shape - two columns, a photograph per cell, the title laid
   over it. The type is Libre Bodoni, the palette is --charcoal, and legibility comes from a scrim
   behind the words rather than from dimming the entire picture.

   WHY A SCRIM AND NOT A FLAT DIM. A flat dim has to be heavy enough for the WORST photograph
   in the set, which means every other photograph is darker than it needs to be. A soft
   ellipse centred on the title darkens where the words are and leaves the corners alone, so
   the pictures stay bright and the text still reads. No hard edge, so it does not look like a
   bar laid across the image.
   ===================================================== */

/* THE SECTION'S OWN SPACING, which it never had.
   .section-portfolio carries no padding - every other band on this page uses .section, which
   is padding:64px 0, and this one relied on the editorial header for its top space. Duncan
   removed the headline on 2026-09-14 and the tiles then started at y=1268, the exact pixel
   the dark intro band ends, with no white between them. The rhythm now comes from the
   section rather than from a heading that may or may not be there. */
.section-portfolio{
  padding:64px 0;
}

.overlay-grid{
  display:grid;
  /* Two equal columns. minmax(0,1fr) rather than 1fr: a grid track sized 1fr floors at its
     content's min-content width, and an image in a cell can therefore refuse to shrink and
     push the row wider than the wrap. */
  grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:clamp(12px, 1.4vw, 20px);
}

/* Below this the columns stack. 670px is 10x15's own breakpoint for this element and it is
   what the `sizes` attribute in parts/home/portfolio.php is written against, so the two agree
   by construction rather than by memory. */
@media (max-width: 670px){
  .overlay-grid{
    grid-template-columns:1fr;
  }
}

/* An odd number of cells leaves the last one alone on its row. Rather than a half-width tile
   beside a hole, give it the full width - it reads as a deliberate closing panel. Currently
   inactive: Stretch Tents is drafted so four cells render, and this exists for the moment
   that page is published and the fifth returns. */
.overlay-grid--2 .overlay-grid__item:last-child:nth-child(odd){
  grid-column:1 / -1;
}

.overlay-item{
  display:block;
  position:relative;
  overflow:hidden;
  border-radius:var(--radius);
  /* The photograph is loaded lazily, so the cell is charcoal rather than page-white until it
     arrives. White would flash against a dark photograph on every scroll. */
  background:var(--charcoal);
  isolation:isolate;
}

.overlay-item__img{
  display:block;
  width:100%;
  height:100%;
  /* A FIXED RATIO, so the row is square even though the sources are not: the four
     photographs are 2.08:1 and 2.24:1, which would leave the two cells in a row at different
     heights. 16/9 is the closest familiar proportion that gives the tile some height; cover
     crops the sides, which on these wide landscape shots is the margin rather than the
     subject. */
  aspect-ratio:16 / 9;
  /* TWO ROWS MUST FIT THE VIEWPORT AFTER AN ANCHOR JUMP, which 16:9 alone does not guarantee.
     Duncan, twice: "anchor is still not pulling all four into view".

     After the jump the section top sits at scroll-margin-top from the top of the viewport, then
     64px of section padding, then the grid. So the space for two rows is
     100vh minus the offset minus that padding, less the 20px row gap between them - and each
     row can have half of it. 220px is offset + padding + gap, rounded up.

     max-height rather than a different aspect-ratio, because it BINDS ONLY WHEN IT MUST: on a
     tall viewport the natural 16:9 height is the smaller number and nothing changes at all. On
     a short one the tile flattens just far enough for the fourth title to clear the fold.
     object-fit:cover below means that costs frame, not distortion.

     Measured before this existed: the fourth title fell 74-94px below the fold at 640-730px
     viewport heights, and 0px at 900 and above. */
  max-height:calc((100vh - 220px) / 2);
  object-fit:cover;
  transform:scale(1);
  transition:transform .45s cubic-bezier(.2,.6,.2,1);
}

/* THE SCRIM. A soft ellipse behind the title over a light overall dim.
   ::after so it sits above the image and below the heading, and pointer-events:none so it
   cannot intercept the click on the anchor underneath it. */
.overlay-item::after{
  content:"";
  position:absolute;
  inset:0;
  background:
    radial-gradient(ellipse 80% 54% at 50% 50%,
      rgba(28,31,34,.60) 0%,
      rgba(28,31,34,.34) 55%,
      rgba(28,31,34,0)  100%),
    linear-gradient(rgba(28,31,34,.22), rgba(28,31,34,.22));
  pointer-events:none;
  transition:opacity .28s ease;
}

.overlay-item__title{
  position:absolute;
  top:50%;
  left:0;
  width:100%;
  margin:0;
  padding:0 clamp(16px, 2.4vw, 32px);
  transform:translateY(-50%);
  z-index:2;

  /* CSG's heading voice: Libre Bodoni from the .serif class on the element, the h2 step so it
     carries a tile, and sentence case. 10x15's uppercase letter-spaced sans is theirs. */
  font-size:var(--h2);
  font-weight:500;
  letter-spacing:-.01em;
  line-height:1.1;
  text-align:center;
  color:#fff;
  /* Belt and braces over the scrim, for the one photograph that is bright exactly where the
     words fall. */
  text-shadow:0 2px 18px rgba(0,0,0,.45);
}

/* Hover and keyboard focus get the same response. The picture eases in and the scrim lightens
   a little, so the tile answers the pointer without the layout moving. */
.overlay-item:hover .overlay-item__img,
.overlay-item:focus-visible .overlay-item__img{
  transform:scale(1.045);
}

.overlay-item:hover::after,
.overlay-item:focus-visible::after{
  opacity:.82;
}

a.overlay-item:focus-visible{
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:3px;
}

/* The title is the link's accessible name and its visible label, so the hover affordance
   belongs on it as well as on the picture. Matches the treatment the portfolio rows used. */
.overlay-item:hover .overlay-item__title,
.overlay-item:focus-visible .overlay-item__title{
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:.18em;
}

@media (prefers-reduced-motion: reduce){
  .overlay-item__img{
    transition:none;
  }
  .overlay-item:hover .overlay-item__img,
  .overlay-item:focus-visible .overlay-item__img{
    transform:none;
  }
}

/* =====================================================
   Featured gallery tiles on a CHANNEL page

   Same grid, same proportions, same scrim as the homepage - Duncan asked for "similar layout to
   portfolio section on the homepage", so nothing about the tile itself is redefined here and the
   max-height viewport rule above is inherited deliberately.

   The one real difference is that these tiles are NOT links. A gallery row carries no
   destination, so page-channel.php renders a <div>, and the homepage's hover affordances - the
   4.5% scale, the scrim lift, the underlined title - would advertise a click that does nothing.

   THESE WIN ON SPECIFICITY, NOT SOURCE ORDER. Three classes plus a pseudo-class (0,4,0) against
   the base rules' (0,3,0), so a future tidy that moves this block cannot silently reverse them
   and leave four hero tiles pretending to be links.
   ===================================================== */
.overlay-grid--channel .overlay-item--static{
  cursor:default;
}

/* SCRIM ONLY WHERE THERE IS A TITLE TO HOLD UP.

   The radial dim on .overlay-item::after exists for exactly one reason - to keep a light title
   legible over an unpredictable photograph. With no title it is not a legibility device, it is
   just 60% charcoal over the middle of a good picture, which is why it was switched off
   wholesale when Duncan said "drop the titles on the real parties cards altogether".

   Titles are back, but only on the rows someone has written one for - Duncan, 2026-09-17:
   "Titles only, no descriptions" - so this is now conditional rather than simply reversed.
   page-channel.php adds .overlay-item--titled when the row has a title, and the scrim follows
   the title rather than the tile. A tile with an empty title renders exactly as it does today.

   The --titled rule is (0,3,1) against the --static rule's (0,3,0)+pseudo, so it wins on
   specificity and not on source order; moving either block cannot silently reverse them. */
.overlay-grid--channel .overlay-item--static::after{
  content:none;
}

.overlay-grid--channel .overlay-item--static.overlay-item--titled::after{
  content:"";
}

/* The hover lift advertises a click, and these tiles are not links. */
.overlay-grid--channel .overlay-item--static:hover .overlay-item__img,
.overlay-grid--channel .overlay-item--static:focus-within .overlay-item__img{
  transform:none;
}

/* NOR DOES THE TITLE UNDERLINE, and this rule is new with the titles rather than left over.
   The base rule underlines .overlay-item__title on hover because on the homepage that title is
   a link's visible label and the affordance belongs on it. These tiles are non-linked <div>s,
   so the same rule would advertise a click that does nothing - the precise thing --static
   exists to prevent. It needed no counterpart before only because there was no title to
   underline. (0,3,0) over the base rule's (0,2,0). */
.overlay-grid--channel .overlay-item--static:hover .overlay-item__title,
.overlay-grid--channel .overlay-item--static:focus-within .overlay-item__title{
  text-decoration:none;
}

/* TALLER TILES. Duncan, 2026-09-17: "can photos be full height on the real parties rather than
   letter box?"

   TWO things were flattening them, and the aspect-ratio was the lesser one.

   The real culprit is the inherited max-height: calc((100vh - 220px) / 2). That exists so two
   rows of HOMEPAGE tiles clear the fold after an anchor jump - Duncan, twice, "anchor is still
   not pulling all four into view" - and 220px is that page's header offset plus section padding
   plus the row gap. At 1440x950 it resolves to 365px, so a tile that wants around 480px is cut
   to three quarters of its height. None of that reasoning applies here: no link targets this
   section, so there is no jump to land, and the cap is pure inherited constraint.

   Then 3/2 instead of 16/9 for the frame itself. The sources are 2.08:1 and 2.24:1, so cover
   crops the sides either way; a taller frame simply takes more of the middle of each photograph
   and gives the row real presence on the page. Both columns keep a single ratio, so the two
   tiles in a row still align exactly.

   Scoped to --channel, so the homepage keeps its cap and its 16:9 untouched. */
.overlay-grid--channel .overlay-item__img{
  aspect-ratio:3 / 2;
  max-height:none;
}

/* THE GAP ABOVE THE TILES. Duncan, 2026-09-17: "the gap between [the intro copy] and Real
   Parties is huge". Measured at 1440 before touching anything: 362px.

   Nothing was wrong with any single rule - they simply stacked, and each was written without
   knowing the others would be above it:

     .channel-intro      padding-bottom    64
     .channel-featured   padding-top       64
     .editorial-header   padding-top      140   <- clamp(72px, 10vw, 140px), maxed out here
     .editorial-header   padding-bottom    42
                                          ---
                                          310, plus the heading's own 32px band

   The 140px is the one that does not belong. The shared header was built for full-bleed
   editorial section breaks on the homepage, where that air IS the design. Inside a channel page
   it lands on top of two lots of section padding and reads as a hole.

   So: this band contributes no top padding of its own, and its header's top space comes down to
   roughly a third. Scoped to .channel-featured, so every other section - including every use of
   the same header part elsewhere - keeps the spacing it was designed with. Lands at about 120px
   between the copy and the tiles. */
.section.channel-featured{
  padding-top:0;
}

.channel-featured .editorial-sectionHeader{
  padding-top:clamp(28px, 3.4vw, 52px);
  padding-bottom:clamp(14px, 1.8vw, 26px);
}

/* =====================================================
   Navigation call to action

   Duncan, 2026-09-14: "Get in Touch using the brand bronze color make this button more
   prominent in the menu". inc/nav-cta.php puts .nav-cta on whichever item points at the
   contact page, so this follows a rename or a reorder.

   THE COLOUR IS --csg-brass, #AFA287, which is the metal in the brandmark and the only
   bronze this palette has. --copper #B67352 is warmer and closer to the word, but it is a
   secondary accent that appears nowhere in the identity, so a button in it would be a new
   brand colour rather than a prominent use of an existing one.

   THE TEXT IS CHARCOAL, NOT WHITE, and that is a contrast decision rather than a taste one.
   White on #AFA287 measures 2.47:1, which fails WCAG AA for text at any size. Charcoal on the
   same fill measures 6.63:1 and passes AA comfortably. Brass is a light metal; it wants dark
   type on it.

   SPECIFICITY: .header is included so these beat .header.is-theme-dark .nav a and
   .header.is-theme-light .nav a, which both set colour at 0,3,1 and would otherwise tie and
   be decided by source order. The button carries its own background, so it reads the same
   whichever way the header has themed itself.
   ===================================================== */

.header .nav .nav-list > li.nav-cta > a{
  background:var(--csg-brass);
  border-color:var(--csg-brass);
  color:var(--charcoal);
  font-weight:540;
  /* Wider than a plain nav link, which is most of what makes it read as a button rather than
     as a highlighted word. The vertical padding matches its neighbours so the rail height
     does not change. */
  padding:10px 22px;
}

.header .nav .nav-list > li.nav-cta > a:hover,
.header .nav .nav-list > li.nav-cta > a:focus-visible{
  background:color-mix(in srgb, var(--csg-brass) 86%, var(--charcoal));
  border-color:color-mix(in srgb, var(--csg-brass) 86%, var(--charcoal));
  color:var(--charcoal);
}

/* The focus ring is drawn outside the fill, since the fill is now the same family of colour
   as the ring would be. */
.header .nav .nav-list > li.nav-cta > a:focus-visible{
  outline:2px solid var(--charcoal);
  outline-offset:2px;
}

/* THE DRAWER. Same item, different shape: the drawer list is a column of Libre Bodoni links with
   no boxes, so a pill sized like the rail's would float. A filled block, inset from the text
   column, reads as the end of the list and the thing to press. */
.csg-oc__list > li.nav-cta > a{
  margin-top:14px;
  padding:13px 18px;
  border-radius:var(--btn-radius);
  background:var(--csg-brass);
  color:var(--charcoal);
  opacity:1;
  text-align:center;
}

.csg-oc__list > li.nav-cta > a:hover,
.csg-oc__list > li.nav-cta > a:focus-visible{
  background:color-mix(in srgb, var(--csg-brass) 86%, var(--charcoal));
  color:var(--charcoal);
  opacity:1;
}

/* =====================================================
   Header phone number

   Duncan, 2026-09-17: "Can we have this underneath the Get in touch button on every page and
   clickable if viewing on mobile", with a mockup showing a handset mark and (407) 871-3397 on
   its own line under the button.

   IT COSTS NO HEADER HEIGHT. Measured before building: at 1440 the header box is 129px because
   the brandmark is 101px tall, and the nav sits at y=49-79 - about 36px of the box was unused.
   The phone row takes 24px of that plus a 6px gap. --header-height is untouched, so the hero,
   which is padded by calc(--header-height + --hero-fullbleed-top-gap), does not move. Same at
   390: header 100px, brandmark 72px, burger 34px, room underneath.

   flex-wrap plus flex-basis:100% on the phone is what puts it on its own line, rather than a
   grid with named areas - the nav disappears below 1350px and a two-column grid would then
   have an empty cell to reason about. Wrapping degrades on its own: row one becomes the burger
   alone and the phone stays underneath it.
   ===================================================== */

.header-actions{
  /* The auto margin that used to be on .nav. It has to live on the wrapper now, or the
     cluster hugs the brandmark instead of the right edge. */
  margin-left:auto;

  /* A GRID, NOT A WRAPPING FLEX ROW, and the difference is the whole alignment.
     Duncan, 2026-09-17: "number is not aligned to button". Wrapping flex right-aligns row two
     against the WRAPPER, whose right edge is the burger's - measured at 1440, the nav ended at
     1358 and the phone at 1416, so it hung 58px past the button it is meant to sit under.
     Two columns lets row two be told to occupy the nav's column only. */
  display:grid;
  grid-template-columns:auto auto;
  align-items:center;
  column-gap:16px;
  row-gap:6px;
  justify-content:end;
}

.header-actions .nav{
  grid-column:1;
  grid-row:1;
}

.header-actions .nav-toggle{
  grid-column:2;
  grid-row:1;
}

/* .nav keeps its own margin-left:auto for any context that still renders it bare; inside the
   wrapper it must not push, or it takes the whole row width and the burger goes with it. */
.header-actions .nav{
  margin-left:0;
}

/* Row two, in the NAV's column, then CENTRED on the button rather than flushed to its right
   edge. Duncan, 2026-09-17: "phone number is still looking off center to the button".

   Sharing a right edge is not centring: measured at 1440 the button ran 1216..1358 and the
   phone 1237..1358, so the phone's centre sat 10.5px right of the button's - exactly half the
   21px width difference, and the same at 1350 because both boxes are fixed width.

   translateX percentages resolve against the ELEMENT'S OWN width, which is what makes this
   expressible at all: translateX(50%) moves right by half the phone, and subtracting half the
   button lands the two centres on each other. A margin percentage would resolve against the
   containing block and could not do it. The transform does not reflow, so nothing else moves. */
.header-phone{
  grid-column:1;
  grid-row:2;
  justify-self:end;
  transform:translateX(calc(50% - (var(--nav-cta-w) / 2)));
}

/* The button's rendered width, measured. The only figure this arrangement needs - and the one
   thing to re-measure if the button is ever relabelled from "Get in Touch". */
.header-actions{
  --nav-cta-w:142px;
}

/* Below 1350px .nav is display:none, so there is no nav column to align to and the phone
   spans both - right-aligned under the burger, which is what the 390px measurement showed
   sitting correctly before this change. */
@media (max-width: 1349px){
  .header-phone{
    grid-column:1 / -1;
    /* No nav and no button to centre on below this width, so the centring offset comes off and
       the number right-aligns under the burger. Leaving the transform in place would shift it
       10.5px left of everything else in the header for no reason. */
    transform:none;
  }
}

.header-phone-link{
  display:inline-flex;
  align-items:center;
  gap:7px;
  /* Smaller than the 17px nav, as the mockup has it: the number supports the button, it does
     not compete with it. */
  font-size:15px;
  line-height:1.2;
  letter-spacing:-.01em;
  /* inherit, NOT a colour. The header themes itself light or dark as it scrolls over
     photography, and .header.is-theme-dark sets colour on .header itself - so inheriting means
     the number and its icon follow the header without a second rule or a second asset. */
  color:inherit;
  text-decoration:none;
  /* The anchor for the shared animated underline above. It keeps display:inline-flex - the base
     rule that .contact-footer-phone joins sets inline-block, which would stop the handset icon
     aligning with the digits. */
  position:relative;
  /* WCAG 2.5.8 target size. The text alone is 18px tall; this floors the tappable box at 24. */
  min-height:24px;
  padding:2px 0;
}

/* The static underline that was here is gone: it fought the shared animated one above, and two
   underline treatments on one link is worse than either. */

.header-phone-link:focus-visible{
  outline:2px solid var(--csg-brass, #AFA287);
  outline-offset:3px;
  border-radius:2px;
}

.header-phone-icon{
  flex:0 0 auto;
  /* Optically level with the digits rather than mathematically centred. */
  margin-top:-1px;
}

/* =====================================================
   Anchor landing offset

   Added 2026-09-17 when the first nav-level anchor link shipped: Duncan pointed the
   "Party Tent Rental" menu item at the homepage portfolio section rather than at the parties
   page. Before this there was no scroll-margin-top anywhere in this file, because nothing in
   the navigation had ever jumped to a fragment.

   .header is position:fixed, so a fragment jump scrolls the section's top edge to the top of
   the VIEWPORT - which is behind the header. Measured header heights: 129px at 1440, 123px at
   1350, 100px at 390. Without an offset the portfolio grid would arrive with its first tile row
   already clipped, which reads as a broken link rather than a missing one.

   scroll-margin-top changes no layout. It only tells the scroller where to stop, so it costs
   nothing when the section is reached by scrolling normally.

   The value is derived, not guessed: --header-height (72px in hero-top mode, 132px otherwise)
   undershoots the rendered box because the brandmark sets the real height, so this clamps a
   little above it and follows the variable if the header is ever resized.
   ===================================================== */

#section-intro,
#section-clients,
#section-capabilities,
#section-contact,
#section-channel-intro,
#section-channel-gallery{
  scroll-margin-top:clamp(96px, calc(var(--header-height) + 60px), 152px);
}

/* THE PORTFOLIO GRID LANDS TIGHTER, on its own line. Duncan, 2026-09-17: "can it jump one mouse
   scroll past so that all four titles are visible".

   Measured before changing anything: at 900px viewport and above all four titles were ALREADY
   visible, so the fault is not universal - it appears on shorter viewports, which is most
   laptops after browser chrome. Shortfall of the fourth title below the fold: 0px at 1920x950,
   69px at 1440x700, 81px at 1536x730, 97px at 1366x640.

   What this reclaims is DEAD SPACE, not content. The jump was leaving 67px of empty white
   between the header and the first tile - the section's own 64px top padding, sitting below a
   header the offset had already cleared. There is no heading above the grid to protect since
   the "What it looks like" headline came off on 2026-09-14, so scrolling past that padding
   costs nothing and gains ~60px of tiles.

   A fixed offset cannot fully solve a viewport-height problem, and the first attempt at this
   proved it the hard way: capping the offset at 100px to chase the fourth title put the first
   tile row 12px BEHIND the header at 1920, where the header is 160px tall. Clipping the top of
   the jump is a worse fault than a title one scroll below the fold.

   --header-height IS NOT THE HEADER'S HEIGHT, which is what made that attempt unsound rather
   than merely mistuned. It reads 72px in hero-top mode while the rendered box is 124-160px,
   because the brandmark sets the real height. Measured across widths:

       1280 -> 118    1366 -> 124    1440 -> 129    1536 -> 136    1920 -> 160

   That is ~9.1vw easing to ~8.3vw, so a vw clamp tracks the real header where the variable
   cannot. 9.2vw sits just above every measured height, with a floor for phones and a ceiling
   so an ultrawide does not scroll half a screen. The +4px is the only breathing room, which is
   what reclaims the section's dead 64px top padding.

   THE RESIDUAL IS HONEST: two rows of 16:9 tiles plus a header exceed a 640-730px viewport, so
   the fourth title still falls short there. That is a tile-height question, not a scroll one. */
#section-portfolio{
  scroll-margin-top:clamp(104px, calc(9.2vw + 4px), 180px);
}
