/* G & S Builders — shared stylesheet.
   Part 1 is the design system carried verbatim from the original build (tokens, resets,
   keyframes, glass, media queries). Part 2 is the generated pseudo-state layer.
   Do not add colours here that are not already in the palette. */


html{scroll-behavior:smooth}
body{margin:0;background:#F1F0ED;color:#5D6675;font:400 1rem/1.65 Inter,sans-serif;-webkit-font-smoothing:antialiased}
a{color:#151B24;text-decoration:none}a:hover{color:#9C7A18}
::selection{background:rgba(201,162,39,.28)}
*:focus-visible{outline:2px solid #C9A227;outline-offset:2px}
@keyframes gsFadeUp{from{transform:translateY(24px)}to{transform:none}}
/* Transform only, deliberately. An element that receives this animation and never advances — a
   throttled timeline, a missed frame — must still be on screen. Presence is the CSS default. */
@media (max-width:1023px){[data-desktop-only]{display:none!important}}
@media (min-width:1024px){[data-mobile-only]{display:none!important}}
@media (max-width:1279px){[data-xl-only]{display:none!important}}
@supports not ((backdrop-filter:blur(4px)) or (-webkit-backdrop-filter:blur(4px))){[data-glass="light"]{background:rgba(244,243,240,.94)!important}[data-glass="dark"]{background:rgba(21,27,36,.94)!important}}

/* ── Stage 8 · material and input craft ─────────────────────────────────────────────────────
   Tokens added to :root above:
     --ease-weight  decisive arrival, no overshoot — panels, cards, buttons, anything with mass
     --ease-settle  slow to start, firm arrival — load-time draws and long settles
     --ease-sweep   light: specular passes, seams, highlights (light has no mass, so it is fast)
     --dur-heavy 560ms / --dur-light 300ms — heavy things move slower than light ones
     --glass-thin   blur(12px) — chips, captions, small overlays
     --glass-deep   blur(28px) — panels, cards, the mobile overlay
     --grain        fractal-noise tile, baked faint; real glass is not perfectly smooth
   No overshoot easing anywhere: every curve's control points stay within 0–1. */
[data-glass]{--gtier:var(--glass-deep)}
span[data-glass],[data-glass-tier="thin"]{--gtier:var(--glass-thin)}
[data-glass]{backdrop-filter:var(--gtier)!important;-webkit-backdrop-filter:var(--gtier)!important}
/* Blurred surfaces get their own paint container. Excluded where a child's focus ring has to
   escape the box (the nav pill) or where the surface is already viewport-sized. */
div[data-glass]{contain:paint}
#gsNavPill,#gsOverlay{contain:none}
/* Edge lighting: light comes from above, so the top edge is bright and the bottom carries a dark
   inset — that pair is what gives a pane thickness. A warm gold tint sits under the top edge and a
   cool slate one above the bottom: dispersion, barely perceptible, and the detail nobody names. */
[data-glass]::before{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;box-shadow:inset 0 1px 0 rgba(255,255,255,.42),inset 0 2px 0 rgba(201,162,39,.10),inset 0 -1px 0 rgba(21,27,36,.26),inset 0 -2px 0 rgba(93,102,117,.14),inset 1px 0 0 rgba(255,255,255,.10),inset -1px 0 0 rgba(255,255,255,.06)}
/* Grain always on; the specular bloom grows from zero at the cursor on hover. Growing a background
   layer rather than fading one in means nothing is ever hidden waiting for a pointer that may
   never arrive, and --mx/--my default to the centre when JS never runs. */
[data-glass]::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;background-image:radial-gradient(circle,rgba(255,255,255,.30),rgba(255,255,255,0) 66%),var(--grain);background-repeat:no-repeat,repeat;background-size:0 0,140px 140px;background-position:var(--mx,50%) var(--my,50%),0 0;transition:background-size .32s var(--ease-sweep)}
@media (pointer:fine){[data-glass]:hover::after{background-size:200% 200%,140px 140px}}
/* Cursor-tracked light for flat light surfaces, where glass is forbidden and the specular has nothing
   to catch. Same contract as the button bloom: the layer is always present and GROWS from zero at the
   cursor (transform-origin follows --mx/--my), so nothing is hidden waiting for a pointer, a surface JS
   never touches stays flat, and the light sits under the text rather than over it. */
[data-light]{position:relative}
[data-light]::before{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;background:radial-gradient(30rem 20rem at var(--mx,50%) var(--my,50%),rgba(201,162,39,.24),rgba(201,162,39,.08) 30%,rgba(93,102,117,.055) 50%,transparent 70%);transform:scale(0);transform-origin:var(--mx,50%) var(--my,50%);transition:transform var(--dur-heavy) var(--ease-sweep)}
@media (pointer:fine){[data-light]:hover::before{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){[data-light]::before{display:none}}
/* backdrop-filter is the most expensive property in the build: mobile drops to the thin tier
   rather than losing glass altogether. */
@media (max-width:767px){[data-glass]:not([data-glass-tier="deep"]){--gtier:var(--glass-thin)}}
[data-glass-tier="deep"]{--gtier:var(--glass-deep)}
/* Links are navigation too: the 44px floor applied only to button/summary/input, which left
   every footer and inline link at 15-18px on a phone. Nav-context links now carry the floor. */
@media (max-width:767px){footer a,nav a,[data-nav],[data-mnav]{min-height:44px;display:flex;align-items:center}}
/* ---- Narrow-screen hardening. Applied in the helmet rather than per element because it is
   viewport policy, not state; the !important entries are the only way a stylesheet can outrank an
   inline style, which is why the display utilities above carry it too. Nothing here hides content
   or gates an interaction — it grows tap targets, lets unbreakable strings wrap, and stops iOS
   inflating type in landscape. ---- */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
/* Fixed sets of four (footer columns, the about stats band). auto-fit cannot help here: any floor
   that fails to fit four lands on 3+1, stranding the last item beside two empty cells. Explicit
   2 → 4 has no orphan state at any width. The 1060px threshold is where four columns clear the
   230px each the footer contact column actually needs; below it, 2×2 reads better than four
   cramped ones. */
@media (min-width:1060px){[data-quad]{grid-template-columns:repeat(4,minmax(0,1fr))!important}}
@media (min-width:860px){[data-triad]{grid-template-columns:repeat(3,minmax(0,1fr))!important}}
@media (min-width:700px){[data-triad-s]{grid-template-columns:repeat(3,minmax(0,1fr))!important}}
/* Drawn underline for inline text links: gold draws left→right on hover. Class-free (attribute),
   background-only, so it cannot fight inline colour styles. */
a[data-ul]{background-image:linear-gradient(90deg,#C9A227,#9C7A18);background-repeat:no-repeat;background-size:0% 1.5px;background-position:0 100%;padding-bottom:2px;transition:background-size .38s cubic-bezier(.65,0,.35,1)}
a[data-ul]:hover,a[data-ul]:focus-visible{background-size:100% 1.5px}
/* Every anchor target clears the fixed header. One rule beats per-element scroll-margin, which the
   articles, FAQ and legal pages already carry inline and which still wins where it is set. */
[id]{scroll-margin-top:96px}
img{max-width:100%}
@media (max-width:767px){
  body{overflow-wrap:break-word}
  [id]{scroll-margin-top:84px}
  button,summary,a[data-btn],[role="tab"],input:not([type="checkbox"]),select,textarea{min-height:44px}
  input[type="checkbox"]{min-width:24px}
}
@media (max-width:479px){
  #gsHeadIn{gap:10px!important;padding-left:16px!important;padding-right:16px!important}
  #gsCta{padding:12px 15px!important;font-size:0.8rem!important}
  [data-title-block]{flex-wrap:wrap!important}
}
@media (hover:none){[data-proj-peek]{display:none!important}}
@supports not ((backdrop-filter:blur(4px)) or (-webkit-backdrop-filter:blur(4px))){[data-glass]::after{background-image:var(--grain)!important;background-size:140px 140px!important}}

/* Buttons. Elaborate on purpose: they only fire when someone deliberately reaches for them. */
[data-btn]{position:relative;isolation:isolate;overflow:hidden;transition:box-shadow var(--dur-heavy) var(--ease-weight),transform var(--dur-heavy) var(--ease-weight),filter var(--dur-light) var(--ease-weight)}
[data-btn]:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
[data-btn]:active{filter:brightness(.94)}
[data-btn]:not([data-magnetic]):hover{transform:translateY(-2px)}
[data-btn]:not([data-magnetic]):active{transform:translateY(1px)}
[data-btn][aria-disabled="true"],[data-btn]:disabled{pointer-events:none;filter:grayscale(.4) opacity(.55)}
/* The arrow leaves top-right and a duplicate arrives from bottom-left. One element, clipped by the
   button's own overflow — the swap reads as a replacement without a second node in the markup. */
@keyframes gsArrowSwap{0%{transform:none}44%{transform:translate(150%,-150%)}45%{transform:translate(-150%,150%)}100%{transform:none}}
[data-btn="primary"]:hover svg,[data-btn="gold"]:hover svg{animation:gsArrowSwap .58s var(--ease-weight)}
/* Primary: gold wipes in from wherever the cursor entered. */
[data-btn="primary"]::before{content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;background:radial-gradient(circle at var(--mx,50%) var(--my,50%),var(--gold-lift),var(--gold) 42%,var(--gold-deep));transform:scale(0);transition:transform .45s var(--ease-weight)}
[data-btn="primary"]:hover::before{transform:scale(1.8)}
[data-btn="primary"]:hover{color:#151B24!important;box-shadow:0 10px 28px rgba(156,122,24,.28)}
/* Gold: the gradient angle rotates 135°→100° while a specular band passes once. Polished metal
   catching light, not a card lifting off the page — so no lift and no glow here. */
[data-btn="gold"]::before{content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;background:linear-gradient(135deg,var(--gold-lift),var(--gold) 52%,var(--gold-deep))}
[data-btn="gold"]::after{content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;background-image:linear-gradient(105deg,transparent 42%,rgba(255,255,255,.55) 50%,transparent 58%),linear-gradient(100deg,var(--gold-lift),var(--gold) 52%,var(--gold-deep));background-size:240% 100%,100% 100%;background-position:-70% 0,0 0;background-repeat:no-repeat;opacity:0;transition:opacity .5s var(--ease-settle),background-position .35s var(--ease-sweep)}
[data-btn="gold"]:hover::after{opacity:1;background-position:170% 0,0 0}
[data-btn="gold"]:not([data-magnetic]):hover{transform:none}
/* Outline: the stroke draws in from the left, then a 6% wash rises behind it. */
/* The secondary button. It used to be drawn in --verify green, which is the palette's status
   colour — the thing that means "complete and occupied" everywhere else on the site. Using it as
   a button accent read as a state rather than an action, and put a third colour next to the
   gunmetal primary. It is gold now: one accent, used once.

   The two mechanics are kept because they are good — a border that sweeps in from the left and a
   wash that rises from the base — and a third is added: the arrow steps right as the wash lands. */
[data-btn="outline"]::before{content:'';position:absolute;inset:0;border-radius:inherit;border:1.6px solid var(--gold);clip-path:inset(0 100% 0 0);transition:clip-path .45s var(--ease-weight);pointer-events:none}
[data-btn="outline"]:hover::before,[data-btn="outline"]:focus-visible::before{clip-path:inset(0 0 0 0)}
[data-btn="outline"]::after{content:'';position:absolute;inset:0;z-index:-1;border-radius:inherit;background:linear-gradient(180deg,rgba(201,162,39,0.07),rgba(201,162,39,0.15));transform:scaleY(0);transform-origin:bottom;transition:transform .45s var(--ease-weight)}
[data-btn="outline"]:hover::after,[data-btn="outline"]:focus-visible::after{transform:scaleY(1)}
[data-btn="outline"] svg{transition:transform .45s var(--ease-weight),color .3s linear}
[data-btn="outline"]:hover svg,[data-btn="outline"]:focus-visible svg{transform:translateX(3px);color:var(--gold-deep)}
@media (prefers-reduced-motion:reduce){
  [data-btn="outline"]::before,[data-btn="outline"]::after,[data-btn="outline"] svg{transition:none}}
/* Icon discs turn, they don't slide — but the ring turns, not the glyph. Rotating a directional
   chevron would point it somewhere it isn't going. */
@keyframes gsRing{from{transform:rotate(0)}to{transform:rotate(180deg)}}
[data-btn="icon"]::before{content:'';position:absolute;inset:-2px;z-index:-1;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 62%,var(--gold) 78%,transparent 92%);opacity:0;transition:opacity var(--dur-light) var(--ease-weight)}
[data-btn="icon"]:hover::before{opacity:1;animation:gsRing .9s var(--ease-weight)}

/* The seam: where a light section meets a dark one, one gold hairline. Present by default; the
   scroll-driven timeline only draws it. No JS, so nothing can strand it. */
section[data-dark]{position:relative}
section[data-dark]::before,section[data-dark]::after{content:'';position:absolute;left:0;right:0;height:1px;pointer-events:none;background:linear-gradient(90deg,transparent,var(--gold) 16%,var(--gold-lift) 50%,var(--gold) 84%,transparent);opacity:.55;transform-origin:left}
section[data-dark]::before{top:0}
section[data-dark]::after{bottom:0}
@supports (animation-timeline:view()){section[data-dark]::before{animation:gsSeam .4s var(--ease-sweep);animation-timeline:view();animation-range:entry 0% entry 55%}section[data-dark]::after{animation:gsSeam .4s var(--ease-sweep);animation-timeline:view();animation-range:entry 0% entry 55%}}
@keyframes gsSeam{from{transform:scaleX(0)}to{transform:scaleX(1)}}
/* Programme bar (home process teaser): five segments of one gold rule, filled in sequence by the
   section's own view() timeline — no JS, so it cannot be stranded, and outside the range the base
   hairline is what renders. Staggered ranges, not staggered timelines: the segments share a row, so
   their view progress is identical and only the range differs. Hover completes a segment outright. */
@supports (animation-timeline:view()){[data-seg-fill]{animation:gsSeam 1s linear;animation-timeline:view()}[data-seg-fill="1"]{animation-range:cover 4% cover 22%}[data-seg-fill="2"]{animation-range:cover 16% cover 34%}[data-seg-fill="3"]{animation-range:cover 28% cover 46%}[data-seg-fill="4"]{animation-range:cover 40% cover 58%}[data-seg-fill="5"]{animation-range:cover 52% cover 70%}}
@media (pointer:fine){[data-step]:hover [data-seg-fill]{transform:scaleX(1)!important}}

/* The laser line: a bright head running just ahead of the timeline fill, like a laser level.
   Driven by --k on the track (set by the page's rAF sweep), so it can't deform with the fill. */
[data-tl-line]::after{content:'';position:absolute;left:-5px;right:-5px;top:calc(var(--k,0) * 100%);height:20px;margin-top:-10px;pointer-events:none;background:radial-gradient(closest-side,rgba(232,199,103,.95),rgba(201,162,39,.32) 55%,transparent 78%);opacity:calc(min(1,var(--k,0) * 12))}

/* Micro-interactions — all input-driven, none scroll-triggered. */
summary svg{transition:transform .4s var(--ease-weight)}
details[open] > summary svg{transform:rotate(180deg)}
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
#gsNavPill a{position:relative}
#gsNavPill a::after{content:'';position:absolute;left:18px;right:18px;bottom:5px;height:1.5px;background:var(--gold);transform:scaleX(0);transform-origin:left;transition:transform .35s var(--ease-weight)}
#gsNavPill a:hover::after{transform:scaleX(1)}
#gsNavPill a[aria-current]::after{display:none}

/* The page-transition panel arrives as a roofline, not a bar: the gable angle from the logo. The
   panel is taller than the viewport so the peaks sit above the top edge once it has landed. */
#gsWipe{clip-path:polygon(0 100%,0 13%,16.7% 3%,33.4% 13%,50% 0,66.6% 13%,83.3% 3%,100% 13%,100% 100%)}
/* Setting-out grid — home hero. Rests at its final 8%; the draw is the only animated part, with no
   fill-mode, so a grid that never animates is simply already there. */
@keyframes gsSetOut{from{clip-path:inset(0 100% 0 0);opacity:.30}to{clip-path:inset(0 0 0 0);opacity:.08}}

@media (prefers-reduced-motion:reduce){
[data-glass]::after{transition:none!important;background-size:0 0,140px 140px!important}
[data-btn]::before,[data-btn]::after,[data-btn]:hover svg{animation:none!important;transition:none!important}
[data-setout]{animation:none!important}
section[data-dark]::before,section[data-dark]::after{animation:none!important;transform:none!important}
[data-tl-line]::after{display:none}
[data-seg-fill]{animation:none!important;transform:scaleX(1)!important}
summary svg{transition:none!important}
}


/* ── Pseudo-state utilities ───────────────────────────────────────────────────────────────
   The .dc.html source expressed hover/focus with style-hover="…" / style-focus="…" attributes,
   which its runtime compiled to real rules. PHP cannot set a pseudo-state inline, so every
   distinct value becomes one rule here and the markup carries data-hv / data-fc.
   1275 hover attributes reduced to 61 rules; 44 focus attributes to 3. */
[data-hv="link"]:hover{color:#9C7A18}
[data-hv="link-dark"]:hover{color:#E8C767}
[data-hv="row"]:hover{background:rgba(21,27,36,0.05)}
[data-hv="card"]:hover{border-color:#C9A227;color:#151B24}
[data-hv="ink"]:hover{color:#151B24}
[data-hv="btn-primary"]:hover{background:#232C3A;color:#F1F0ED}
[data-hv="gold"]:hover{color:#C9A227}
[data-hv="btn-outline"]:hover{border-color:#151B24;color:#151B24}
[data-hv="btn-gold"]:hover{background:#B8921F;color:#151B24}
[data-hv="row-gold"]:hover{background:rgba(201,162,39,0.1)}
[data-hv="icon"]:hover{--ik:1}
[data-hv="h1"]:hover{border-color:#C9A227;color:#9C7A18}
[data-hv="show"]:hover{opacity:1}
[data-hv="h2"]:hover{border-color:#E8C767;color:#E8C767}
[data-hv="h3"]:hover{background:#232C3A}
[data-hv="h4"]:hover{border-color:#C9A227;color:#C9A227}
[data-hv="h5"]:hover{background:#B8921F}
[data-hv="h6"]:hover{--seg:1;--segc:#8A6C15}
[data-hv="h7"]:hover{transform:translateY(-5px);box-shadow:0 4px 12px rgba(21,27,36,.06),0 26px 56px rgba(21,27,36,.12);--ik:1;--ph:1.05}
[data-hv="h8"]:hover{background:rgba(255,255,255,0.72);--ik:1}
[data-hv="h9"]:hover{--num:#E8C767;--discbg:#C9A227;--discfg:#151B24;--discbd:#C9A227;background:rgba(241,240,237,0.055);box-shadow:inset 3px 0 0 #C9A227;border-color:rgba(241,240,237,0.10)}
[data-hv="h10"]:hover{border-color:#151B24}
[data-hv="h11"]:hover{color:#F1F0ED}
[data-hv="h12"]:hover{border-color:rgba(232,199,103,0.5);background:rgba(255,255,255,0.07);color:#F1F0ED}
[data-hv="h13"]:hover{transform:translateY(-3px);background:rgba(255,255,255,0.82);color:#151B24}
[data-hv="h14"]:hover{color:#9C7A18;border-color:#9C7A18}
[data-hv="h15"]:hover{border-color:#C9A227;background:rgba(201,162,39,0.06)}
[data-hv="h16"]:hover{background:rgba(21,27,36,0.06);color:#151B24}
[data-hv="h17"]:hover{box-shadow:0 24px 64px rgba(21,27,36,.16);border-color:#C9C6BE}
[data-hv="h18"]:hover{border-color:#E8C767;background:rgba(241,240,237,0.06)}
[data-hv="h19"]:hover{border-color:rgba(47,125,79,0.55);background:rgba(255,255,255,0.075);color:#F1F0ED;transform:translateY(-3px)}
[data-hv="h20"]:hover{border-color:#C9A227}
[data-hv="h21"]:hover{border-color:#E8C767;background:rgba(241,240,237,0.08);color:#F1F0ED}
[data-hv="h22"]:hover{border-color:#C9A227;color:#F1F0ED}
[data-hv="h23"]:hover{--ik:1;--ph:1.04}
[data-hv="h24"]:hover{background:rgba(241,240,237,0.09);border-color:rgba(232,199,103,0.46);color:#F1F0ED}
[data-hv="h25"]:hover{background:rgba(47,125,79,0.07);color:#2F7D4F}
[data-hv="h26"]:hover{background:rgba(201,162,39,0.20);color:#151B24}
[data-hv="h27"]:hover{transform:translateY(-4px) !important;box-shadow:0 6px 14px rgba(21,27,36,.06),0 22px 48px rgba(21,27,36,.1) !important;color:#151B24 !important}
[data-hv="h28"]:hover{background:#FFFFFF;color:#2F7D4F}
[data-hv="h29"]:hover{background:rgba(255,255,255,0.08);border-color:rgba(232,199,103,0.55);color:#F1F0ED}
[data-hv="h30"]:hover{border-color:#C9A227;--ik:1}
[data-hv="h31"]:hover{background:#E8C767}
[data-hv="h32"]:hover{background:#FFFFFF;color:#151B24}
[data-hv="h33"]:hover{border-color:#C9A227;background:rgba(241,240,237,0.10);color:#F1F0ED}
[data-hv="h34"]:hover{background:rgba(21,27,36,0.035);color:#151B24}
[data-hv="h35"]:hover{border-color:rgba(232,199,103,0.5);background:rgba(241,240,237,0.075)}
[data-hv="h36"]:hover{border-color:rgba(232,199,103,0.5)}
[data-hv="h37"]:hover{border-color:rgba(232,199,103,0.55);background:rgba(241,240,237,0.07);transform:translateY(-2px);color:#F1F0ED}
[data-hv="h38"]:hover{transform:translateY(-3px)}
[data-hv="h39"]:hover{color:#E8C767;border-color:rgba(241,240,237,0.24)}
[data-hv="h40"]:hover{border-color:#F1F0ED;color:#F1F0ED}
[data-hv="h41"]:hover{border-color:#151B24;background:rgba(21,27,36,0.04)}
[data-hv="h42"]:hover{background:rgba(47,125,79,0.07)}
[data-hv="h43"]:hover{border-color:#C9A227;background:rgba(201,162,39,0.05)}
[data-hv="h44"]:hover{filter:grayscale(0)}
[data-hv="h45"]:hover{background:rgba(180,35,24,0.06)}
[data-hv="h46"]:hover{animation-play-state:paused}
[data-hv="h47"]:hover{border-color:rgba(232,199,103,0.5);transform:translateY(-4px)}
[data-hv="h48"]:hover{border-color:#99A0AB;color:#151B24}
[data-hv="h49"]:hover{background:rgba(47,125,79,0.08);color:#2F7D4F}
[data-fc="f1"]:focus,[data-fc="f1"]:focus-visible{border-color:#C9A227;box-shadow:0 0 0 3px rgba(201,162,39,0.18);outline:none}
[data-fc="f2"]:focus,[data-fc="f2"]:focus-visible{left:16px}
[data-fc="f3"]:focus,[data-fc="f3"]:focus-visible{--num:#E8C767;--discbg:#C9A227;--discfg:#151B24;--discbd:#C9A227;background:rgba(241,240,237,0.055);box-shadow:inset 3px 0 0 #C9A227;border-color:rgba(241,240,237,0.10);outline:2px solid #C9A227;outline-offset:3px}

/* ── Contact map ────────────────────────────────────────────────────────────────────────────
   The embed is filtered into the palette at rest and lifts to full colour on hover, so the
   map reads as part of the page until you actually want to use it. This is a state change,
   not motion, so reduced motion keeps it — only the easing goes. */
[data-map]:hover [data-map-frame],
[data-map]:focus-within [data-map-frame]{filter:grayscale(0) sepia(0) saturate(1) contrast(1) brightness(1)}
@media (prefers-reduced-motion:reduce){[data-map-frame]{transition:none}}

/* On a phone the frame is short and the card was covering most of the map. Give the frame more
   height and trim the card so the map is still the thing you are looking at. */
@media (max-width:767px){
  [data-map]{min-height:clamp(460px,120vw,560px)!important}
  [data-map] [data-glass]{padding:18px 18px!important;margin:12px!important}
}

/* ── Logo lockups ───────────────────────────────────────────────────────────────────────────
   The horizontal lockup needs room for the wordmark. Under 520px the header is carrying the
   CTA pill and the menu button too, and the wordmark drops to ~12px — unreadable. Below that
   the mark-only lockup takes over, which is what it exists for. */
/* The stacked footer lockup is 116px on desktop; give it a little back on a phone. */
@media (max-width:767px){
  footer gs-logo[variant="stacked"]{height:92px}
}

/* ── Vertical clip reel ──────────────────────────────────────────────────────────────────────
   Three stacked 9:16 frames would be about 1,800px of scrolling on a phone, so below the
   data-triad breakpoint the reel becomes a horizontal snap rail that shows a slice of the next
   clip. At 860px it is the same plain three-up grid as every other triad on the site. */
[data-reel]{display:grid;grid-auto-flow:column;grid-auto-columns:min(63vw,290px);gap:clamp(12px,2vw,22px);overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scroll-padding:0 clamp(20px,4vw,40px);padding-bottom:4px}
[data-reel]>*{scroll-snap-align:center}
@media (min-width:860px){
  /* capped, not full-width: three 9:16 frames across 1280px would be 730px tall each */
  [data-reel]{grid-auto-flow:row;grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-columns:auto;overflow-x:visible;scroll-snap-type:none;padding-bottom:0;max-width:1040px;margin-inline:auto}
}
/* A clip sitting beside a column of text should not tower over it on a wide screen. */
[data-clip-col]{max-width:340px}
@media (max-width:767px){[data-clip-col]{max-width:290px;margin-left:auto;margin-right:auto}}

/* ── Project rows (home) ─────────────────────────────────────────────────────────────────────
   The row shifts right and its circle arrow fills gold. Done in CSS, not JS, so a coarse
   pointer and a keyboard both get it and nothing depends on a script arriving. */
[data-hv="proj-row"]:hover,[data-hv="proj-row"]:focus-visible{padding-left:14px}
[data-hv="proj-row"]:hover [data-row-arrow],[data-hv="proj-row"]:focus-visible [data-row-arrow]{background:#C9A227;border-color:#C9A227;color:#151B24}
/* The verify-green outline button keeps its own colour on hover; the shared btn-outline rule
   turns gunmetal, which is right for the gunmetal-stroked variants and wrong for this one. */
[data-hv="btn-verify"]:hover{background:transparent;color:#151B24}
[data-hv="card-lift"]:hover{transform:translateY(-4px);box-shadow:0 6px 14px rgba(21,27,36,.06),0 22px 48px rgba(21,27,36,.1);color:#151B24}
[data-hv="edge-ivory"]:hover{border-color:#F1F0ED}

/* ── Box model ───────────────────────────────────────────────────────────────────────────────
   The layout idiom all through this build is `flex:1 1 380px; min-width:min(380px,100%)` with
   padding on the same element. Under the default content-box that padding is ADDED to the
   min-width, so a 335px card ended up holding a 407px column — and because those cards clip
   (border-radius + overflow:hidden) the extra was not a scrollbar, it was text silently cut off
   the right edge. border-box is what every one of those declarations already meant. */
*,*::before,*::after{box-sizing:border-box}

/* ── Mobile: home project rows ───────────────────────────────────────────────────────────────
   The row is built for a wide screen: a small thumbnail, the name, a fact strip and an arrow all
   on one line. Wrapped onto a phone it became four disconnected fragments with an 80x60 stamp of
   an image and the arrow orphaned on its own line. Below 640px the same four children lay out as
   a card instead — photograph across the top, name and arrow on one line, facts underneath. */
@media (max-width:639px){
  /* the row carries display:flex inline, so the switch to grid has to outrank it */
  [data-proj-row]{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:14px!important;align-items:center;padding-left:0!important;padding-right:0!important}
  [data-proj-row]>span:nth-child(1){grid-column:1/-1;width:100%!important;aspect-ratio:16/9;border-radius:16px}
  [data-proj-row]>span:nth-child(2){grid-column:1;grid-row:2;min-width:0}
  [data-proj-row]>span:nth-child(4){grid-column:2;grid-row:2;justify-self:end;align-self:center}
  [data-proj-row]>span:nth-child(3){grid-column:1/-1;grid-row:3}
  /* the row already shifts on hover; on a phone that would just crop the photograph */
  [data-hv="proj-row"]:hover,[data-hv="proj-row"]:focus-visible{padding-left:0}
}

/* ── Mobile: reach ───────────────────────────────────────────────────────────────────────────
   Anything a thumb has to hit gets 44px. Inline links inside a sentence are left alone — padding
   them would break the line box — but a standalone phone, email or WhatsApp link is a target. */
@media (max-width:767px){
  /* Vertical padding, not inline-flex. inline-flex makes the link an atomic inline that cannot
     wrap, which pushed a 29-character email address straight off a 320px screen. Padding grows
     the hit area to 44px while the text still breaks across lines. */
  a[href^="tel:"],a[href^="mailto:"],a[href*="wa.me"]{padding-block:12px}
  /* Links that already lay out as a box can take the honest minimum. */
  a[href^="tel:"][style*="inline-flex"],a[href*="wa.me"][style*="inline-flex"]{padding-block:0;min-height:44px}
  header a[aria-label]{min-height:44px;display:inline-flex;align-items:center}
}

/* An email address is one unbreakable 29-character string; at 320px it needs more room than the
   column has. Let it break rather than run off the edge. */
a[href^="mailto:"],[data-wrap-anywhere]{overflow-wrap:anywhere}

/* An unbreakable token — an email address, a URL — inside a flex chip cannot shrink below its own
   width, so the chip pushes the row wider than a 320px screen. Let it break instead. */
[data-placeholder]{overflow-wrap:anywhere;min-width:0}

/* Inline underlined links sit inside a sentence, so they cannot be given a 44px box without
   breaking the line. Padding on an inline box still takes the tap, so the reachable area grows
   from 20px to 44px while the text keeps flowing. */
@media (max-width:767px){ a[data-ul]{padding-block:12px} }

/* ── Mobile: form controls ───────────────────────────────────────────────────────────────────
   iOS Safari zooms the entire page in when a focused input is under 16px, and it does not zoom
   back out afterwards — the visitor is left on a magnified, sideways-scrolling page mid-form.
   The design sets these at 0.95rem (15.2px), which is just under the trigger. 16px on touch
   only, so the desktop typography is untouched. */
@media (max-width:767px){
  input,select,textarea{font-size:16px!important}
}


/* The office email is 29 unbroken characters. In the contact card's text column it lands a couple
   of pixels over one line on a 390px phone and breaks mid-domain. A slightly smaller value keeps
   it on one line; the label above it is unchanged. */
@media (max-width:479px){
  [data-contact-email] span span:last-child{font-size:0.92rem!important;letter-spacing:-0.01em}
}

/* ── Investment: the handover datum ──────────────────────────────────────────────────────────
   The receipt above the line is a torn slip: the scallops are a repeating radial gradient in the
   page ivory, punched out of the slip's own bottom edge. Nothing here is an image, so it stays
   crisp at any zoom and costs no request. */
[data-hv-slip]::after{content:'';position:absolute;left:-1px;right:-1px;bottom:-1px;height:9px;
  background-image:radial-gradient(circle at 5px 9px,#F1F0ED 5px,transparent 5.5px);
  background-size:14px 9px;background-repeat:repeat-x;pointer-events:none}

/* The datum rule and the asset's gold bar rest fully drawn — this only animates the drawing of
   something already there, so a browser without view() timelines shows the finished state. */
@supports (animation-timeline: view()){
  [data-hv-rule]{animation:gsDatumDraw .01s linear;animation-timeline:view();animation-range:entry 12% entry 62%}
  [data-hv-bar]{animation:gsBarDrop .01s linear;animation-timeline:view();animation-range:entry 24% entry 78%}
}
@keyframes gsDatumDraw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes gsBarDrop{from{transform:scaleY(0)}to{transform:scaleY(1)}}
@media (prefers-reduced-motion:reduce){
  [data-hv-rule],[data-hv-bar]{animation:none!important;transform:none!important}
}

/* The ledger rail fills as the row scrolls through. It rests filled, so a browser without view()
   timelines shows a finished building as finished — the animation only draws what is already true. */
@supports (animation-timeline: view()){
  [data-rail-fill]{animation:gsDatumDraw .01s linear;animation-timeline:view();animation-range:entry 20% entry 85%}
}
@media (prefers-reduced-motion:reduce){[data-rail-fill]{animation:none!important;transform:none!important}}

/* Paired figures — two renders of one building shown side by side above 760px, stacked below so
   neither is squeezed to a letterbox on a phone. */

/* The brand name is held on one line inside a display heading. At 44px it needs 278px, which is
   2px more than a 320px screen has — so the rule starts at 360px and the narrowest phones let it
   break rather than run off the edge. Declared here rather than inline so the breakpoint can win. */
@media (min-width:360px){ [data-brand-nowrap]{white-space:nowrap} }


/* ── Mobile legibility floor ───────────────────────────────────────────────────────────────────
   The small-caps labels across the site — eyebrows, FIG captions, spec-table keys, plate tags —
   are authored between 0.58rem and 0.68rem. On a desktop that reads as a fine label; on a phone
   it lands between 9.3px and 10.9px, and uppercase letter-spaced text needs MORE size than body
   copy to stay readable, not less.

   Every one of those values is inside a `font:` shorthand in an inline style (verified: 93 of 93
   occurrences), so matching the declaration text is exact — there is no other property in the
   codebase carrying these values. Relative order is preserved so the label hierarchy survives. */
@media (max-width:767px){
  [style*="0.58rem"]{font-size:0.72rem!important}
  [style*="0.62rem"]{font-size:0.72rem!important}
  [style*="0.64rem"]{font-size:0.73rem!important}
  [style*="0.66rem"]{font-size:0.74rem!important}
  [style*="0.68rem"]{font-size:0.75rem!important}
}


/* ── Quote form on a phone ─────────────────────────────────────────────────────────────────────
   The form sits three padded boxes deep: the page gutter, the dark column, then the glass panel.
   At 390px that stacked chrome ate 148px and left the fields 241px wide — narrow enough that the
   longest option in the "What is it about?" dropdown was cut off in the closed field. Tightening
   the two inner boxes on small screens gives the controls back ~50px without touching the
   desktop composition. Scoped through the column so no other dark glass panel is affected. */
@media (max-width:520px){
  div[style*="flex:1.1 1 420px"]{padding-left:14px!important;padding-right:14px!important}
  div[style*="flex:1.1 1 420px"] [data-glass="dark"]{padding-left:15px!important;padding-right:15px!important}
}

/* A control below 16px makes iOS zoom the page on focus and never zooms back. The existing rule
   stopped at 767px, which leaves an iPad in portrait doing exactly that. Form controls are 16px
   at every width instead — against the authored 15.2px the difference is invisible. !important
   because the size is set inline through a `font:` shorthand, which outranks a plain rule. */
input,select,textarea{font-size:16px!important}
