/* Design tokens recovered from the live site definition (meta/colors.json).
   These are the site's own named tokens, not values sampled off screenshots. */
:root{
  --bg:#ffffff;
  --bg-2:#f8f9fa;
  --text:#212529;
  --text-2:#6c757d;
  --accent:#0064b9;
  --accent-hover:#005daf;
  --overlay:rgba(0,0,0,.30);
  --card-bg:rgba(44,44,44,.50);
  --icon:#007bff;
  /* One black across the whole site. The panel-build clip is rendered on a true
     black ground, and the film band has to sit flush with it — so rather than
     keeping a separate #131313 for surfaces and letting the film band be the odd
     one out, every dark surface uses the film's black. Footer, CTA, Trusted By,
     the mission band, the stat badge and the film band are now the same value. */
  --dark:#000000;
  --gold:#f8d71a;
  --line:#e6eaee;

  /* The live site's hero scrim is .bg-overlay{background-color:#0000004d} —
     black at 30%, NOT blue. The blue gradient token (.bg-81d990e3) is declared
     as `background-color:linear-gradient(...)`, which is invalid CSS, so it has
     never actually painted on the live site. It is deliberately not used here. */

  /* Motion. Every one of the 33 reveal instances on the live site passes
     easeInOut, overriding the library's easeOutQuad, so this is the real curve. */
  --ease:cubic-bezier(.42,0,.58,1);
  --dur:1s;      /* slideBottom */
  --dur-slow:1.5s;  /* text reveal */

  --shell:1140px;
  --gutter:28px;
}

/* Self-hosted: the live site pulls 42 @font-face rules from gstatic
   (Poppins in 9 weights). Only the weights actually used are shipped here. */
@font-face{font-family:Poppins;src:url(/assets/fonts/Poppins-500.woff2) format('woff2');font-weight:500;font-display:swap}
@font-face{font-family:Poppins;src:url(/assets/fonts/Poppins-600.woff2) format('woff2');font-weight:600;font-display:swap}
@font-face{font-family:Poppins;src:url(/assets/fonts/Poppins-300.woff2) format('woff2');font-weight:300;font-display:swap}
@font-face{font-family:Roboto;src:url(/assets/fonts/Roboto-300.woff2) format('woff2');font-weight:300;font-display:swap}
@font-face{font-family:Roboto;src:url(/assets/fonts/Roboto-400.woff2) format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:Roboto;src:url(/assets/fonts/Roboto-500.woff2) format('woff2');font-weight:500;font-display:swap}
@font-face{font-family:Roboto;src:url(/assets/fonts/Roboto-700.woff2) format('woff2');font-weight:700;font-display:swap}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--text);
  font:400 16px/1.7 Roboto,system-ui,-apple-system,sans-serif;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
img{max-width:100%;display:block}
a{color:var(--accent);text-decoration:none;transition:color .3s}
a:hover{color:var(--accent-hover)}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

h1,h2,h3,h4{font-family:Poppins,sans-serif;font-weight:600;margin:0;
  letter-spacing:-.01em;text-wrap:balance;line-height:1.18}
/* Type scale, stepped up one notch. The minimum matters as much as the maximum:
   the clamp floor is what a phone actually renders, so both ends move. */
h1{font-size:clamp(38px,5.2vw,66px)}
h2{font-size:clamp(31px,3.8vw,48px)}
h3{font-size:23px}
p{margin:0 0 18px}

.shell{max-width:var(--shell);margin:0 auto;padding:0 var(--gutter)}
.eyebrow{font:500 13px Poppins,sans-serif;letter-spacing:.18em;
  text-transform:uppercase;color:var(--accent);margin:0 0 14px}
.eyebrow-gold{color:var(--gold)}
.lede{font-size:19px;line-height:1.65;color:var(--text-2);max-width:68ch}
.band{padding:86px 0}
.band-alt{background:var(--bg-2)}
.skip{position:absolute;left:-9999px}
.skip:focus{left:16px;top:12px;z-index:200;background:var(--gold);
  color:var(--dark);padding:10px 18px;border-radius:3px}

/* ---------- header ---------- */
.hdr{background:var(--dark);position:sticky;top:0;z-index:100;
  transition:box-shadow .3s}
/* depth once the page is scrolled; reveal.js toggles the class */
.hdr.scrolled{box-shadow:0 10px 28px rgba(0,0,0,.38)}
.hdr-in{display:flex;align-items:center;gap:26px;min-height:66px}
.hdr-logo{flex:0 0 auto;order:1}
.hdr img{height:40px;width:auto}
.nav{display:flex;gap:22px;margin-left:auto;align-items:center;flex-wrap:wrap;order:2}
.nav a{color:#fff;font-size:15px;font-weight:400}
.nav a:hover,.nav a[aria-current]{color:var(--gold)}
.nav .has-sub{position:relative}
.nav .sub{position:absolute;top:100%;left:-14px;background:var(--dark);
  padding:10px 0;min-width:196px;
  visibility:hidden;opacity:0;transform:translateY(8px);
  transition:opacity .25s var(--ease),transform .25s var(--ease),visibility 0s .25s;
  box-shadow:0 12px 30px rgba(0,0,0,.34)}
.nav .has-sub:hover .sub,.nav .has-sub:focus-within .sub{
  visibility:visible;opacity:1;transform:none;transition-delay:0s}
.nav .sub a{display:block;padding:9px 18px;font-size:14px}
.hdr-tel{display:flex;align-items:center;gap:9px;color:#fff;font-weight:500;
  white-space:nowrap;order:3}
.hdr-tel:hover{color:var(--gold)}
.hdr-tel .ico{flex:0 0 auto}

/* ---------- navigation toggle ----------
   Rendered on every page, shown only under the breakpoint. The collapsed state
   is gated behind html.js-on, which reveal.js sets: if that script never runs the
   navigation is a plain visible list rather than a menu with no way to open it. */
.navtoggle{display:none;order:4;align-items:center;gap:10px;
  background:transparent;border:1px solid rgba(255,255,255,.34);border-radius:4px;
  color:#fff;font:500 14px Roboto,sans-serif;padding:9px 14px;cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.navtoggle:hover{border-color:var(--gold);color:var(--gold)}
.navtoggle .bars{position:relative;width:18px;height:2px;background:currentColor;
  display:block;transition:background-color .2s}
.navtoggle .bars::before,.navtoggle .bars::after{content:"";position:absolute;
  left:0;width:18px;height:2px;background:currentColor;
  transition:transform .25s var(--ease),top .2s var(--ease)}
.navtoggle .bars::before{top:-6px}
.navtoggle .bars::after{top:6px}
.navtoggle[aria-expanded="true"] .bars{background:transparent}
.navtoggle[aria-expanded="true"] .bars::before{top:0;transform:rotate(45deg)}
.navtoggle[aria-expanded="true"] .bars::after{top:0;transform:rotate(-45deg)}
@media(prefers-reduced-motion:reduce){
  .navtoggle .bars,.navtoggle .bars::before,.navtoggle .bars::after{transition:none}
}

/* ---------- hero ---------- */
/* Matches the original: the photograph is a section background (bg-cover
   bg-center), scrimmed with .bg-overlay = black 30%. The earlier build put a
   blue gradient here; the live site has no blue wash anywhere. */
/* Taller than the first attempt. At 470px a 4:3 photograph loses most of its
   frame to the crop, which is what made the about / services / insights heroes
   look like a slice rather than a picture. */
.hero{position:relative;min-height:clamp(520px,58vw,700px);
  display:flex;align-items:center;overflow:hidden;
  background-size:cover;background-position:center;background-color:var(--dark);
  background-image:var(--bg-lg)}
@media(max-width:1200px){.hero{background-image:var(--bg-md,var(--bg-lg))}}
@media(max-width:900px){.hero{background-image:var(--bg-sm,var(--bg-lg))}}
/* The original's flat 30% scrim is tuned for a dark photograph. Measured on the
   article heroes, which are bright, it puts the h1 at 2.86:1 and the byline at
   2.72:1 — both failing. Raising the flat value far enough to fix the byline
   (about 55%) would flatten every photograph on the site.
   So the scrim is weighted instead: heavy under the text column on the left,
   clearing to roughly the original value on the right where the picture is doing
   the work. Same photographs, legible text. */
.hero::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,
    rgba(0,0,0,.70) 0%,
    rgba(0,0,0,.60) 40%,
    rgba(0,0,0,.34) 70%,
    rgba(0,0,0,.26) 100%)}
/* below the breakpoint the copy spans the full width, so the weighting has
   nothing to weight towards and an even scrim is the correct one */
/* 800px missed every iPad. A portrait tablet has the copy running nearly the full
   width, so a left weighted scrim leaves the right hand words on bare photograph. */
@media(max-width:1024px){
  .hero::after{background:linear-gradient(180deg,rgba(0,0,0,.48),rgba(0,0,0,.58))}
}
/* padding-block, NOT the `padding` shorthand. .hero-in also carries .shell,
   whose `padding:0 var(--gutter)` is the only thing holding text off the screen
   edge once the viewport is narrower than the shell. A shorthand here silently
   zeroed it, which is why phone heroes ran flush to the edge and clipped.
   Third time a shorthand has quietly reset something in this file: see the
   `background:` note on .trust and the .band rename. */
.hero-in{position:relative;z-index:2;width:100%;padding-block:60px}
.hero h1{color:#fff;max-width:20ch}
.hero .lede{color:rgba(255,255,255,.90);margin-top:18px}

/* The homepage hero is full-viewport on the original (h-[100vh]).
   dvh first so the iOS address bar collapsing does not resize the hero mid scroll;
   vh stays as the fallback for browsers without dvh. */
.hero-full{min-height:100vh;min-height:100dvh}
/* Kept as a no-op alias: every page hero is now the same height, so about
   and insights no longer get their own taller variant. */
.hero-tall{min-height:clamp(520px,58vw,700px)}

/* A wide, short viewport is the case the vw based clamp gets wrong: on an iPad in
   landscape 58vw is taller than most of the screen, so the hero fills it and the
   page looks like it has no content. Cap against the viewport HEIGHT there. */
@media(orientation:landscape) and (max-height:900px){
  .hero{min-height:min(clamp(460px,52vw,700px),78vh)}
  .hero-full{min-height:min(100dvh,660px)}
  .hero-in{padding-block:40px}
}
@media(orientation:landscape) and (max-height:560px){
  .hero,.hero-full{min-height:max(340px,92vh)}
  .hero h1{font-size:clamp(28px,5vw,40px)}
  .hero .lede{font-size:16px;margin-top:12px}
  .hero-in{padding-block:28px}
}

/* ---------- parallax ----------
   The original pins three bands with `bg-fixed max-md:bg-local`: the homepage
   hero, the Trusted By strip and the mission band. background-attachment:fixed
   is switched off below the md breakpoint because iOS Safari cannot composite
   it, which is exactly what max-md:bg-local does. */
.px{background-attachment:fixed}
/* WIDTH IS THE WRONG TEST. Every iPad is wider than 800px, so tablets kept
   background-attachment:fixed, which iOS Safari cannot composite: it resolves the
   background box against the VIEWPORT rather than the element, so the photograph
   is scaled to the screen and cropped to a fragment. That is the "too zoomed in"
   hero. Switch on the input device instead, which is what actually predicts it. */
@media(hover:none),(pointer:coarse){.px{background-attachment:scroll}}
@media(max-width:1000px){.px{background-attachment:scroll}}
@media(prefers-reduced-motion:reduce){.px{background-attachment:scroll}}

/* ---------- project meta ---------- */
.meta{display:flex;flex-wrap:wrap;gap:34px 52px;margin-top:34px;
  padding-top:26px;border-top:1px solid rgba(255,255,255,.26)}
.meta div{min-width:120px}
.meta dt{font:500 11px Poppins,sans-serif;letter-spacing:.16em;
  text-transform:uppercase;color:var(--gold);margin:0 0 5px}
.meta dd{margin:0;color:#fff;font-size:15.5px;font-weight:300}

.tags{display:flex;gap:9px;flex-wrap:wrap;margin:0 0 4px;padding:0;list-style:none}
.tags a{display:inline-block;font-size:12.5px;font-weight:500;
  letter-spacing:.04em;padding:5px 13px;border-radius:2px;
  background:var(--bg-2);color:var(--text-2);border:1px solid var(--line)}
.tags a:hover{background:var(--accent);border-color:var(--accent);color:#fff}

/* ---------- gallery ---------- */
.gal{display:grid;grid-template-columns:repeat(auto-fit,minmax(310px,1fr));gap:24px}
.gal figure{margin:0}
.gal img{width:100%;height:270px;object-fit:cover;background:var(--bg-2)}
.gal figcaption{font-size:13.5px;color:var(--text-2);padding-top:10px}

/* ---------- cards ---------- */
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(316px,1fr));gap:26px}
.card{background:#fff;border:1px solid var(--line);overflow:hidden;
  position:relative;display:flex;flex-direction:column;
  transition:box-shadow .3s,transform .3s}
.card:hover{box-shadow:0 14px 34px rgba(0,0,0,.13);transform:translateY(-3px)}
.card>a,.card-media{display:block;overflow:hidden}
.card img{width:100%;height:196px;object-fit:cover;background:var(--bg-2);
  transition:transform .5s var(--ease)}
/* the site's own "grow" preset (scale 1 -> 1.1), used gently as a hover */
.card:hover img{transform:scale(1.05)}
.card-b{padding:22px 24px 26px;display:flex;flex-direction:column;flex:1}
.card h3{margin:0 0 9px;font-size:20px;line-height:1.3}
.card h3 a{color:var(--text)}
.card h3 a:hover{color:var(--accent)}
/* Stretched link: the heading's anchor covers the whole card, so the card is
   clickable everywhere while staying ONE link. The same pattern .pcard uses.
   Text stays selectable; only the ::after intercepts the pointer. */
.card .stretch::after{content:"";position:absolute;inset:0;z-index:1}
.card:focus-within{outline:2px solid var(--accent);outline-offset:3px}
.card .stretch:focus{outline:none}
/* the CTA looked like a link and was not one; now it reads as the card's own
   affordance and follows the heading's hover */
.card:hover .more{color:var(--accent-hover)}
.card .sub{font-size:13.5px;color:var(--text-2);margin:0 0 14px}
.card .more{margin-top:auto;font-size:14px;font-weight:500}

/* ---------- trusted by ---------- */
/* partner-bg is the band's background on 27 of the original's pages. The first
   rebuild dropped it and painted flat --dark instead. */
/* Longhand on purpose: the `background:` shorthand resets background-attachment
   to `scroll`, and since .trust is declared after .px it silently cancelled the
   parallax on this band. */
.trust{position:relative;padding:78px 0;
  background-color:var(--dark);
  background-image:url(/assets/media/partner-bg-5623a82c-900.webp);
  background-position:center;background-size:cover;background-repeat:no-repeat;
  overflow:hidden}
/* The band has to do two things at once: show the partner photograph, and keep
   ten logos legible that run from Caterpillar black to TAQA orange. Washing the
   whole band white did the second and killed the first — the two requirements
   were fighting over one surface.
   They stop fighting once each logo carries its own ground: the band goes back
   to the original's dark scrim, so the photograph reads exactly as it did, and
   every logo sits on a white plate that its brand colours were drawn for.
   The scrim is then the site's own .bg-overlay, black at 30% — the same value
   every hero photograph carries — so the band is back to the treatment it had
   before the logos went colour. It is there for the heading, not the logos. */
.trust::after{content:"";position:absolute;inset:0;background:var(--overlay)}
.trust-in{position:relative;z-index:2;text-align:center}
.trust h2{color:#fff;margin-bottom:38px}
.logos{display:flex;flex-wrap:wrap;justify-content:center;
  align-items:stretch;gap:22px}
/* the plate. Fixed height so fifteen marks of fifteen different proportions sit
   on one optical line; the width follows each mark. */
.logos>div{display:flex;align-items:center;justify-content:center;
  min-width:168px;height:88px;padding:14px 24px;background:#fff;border-radius:10px;
  box-shadow:0 10px 26px rgba(0,0,0,.30);
  transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
.logos>div:hover{transform:translateY(-3px);box-shadow:0 16px 34px rgba(0,0,0,.38)}
/* Bounded on BOTH axes rather than set to one height. The set runs from a square
   GE roundel to Etihad Rail at better than 5:1; sizing everything to one height
   makes the wide wordmarks enormous and the stacked lockups (ADNEC, Etihad WE)
   unreadable. Capping width and height instead is what optically balances them:
   a wide mark hits the width cap, a compact one hits the height cap. */
.logos img{max-height:54px;max-width:176px;width:auto;height:auto;object-fit:contain}
@media(max-width:600px){
  .logos{gap:14px}
  .logos>div{min-width:128px;height:70px;padding:12px 16px}
  .logos img{max-height:42px;max-width:132px}
}
@media(prefers-reduced-motion:reduce){.logos>div{transition:none}
  .logos>div:hover{transform:none}}

/* ---------- film band ----------
   The clip's own background is flattened to pure black at encode time
   (colorlevels, see notes in FINDINGS 16.2), so the band is pure black too and
   the two are the *same* black — the video has no edge at all. No frame, no
   border, no radius, no card. Full-bleed regardless of the shell width. */
.filmband{background:var(--dark);color:#fff;padding:104px 0 96px;
  position:relative;overflow:hidden}
@media(max-width:800px){.filmband{padding:64px 0 56px}}
.filmband .lead{max-width:760px;margin:0 auto;text-align:center;padding:0 var(--gutter)}
.filmband .eyebrow{color:var(--gold)}
.filmband h2{color:#fff;margin:14px 0 0;font-size:clamp(31px,4.2vw,52px);
  line-height:1.12;text-wrap:balance}
.filmband p{color:rgba(255,255,255,.72);margin:18px 0 0;font-size:17px;
  line-height:1.62}
/* Capped at the clip's native 1280 so it is never upscaled. It still reads as
   full-bleed: the black around it is the same black as the band, so there is no
   perceivable boundary between video and page. */
.filmband .film{display:block;width:100%;max-width:1280px;margin:8px auto 0;
  height:auto;background:var(--dark)}
/* The component labels sit close to the left and right edges of the frame, so
   the clip is never cropped — on narrow screens it simply scales down. */
.filmband .cap{max-width:760px;margin:6px auto 0;text-align:center;
  padding:0 var(--gutter);font-size:13.5px;color:rgba(255,255,255,.46)}

/* ---------- service tiles ----------
   The original shows these as a centred glyph over a title (object-cover icon +
   a 25px heading), NOT as photo cards. The source icons are only 56x56, so the
   first build — which stretched them across a 196px card image — was rendering
   them at 3.5x their real size. They are drawn at native size here. */
.stilewrap{background:var(--bg-2);border-radius:24px;padding:40px}
@media(max-width:600px){.stilewrap{padding:24px}}
.stiles{display:grid;grid-template-columns:repeat(4,1fr);gap:24px}
@media(max-width:900px){.stiles{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.stiles{grid-template-columns:1fr}}
.stile{display:flex;flex-direction:column;align-items:center;gap:18px;
  text-align:center;padding:40px 24px 52px;background:#fff;border-radius:16px;
  transition:background .3s var(--ease),transform .3s var(--ease)}
.stile:hover{background:#1c3d6b;transform:translateY(-3px)}
.stile img{width:56px;height:56px;object-fit:contain}
/* No filter here. The service icons are opaque gold tiles rather than
   transparent glyphs, so brightness(0)+invert(1) flattened each one to a
   blank white square. Gold reads well on the navy hover as it is. */
.stile .ico{color:var(--accent)}
.stile:hover .ico{color:#fff}
.stile h3{margin:0;font:500 27px Roboto,sans-serif;color:var(--text);line-height:1.15}
.stile p{margin:0;font:400 16px Roboto,sans-serif;color:var(--text-2);line-height:1.5}
.stile:hover h3,.stile:hover p{color:#fff}

/* ---------- intro figure ----------
   The original pairs this section with a 400px rounded photograph carrying a
   floating stat badge across its lower edge. Its own image is an AI graphic with
   misspelt words baked into it ("Internsty", "Interment Panls"), so a real
   photograph is used instead. */
.introfig{position:relative;margin:0}
.introfig img{width:100%;height:400px;object-fit:cover;border-radius:16px;
  display:block}
@media(max-width:700px){.introfig img{height:280px}}
/* the floating stat badge — #23272f + 1px #333, straight from the original */
.introfig .badge{position:absolute;left:10px;right:10px;bottom:-41px;
  display:flex;justify-content:space-between;align-items:center;gap:16px;
  background:#23272f;border:1px solid #333;color:#fff;border-radius:12px;
  padding:22px 30px;box-shadow:0 14px 34px rgba(0,0,0,.26)}
.introfig .badge b{display:block;font:700 36px Poppins,sans-serif;line-height:1}
@media(max-width:520px){.introfig .badge b{font-size:26px}}
.introfig .badge span{font-size:14px;color:#ccc}
.introfig .badge .r{display:flex;align-items:center;gap:6px;font-size:14px;
  color:var(--gold);text-align:right}

/* ---------- band over a photograph (the original's "OUR MISSION" block) ----
   Section keeps a fixed photo; the content sits on frosted cards. Recovered
   from bg-bc8b92db + backdrop-blur-[5px] backdrop-brightness-[0.9]
   backdrop-contrast-[1.35] border-[1px] border-08eb6cdb rounded-[16px]. */
.photoband{position:relative;background-size:cover;background-position:center;
  background-color:var(--dark);background-image:var(--bg-lg);
  padding:120px 0;color:#fff}
@media(max-width:800px){.photoband{padding:56px 0}}
.photoband h2,.photoband h3{color:#fff}
.photoband .lede{color:rgba(255,255,255,.92)}
/* The band had no scrim, so white text sat directly on a bright pump-hall
   photograph. This is what made the mission copy unreadable. */
.photoband::before{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg,rgba(0,0,0,.66),rgba(0,0,0,.58) 45%,rgba(0,0,0,.72))}
.photoband-in{position:relative;z-index:2}

.gcards{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:48px}
@media(max-width:980px){.gcards{grid-template-columns:1fr}}
.gcard{display:flex;gap:20px;align-items:flex-start;
  background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.34);
  border-radius:16px;padding:32px 24px;
  -webkit-backdrop-filter:blur(5px) brightness(.9) contrast(1.35);
  backdrop-filter:blur(5px) brightness(.9) contrast(1.35)}
@media(max-width:640px){.gcard{flex-direction:column;align-items:center;
  text-align:center;padding:24px 20px}}
.gcard h3{margin:0 0 8px;font:600 22px Roboto,sans-serif;line-height:1.12}
.gcard p{margin:0;font-size:14.5px;line-height:1.55;color:rgba(255,255,255,.94)}
.gcard .disc{flex:0 0 auto;width:64px;height:64px;border-radius:50%;
  background:var(--accent);display:flex;align-items:center;justify-content:center}
.gcard .disc svg{width:26px;height:26px;color:#fff}

/* the 2px accent hairline under centred headings */
.rule{width:100%;max-width:400px;height:2px;margin:4px auto 0;
  background:linear-gradient(90deg,transparent 0%,var(--accent) 50%,transparent 100%)}

/* ---------- icons ----------
   The original renders Heroicons v2 client-side into empty
   <span class="inline-block w-5 h-5"> shells, so they are absent from the
   served HTML and never survived the scrape. Shipped inline here instead. */
/* Solid Heroicons: filled, never stroked. Width/height come from the element's
   own attributes (the original's sizes), so no rule may scale them here. */
.ico{flex:0 0 auto;vertical-align:-.18em;fill:currentColor;stroke:none}

/* ---------- cta ---------- */
.cta{background:var(--dark);color:#fff;padding:52px 0;text-align:center}
.cta h2{color:#fff;margin-bottom:14px}
.cta p{color:rgba(255,255,255,.76);max-width:58ch;margin:0 auto 28px}
/* The original's button motion is hover:gap-[10px]: the arrow drifts away from
   the label because the flex gap itself animates, 300ms linear. Rest gap is
   8px here instead of the original's 0 so the icon never touches the label;
   the 10px of travel is identical. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  background:var(--gold);color:var(--dark);
  font:500 15px Roboto,sans-serif;padding:14px 30px;border-radius:3px;
  transition:filter .3s,gap .3s linear,background .3s,color .3s}
.btn:hover{filter:brightness(1.07);color:var(--dark);gap:18px}
.btn-ghost{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.42)}
.btn-ghost:hover{background:#fff;color:var(--dark)}
.btn+.btn{margin-left:12px}

/* ---------- footer ---------- */
.ftr{background:var(--dark);color:rgba(255,255,255,.82);padding:48px 0 0;font-size:14px}
/* brand block, then one track per footer column plus Contact. Kept as an
   explicit repeat() count so adding a column to site.json without widening
   this shows up immediately rather than silently squeezing the others. */
.ftr-top{display:grid;grid-template-columns:minmax(240px,1.4fr) repeat(4,minmax(132px,1fr));
  gap:28px}
.ftr img{height:38px;margin-bottom:16px}
.ftr h3{color:#fff;font-size:13px;margin:0 0 12px;letter-spacing:.12em;
  text-transform:uppercase;font-family:Poppins,sans-serif;font-weight:500}
.ftr ul{list-style:none;margin:0;padding:0}
.ftr li{margin-bottom:11px}
.ftr a{color:rgba(255,255,255,.82)}
.ftr a:hover{color:var(--gold)}
.ftr li{margin-bottom:7px}
.ftr a:hover{color:var(--gold)}
.ftr .tag{max-width:44ch;line-height:1.65;margin:0;color:rgba(255,255,255,.74)}
.ftr .ph{display:flex;gap:8px;margin-bottom:9px;flex-wrap:wrap}
.ftr .ph b{color:rgba(255,255,255,.62);font-weight:400;min-width:62px;font-size:12.5px}
.ftr-btm{margin-top:36px;padding:18px 0;border-top:1px solid rgba(255,255,255,.16);
  font-size:13px;color:rgba(255,255,255,.66)}

/* ---------- header under the breakpoint ----------
   Every iPad is wider than 800px, so the previous 960px rule caught them with a
   layout meant for a stacked phone menu: the Projects dropdown rendered
   permanently open, inline, inside a still-horizontal bar. It overlapped the
   other links and overflowed the viewport. This replaces it with a real drawer. */
@media (max-width:1000px){
  .ftr-top{grid-template-columns:1fr 1fr;gap:36px}
  .hdr-in{flex-wrap:wrap;gap:14px;padding-block:10px}
  .hdr-tel{margin-left:auto;font-size:15px}
  .navtoggle{display:inline-flex}
  .nav{order:5;margin-left:0;width:100%;gap:0;flex-direction:column;
    align-items:stretch;border-top:1px solid rgba(255,255,255,.16);
    padding:6px 0 12px;max-height:min(70vh,520px);overflow-y:auto;
    -webkit-overflow-scrolling:touch}
  .nav > a,.nav .has-sub > a{display:block;padding:13px 2px;font-size:16px;
    border-bottom:1px solid rgba(255,255,255,.09)}
  .nav .has-sub{position:static}
  .nav .sub{position:static;box-shadow:none;background:transparent;
    padding:2px 0 8px 16px;min-width:0;display:block;
    visibility:visible;opacity:1;transform:none;transition:none}
  .nav .sub a{padding:10px 2px;font-size:15px;color:rgba(255,255,255,.78)}
  /* collapsed only once the script is confirmed running */
  html.js-on .nav{display:none}
  html.js-on .nav.open{display:flex}
  .band{padding:60px 0}
}
@media (max-width:520px){
  .hdr-tel span{display:none}
  .hdr-tel{gap:0;padding:8px;border:1px solid rgba(255,255,255,.3);border-radius:4px}
  .navtoggle-label{display:none}
  .navtoggle{padding:10px 12px}
  .hdr img{height:34px}
}
@media (max-width:620px){
  .ftr-top{grid-template-columns:1fr}
  .gal img{height:210px}
}

/* ---------- layout helpers ---------- */
.shell.narrow{max-width:820px}
.split{display:grid;grid-template-columns:1fr 1fr;gap:56px;align-items:start}
/* The right-hand column of a .split (tick list, contact block) starts with
   body text while the left starts with a 13px eyebrow. Nudging it down puts
   the first line of each column on the same optical line. */
@media(min-width:861px){
  .split > .ticks,.split > .contact-block{margin-top:44px}
}
/* the enquiry form needs the wider track: two input columns inside an even half
   of the shell leaves each field around 250px, which is narrower than the text
   people type into them */
.split.form-split{grid-template-columns:1.3fr 1fr;gap:64px}
@media(max-width:960px){.split.form-split{grid-template-columns:1fr;gap:44px}}
.cards.four{grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
.feats{display:grid;grid-template-columns:1fr 1fr;gap:28px}
.feats.three{grid-template-columns:repeat(auto-fit,minmax(260px,1fr));margin-top:8px}
/* icon + title on one row, paragraph beneath — the original's layout
   (flex-row, gap-12px, 20px icon in the accent colour) */
.feat h3{font-size:19px;margin:0 0 8px;display:flex;align-items:center;gap:12px}
.feat h3 .ico{color:var(--accent)}
.feat p{font-size:14.5px;color:var(--text-2);margin:0}
.more{font-weight:500;font-size:14px;color:var(--accent)}

/* ---------- stat rows ---------- */
.statrow{display:flex;flex-wrap:wrap;gap:40px 60px}
.statrow>div{min-width:150px;flex:1}
.statrow b{display:block;font:300 clamp(42px,5vw,76px)/.92 Poppins,sans-serif;color:var(--dark)}
/* The unit that follows the number — "+", "kV", "%" — is its own <i> node so the
   count-up can animate the digits and leave it alone, and so it can be coloured
   away from the number. Bigger, heavier and in the accent, it reads as a label
   rather than as part of the figure. */
.statrow b i{font-size:.44em;font-style:normal;color:var(--accent);
  margin-left:.08em;font-weight:600;letter-spacing:-.01em}
.statrow span{display:block;font-size:17px;font-weight:300;color:var(--text);margin-top:10px}
.statrow em{display:block;font-style:normal;font-size:13.5px;color:var(--text-2);margin-top:5px;line-height:1.55}
.statrow.light{margin-top:34px}
.statrow.light b{color:#fff}
/* On the dark bands the accent blue goes muddy, so the unit takes the site's own
   dark-ground highlight — the same gold as the eyebrows over photographs.
   (A second rule here used to overwrite this with white at 66%, which is why the
   units read as a dimmer shade of the number instead of a colour of their own.) */
.statrow.light b i{color:var(--gold)}
.statrow.light span{color:#fff}
.statrow.light em{color:rgba(255,255,255,.72)}
/* A stat row carried on a photograph rather than a flat tint. Same scrim
   machinery as the mission band (.photoband), so there is one dark-over-photo
   treatment on the site instead of two that drift apart. */
.statband{padding:96px 0}
@media(max-width:800px){.statband{padding:64px 0}}
/* Grid rather than the flex row here. One of the five cells carries a whole
   sentence as its label, and under flex that one cell stretches and drags the
   others out of line; a grid keeps all five on the same column rhythm however
   long the labels run. */
.statband .statrow{display:grid;grid-template-columns:repeat(5,1fr);gap:44px 30px}
.statband .statrow.light{margin-top:0}
.statband .statrow>div{min-width:0}
@media(max-width:1080px){.statband .statrow{grid-template-columns:repeat(3,1fr)}}
@media(max-width:720px){.statband .statrow{grid-template-columns:repeat(2,1fr)}}
@media(max-width:420px){.statband .statrow{grid-template-columns:1fr}}

.bigstat{display:flex;align-items:baseline;gap:16px;margin-top:34px;
  padding-top:26px;border-top:1px solid var(--line)}
.bigstat b{font:300 62px/1 Poppins,sans-serif;color:var(--accent)}
.bigstat span{font-size:16px;color:var(--text)}
.bigstat em{display:block;font-style:normal;font-size:13.5px;color:var(--text-2)}

/* ---------- about ---------- */
.pull{margin:0 0 30px;padding:0 0 0 22px;border-left:3px solid var(--gold);
  font:300 23px/1.55 Poppins,sans-serif;color:var(--text)}
.team{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:30px}
.person{margin:0}
.person img{width:100%;height:290px;object-fit:cover;object-position:center top;background:var(--bg-2)}
.person figcaption{padding-top:14px}
.person b{display:block;font-family:Poppins,sans-serif;font-size:19px}
.person span{font-size:14px;color:var(--text-2)}

/* ---------- certificates ---------- */
.certs{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:30px}
.cert{border:1px solid var(--line);background:#fff;display:flex;flex-direction:column}
.cert img{width:100%;height:250px;object-fit:contain;background:var(--bg-2);padding:16px}
.cert>div{padding:20px 24px 26px}
.cert h3{margin:0 0 8px;font-size:19px}
.cert p{margin:0;font-size:14px;color:var(--text-2)}

/* ---------- lists ---------- */
.ticks{list-style:none;margin:0;padding:0}
.ticks li{position:relative;padding:0 0 15px 32px;font-size:16.5px;
  line-height:1.55;color:var(--text)}
.ticks li::before{content:"";position:absolute;left:4px;top:9px;width:9px;height:9px;
  border-right:2px solid var(--gold);border-bottom:2px solid var(--gold);
  transform:rotate(45deg)}
.contact-block .ph{display:flex;gap:14px;margin-bottom:14px;align-items:baseline}
.contact-block .ph b{min-width:100px;font-weight:400;color:var(--text-2);font-size:14px;
  display:inline-flex;align-items:center;gap:8px}
.contact-block .ph b .ico{width:16px;height:16px;color:var(--accent);vertical-align:0}
.contact-block address{font-style:normal;line-height:1.65}

/* ---------- long-form article body ---------- */
/* Reading measure. Measured at 92 to 94 characters per line on every viewport
   above a phone, against a comfortable 65 to 75. Constrain the TEXT elements
   rather than the container, so images, tables and figures keep the full column
   width and only the prose is narrowed. */
.prose{font-size:17px;line-height:1.78}
.prose > p,.prose > ul,.prose > ol,.prose > blockquote,
.prose > h2,.prose > h3,.prose > h4{max-width:60ch}
.faq-item p{max-width:64ch}
.prose h2{font-size:29px;margin:46px 0 14px}
.prose h3{font-size:21px;margin:32px 0 10px}
.prose p{margin:0 0 20px}
.prose ul,.prose ol{margin:0 0 22px;padding-left:22px}
.prose li{margin-bottom:9px}
.prose img{margin:28px 0;width:100%;height:auto}
.prose table{width:100%;border-collapse:collapse;margin:26px 0;font-size:15px;display:block;overflow-x:auto}
.prose th,.prose td{border:1px solid var(--line);padding:11px 14px;text-align:left;vertical-align:top}
.prose th{background:var(--bg-2);font-family:Poppins,sans-serif;font-weight:600;font-size:14px}
.prose hr{border:0;border-top:1px solid var(--line);margin:40px 0}
.prose strong{font-weight:700}
.prose em{color:var(--text-2)}

@media (max-width:860px){
  .split,.feats{grid-template-columns:1fr;gap:34px}
  .statrow{gap:30px 40px}
}

/* ---------- card meta (matches the original card: category + date) ---------- */
.cardmeta{display:flex;gap:22px;flex-wrap:wrap;margin:0 0 16px}
.cardmeta div{min-width:0}
.cardmeta dt{font:500 10.5px Poppins,sans-serif;letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-2);margin:0 0 3px}
.cardmeta dd{margin:0;font-size:13px;color:var(--text)}

/* ---------- homepage: call-now + goal block ---------- */
.callnow{display:inline-flex;flex-direction:column;margin-left:20px;vertical-align:middle}
.callnow span{font-size:12.5px;color:var(--text-2)}
.callnow b{font-family:Poppins,sans-serif;font-size:16px;color:var(--text)}
.callnow:hover b{color:var(--accent)}
.btn-dark{background:var(--dark);color:#fff}
.btn-dark:hover{background:var(--accent);color:#fff}
.goal{margin-top:26px;max-width:34ch}
.goal .goal-h{font:300 clamp(29px,3.8vw,46px)/1.1 Poppins,sans-serif;margin:0;
  letter-spacing:-.01em}
.kicker{font:500 11px Poppins,sans-serif;letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent);margin:0 0 8px}

/* ---------- project scope of work ---------- */
.scope{margin:38px 0 6px;max-width:78ch}
.scope h2{font-size:27px;margin:0 0 18px}
.scope ul{list-style:none;margin:0;padding:0}
.scope li{position:relative;padding:0 0 12px 26px;font-size:16px;line-height:1.7;
  color:var(--text)}
.scope li::before{content:"";position:absolute;left:2px;top:11px;width:7px;height:7px;
  background:var(--gold)}
.hero-sub{color:var(--gold);font:500 17px Poppins,sans-serif;margin:12px 0 0}

/* ---------- project photo-card ----------
   Recovered from the original listing markup: the card IS the photograph
   (rounded-[24px], h-[320px], content pinned to the bottom, p-[24px], `group`).
   Three moves on hover, each transition-all duration-[300ms] ease-linear,
   exactly as on the live site:
     1. the caption strip fills with --accent-hover  (group-hover:bg-accent-hover)
     2. the 48px circle turns gold                   (group-hover:bg-f9ff78ed +
                                                      group-hover:border-[#f8d71aff])
     3. a hidden Date/Category row expands 0 -> 63px (group-hover:h-[63px])
   Departures, on purpose: the photo is a real <img> (alt text, indexable); a
   soft bottom scrim keeps white text legible on light photographs (the original
   set text straight on the photo and lost it on bright frames); :focus-within
   mirrors every hover state so keyboard users get the same card. */
.pcard{position:relative;isolation:isolate;display:flex;flex-direction:column;
  justify-content:flex-end;min-height:320px;padding:24px;border-radius:24px;
  overflow:hidden;background:var(--dark)}
.pcard>img{position:absolute;inset:0;z-index:-2;width:100%;height:100%;
  object-fit:cover;transition:transform .55s var(--ease)}
.pcard::after{content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(0,0,0,0) 34%,rgba(0,0,0,.62) 100%)}
.pcard:hover>img,.pcard:focus-within>img{transform:scale(1.05)}
.pc-body{padding:12px;border-radius:8px;transition:background-color .3s linear}
.pcard:hover .pc-body,.pcard:focus-within .pc-body{background-color:var(--accent-hover)}
.pc-top{display:flex;align-items:center;justify-content:space-between;gap:12px}
.pc-info{min-width:0}
.pc-info h3{margin:0;font:600 18px/1.4 Roboto,sans-serif;letter-spacing:0}
.pc-info h3 a{color:#fff}
/* the whole card is the link */
.pc-info h3 a::after{content:"";position:absolute;inset:0}
.pc-info h3 a:focus{outline:none}
.pcard:focus-within{outline:2px solid var(--accent);outline-offset:3px}
.pc-info p{margin:0;font:400 14px/1.5 Roboto,sans-serif;color:rgba(255,255,255,.82)}
.pc-disc{flex:0 0 auto;width:48px;height:48px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;color:#fff;
  border:1px solid var(--bg-2);
  transition:background-color .3s linear,border-color .3s linear,color .3s linear}
.pcard:hover .pc-disc,.pcard:focus-within .pc-disc{background-color:var(--gold);
  border-color:var(--gold);color:var(--dark)}
.pc-btm{height:0;overflow:hidden;transition:height .3s linear}
.pcard:hover .pc-btm,.pcard:focus-within .pc-btm{height:63px}
.pc-btm-in{display:flex;gap:12px;margin-top:12px;padding-top:12px;
  border-top:1px solid rgba(255,255,255,.36)}
.pc-cell{flex:1;min-width:0}
.pc-cell+.pc-cell{border-left:1px solid rgba(255,255,255,.36);padding-left:12px}
.pc-cell em{display:block;font:500 10.5px Poppins,sans-serif;font-style:normal;
  letter-spacing:.14em;text-transform:uppercase;color:rgba(255,255,255,.72)}
.pc-cell b{display:block;font:400 14px/1.4 Roboto,sans-serif;color:#fff;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
/* no hover on touch: the meta row is simply open */
@media (hover:none){
  .pc-btm{height:63px}
}
@media (prefers-reduced-motion:reduce){
  .pcard>img,.pc-body,.pc-disc,.pc-btm,.card img,.btn,.nav .sub{transition:none}
  .pcard:hover>img,.pcard:focus-within>img,.card:hover img{transform:none}
}

/* ---------- intro: the original's "Automation you can trust" section --------
   Recovered layout, not an invention: a dark band (bg-74b8682e) split in two.
   The media column is `sticky top-[25%]`, so the photograph and its floating
   stat badge PIN in place while the heading, paragraph and six capability
   cards scroll past them. Cards and badge are #23272f with a 1px #333
   hairline, straight from the original's markup. Below 860px the columns
   stack and sticky is released — pinned media over stacked text just covers
   the copy on a phone. */
.intro-dark{background:var(--dark);color:#fff}
/* the intro photograph carries the column: taller than the original's 400px,
   a slight lift off the black, and the wider of the two grid tracks */
.intro-dark .introfig img{height:520px;box-shadow:0 30px 60px rgba(0,0,0,.5)}
@media(max-width:700px){.intro-dark .introfig img{height:340px}}
.introgrid{display:grid;grid-template-columns:1.1fr 1fr;gap:60px;align-items:start}
.intro-media{position:sticky;top:max(96px,18vh);padding-bottom:41px}
.intro-h{color:#fff;font:300 clamp(34px,4.8vw,64px)/1.1 Poppins,sans-serif;
  letter-spacing:-.01em;margin:6px 0 0}
.intro-dark .lede{color:#ccc}
.capgrid{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-top:26px}
.capcard{background:#23272f;border:1px solid #333;border-radius:12px;padding:24px 20px}
.capcard h3{display:flex;align-items:center;gap:12px;margin:0 0 12px;
  font:600 19px/1.25 Roboto,sans-serif;color:#fff;letter-spacing:0}
.capcard h3 .ico{color:var(--gold)}
.capcard p{margin:0;font-size:14.5px;line-height:1.6;color:#ccc}
.intro-dark .callnow span{color:rgba(255,255,255,.6)}
.intro-dark .callnow b{color:#fff}
.intro-dark .callnow:hover b{color:var(--gold)}
@media (max-width:860px){
  .introgrid{grid-template-columns:1fr;gap:72px}
  .intro-media{position:relative;top:0}
  .capgrid{grid-template-columns:1fr}
}

/* Suffixes are separate <i> nodes so the count-up can animate the number and
   leave the "+" / "%" alone (see reveal.js countUp). */
.badge b i,.bigstat b i{font-size:.5em;font-style:normal;font-weight:600;
  margin-left:.06em}
.bigstat b i{color:var(--gold)}
.badge b i{color:var(--gold)}

/* The whole-team photograph, above the three portraits on About. Wide and
   uncropped-feeling: the group sits in the upper two thirds of the frame, so the
   crop is biased upward rather than centred. */
.teamshot{margin:0 0 46px}
.teamshot img{width:100%;height:clamp(280px,38vw,460px);object-fit:cover;
  object-position:50% 34%;border-radius:16px;display:block}
.teamshot figcaption{font-size:13.5px;color:var(--text-2);padding-top:12px}

/* ---------- contact form ----------
   A real form on a static site. It posts to whatever endpoint site.json names;
   with no endpoint configured it still works, because reveal.js composes the
   same fields into a mailto: on submit. Nothing here depends on a backend. */
.cform{display:grid;grid-template-columns:1fr 1fr;gap:18px 20px;margin-top:6px}
.cform .full{grid-column:1/-1}
.cform label{display:block;font:500 13px Poppins,sans-serif;letter-spacing:.04em;
  color:var(--text-2);margin:0 0 7px}
.cform label .req{color:var(--accent)}
.cform input,.cform select,.cform textarea{
  width:100%;font:400 16px/1.5 Roboto,sans-serif;color:var(--text);
  background:#fff;border:1px solid var(--line);border-radius:6px;
  padding:12px 14px;transition:border-color .25s,box-shadow .25s}
.cform textarea{min-height:150px;resize:vertical}
.cform input:focus,.cform select:focus,.cform textarea:focus{
  outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(0,100,185,.16)}
.cform button{justify-self:start}
.cform .note{grid-column:1/-1;margin:0;font-size:13.5px;color:var(--text-2)}
/* honeypot: a field a person never sees and a bot fills in */
.cform .hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
/* Web3Forms' own server side honeypot, alongside ours. Ours catches bots in the
   browser; this one catches anything posting straight at the endpoint. */
.cform .hp-check{position:absolute;left:-9999px;width:1px;height:1px;opacity:0;
  pointer-events:none}
.cform .status{grid-column:1/-1;margin:0;font-size:15px;padding:12px 16px;
  border-radius:6px;display:none}
.cform .status.on{display:block}
.cform .status.ok{background:#e8f5ec;color:#175c31;border:1px solid #b6dfc4}
.cform .status.err{background:#fdeceb;color:#8a1c14;border:1px solid #f3c2be}
@media(max-width:700px){.cform{grid-template-columns:1fr}}

/* ---------- article FAQ ----------
   Question and answer as plain content, not an accordion. A disclosure widget
   hides the answer behind a click, and an answer nobody has opened is an answer
   a reader has to work for. The whole point of this block is that it can be read
   and lifted straight off the page. */
.faq{margin-top:34px;border-top:1px solid var(--line)}
.faq-item{padding:26px 0;border-bottom:1px solid var(--line)}
.faq-item h3{margin:0 0 10px;font-size:19px;line-height:1.35;color:var(--text)}
.faq-item p{margin:0;font-size:16.5px;line-height:1.7;color:var(--text-2);max-width:70ch}
.hero-date{color:rgba(255,255,255,.72);font-size:14px;margin:10px 0 0}
.hero-date time{font-variant-numeric:tabular-nums}

/* ---------- service page blocks ----------
   Used by the natively rendered service pages. The one legacy Nodetree service
   page ships its own scoped stylesheet instead; these rules deliberately do not
   reach it, so converting that page is a clean swap rather than a fight between
   two stylesheets. */
.svcgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:26px;margin-top:36px}
.svcitem{background:#fff;border:1px solid var(--line);border-top:3px solid var(--accent);
  padding:28px 26px}
.svcitem h3{margin:0 0 10px;font:600 19px/1.3 Poppins,sans-serif}
.svcitem p{margin:0;font-size:15px;line-height:1.65;color:var(--text-2)}
/* The scope list runs long with short entries, so one column wastes the width
   and makes the reader scroll past nothing. */
.svccols{columns:2;column-gap:48px;margin-top:30px}
.svccols li{break-inside:avoid}
.chips{display:flex;flex-wrap:wrap;gap:10px;margin-top:28px;padding:0;list-style:none}
.chips li{border:1px solid var(--line);background:#fff;border-radius:999px;
  padding:9px 17px;font-size:14px;color:var(--text)}
/* Numbered because these genuinely are sequential: a site test before a factory
   test is not a method, it is a different project. */
.svcsteps{counter-reset:s;list-style:none;margin:36px 0 0;padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);gap:34px 36px}
.svcsteps li{counter-increment:s;position:relative;padding-top:46px}
.svcsteps li::before{content:counter(s,decimal-leading-zero);position:absolute;top:0;left:0;
  font:600 26px Poppins,sans-serif;color:var(--accent);opacity:.38;
  font-variant-numeric:tabular-nums}
.svcsteps h3{margin:0 0 8px;font:600 18px/1.3 Poppins,sans-serif}
.svcsteps p{margin:0;font-size:15px;line-height:1.65;color:var(--text-2)}
@media(max-width:900px){
  .svcgrid,.svcsteps{grid-template-columns:1fr}
  .svccols{columns:1}
}

/* ---------- disciplines index ----------
   Replaces a three-up card grid of the same seven items. Seven cards leave an
   orphan on the last row beside two dead cells, and each card's top half was a
   150px plate holding a 60px mark, so most of the grid's height was empty.

   The marks are two layers of one glyph off the Material Symbols fill axis
   (see serviceIcon in src/icons.js): the body is held back by opacity alone, so
   colour is inherited and a single change on the row carries the whole mark. */
.svcico-body{opacity:.28;transition:opacity .3s var(--ease)}
@media(prefers-reduced-motion:reduce){.svcico-body{transition:none}}

.disc-band{background:var(--dark);color:#fff}
.discgrid{display:grid;grid-template-columns:.85fr 1.15fr;gap:64px;align-items:start}
/* The heading is much shorter than seven rows of copy, so it pins rather than
   leaving a column of dead black beside the list. */
.discintro{position:sticky;top:104px}
.discintro .eyebrow{color:var(--gold)}
.discintro .lede{color:rgba(255,255,255,.72);margin-top:16px}
.discintro-cta{margin:22px 0 0}
.discintro .more{color:#fff}
.discintro .more:hover{color:var(--gold)}
/* --accent on black measures 3.5:1. Legal for a focus ring, but weak, and the
   band already carries gold at 14.7:1. */
.disc-band :focus-visible{outline-color:var(--gold)}

/* ---- the media stage ----
   Seven photographs stacked in one frame; hovering a row cross-fades to that
   row's picture. :has() keeps the whole thing in CSS. Where it is unsupported
   the stage simply stays on the first image, which is what it shows at rest
   anyway, so there is nothing to fall back to. */
.discmedia{position:relative;margin:34px 0 0;aspect-ratio:4/3;
  border-radius:14px;overflow:hidden;background:#0d0d0d}
.discmedia img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .5s var(--ease)}
.discmedia img:first-child{opacity:1}
.discgrid:has(.disclist li:is(:hover,:focus-within)) .discmedia img{opacity:0}
.discgrid:has(.disclist li:nth-child(1):is(:hover,:focus-within)) .discmedia img:nth-child(1),
.discgrid:has(.disclist li:nth-child(2):is(:hover,:focus-within)) .discmedia img:nth-child(2),
.discgrid:has(.disclist li:nth-child(3):is(:hover,:focus-within)) .discmedia img:nth-child(3),
.discgrid:has(.disclist li:nth-child(4):is(:hover,:focus-within)) .discmedia img:nth-child(4),
.discgrid:has(.disclist li:nth-child(5):is(:hover,:focus-within)) .discmedia img:nth-child(5),
.discgrid:has(.disclist li:nth-child(6):is(:hover,:focus-within)) .discmedia img:nth-child(6),
.discgrid:has(.disclist li:nth-child(7):is(:hover,:focus-within)) .discmedia img:nth-child(7)
  {opacity:1}
@media(prefers-reduced-motion:reduce){.discmedia img{transition:none}}

.disclist{list-style:none;margin:0;padding:0;border-top:1px solid rgba(255,255,255,.16)}
.disclist li{border-bottom:1px solid rgba(255,255,255,.16)}
.discrow{display:grid;grid-template-columns:58px 1fr;gap:22px;
  padding:26px 18px 26px 6px;color:inherit;
  transition:background-color .3s var(--ease),padding-left .3s var(--ease)}
.discrow:hover{background:rgba(255,255,255,.05);padding-left:16px}
/* Gold at rest, not on hover. The mark is the one piece of colour in the row,
   which gives the list a rhythm down its left edge and leaves the type to carry
   the reading order on its own. */
.discico{display:flex;align-items:flex-start;justify-content:flex-start;
  color:var(--gold)}
.discico .svcico{width:52px;height:52px;display:block}
/* Gold on black is a far brighter pairing than white on black, so the body
   layer needs holding back further here or it reads as an olive halo. */
.discico .svcico-body{opacity:.20}
.disch{margin:0;font:600 22px/1.3 Poppins,sans-serif;color:#fff}
.discp{margin:8px 0 0;font-size:15.5px;line-height:1.65;
  color:rgba(255,255,255,.70);max-width:60ch}
/* Muted at rest. Seven rows each ending in a gold link was seven competing
   calls to action and no hierarchy; gold is the hover state instead. */
.discmore{display:inline-block;margin:12px 0 0;font-weight:500;font-size:14px;
  color:rgba(255,255,255,.55);transition:color .3s var(--ease)}
.discmore i{font-style:normal;display:inline-block;transition:transform .3s var(--ease)}
.discrow:hover .discmore{color:var(--gold)}
.discrow:hover .discmore i{transform:translateX(5px)}
@media(prefers-reduced-motion:reduce){
  .discrow,.discmore,.discmore i{transition:none}
}
@media(max-width:980px){
  .discgrid{grid-template-columns:1fr;gap:34px}
  .discintro{position:static}
  /* No hover to swap them, so the other six are weight with nothing to show.
     display:none also keeps a lazy image from ever being fetched. */
  .discmedia{aspect-ratio:16/9;margin-top:28px}
  .discmedia img:not(:first-child){display:none}
}
@media(max-width:560px){
  .discrow{grid-template-columns:40px 1fr;gap:16px;padding:22px 0}
  .discrow:hover{padding-left:0}
  .discico .svcico{width:38px;height:38px}
  .disch{font-size:19px}
}

/* ---------- where we are ----------
   Photograph in its own column beside the addresses: the source frame is
   portrait, and a portrait photo forced into a wide band loses the panel. */
.wheregrid{display:grid;grid-template-columns:.8fr 1fr;gap:40px;
  align-items:start;margin-top:34px}
@media(max-width:860px){.wheregrid{grid-template-columns:1fr;gap:30px}}
.wsfig{margin:0}
.wsfig img{width:100%;height:auto;border-radius:14px;display:block}
.wsfig figcaption{font-size:13.5px;color:var(--text-2);padding-top:12px}
.locs{display:grid;gap:26px}
.loc{background:#fff;border:1px solid var(--line);border-radius:14px;
  padding:28px 26px;display:flex;flex-direction:column}
.loc .kicker{margin-bottom:6px}
.loc h3{margin:0 0 12px;font-size:21px}
.loc address{font-style:normal;line-height:1.7;color:var(--text-2);margin:0 0 14px}
.loc .what{margin:0 0 18px;font-size:15px;line-height:1.6;color:var(--text)}
.loc .maplink{margin-top:auto;font-weight:500;font-size:14.5px;
  display:inline-flex;align-items:center;gap:8px}


/* ================= TABLET AND PHONE LAYER =================================
   Everything above was written against a desktop shell with a phone fallback,
   which left the 700 to 1100px band (every iPad, and every phone held sideways)
   falling through to desktop rules it does not have the width for. These are the
   in between cases, ordered widest first. */

/* --- tablet landscape, roughly 1000 to 1180 --- */
@media(max-width:1180px){
  /* five stat columns at 200px each is narrower than the numbers inside them */
  .statband .statrow{grid-template-columns:repeat(3,1fr)}
  /* three frosted cards across a tablet leaves ~300px each with 64px discs */
  .gcards{grid-template-columns:repeat(2,1fr)}
}

/* --- tablet, portrait and landscape --- */
@media(max-width:1024px){
  :root{--gutter:24px}
  .band{padding:68px 0}
  .cards{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:22px}
  .certs{grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:24px}
  .gal{grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}
  .team{grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:24px}
  .stiles{grid-template-columns:repeat(2,1fr)}
  .filmband{padding:74px 0 66px}
  .intro-dark .introfig img{height:440px}
  /* the sticky media column has nothing useful to pin against once the copy is
     the taller of the two, and pinned media over scrolling text reads as broken */
  .intro-media{position:relative;top:0}
}

/* --- portrait tablet and large phone --- */
@media(max-width:860px){
  .gcards{grid-template-columns:1fr}
  .statband .statrow{grid-template-columns:repeat(2,1fr)}
  .meta{gap:22px 34px}
  .prose{font-size:16.5px}
  .filmband .lead,.filmband .cap{padding:0 var(--gutter)}
}

/* --- phone --- */
@media(max-width:640px){
  :root{--gutter:20px}
  .band{padding:52px 0}
  .cards,.certs,.gal,.team{grid-template-columns:1fr}
  .stiles{grid-template-columns:1fr}
  .stilewrap{padding:18px;border-radius:16px}
  .gal img{height:220px}
  .card img{height:210px}
  .hero-in{padding-block:44px}
  .lede{font-size:17px}
  .statband .statrow{grid-template-columns:1fr;gap:30px}
  .statrow b{font-size:clamp(38px,13vw,58px)}
  .pull{font-size:20px;padding-left:16px}
  .meta{gap:18px 26px}
  .meta div{min-width:0;flex:1 1 45%}
  .filmband{padding:44px 0 38px}
  .cta{padding:40px 0}
  .btn{width:100%;max-width:340px}
  .btn+.btn{margin-left:0;margin-top:12px}
  .faq-item h3{font-size:17.5px}
  .loc{border-radius:12px}
  .prose table{font-size:14px}
}

/* --- small phone --- */
@media(max-width:400px){
  .hdr img{height:30px}
  h1{font-size:clamp(30px,8.6vw,38px)}
  h2{font-size:clamp(25px,7vw,31px)}
  .logos>div{min-width:112px;padding:12px 14px}
  .logos img{max-height:30px;max-width:104px}
  .introfig .badge{padding:16px 18px;left:6px;right:6px}
}

/* Touch targets. A 15px link in a list is a 20px tall tap target, which is under
   half the 44px Apple and WCAG both ask for. */
@media(hover:none),(pointer:coarse){
  .nav a,.ftr a,.tags a,.more,.maplink{min-height:44px;
    display:inline-flex;align-items:center}
  .ftr li{margin-bottom:2px}
  .tags a{padding:11px 15px}
  /* hover-only affordances never fire on touch, so open the card meta row */
  .pc-btm{height:63px}
}

/* Nothing may push the page sideways. overflow-x:hidden on body hides the
   symptom; these stop the causes. */
img,video,svg,table,pre{max-width:100%}
.prose>*{max-width:100%}

/* ---------- footer social links ---------- */
.ftr-social{margin-top:22px}
.ftr-social h3{margin-bottom:12px}
.soc{display:flex;gap:12px}
.soc a{display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:3px;
  border:1px solid rgba(255,255,255,.22);color:rgba(255,255,255,.78);
  transition:background .3s,color .3s,border-color .3s}
.soc a:hover{background:var(--gold);border-color:var(--gold);color:var(--dark)}
.soc svg{display:block}

/* ============================================================================
   Motion, reproduced from the live site's GSAP + ScrollTrigger config.

   Source of truth: meta/animations.json (the 15-preset library) and
   meta/anim-instances.json (33 per-element instances). Measured, not guessed:

     slideBottom   translateY 50px + opacity 0 -> 0 / 1     duration 1s
     text reveal   background-size 0%,100% -> 100%,100%     duration 1.5s
     slideLeft     translateX -150px + opacity 0 -> 0 / 1   duration 1s
     slideRight    translateX  150px + opacity 0 -> 0 / 1   duration 1s
     fade          opacity 0 -> 1                           duration 1s

   Every instance passes easeInOut and reversable:false, so these are one-shot,
   time-based transitions rather than scroll-scrubbed animations. That is what
   makes the site feel smooth; a scroll-linked version inherits wheel jitter.

   FAIL-SAFE: everything is visible by default. The hidden state only applies
   once reveal.js has added .rv-on to <html>. A blank page cannot happen if the
   script fails to load, which is exactly how the Nodetree build broke.
   ========================================================================== */

.rv{opacity:1;transform:none}

html.rv-on .rv{
  opacity:0;
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  will-change:opacity,transform;
}
html.rv-on .rv-up   {transform:translateY(50px)}
html.rv-on .rv-left {transform:translateX(-150px)}
html.rv-on .rv-right{transform:translateX(150px)}
html.rv-on .rv-fade {transform:none}

html.rv-on .rv.in{opacity:1;transform:none}

/* Stagger. The real delays measured on the site: .2 .5 .6 .8 1 */
html.rv-on .rv-d1{transition-delay:.2s}
html.rv-on .rv-d2{transition-delay:.5s}
html.rv-on .rv-d3{transition-delay:.6s}
html.rv-on .rv-d4{transition-delay:.8s}
html.rv-on .rv-d5{transition-delay:1s}

/* ---- "text reveal" -------------------------------------------------------
   Two stacked background layers clipped to the glyphs. Layer 1 is the paint
   that wipes in; layer 2 is the dim base that is always present. */
.tr{
  display:inline-block;
  background-image:linear-gradient(var(--text),var(--text)),
                   linear-gradient(#d3d8dd,#d3d8dd);
  background-repeat:no-repeat;
  background-size:100% 100%,100% 100%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;
}
html.rv-on .tr{
  background-size:0% 100%,100% 100%;
  transition:background-size var(--dur-slow) var(--ease);
}
html.rv-on .tr.in{background-size:100% 100%,100% 100%}

/* over a photo: white paint over a translucent white base */
.tr-light{
  background-image:linear-gradient(#fff,#fff),
                   linear-gradient(rgba(255,255,255,.28),rgba(255,255,255,.28));
}

@media (prefers-reduced-motion:reduce){
  html.rv-on .rv{opacity:1!important;transform:none!important;transition:none!important}
  html.rv-on .tr{background-size:100% 100%,100% 100%!important;transition:none!important}
}
