/* SLAB -- dark, photo-led, condensed. Tokens first; every value below is one a developer can build.
   Display: Saira Extra Condensed. Body: Instrument Sans. Ground: near-black / iron / steel.
   One machine-yellow word per band and the phone number; nothing else is coloured. */
/* ROUND 8, ITEM 3 -- THE THREE-GROUND RAMP. Jonah: "each page is quite dark. It's a dark
   band, followed by a dark band, followed by a dark band... We need to brighten them up
   somehow but in a tasteful way. Just ever so slightly."
   HE IS DESCRIBING RHYTHM AND THE MEASUREMENT AGREED EXACTLY. Measured band-by-band at 1440
   before this edit (cb=1789054278330): /about ran hero -> statement -> filmstrip -> strip as
   FOUR CONSECUTIVE BANDS OF THE IDENTICAL HEX at 1.000:1 across 1,350px; /civil ran 1,618px
   unbroken; and the largest adjacent step anywhere on the site was --bg vs --iron at
   1.104:1. That was the entire tonal range of the surface.
   LIFTING EVERY GROUND A FEW POINTS CHANGES NOTHING A PERSON CAN SEE inside a 1.1:1 range,
   and it throws away the direction he ratified. The only thing that raises a page's vibe is
   a genuine change of ground somewhere on it, so: --bg is UNCHANGED, --iron deepens by 12
   perceptual points so the two dark grounds stop reading as one, and one warm bone rung is
   added to the top of the existing greyscale ladder for exactly ONE band per page.
   --stone is not a new palette. This surface already runs #0C0D0E / #15181C / #17191C /
   #20242B / #22252A -- a neutral ramp that stops before it gets anywhere. This is the
   missing top rung. Bar: oldtomcapital.com, on this account's own reference list, which is
   dark and institutional and not dim because it puts one bone band where the reading is;
   its dark-to-bone step measures 14.61:1 and its bone is #EFEDE7.
   STEPS THIS BUYS:  --bg/--iron 1.104 -> 1.233 : 1   --iron/--stone 12.32 : 1
                     --bg/--stone 15.19 : 1 */
:root{
  --bg:#0C0D0E; --iron:#1F2329; --steel:#22252A; --lift:#15181C; --card:#20242B;
  /* warm bone, one band per page. Contrast-checked against every token that lands on it. */
  --stone:#E7E3DA; --stone-ink:#14171B; --stone-mut:#4E535A; --stone-rule:#9C948A; --stone-line:#CFC8BA;
  --line:rgba(255,255,255,.18); --line-2:rgba(255,255,255,.10);
  --ink:#ECEAE4; --mut:#A3A6AB; --yel:#F2B21B; --ghost:#6A6F76; --wm:#7E828A;
  --disp:"Saira Extra Condensed","Arial Narrow",Impact,sans-serif; --body:"Instrument Sans","Helvetica Neue",Arial,sans-serif;
  --gut:20px; --band:40px; --strip:12px; --hdr:64px;
  /* motion: the full JMEX-MOTION-SPEC.md 3.1 token set, not a second language */
  --m-fast:120ms; --m-ui:180ms; --m-enter:380ms; --m-slow:520ms;
  --e-out:cubic-bezier(.22,.61,.36,1); --e-ui:cubic-bezier(.4,0,.2,1); --e-exit:cubic-bezier(.4,0,1,1);
  --m-rise:12px; --m-stagger:60ms;
}
@media (min-width:1024px){:root{--gut:40px; --band:64px; --strip:28px; --hdr:80px}}
*{box-sizing:border-box} html{-webkit-text-size-adjust:100%; scroll-behavior:smooth}
body{margin:0; background:var(--bg); color:var(--ink); font:16px/1.55 var(--body); padding-bottom:56px}
@media (min-width:1024px){body{padding-bottom:0; font-size:17px}}
img{display:block; max-width:100%} a{color:inherit} p{margin:0 0 1em} p:last-child{margin-bottom:0}
ul,ol{margin:0; padding:0; list-style:none} h1,h2,h3{margin:0} figure{margin:0}
.wrap{max-width:1280px; margin:0 auto; padding:0 var(--gut)}
:where(a,button,summary,input,select,textarea):focus-visible{outline:2px solid var(--yel); outline-offset:3px}

/* type */
.eyebrow{font:600 12px/1.3 var(--body); letter-spacing:.2em; text-transform:uppercase; color:var(--mut); margin:0 0 14px}
/* THE STENCIL CHIP -- replaces the 22x1px drawn rule that read as an em dash (Jonah, round 5).
   Carries its own ground, so a label over any photograph on the site clears AA. */
.stamp,.hero .eyebrow,.print-type .eyebrow{
  display:inline-flex; align-items:center; align-self:flex-start; width:auto;
  font:600 11px/1 var(--body); letter-spacing:.18em; text-transform:uppercase;
  color:var(--ink); background:rgba(12,13,14,.82); border:1px solid var(--line);
  padding:7px 11px 6px; margin:0 0 14px;
}
.stamp::before{content:none}
.h1{font:800 clamp(64px,11.5vw,168px)/.9 var(--disp); text-transform:uppercase; letter-spacing:0; text-wrap:balance; max-width:9em}
.hero-print .h1{font-size:clamp(58px,9.4vw,136px)}
.h2{font:800 clamp(38px,4.8vw,68px)/.94 var(--disp); text-transform:uppercase; letter-spacing:.005em; text-wrap:balance}
.h3{font:700 27px/1 var(--disp); text-transform:uppercase; letter-spacing:.015em}
@media (min-width:1024px){.h3{font-size:31px}}
.hl{color:var(--yel)}
.lede{font-size:17px; line-height:1.5; color:var(--ink); max-width:40ch}
@media (min-width:1024px){.lede{font-size:18px}}
.deck{color:var(--mut); max-width:44ch}
.tlink{display:inline-block; font:600 12px/1 var(--body); letter-spacing:.16em; text-transform:uppercase; text-decoration:none; padding:20px 0 6px; border-bottom:1px solid var(--line); margin:-12px 0 0; transition:border-color var(--m-ui) var(--e-ui)}
.tlink:hover{border-color:var(--ink)}

/* buttons */
.btns{display:flex; flex-direction:column; gap:10px}
@media (min-width:640px){.btns{flex-direction:row; flex-wrap:wrap; gap:12px}}
.btn{display:inline-flex; align-items:center; justify-content:center; gap:10px; height:48px; padding:0 22px; font:600 13px/1 var(--body); letter-spacing:.08em; text-transform:uppercase; text-decoration:none; border:1px solid transparent; cursor:pointer; white-space:nowrap; transition:background var(--m-ui) var(--e-ui), color var(--m-ui) var(--e-ui), border-color var(--m-ui) var(--e-ui)}
@media (min-width:1024px){.btn{height:52px; padding:0 26px}}
.btn .ic{width:16px; height:16px}
.btn-solid{background:var(--ink); color:#0C0D0E; border-color:var(--ink)} .btn-solid:hover{background:var(--yel); border-color:var(--yel)}
.btn-line{background:transparent; color:var(--ink); border-color:rgba(255,255,255,.45)} .btn-line:hover{border-color:var(--ink); background:rgba(255,255,255,.06)}
.btn:active{transform:translateY(1px)}

/* header */
/* the header sits on raw photograph until scroll: it carries its own scrim so the nav
   never depends on the hero image being dark (ported from the real site's nav-contrast fix) */
.hdr{position:fixed; inset:0 0 auto 0; z-index:50; transition:background var(--m-ui) var(--e-ui);
  background:linear-gradient(to bottom, rgba(12,13,14,.78) 0%, rgba(12,13,14,.34) 62%, rgba(12,13,14,0) 100%)}
.hdr.scrolled,.hdr.open{background:rgba(12,13,14,.94); -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px); border-bottom:1px solid var(--line-2)}
.hdr-in{max-width:1280px; margin:0 auto; padding:0 var(--gut); height:var(--hdr); display:flex; align-items:center; justify-content:space-between; gap:24px}
.brand{display:inline-flex; align-items:center; min-height:44px; color:var(--ink)} .brand .logo{height:34px; width:auto}
@media (min-width:1024px){.brand .logo{height:40px}}
.nav{display:none} .hdr-call{display:none}
/* ROUND 10 ITEM 1. Jonah: "the top home, Civil Pipeline Servicing, Landscape, About, and
   Contact need to be centered. It feels like it's too offset to the right now."
   CAUSE, measured on the live render at 1440: .hdr-in was a space-between FLEX row and .nav
   carried margin-left:auto, so the nav was shoved right until it touched the call button.
   Header centre 720px, nav centre 805.5px -- 85.5px right of centre. The logo on the left had
   no counterweight on the right, so nothing could ever centre it.
   FIX AT THE PROPERTY, NOT THE INSTANCE: a three-track grid whose OUTER TRACKS ARE EQUAL
   BY CONSTRUCTION (minmax(0,1fr) each). The middle track is then centred in the header at
   every width no matter how wide the logo or the call button grow, which is the failure
   round 4 had on the trust band and took three attempts because it kept being patched on
   the instance. Below 1024 the header stays flex (logo left, hamburger right) -- .nav and
   .hdr-call are display:none there, so a 3-track grid would centre the HAMBURGER. */
@media (min-width:1024px){
  .hdr-in{display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); align-items:center}
  .hdr-in>.brand{justify-self:start}
  .hdr-in>.hdr-call{justify-self:end}
  .nav{display:flex; gap:30px; margin-left:0; justify-self:center} .nav a{font:500 14px/1 var(--body); letter-spacing:.04em; text-transform:uppercase; text-decoration:none; color:var(--ink); opacity:1; padding:10px 0; border-bottom:1px solid transparent}
  .nav a:hover,.nav a[aria-current=page]{opacity:1; border-bottom-color:var(--yel)}
  .hdr-call{display:inline-flex; align-items:center; gap:8px; height:44px; padding:0 16px; border:1px solid rgba(255,255,255,.4); font:600 13px/1 var(--body); letter-spacing:.06em; text-transform:uppercase; text-decoration:none; color:var(--ink)}
  .hdr-call .ic{width:15px; height:15px} .hdr-call:hover{border-color:var(--ink)}
}
.menu-btn{display:flex; flex-direction:column; justify-content:center; gap:6px; width:44px; height:44px; margin-right:-10px; background:none; border:0; cursor:pointer; padding:0 10px}
.menu-btn span{display:block; height:2px; background:var(--ink); transition:transform var(--m-ui) var(--e-ui)}
.hdr.open .menu-btn span:first-child{transform:translateY(4px) rotate(45deg)} .hdr.open .menu-btn span:last-child{transform:translateY(-4px) rotate(-45deg)}
@media (min-width:1024px){.menu-btn{display:none}}
.mnav{display:none; padding:8px var(--gut) 24px; border-top:1px solid var(--line-2)}
.hdr.open .mnav{display:block}
.mnav ul{display:flex; flex-direction:column} .mnav li{border-bottom:1px solid var(--line-2)}
.mnav li:last-of-type{border-bottom:0}
/* one declared row height rather than a height derived from padding fighting a border */
.mnav a:not(.btn){display:flex; align-items:center; min-height:56px; padding:0; font:600 18px/1 var(--disp); letter-spacing:.06em; text-transform:uppercase; text-decoration:none}
.mnav .btn{width:100%; margin-top:16px}

/* hero, home + wide */
.hero{position:relative; background:var(--iron); overflow:hidden}
/* full-bleed at desktop; photo-above-type at phone (see the block below the crop note) */
.hero-home{height:min(88svh,640px); display:flex}
@media (min-width:1024px){.hero-home{height:min(92vh,860px)}}
.hero-img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* HOME HERO CROP -- REWRITTEN ROUND 10 FOR AMY'S NEW FRAME. The note that stood here described
   the OLD 1440x1920 PORTRAIT master (truck decal at source x 5-24%) and every number in it is
   now wrong, so it is replaced rather than left to mislead the next seat.
   NEW MASTER: jmex-hero-golden-hour, 1440x1080 -- 4:3 LANDSCAPE. Excavator at source x 41-99%,
   y 31-58%. Detached bucket x 23-31%. Mountains and sky occupy the top 45%.
   393 (the 322px band): source ratio 1.333 vs band ratio 1.221, so HEIGHT binds -- cover scale
   .298, scaled width 429 against a 393 box, only 36px of horizontal crop. Essentially the whole
   frame survives on the phone, which is why this breakpoint needs no re-aiming and keeps 50% 50%.
   1440: band 1440x860 = 1.674 against a 1.333 source, so WIDTH binds and 20% of the height is
   discarded. 44% (was 52%) keeps the mountain line and the sky, which is what carries this frame;
   52% cropped into it. */
@media (min-width:1024px){.hero-home .hero-img{object-position:50% 44%}}
/* AT 393 THE PHOTOGRAPH AND THE TYPE CANNOT SHARE THE BAND, and the arithmetic says so:
   the type block is 478px, so a full-bleed hero needs ~720px to leave any photograph showing.
   At 720 on a 393-wide viewport the cover scale is height-driven, so there is NO vertical crop
   and the decal sits where the master puts it -- source y 58-67%, i.e. squarely under the type.
   Any scrim strong enough to carry the type also blacks out the thing he asked to see.
   So the phone gets the same construction the four pages he liked already use: photograph on
   top in its own box, type below on solid ground. 340px box, cover scale .273, visible source
   y 17.5-82.5% and x 0-100% -- machine, decal, sky above, ground below, nothing clipped. */
@media (max-width:1023px){
  .hero-home{height:auto; display:block; padding-top:322px; background:var(--bg)}
  .hero-home .hero-img{inset:0 0 auto 0; height:322px; object-position:50% 50%}
  .hero-home .hero-scrim{inset:0 0 auto 0; height:322px;
    background:linear-gradient(to top, var(--bg) 0 5%, rgba(12,13,14,.38) 26%, rgba(12,13,14,0) 56%)}
  .hero-home .hero-in{padding-top:0; margin-top:-52px; padding-bottom:22px; gap:18px}
}
/* the scrim is sized to the TYPE, not to the band: >=.82 alpha everywhere type sits, so the
   contrast is guaranteed by the scrim rather than by hoping the photograph is dark */
.hero-scrim{position:absolute; inset:0; background:linear-gradient(to top, rgba(12,13,14,.97) 0%, rgba(12,13,14,.93) 34%, rgba(12,13,14,.82) 58%, rgba(12,13,14,.34) 80%, rgba(12,13,14,0) 100%)}
.hero-in{position:relative; width:100%; display:flex; flex-direction:column; justify-content:flex-end; gap:20px; padding-top:calc(var(--hdr) + 16px); padding-bottom:30px}
@media (min-width:1024px){.hero-in{padding-bottom:52px; gap:26px}}
.hero-foot{display:grid; gap:18px}
@media (min-width:1024px){.hero-foot{grid-template-columns:minmax(0,1fr) auto; align-items:end; gap:56px}}
/* HIS ITEM 3, and only this paragraph. 249 characters at a 40ch measure ran to 6 lines at 1440
   and 7 at 393 -- "very stacked, very tight". Widening the measure and opening the leading fixes
   the look he named without moving the alignment he did not ask to move. */
.hero-home .lede{max-width:62ch; font-size:16.5px; line-height:1.66}
@media (min-width:1024px){.hero-home .lede{max-width:74ch; font-size:18px; line-height:1.6}}

/* hero, the contact print (portrait masters): the photo is an inset print on the right, the headline overruns its left edge */
.hero-print{background:var(--bg); position:relative}
.print-ph{position:relative; height:min(52svh,428px); overflow:hidden}
.print-ph img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* the type overlaps the photo's foot by 56px, so the foot must be solid there, not 35% */
.print-ph::after{content:""; position:absolute; inset:0; background:linear-gradient(to top, var(--bg) 0 12%, rgba(12,13,14,.45) 34%, rgba(12,13,14,0) 62%)}
.print-in{position:relative} .print-type{margin-top:-56px; padding-bottom:26px; display:flex; flex-direction:column; gap:14px}
.print-type .h1{text-shadow:0 2px 24px rgba(0,0,0,.6)}
@media (min-width:1024px){
  .hero-print{display:flex; align-items:flex-end; height:min(74vh,730px)}
  .print-ph{position:absolute; inset:0 0 0 44%; height:auto}
  .print-ph::after{background:linear-gradient(to right, var(--bg) 0 15%, rgba(12,13,14,.88) 34%, rgba(12,13,14,.30) 52%, rgba(12,13,14,0) 66%), linear-gradient(to top, rgba(12,13,14,.55) 0, rgba(12,13,14,0) 35%)}
  .print-in{width:100%; padding-top:var(--hdr)}
  .print-type{margin:0; padding-bottom:52px; width:66%; gap:20px}
  .print-type .lede{max-width:46ch} .print-type .h1{max-width:8em}
}

/* trust strip */
.trust{background:var(--iron); padding:var(--strip) 0}
/* ROUND 2: "On mobile, the band with the badges / fully insured, etc... the balance &
   spacing & alignment seems off." STILL STANDING ON THIS SURFACE IN ROUND 8, and the real
   site fixed it three rounds ago -- which is why parity cannot be assumed to travel in only
   one direction. This is the first fix on this engagement to go REAL -> SLAB.
   MEASURED at 393 before this edit (cb=1789054278330):
     REAL  one column, four cells, each h=21.0 at y = 0 / 31 / 62 / 93.   EVEN.
     SLAB  two columns at 143.4 / 193.6 (unequal), rows h = 19.6 / 19.6 / 39.2 / 39.2.
   The second row is exactly double the first because the long badge wraps to two lines and
   drags the one-line badge beside it. The 1.35fr was itself an earlier attempt at this same
   defect and it does not survive a two-line badge: one column cannot be lopsided.
   HEIGHT COST, stated rather than buried: 4 x 19.6 + 3 x 10 = 108.4px against 68.8px today,
   so +39.6px on /home and /about at 393. It is the one place in this pass that costs height
   and it is paid for several times over by the gallery measure on the same surface. A band
   he complained about does not get left lopsided to protect a number. */
.trust ul{display:grid; grid-template-columns:1fr; gap:6px}
@media (min-width:1024px){.trust ul{display:flex; justify-content:space-between}}
.trust li{display:flex; align-items:flex-start; gap:10px; font:500 14px/1.4 var(--body)}
@media (min-width:1024px){.trust li{font-size:15px}}
.tick{flex:0 0 8px; width:8px; height:8px; margin-top:6px; background:var(--mut)}
.trust-dark{background:var(--bg); border-top:1px solid var(--line-2); border-bottom:1px solid var(--line-2)}

/* bands */
.band-head{display:grid; gap:10px; margin-bottom:18px}
@media (min-width:1024px){.band-head{grid-template-columns:1fr 1fr; align-items:end; gap:48px; margin-bottom:40px} .band-head .deck{justify-self:end; text-align:right}}
.what{padding:var(--band) 0; background:var(--bg)}
.tiles{display:grid; gap:6px}
@media (min-width:1024px){.tiles{grid-template-columns:repeat(3,1fr); gap:24px}}
/* at 393 the list moves OUT of the 173px body column into a full-width row of its own, so the
   six services get a real measure and can sit two-up. That is what pays for dropping the middot. */
.tile{display:grid; grid-template-columns:42% minmax(0,1fr); grid-template-areas:"ph body" "list list" "link link"; background:var(--iron); text-decoration:none; color:inherit; position:relative; border-bottom:2px solid transparent; transition:background var(--m-ui) var(--e-ui), border-color var(--m-ui) var(--e-ui)}
.tile:hover{background:var(--steel); border-bottom-color:var(--yel)}
.tile-ph{grid-area:ph; position:relative; min-height:96px} .tile-ph img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.tile-body{grid-area:body; padding:12px 16px 12px; display:flex; flex-direction:column; gap:4px}
/* ROUND 9 (a) AND (b). His words: "too tight to the photo" and "isn't spaced".
   MEASURED ON THE LIVE RENDER BEFORE THIS EDIT (WebKit 393, cb=1789...): the gap from the
   photo's bottom edge to the top of the list box was EXACTLY 0px, while BELOW the list sat
   23.5px of dead space -- 10px of padding plus the paragraph's own 1em bottom margin, which
   nobody had zeroed. All of the air in this block was on the wrong side of the text.
   So: take the 23.5px below, spend 10px of it above the first row, and leave 12px under the
   last row before the VIEW link. The margin goes to 0 because a <p> default is not a design
   decision. Net cost of (b): NEGATIVE. */
.tile-list{grid-area:list; padding:10px 16px 16px; margin:0}
.tile-body .eyebrow{margin:0 0 3px} .tile-body .h3{font-size:24px}
/* the middot separator is gone. It bound to the word before it inside a nowrap span, so every
   wrapped line ENDED with a hanging dot -- the same defect the real site logged at f907a2c.
   A two-column grid has no glyph left to orphan, uses the width, and is a line shorter. */
/* UNIFORM -- Jonah's actual word, and the one part of item 1 this surface did not yet do.
   Two columns produce ceil(n/2) rows, so the three cards ran 3 / 2 / 2. MEASURED at 393
   before this edit: list heights 74.7 / 52.1 / 52.1, and at 1440 85 / 85 / 85. The cards are
   already equalised to the tallest by grid-auto-rows:1fr, so that slack ALREADY EXISTS -- it
   was sitting below the VIEW link on the two short cards. This moves it into the list rather
   than adding it, so all three lists occupy one block and the band is uniform. Zero height.
   13.5 * 1.45 * 3 + 3 * 2 = 61.7. */
/* ROUND 9 (c). His words: "not utilizing the space of each of those cards".
   MEASURED AT 393 BEFORE THIS EDIT: two equal 153.5px tracks left 73.7 / 71.4 / 81.8px of
   the card's width completely empty on the right of the three cards -- the dead strip he
   photographed. Equal tracks CANNOT fix that: the words are left-aligned inside a track that
   is wider than any of them, so the slack always piles up at the right edge.
   minmax(0,1fr) on the first track and `auto` on the second sizes column two to its own
   content and lets column one absorb the rest, so the widest item in column two lands flush
   against the card's right padding. MEASURED AFTER: dead strip 73.7 -> 16.0px on all three.
   It also stops the wrap that the bigger type would otherwise cause: at 14.5px in a 153.5px
   equal track "Backfill and Compaction" breaks to two lines and the Pipeline card grows 21px
   (measured as variant A). With a content-sized column it does not break at all.
   ROW GAP 4px and 13.5 -> 14.5px are (a), "cramped": the rows sat on bare 1.45 leading with
   no gap at all, at the smallest type on the page. Both are paid for out of the 23.5px that
   was under the list, the 4px off the photo floor and the 2px off the card gap -- NOT out of
   page height. */
/* WHY THE SECOND TRACK HAS A FLOOR AND IS NOT JUST `auto`, WHICH IS WHAT I TRIED FIRST AND
   MEASURED: with `auto` the track sizes to each CARD's own longest string, so column two
   started at 254.1 / 251.6 / 262.8 on the three stacked cards -- an 11px stagger down the
   band, which is exactly the "uniform" that round 8 closed. The floor is the widest of the
   three measured tracks (105.4px), so every card puts column two on ONE axis and the widest
   item in it still lands flush against the card's padding. max-content is the escape hatch:
   if the copy ever gains characters the track grows rather than overrunning. */
.tile-list{display:grid; grid-template-columns:minmax(0,1fr) minmax(106px,max-content); gap:4px 16px; font-size:14.5px; color:var(--mut); line-height:1.45}
/* NO white-space:nowrap -- it was carried over from the inline construction and inside a
   grid cell it is what BREAKS the layout. FOUND BY LOOKING AT THE DEPLOYED PHONE RENDER:
   'Backfill and Compaction' is ~180px at 13.5px against a 153.5px track, so with nowrap it
   ran over 'Site Restoration' in the next column and the two strings overprinted. The
   programmatic overrun check passed because it compared each item to the LIST's right edge
   -- an item overflowing column 1 into column 2 is still inside the list. */
.tile-list span{min-width:0; overflow-wrap:anywhere} .tile-list span::after{content:none}
.tile-link{grid-area:link; padding:0 16px 10px; font:600 11px/1 var(--body); letter-spacing:.16em; text-transform:uppercase; color:var(--ink)}
@media (min-width:1024px){
  .tile{grid-template-columns:1fr; grid-template-areas:"ph" "body" "list" "link"; grid-template-rows:auto auto 1fr auto}
  .tile-ph{aspect-ratio:16/10; min-height:0} .tile-body{padding:18px 22px 8px; gap:7px} .tile-body .h3{font-size:30px}
  /* THE THREE-ROW RESERVATION IS DESKTOP-ONLY. Two columns give ceil(n/2) rows, so the
     three cards run 3/2/2. At >=1024 they sit SIDE BY SIDE in one grid row -- which is
     where 'uniform / symmetrical / balanced' is judged, and where the row already
     equalises the cards, so reserving the third row MOVES slack that already exists from
     below the VIEW link into the list at zero height cost. At 393 the cards are STACKED
     and each is sized to its own content, so nothing sits beside anything else to read as
     uneven and a reservation is pure added height: MEASURED at +20px on this surface
     before it was scoped to desktop. 14.5 * 1.45 * 3 = 63.1. */
  /* ROUND 9 at desktop: same three faults, same construction. The reservation grows with the
     row gap -- 14.5 * 1.45 * 3 + 4 * 2 = 71.1 -- and is paid for by zeroing the paragraph's
     1em bottom margin (14.5px) and trimming the list's own bottom padding 14 -> 6. */
  .tile-list{font-size:14.5px; gap:4px 20px; padding:4px 22px 10px; min-height:71.1px} .tile-link{padding:0 22px 20px}
}
.tile:active{background:rgba(255,255,255,.06); transition-duration:var(--m-fast)}

.split{background:var(--iron); display:grid}
.split-ph{position:relative; aspect-ratio:2/1} .split-ph img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.split-body{padding:22px var(--gut) 26px; display:grid; gap:18px}
.split-blk{display:flex; flex-direction:column; align-items:flex-start; gap:6px} .split-blk p{color:var(--mut); max-width:40ch}
.split-blk + .split-blk{border-top:1px solid var(--line-2); padding-top:18px}
@media (min-width:1024px){
  .split{grid-template-columns:5fr 7fr; min-height:520px}
  .split-ph{aspect-ratio:auto} .split-body{padding:56px 60px 56px 68px; align-content:center; gap:30px}
}

.quote{background:var(--iron); border-top:1px solid var(--line-2); padding:var(--strip) 0; text-align:center}
.q{font:700 clamp(26px,4vw,44px)/1.05 var(--disp); text-transform:uppercase; letter-spacing:.02em; margin-bottom:10px}
.q-attr{font:600 12px/1 var(--body); letter-spacing:.2em; text-transform:uppercase; color:var(--mut)}

.faq{padding:var(--band) 0; background:var(--bg)}
.faq-grid{display:grid; gap:24px}
@media (min-width:1024px){.faq-grid{grid-template-columns:4fr 8fr; gap:48px}}
.faq-row{border-top:1px solid var(--line)} .faq-rows{border-bottom:1px solid var(--line)}
.faq-row summary{display:flex; justify-content:space-between; align-items:center; gap:16px; padding:13px 0; cursor:pointer; list-style:none; font:600 16px/1.35 var(--body)}
.faq-row summary::-webkit-details-marker{display:none}
/* the open state used to lay both bars flat, which drew a dash. It now rotates to an x --
   no dash-shaped glyph in any state, and a better close affordance. */
.plus{flex:0 0 14px; width:14px; height:14px; position:relative; transition:transform var(--m-ui) var(--e-ui)}
.plus::before,.plus::after{content:""; position:absolute; background:var(--mut); left:0; top:6px; width:14px; height:2px}
.plus::after{transform:rotate(90deg)}
.faq-row[open] .plus{transform:rotate(45deg)}
.faq-row[open] summary .plus::before,.faq-row[open] summary .plus::after{background:var(--ink)}
.faq-a{padding:0 40px 16px 0; color:var(--mut)}

/* THE ONE STONE BAND. It is ONE component, so it lands on five of six pages from a single
   rule (Jonah has now asked four times for things to be fixed at the component and not per
   page); it measures 373/373/373/364/364px, so every page gets the SAME amount of relief --
   "the vibe of each page", his words; and it is the last band before the footer, so the page
   ends on light and closes on black, which is a cadence rather than a fade-out and puts the
   brightest thing on the page underneath the phone number. Five of six bands are untouched
   and zero height is added: this is a recolour of a band that already exists.
   THE YELLOW CANNOT SURVIVE AS TEXT HERE -- measured, --yel on --stone is 1.47:1, invisible.
   Since the one-yellow-word device is ratified it INVERTS rather than being dropped: the
   word becomes a marker, which is what a highlight actually is and is stronger than coloured
   text. Contrast-checked, every pairing: stone-ink on stone 14.04:1 AAA / stone-mut on stone
   6.05:1 AA / stone-ink on yel 9.55:1 AAA / btn-solid 14.04:1 AAA / btn-line border 14.04:1,
   past the 3:1 non-text threshold. */
.cta{background:var(--stone); color:var(--stone-ink); border-top:0; padding:var(--band) 0; text-align:center}
.cta .h2,.cta .cta-h{color:var(--stone-ink)}
.cta .cta-p{color:var(--stone-mut)}
/* THE MARKER IS PAINTED, NOT A BACKGROUND COLOUR, and that is a bug fix found by looking
   at the deployed band. An inline element's background fills its FONT box (~1.3em tall),
   while .h2 runs at line-height .94 -- so at a 100px display size the yellow block stood
   ~36px taller than its own line and cut straight through 'SITE THAT' above it and the
   deck below it. A gradient gives the painted band an exact height in em, so it scales
   with the clamp() type and stays inside its line at every width. */
.cta .hl{color:var(--stone-ink); background-image:linear-gradient(var(--yel),var(--yel));
  /* NOT GUESSED. Measured off the real loaded face with canvas TextMetrics at 1440:
     Saira Extra Condensed fontBoundingBoxAscent 1.10em, cap top 0.73em above the
     baseline, glyph bottom 0.01em below it. background-position is measured from the top
     of the inline padding box, which sits one ascent above the baseline, so cap top is
     at 0.37em and the cap-to-baseline run is 0.74em. Opened up 0.03em each way so the
     marker breathes. It is in em, so it holds at every clamp() size. */
  background-repeat:no-repeat; background-size:100% .80em; background-position:0 .34em;
  padding:0 .1em; box-decoration-break:clone; -webkit-box-decoration-break:clone}
.cta .btn-solid{background:var(--stone-ink); color:var(--stone); border-color:var(--stone-ink)}
.cta .btn-solid:hover{background:var(--yel); color:var(--stone-ink); border-color:var(--yel)}
.cta .btn-line{color:var(--stone-ink); border-color:rgba(20,23,27,.42)}
.cta .btn-line:hover{border-color:var(--stone-ink); background:rgba(20,23,27,.06)}
.cta-in{display:flex; flex-direction:column; align-items:center; gap:14px}
.cta-h{font-size:clamp(42px,7vw,100px)}
.cta-p{max-width:44ch; color:var(--mut); font-size:17px}
.cta .btns{justify-content:center; width:100%} @media (min-width:640px){.cta .btns{width:auto}}

.ftr{background:var(--bg); border-top:1px solid var(--line); padding:22px 0 16px; font-size:14px; color:var(--mut)}
@media (min-width:1024px){.ftr{padding:44px 0 24px; font-size:15px}}
.ftr-row1{display:flex; flex-direction:column; gap:6px; padding-bottom:12px; border-bottom:1px solid var(--line-2)}
@media (min-width:1024px){.ftr-row1{flex-direction:row; align-items:center; justify-content:space-between; gap:24px; padding-bottom:16px}}
/* Jonah: "make the JMEX logo in the top left bigger... make sure it's still balanced and spaced
   properly". 30 -> 44 / 56, and the row and column padding pay for the extra height. */
.ftr .logo{height:44px} .ftr .brand{color:var(--ink); align-self:flex-start; min-height:44px; align-items:center}
@media (min-width:1024px){.ftr .logo{height:56px}}
.ftr-nav{display:flex; flex-wrap:wrap; gap:0 20px} .ftr-nav a{display:inline-flex; align-items:center; min-height:40px; text-decoration:none; color:var(--ink); font:500 14px/1 var(--body); letter-spacing:.04em; text-transform:uppercase}
.ftr-cols{display:grid; gap:10px; padding:12px 0}
@media (min-width:1024px){.ftr-cols{grid-template-columns:5fr 3.5fr 3.5fr; gap:36px; padding:22px 0}}
.ftr-col p{margin:0 0 4px} .ftr-col a{color:var(--ink); text-decoration:none; display:inline-block; padding:10px 0; margin:-7px 0} .ftr-col a:hover{text-decoration:underline}
.ftr .tag{font-style:italic}
.ftr-label{font:600 11px/1 var(--body); letter-spacing:.2em; text-transform:uppercase; color:var(--ink); margin-bottom:8px !important}
.ftr-copy{display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:6px 20px; border-top:1px solid var(--line-2); padding-top:12px; font-size:12px}
.ftr-copy p{margin:0}
/* the agency mark. Subtle by design and still AA: --wm is 4.63:1 on --bg, and the mark is a
   fraction of the JMEX logo's area, so it cannot compete with the client's own footer logo. */
.wm{font:400 11px/1 var(--body); letter-spacing:0; color:var(--wm); text-decoration:none; padding:17px 0; margin:-17px 0; transition:color var(--m-ui) var(--e-ui)}
@media (min-width:1024px){.wm{font-size:12px}}
.wm:hover,.wm:focus-visible{color:var(--yel)}

.callbar{position:fixed; left:0; right:0; bottom:0; z-index:40; height:56px; display:flex; align-items:center; justify-content:center; gap:10px; background:var(--yel); color:#0C0D0E; font:700 14px/1 var(--body); letter-spacing:.08em; text-transform:uppercase; text-decoration:none}
.callbar .ic{width:16px; height:16px}
@media (min-width:1024px){.callbar{display:none}}

/* galleries */
.gallery{padding:34px 0 var(--band); background:var(--bg)}
.g-grid{display:grid; gap:0}
/* ROUND 6 "no text that's aligned weirdly or stacked too vertically", said in five separate
   rounds and true on BOTH surfaces in the same way, which is exactly why comparing the two
   found nothing. MEASURED at 393 before this edit (cb=1789054278330): the description ran a
   188.8px measure at 22-26 characters per line, 5-6 lines on /civil and up to 8 on
   /landscape. Comfortable prose wants 45-75 characters.
   THE CAUSE IS THE CARD, NOT THE COPY. 42% of a 353px card gave the photograph 148px and
   left the prose 188.8px. 36% takes the photo to 127px and the measure to ~210px / 28-29
   characters, which drops a line off most cards.
   THE HONEST LIMIT: 28 characters is the ceiling. At 393, in a card that also holds a
   photograph, you cannot have a 45-character measure, a real photograph and the current card
   height at once. Reaching 45 means dropping the photograph to full-width-above-text, which
   the service-tile arithmetic already showed costs +308px on one band alone. */
.g-card{display:grid; grid-template-columns:36% minmax(0,1fr); gap:14px; padding:9px 0; border-top:1px solid var(--line-2); align-items:stretch}
.g-card:last-child{border-bottom:1px solid var(--line-2)}
.g-ph,.pair{position:relative; min-height:112px} .g-ph img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.g-body{display:flex; flex-direction:column; gap:3px; min-width:0} .g-body p{font-size:14.5px; line-height:1.45; color:var(--mut)}
/* ROUND 1: "The O1 and the title of each photo need to be aligned properly according to the
   photo and balanced." MEASURED before this edit, left offset from each card's own left edge:
     SLAB /civil + /landscape, 393 and 1440:  numeral 0 | TITLE 23.3-25.0 | description 0
     REAL /civil + /landscape, 393 and 1440:  numeral 0 | TITLE 26.7      | description 0
   Three elements, two axes, on BOTH surfaces. The parity spec called this REAL-only because
   it measured this <h3>'s BOX (which does start at 0) rather than its title TEXT, which
   starts after the inline numeral exactly as the real site's does. Fixed on both. */
.g-body .h3{font-size:24px; display:flex; flex-direction:column; align-items:flex-start; gap:0}
.g-body .h3 .num{line-height:1}
.num{font:700 14px/1 var(--disp); letter-spacing:.14em; color:var(--mut)}
@media (min-width:1024px){
  .g-grid{gap:32px 24px} .cols-3 .g-grid{grid-template-columns:repeat(3,1fr)} .cols-4 .g-grid{grid-template-columns:repeat(4,1fr)}
  /* 16 -> 10px: this is the payback for the numeral moving onto its own line above the
     title. MEASURED at +30px on /civil and +13px on /landscape at 1440 before it. */
  .g-card{grid-template-columns:1fr; grid-template-rows:auto 1fr; gap:8px; padding:0; border:0} .g-card:last-child{border:0}
  .g-ph{aspect-ratio:4/3; min-height:0} .g-body .h3{font-size:28px} .g-body p{font-size:15.5px; line-height:1.5}
}
.pair{display:grid; grid-template-columns:1fr 1fr; gap:4px}
@media (min-width:1024px){.pair{aspect-ratio:4/3}}
.pair-cell{position:relative; overflow:hidden} .pair-cell img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.pair-cap{position:absolute; left:8px; bottom:8px; font:700 11px/1 var(--disp); letter-spacing:.16em; text-transform:uppercase; color:#fff; padding:5px 7px; background:rgba(12,13,14,.72)}

/* photos-only project gallery -- 0923-H, JMEX-PROJECT-GALLERY-SPEC.md sections 1-2 */
.pgal-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:6px; margin:0; padding:0; list-style:none}
.pgal-cell{position:relative; aspect-ratio:1/1; overflow:hidden; background:var(--iron)}
.pgal-cell a{position:absolute; inset:0; display:block; cursor:zoom-in}
.pgal-cell a:focus-visible{outline:2px solid var(--ink); outline-offset:-3px}
.pgal-cell img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:50% 50%}
@media (min-width:1024px){.pgal-grid{gap:16px} .pgal-cell{aspect-ratio:4/3}}
.pgal-dlg{width:100vw; height:100dvh; max-width:none; max-height:none; padding:0; border:0; background:transparent}
html:has(.pgal-dlg[open]){overflow:hidden}
.pgal-dlg::backdrop{background:rgba(12,13,14,.94)}
.pgal-dlg img{width:100%; height:100%; object-fit:contain; padding:var(--gut)}
.pgal-x{position:fixed; top:12px; right:12px; width:44px; height:44px; border:0; background:none; color:var(--ink); font:400 32px/1 var(--body)}

/* pipeline process */
.process{padding:var(--band) 0; background:var(--iron)}
.proc-grid{display:grid; gap:28px}
.proc-ph{position:relative; aspect-ratio:16/10} .proc-ph img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.steps{display:grid} .step{display:grid; grid-template-columns:1fr; gap:4px; padding:16px 0; border-top:1px solid var(--line)} .step:last-child{border-bottom:1px solid var(--line)}
.num-big{font:800 30px/.9 var(--disp); color:var(--mut)}
.step .h3{font-size:24px; margin-bottom:4px} .step p{font-size:15.5px; color:var(--mut)}
@media (min-width:1024px){
  .proc-grid{grid-template-columns:5fr 7fr; gap:48px; align-items:start}
  .proc-ph{aspect-ratio:4/3; position:sticky; top:calc(var(--hdr) + 24px)}
  /* --steel on --iron measured 1.15:1 -- the numerals were invisible. --ghost is 3.48:1. */
  .step{grid-template-columns:76px 1fr; gap:16px; padding:22px 0} .num-big{font-size:64px; line-height:.8; color:var(--ghost)} .step .h3{font-size:28px}
}

/* about */
/* NO PAGE MAY RUN MORE THAN TWO CONSECUTIVE BANDS ON THE SAME GROUND. Applied to the
   measured ladders that is exactly one further change beyond the CTA: /about ran hero ->
   statement -> filmstrip -> strip on four identical grounds. This is the one block of real
   reading on the page and a lifted ground behind it is right. /civil's hero -> gallery run
   is two, which is allowed. Zero height. */
.statement{padding:var(--band) 0; background:var(--iron)}
.stmt-grid{display:grid; gap:24px}
/* ROUND 10 ITEM 3, the About "Built by an Operator" band. Jonah: the title is "stacked
   vertically", the two paragraphs are "stacked super weirdly on top of each other", and the
   band does not "utilize the space horizontally". He added "I don't know what happened or
   why that changed."
   WHAT ACTUALLY HAPPENED -- and it is NOT a regression. The markup and every .stmt-* rule
   are byte-identical in all four SLAB commits (9a942b4, b9a809d, 99aab97, 8ed7769); the only
   edit this band has ever had is background:var(--bg) -> var(--iron) in 99aab97, a colour.
   The band has looked like this since SLAB was first generated. What changed is the REAL
   site: about.astro centred this same statement on Jonah's own earlier instruction ("I'd
   rather we improve the copy and center this 'Built by an Operator'"), and SLAB, generated
   from an older snapshot, never received that decision. This is round 8's parity failure
   again, not a bad commit.
   THE MECHANISM, measured live at 1440: the head track was a fixed 5fr = 473.3px while the
   headline's max-content is ~480px. It missed by ~7px, so it wrapped, and text-wrap:balance
   then split it "BUILT BY" / "AN OPERATOR" -- the vertical stack he saw. The wrap also left
   185px dead inside the head track and a 249px void before the copy. Meanwhile columns:2
   cut the copy into two 311px towers (~38 characters), which is the "stacked" reading.
   FIX AT THE CAUSE: size the head track to its CONTENT instead of to an arbitrary fraction,
   so the headline can never miss its own line box at any width or type size; give the copy
   everything left over as ONE column capped at a readable measure. A 360px floor on the copy
   track means an overlong future headline shrinks and wraps rather than crushing the copy. */
@media (min-width:1024px){.stmt-grid{grid-template-columns:minmax(0,max-content) minmax(360px,1fr); gap:64px; align-items:start}}
.stmt-copy p{font-size:17px; line-height:1.6; color:var(--ink)} .stmt-copy p+p{margin-top:1em}
@media (min-width:1024px){.stmt-copy{max-width:66ch} .stmt-copy p{font-size:17px}}
.filmstrip{padding:0 0 var(--band); background:var(--bg)}
.film-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:6px}
@media (min-width:1024px){.film-grid{gap:16px}}
.film-cell{position:relative; aspect-ratio:1/1} .film-cell img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
@media (min-width:1024px){.film-cell{aspect-ratio:16/10}}

/* contact */
/* Jonah, round 5: "the contact page is a little bit dark... can we fix that subtly?"
   THE EARLIER ANSWER (below, kept for the record) LIFTED THE BAND 11 LUMINANCE POINTS AND
   THE FORM 9. Measured at 1440 before this edit, that bought a largest adjacent step of
   1.092:1 across the whole page -- hero 1.092 contact, contact 1.011 strip, strip 1.104
   footer. Nothing a person can see. He said it again in round 8 about every page.
   /contact IS THE ONE PAGE WITH NO CTA BAND (hero, contact, strip, footer -- verified by
   enumerating the data-band sequence of all six pages, not assumed), so the one-stone-band-
   per-page rule has to land on a different band here. It lands on this one, which is the
   page's reading band and the one he named.
   THE FORM CARD DELIBERATELY STAYS DARK. A dark card on a bone ground is the composition the
   original comment was already reaching for -- "the form reads as a card on a ground rather
   than a hole in one" -- and it means every input, label, focus ring and placeholder keeps
   the contrast work already done on it. Nothing inside .c-form is touched. */
.contact{padding:var(--band) 0; background:var(--stone); color:var(--stone-ink)}
/* EVERY RULE BELOW IS SCOPED TO .c-left, WHICH IS THE FORM CARD'S SIBLING -- verified by
   walking the band's DOM (c-grid > c-left{c-call,c-before,c-trust} + c-form), not assumed.
   The submit button lives inside .c-form and must NOT invert, or it would go dark-on-dark. */
.contact .c-left p.eyebrow{color:var(--stone-mut)}  /* 6.05:1 on --stone. `p.eyebrow` and
   not `.eyebrow`: this band has TWO of them and `.c-before p` outranks a bare class, which
   left the second one a different colour from the first. Caught by measuring both. */
/* --yel on --stone is 1.47:1. The number is the most important element on the page, so it
   takes the ink rather than a marker: an 80px block of highlight would shout. 14.04:1. */
.contact .c-left .c-num{color:var(--stone-ink)}
.contact .c-left .c-line,.contact .c-left .c-outro,.contact .c-left .c-before p{color:var(--stone-mut)}
.contact .c-left .c-mail{color:var(--stone-ink)}
/* the yellow survives here as a non-text marker, where 3:1 is the threshold, not 4.5 */
.contact .c-left .c-list li{border-left-color:var(--yel)}
.contact .c-left .c-trust{border-top-color:var(--stone-rule)}
.contact .c-left .c-trust li{color:var(--stone-ink)} .contact .c-left .c-trust .tick{background:var(--stone-mut)}
.contact .c-left .btn-solid{background:var(--stone-ink); color:var(--stone); border-color:var(--stone-ink)}
.contact .c-left .btn-solid:hover{background:var(--yel); color:var(--stone-ink); border-color:var(--yel)}
.c-grid{display:grid; gap:36px}
@media (min-width:1024px){.c-grid{grid-template-columns:5fr 7fr; gap:56px; align-items:stretch}}
.c-left{display:flex; flex-direction:column; gap:22px}
@media (min-width:1024px){.c-left{justify-content:space-between; gap:28px}}
.c-call{display:flex; flex-direction:column; align-items:flex-start; gap:12px}
/* ROUND 8 item 4, the stacking sweep, found by LOOKING at the deployed 1440 render: the
   number broke as '604-226-' / '1233' in the 5fr column. A phone number split across two
   lines mid-number is the plainest stacking defect on either surface. It fits on one line
   at both widths -- ~370px at 80px in a 476px column, ~240px at 52px in a 353px one -- and
   the horizontal-overflow check is re-run at 375/393/430/1440 after this. */
.c-num{font:800 clamp(52px,10vw,80px)/.9 var(--disp); text-decoration:none; color:var(--yel); letter-spacing:0}
/* >=1024 ONLY, and the reason is a measured overflow, not caution. At 1440 the number broke
   as '604-226-' / '1233' in the 5fr column -- the plainest stacking defect on either
   surface -- and nowrap fixes it. Applied at every width it OVERFLOWS THE DOCUMENT at 375:
   clamp() bottoms out at 52px there, the string renders ~361px, and the column is 335px,
   so scrollWidth went 375 -> 401. Caught by the horizontal-overflow sweep at 375 before
   deploy, not after. Making it fit on the phone needs the display size to drop below the
   52px floor, and that floor is part of the type ramp B82 locks -- Jonah's call, flagged. */
@media (min-width:1024px){.c-num{white-space:nowrap}}
.c-line{color:var(--mut); max-width:36ch} .c-mail{color:var(--ink); display:inline-block; padding:10px 0; margin:-10px 0}
.c-before p{color:var(--mut)} .c-list{display:grid; gap:10px; margin:12px 0}
.c-list li{padding-left:16px; border-left:2px solid rgba(242,178,27,.35); font-size:15.5px}
.c-outro{font-size:15px}
.c-trust{display:grid; grid-template-columns:1fr 1fr; gap:10px 16px; padding-top:18px; border-top:1px solid var(--line-2)}
.c-trust li{display:flex; gap:10px; align-items:flex-start; font:500 14px/1.4 var(--body)}
.c-form{background:var(--card); padding:20px 16px 24px}
@media (min-width:1024px){.c-form{padding:30px}}
.form-intro{color:var(--mut); margin-bottom:16px}
.form{display:grid; grid-template-columns:1fr 1fr; gap:12px 12px}
.fld{display:flex; flex-direction:column; justify-content:flex-end; gap:5px; min-width:0} .fld.full{grid-column:1/-1}
/* a two-line label must not push its own input out of line with the one beside it:
   the cells stretch, the contents sit at the bottom, so every input in a row aligns */
.fld label,.fld legend{font:500 13px/1.3 var(--body); color:var(--mut); padding:0}
.fld input:not([type=radio]):not([type=checkbox]),.fld select,.fld textarea{width:100%; height:44px; background:var(--bg); border:1px solid var(--line); color:var(--ink); font:16px/1.3 var(--body); padding:0 12px; border-radius:0; -webkit-appearance:none; appearance:none}
.fld textarea{height:auto; padding:10px 12px; resize:vertical}
.fld input:focus,.fld select:focus,.fld textarea:focus{outline:0; border-color:var(--yel); box-shadow:0 0 0 3px rgba(242,178,27,.18)}
.sel{position:relative} .sel::after{content:""; position:absolute; right:14px; top:18px; width:8px; height:8px; border-right:2px solid var(--mut); border-bottom:2px solid var(--mut); transform:rotate(45deg); pointer-events:none}
.sel select{padding-right:36px}
.hint{font-size:12.5px; color:var(--mut)}
.radios{border:0; margin:0; padding:0} .radio-row{display:flex; gap:20px; padding-top:4px}
.radio{display:inline-flex; align-items:center; gap:8px; font-size:15px; cursor:pointer}
.radio{min-height:44px} .radio input{width:22px; height:22px; margin:0; accent-color:var(--yel)}
.btn-submit{width:100%; margin-top:6px}

/* contact form states, 0923-O. .contact sets color:var(--stone-ink) and .c-form sets none, so every text rule here names its colour */
.form-err,.form-sent{display:none} .form.has-err .form-err{display:block}
.form-err{grid-column:1/-1; background:var(--bg); border-left:3px solid var(--yel); padding:12px 14px; font-size:15px; line-height:1.45; color:var(--ink); scroll-margin:calc(var(--hdr) + 16px) 0 72px}
.form-err:focus,.form-sent-h:focus{outline:0}
.form-err-tel{display:inline-block; padding:10px 0; margin:-10px 0; color:var(--yel); font-weight:600; white-space:nowrap; text-underline-offset:3px}
.form-live{position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}
.form.is-sending .btn-submit{opacity:.72; pointer-events:none} .c-form{scroll-margin-top:calc(var(--hdr) + 16px)}
.c-form.is-sent .form-intro,.c-form.is-sent .form{display:none}
.c-form.is-sent .form-sent{display:block; color:var(--ink); padding:4px 0; animation:sent-in var(--m-enter) var(--e-out) both}
.form-sent-ic{display:block; width:40px; height:40px; color:var(--ink); margin:0 0 18px}
.form-sent-h{font-size:clamp(38px,4.4vw,56px); max-width:11em; margin:0 0 12px}
.form-sent-p{color:var(--mut); max-width:40ch; margin:0 0 22px}
@keyframes sent-in{from{opacity:0; transform:translateY(var(--m-rise))}} @media (prefers-reduced-motion:reduce){.c-form.is-sent .form-sent{animation:none}}
@media (min-width:1024px){.c-form.is-sent{display:flex; flex-direction:column} .c-form.is-sent .form-sent{margin:auto 0}}

/* reveal -- JMEX-MOTION-SPEC.md 3.1: 12px rise, 380ms, --e-out, 60ms stagger, max 3 steps */
.rv{opacity:0; transform:translateY(var(--m-rise)); transition:opacity var(--m-enter) var(--e-out), transform var(--m-enter) var(--e-out)}
.rv.on{opacity:1; transform:none}
.rv .tile,.rv .g-card,.rv .step,.rv .film-cell,.rv .faq-row{opacity:0; transform:translateY(var(--m-rise)); transition:opacity var(--m-enter) var(--e-out), transform var(--m-enter) var(--e-out)}
.rv.on .tile,.rv.on .g-card,.rv.on .step,.rv.on .film-cell,.rv.on .faq-row{opacity:1; transform:none}
.rv.on :is(.tile,.g-card,.step,.film-cell,.faq-row):nth-child(2){transition-delay:var(--m-stagger)}
.rv.on :is(.tile,.g-card,.step,.film-cell,.faq-row):nth-child(3){transition-delay:calc(var(--m-stagger) * 2)}
.rv.on :is(.tile,.g-card,.step,.film-cell,.faq-row):nth-child(n+4){transition-delay:calc(var(--m-stagger) * 3)}
@media (prefers-reduced-motion:reduce){
  .rv,.rv .tile,.rv .g-card,.rv .step,.rv .film-cell,.rv .faq-row{opacity:1; transform:none; transition:none}
  *{transition-duration:0s !important; transition-delay:0s !important}
}
