:root{
  --navy:#0793a1; --navy-2:#0793a1; --navy-3:#0793a1;
  --orange:#f48847; --orange-2:#e0733a; --teal:#0793a1;
  --ink:#1f2933; --muted:#5b6770; --line:#e3e8ec; --bg:#ffffff; --bg-2:#f5f7f9;
  --head:"Oswald","Barlow Condensed","Arial Narrow",sans-serif;
  --script:"Pinyon Script","Snell Roundhand","Segoe Script","Brush Script MT",cursive;
  --body:"Inter","Helvetica Neue",Arial,sans-serif;
  --max:1180px; --r:6px; --shadow:0 10px 30px rgba(7,147,161,.12);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
/* 1rem, not 16px: the accessibility panel scales text by changing the root font
   size, and a px value on body would pin the whole page against it. */
body{margin:0;font-family:var(--body);color:var(--ink);background:var(--bg);line-height:1.6;font-size:1rem}
img{max-width:100%;display:block}
a{color:var(--teal);text-decoration:none}
/* Inline links in running text are underlined. --teal against .lead's --muted is
   1.09:1 and against --ink 2.33:1, so colour alone does not meet WCAG 1.4.1 here.
   Cards, tiles and .more are excluded - they are block targets, not inline text. */
main p a:not(.btn):not(.more),main .checks li a,main details a,main .form-note a,main dd a{text-decoration:underline;text-underline-offset:2px}
a:hover{text-decoration:underline}
h1,h2,h3,h4{font-family:var(--head);text-transform:uppercase;letter-spacing:.01em;line-height:1.05;margin:0 0 .5em;color:var(--navy);font-weight:600}
h1{font-size:clamp(2.4rem,5.2vw,4.2rem)}
h2{font-size:clamp(1.9rem,3.4vw,2.7rem)}
h3{font-size:1.25rem;font-weight:600}
p{margin:0 0 1em}
.wrap{max-width:var(--max);margin:0 auto;padding:0 24px}
.eyebrow{font-family:var(--head);text-transform:uppercase;letter-spacing:.14em;color:var(--orange);font-size:.85rem;font-weight:600;margin-bottom:.6rem;display:block}
.accent{color:var(--orange)}
.rule{width:56px;height:3px;background:var(--orange);margin:1.2rem 0 1.4rem;border:0}
.center{text-align:center}
.center .rule{margin-left:auto;margin-right:auto}
.lead{font-size:1.08rem;color:var(--muted);max-width:640px}
.center .lead{margin-left:auto;margin-right:auto}

/* buttons */
.btn{display:inline-flex;align-items:center;gap:.55rem;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.86rem;font-weight:600;padding:.85rem 1.35rem;border-radius:3px;border:2px solid transparent;cursor:pointer;line-height:1;transition:.18s;text-decoration:none!important}
.btn svg{width:14px;height:14px;transition:transform .18s}
.btn:hover svg{transform:translateX(3px)}
.btn-primary{background:var(--orange);color:#fff;border-color:var(--orange)}
.btn-primary:hover{background:var(--orange-2);border-color:var(--orange-2)}
.btn-outline{background:transparent;color:var(--navy);border-color:var(--navy)}
.btn-outline:hover{background:var(--navy);color:#fff}
.btn-outline-light{background:transparent;color:#fff;border-color:rgba(255,255,255,.7)}
.btn-outline-light:hover{background:#fff;color:var(--navy);border-color:#fff}
.btn-outline-orange{background:#fff;color:var(--orange);border-color:var(--orange)}
.btn-outline-orange:hover{background:var(--orange);color:#fff}
.btn-row{display:flex;gap:14px;flex-wrap:wrap}

/* header */
.topbar{background:var(--navy-3);color:#e6f4f6;font-size:.82rem}
.topbar .wrap{display:flex;justify-content:space-between;align-items:center;min-height:34px;gap:16px}
.topbar a{color:#fff}
.topbar .left{display:flex;gap:22px;flex-wrap:wrap}
.header{position:sticky;top:0;z-index:50;background:#fff;box-shadow:0 1px 0 var(--line)}
.header .wrap{display:flex;align-items:center;justify-content:space-between;min-height:84px;gap:20px}
.brand img{height:54px;width:auto}
.nav{display:flex;align-items:center;gap:6px;list-style:none;margin:0;padding:0}
.nav>li{position:relative}
/* the mega item hands its containing block back to .header, so the panel below it
   runs the full width of the viewport instead of the width of the nav item */
.nav>li.has-mega{position:static}
.nav a{display:block;white-space:nowrap;padding:12px 12px;font-family:var(--head);text-transform:uppercase;letter-spacing:.09em;font-size:.82rem;font-weight:600;color:var(--navy);text-decoration:none}
.nav a:hover,.nav li.active>a{color:var(--orange)}
/* Company has no page of its own, so its trigger is a button. Same face as .nav a. */
.nav>li>button{display:block;white-space:nowrap;padding:12px 12px;font-family:var(--head);text-transform:uppercase;letter-spacing:.09em;font-size:.82rem;font-weight:600;line-height:inherit;color:var(--navy-ink);background:none;border:0;cursor:pointer}
.nav>li>button:hover,.nav li.active>button,.nav li.open>button,.nav li.open>a{color:var(--orange-ink)}
.nav .has-mega>a::after,.nav .has-mega>button::after{content:"";display:inline-block;width:6px;height:6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);margin-left:7px;position:relative;top:-3px;transition:transform .2s,top .2s}
.nav li.open>a::after,.nav li.open>button::after{transform:rotate(-135deg);top:1px}
/* Full-width dropdown. Slides and fades down from under the sticky header; the
   hairline top border carries on the header's own 1px shadow line. Closed is
   visibility:hidden rather than display:none, so the links drop out of the tab
   order and the panel can still animate on the way out. */
.mega{position:absolute;left:0;right:0;top:100%;z-index:1;background:#fff;border-top:1px solid var(--line);box-shadow:0 16px 34px rgba(7,147,161,.13),0 40px 80px rgba(31,41,51,.06);opacity:0;visibility:hidden;transform:translateY(-12px);pointer-events:none;transition:opacity .26s ease,transform .26s ease,visibility 0s linear .26s}
.nav li.open>.mega{opacity:1;visibility:visible;transform:none;pointer-events:auto;transition:opacity .26s ease,transform .26s ease,visibility 0s}
/* the panel spans the window; its contents sit in the same 1180px column as the
   rest of the page, aligned left with the logo, never centred under the parent */
.mega-in{max-width:var(--max);margin:0 auto;padding:30px 24px 36px;display:flex;flex-wrap:wrap;gap:18px 64px;text-align:left}
.mega-col{min-width:190px}
.mega-h{font-family:var(--head);text-transform:uppercase;letter-spacing:.14em;font-size:.72rem;font-weight:600;color:var(--muted);margin:0 0 8px}
.mega-col ul{list-style:none;margin:0;padding:0}
.nav .mega a{display:block;white-space:normal;padding:7px 0;font-family:var(--body);text-transform:none;letter-spacing:normal;font-size:1.02rem;font-weight:500;color:var(--ink)}
.nav .mega a:hover{color:var(--orange-ink);text-decoration:underline;text-underline-offset:3px}
/* no-script fallback: without main.js the panel still opens on hover and on focus */
html:not(.sp-nav-js) .nav .has-mega:hover>.mega,html:not(.sp-nav-js) .nav .has-mega:focus-within>.mega{opacity:1;visibility:visible;transform:none;pointer-events:auto;transition:none}
@media (prefers-reduced-motion:reduce){.mega,.nav li.open>.mega{transition:none}}
.header .cta{margin-left:10px}
.burger{display:none;background:none;border:0;width:44px;height:44px;cursor:pointer;padding:8px}
.burger span{display:block;height:2px;background:var(--navy);margin:7px 0;transition:.2s}
@media (max-width:980px){
  .header .cta{display:none}
  .burger{display:block}
  .nav{position:absolute;top:100%;left:0;right:0;background:#fff;flex-direction:column;align-items:stretch;gap:0;padding:8px 0 16px;box-shadow:var(--shadow);display:none;max-height:calc(100vh - 84px);overflow:auto}
  .nav.open{display:flex}
  .nav a{padding:12px 24px;font-size:.95rem}
  /* inside the burger the panels are plain indented lists, on screen with the menu */
  .nav>li.has-mega{position:relative}
  .nav>li>button{width:100%;text-align:left;padding:12px 24px;font-size:.95rem}
  .nav .has-mega>a::after,.nav .has-mega>button::after{transform:rotate(-135deg);top:1px}
  .mega{position:static;opacity:1;visibility:visible;transform:none;pointer-events:auto;background:transparent;border:0;box-shadow:none;transition:none}
  .mega-in{display:block;max-width:none;margin:0;padding:0 0 6px 12px}
  .mega-col{min-width:0}
  .mega-h{margin:0;padding:8px 24px 0;font-size:.7rem}
  .nav .mega a{padding:9px 24px;font-size:.85rem;font-weight:400;color:var(--muted)}
  .nav li.mobile-cta{padding:10px 24px}
}
.mobile-cta{display:none}
@media (max-width:980px){.mobile-cta{display:block}}

/* hero */
.hero{position:relative;background:var(--bg-2);overflow:hidden}
.hero .grid{display:grid;grid-template-columns:1.05fr 1fr;min-height:560px;align-items:center;gap:40px}
.hero .copy{padding:70px 0 120px;position:relative;z-index:2}
.hero .photo{position:absolute;right:0;top:0;bottom:0;width:52%;z-index:1}
.hero .photo img{width:100%;height:100%;object-fit:cover}
.hero .photo::before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,var(--bg-2) 0%,rgba(245,247,249,.6) 12%,rgba(245,247,249,0) 40%)}
.hero .mtn{position:absolute;left:0;right:0;bottom:-2px;z-index:3;width:100%;height:auto;pointer-events:none}
.hero h1{margin-bottom:.2em}
.hero .lead{margin-bottom:1.6rem}
@media (max-width:900px){
  .hero .grid{grid-template-columns:1fr;min-height:0}
  .hero .photo{position:relative;width:100%;height:260px}
  .hero .photo::before{background:none}
  .hero .copy{padding:40px 0 90px}
}
.hero-inner{background:var(--navy);color:#fff;position:relative;overflow:hidden}
.hero-inner .wrap{padding:76px 24px 110px;position:relative;z-index:2;max-width:var(--max)}
.hero-inner h1{color:#fff}
.hero-inner .lead{color:#eaf6f8}
.hero-inner .bg{position:absolute;inset:0;z-index:0}
.hero-inner .bg img{width:100%;height:100%;object-fit:cover;opacity:.32}
.hero-inner .bg::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(6,110,120,.95) 30%,rgba(6,110,120,.55) 100%)}
.hero-inner .mtn{position:absolute;left:0;right:0;bottom:-2px;width:100%;height:auto;z-index:1}
.crumbs{font-size:.8rem;letter-spacing:.1em;text-transform:uppercase;color:#ffe2c9;font-family:var(--head);margin-bottom:1rem}
.crumbs a{color:#ffe2c9}

/* stats bar */
.stats{border-bottom:1px solid var(--line);background:#fff}
.stats .wrap{display:grid;grid-template-columns:repeat(6,1fr);gap:0}
.stat{display:flex;align-items:center;gap:12px;padding:26px 16px;border-left:1px solid var(--line);font-family:var(--head);text-transform:uppercase;font-size:.78rem;letter-spacing:.06em;color:var(--navy);line-height:1.25;font-weight:600}
.stat:first-child{border-left:0}
.stat svg{width:34px;height:34px;flex:none;color:var(--orange)}
@media (max-width:980px){.stats .wrap{grid-template-columns:repeat(3,1fr)}.stat:nth-child(4){border-left:0}}
@media (max-width:560px){.stats .wrap{grid-template-columns:repeat(2,1fr)}.stat:nth-child(odd){border-left:0}}

/* sections */
.section{padding:80px 0}
.section.tight{padding:56px 0}
.section.alt{background:var(--bg-2)}
.section-head{margin-bottom:44px}

/* cards */
.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:22px}
.cards.three{grid-template-columns:repeat(3,1fr)}
.cards.two{grid-template-columns:repeat(2,1fr)}
@media (max-width:980px){.cards,.cards.three{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.cards,.cards.three,.cards.two{grid-template-columns:1fr}}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;display:flex;flex-direction:column;transition:.2s;position:relative}
.card:hover{box-shadow:var(--shadow);transform:translateY(-3px)}
.card .ph{aspect-ratio:4/3;overflow:hidden}
.card .ph img{width:100%;height:100%;object-fit:cover}
.card .body{padding:22px 22px 24px;flex:1;display:flex;flex-direction:column}
.card .body p{color:var(--muted);font-size:.93rem;flex:1}
.card .more{font-family:var(--head);text-transform:uppercase;letter-spacing:.1em;font-size:.76rem;color:var(--orange);font-weight:600;text-decoration:none}
.card .more:hover{text-decoration:underline}
.card .icon{width:54px;height:54px;border-radius:50%;background:var(--navy);display:grid;place-items:center;margin:-27px 0 12px;position:relative;z-index:2;color:#fff;border:3px solid #fff}
.card .icon svg{width:24px;height:24px}
.card h3{font-size:1.15rem}

/* projects */
.projects{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
@media (max-width:980px){.projects{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.projects{grid-template-columns:1fr}}
.project{position:relative;border-radius:var(--r);overflow:hidden;aspect-ratio:4/3;background:var(--navy);display:block;text-decoration:none!important}
.project img{width:100%;height:100%;object-fit:cover;transition:transform .5s}
.project:hover img{transform:scale(1.05)}
/* The gradient is --surface-teal #057078, not the display teal #0793a1 it used to
   be. Everything in this cap is small text - the name at .9rem, the place at .78,
   the credit at .68 - and #0793a1 under white is 3.69:1, which is the display-type
   figure and not the 4.5 that small text wants. On #057078 white is 5.84:1 and the
   credit line clears 4.5 as well, and the tile opens a fourth and fifth line on
   hover, so it is carrying more of that text than it ever was. */
.project .cap{position:absolute;left:0;right:0;bottom:0;padding:14px 12px 12px;background:linear-gradient(180deg,rgba(5,112,120,0),rgba(5,112,120,.96) 55%);color:#fff;text-align:center}
.project .cap b{display:block;font-family:var(--head);text-transform:uppercase;font-size:.9rem;letter-spacing:.06em;font-weight:600}
.project .cap span{font-size:.78rem;color:#e6f4f6}
.projects.wide{grid-template-columns:repeat(3,1fr)}
/* Recent work: the tile carries its whole portfolio record and opens the rest of it
   on hover or on keyboard focus - who owns the building, what we did on it, and the
   way through to the full entry. The detail is in the markup at rest and only its
   height and opacity move, so the link's accessible name is the whole record either
   way and a pointer is never what makes the information exist. */
.project .detail{display:block;overflow:hidden;max-height:0;opacity:0;
  transition:max-height .45s cubic-bezier(.4,0,.2,1),opacity .3s ease,margin-top .45s cubic-bezier(.4,0,.2,1)}
.project:hover .detail,.project:focus-visible .detail{max-height:13em;opacity:1;margin-top:8px}
/* Owner and general contractor, each with its label, on the pattern the photo credits
   already use further down this file - "Donaldson" on its own is a name with no job
   attached to it. The label has to restate display, because .project .cap b sets the
   project name in block-level display type and this b is a four-word tag inside a
   sentence. */
.project .detail .meta{display:block;font-size:.74rem;line-height:1.4;margin-top:3px}
.project .detail .meta b{display:inline;font-family:var(--head);text-transform:uppercase;
  letter-spacing:.08em;font-size:.64rem;font-weight:600;color:var(--eyebrow-dark);margin-right:5px}
/* Three lines and no more. The cap grows upward from the bottom of a fixed 4:3 box,
   so anything that overruns is clipped off the TOP - which is the project name. Two
   was the budget while the tile still carried a photo credit under this; with that
   line gone there is room for the third, and the Wren's services line needs it. */
.project .detail .did{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;
  overflow:hidden;font-size:.78rem;line-height:1.45;margin-top:5px}
/* the gradient has more to hold once the tile is open, so it comes further up */
.project .cap{transition:background .4s ease}
.project:hover .cap,.project:focus-visible .cap{background:linear-gradient(180deg,rgba(5,112,120,.12),rgba(5,112,120,.97) 40%)}
/* A touch screen has no hover, and a record that only exists under a pointer is a
   record nobody on a phone ever sees. There the tile is open from the start. */
@media (hover:none){
  .project .detail{max-height:none;opacity:1;margin-top:8px;transition:none}
  .project .cap{background:linear-gradient(180deg,rgba(5,112,120,0),rgba(5,112,120,.96) 30%)}
}
@media (prefers-reduced-motion:reduce){.project .detail,.project .cap,.project img{transition:none}}
html[data-a11y-motion] .project .detail,html[data-a11y-motion] .project .cap{transition:none}

/* ---- active projects -------------------------------------------------------
   The board of what is on site now, which is a different object from the grid
   above it: .project is a portfolio entry with a photograph behind it, .active is
   a job in progress with nothing finished to show yet, so the tile is the name,
   the town and one line of scope. The orange edge is the whole difference in
   weight between "we are there this week" and "here is what we delivered" - it is
   also the only status mark left on the tile, the "On site now" badge having come
   off on 2026-09-04 as a third restatement of the section heading. */
.active-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
@media (max-width:1000px){.active-grid{grid-template-columns:1fr 1fr}}
@media (max-width:600px){.active-grid{grid-template-columns:1fr}}
.active{background:#fff;border:1px solid var(--line);border-left:3px solid var(--orange);
  border-radius:var(--r);padding:22px 20px;display:flex;flex-direction:column;align-items:flex-start;gap:6px}
.active h3{font-size:1.02rem;line-height:1.25;margin:0}
.active .where{font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;
  font-size:.72rem;font-weight:600;color:var(--navy)}
.active p{color:var(--muted);font-size:.9rem;margin:0}
@media (max-width:980px){.projects.wide{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.projects.wide{grid-template-columns:1fr}}

/* industries
   --icols comes from build.py, which picks the widest column count that divides the
   number of tiles exactly, so both rows come out full (ten industries = 5 + 5, not the
   old 6 + 4). The dividers are the 1px grid gap with the rule colour showing through
   from the container behind the opaque tiles: that draws every internal rule and no
   outer one at any column count, without the stack of :nth-child rules the old
   fixed-column version needed. */
.industries{display:grid;grid-template-columns:repeat(var(--icols,5),1fr);gap:1px;background:var(--line);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.industry{padding:26px 8px;text-align:center;background:var(--bg);font-family:var(--head);text-transform:uppercase;font-size:.72rem;letter-spacing:.06em;color:var(--navy);font-weight:600;line-height:1.25;text-decoration:none!important}
.industry svg{width:38px;height:38px;color:var(--orange);margin:0 auto 10px;display:block}
a.industry:hover{background:var(--bg-2)}
/* squares off a short last row; see industry_grid() in build.py */
.ifill{background:var(--bg)}
@media (max-width:980px){.industry{padding:22px 6px;font-size:.68rem}.industry svg{width:32px;height:32px}}
@media (max-width:620px){.industries{grid-template-columns:repeat(2,1fr)}.industry{padding:24px 10px;font-size:.74rem}.industry svg{width:36px;height:36px}}

/* about split */
.split{display:grid;grid-template-columns:1fr 1fr;align-items:stretch}
.split .photo{position:relative;min-height:380px}
.split .photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.split .text{padding:64px 56px;background:#fff;position:relative;overflow:hidden}
.split .text .lead{font-size:.98rem}
.split .text.on-bg{background:var(--bg-2)}
@media (max-width:900px){.split{grid-template-columns:1fr}.split .text{padding:44px 24px}}
.split.flip .photo{order:2}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:56px;align-items:center}
@media (max-width:900px){.two-col{grid-template-columns:1fr;gap:32px}}
.two-col .pic{border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow)}
.two-col .pic img{width:100%;height:100%;object-fit:cover;aspect-ratio:4/3}
.checks{list-style:none;padding:0;margin:0 0 1.4rem;display:grid;gap:10px}
.checks li{padding-left:30px;position:relative;color:var(--ink)}
.checks li::before{content:"";position:absolute;left:0;top:5px;width:16px;height:16px;border-radius:50%;background:var(--orange)}
.checks li::after{content:"";position:absolute;left:5px;top:9px;width:6px;height:3px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg)}

/* cta band */
.cta-band{background:var(--navy);color:#fff;position:relative;overflow:hidden}
.cta-band .wrap{position:relative;z-index:2;display:flex;align-items:center;justify-content:space-between;gap:30px;padding:70px 24px 120px;flex-wrap:wrap}
.cta-band h2{color:#fff;margin-bottom:.35em}
.cta-band p{color:#eaf6f8;max-width:520px;margin:0}
.cta-band .mtn{position:absolute;left:0;right:0;bottom:-2px;width:100%;height:auto;z-index:1;opacity:.95}

/* footer */
.footer{background:var(--navy);border-top:1px solid rgba(255,255,255,.12);color:#e6f4f6;font-size:.9rem}
.footer .wrap{display:grid;grid-template-columns:1.4fr 1.2fr 1fr;gap:32px;padding:56px 24px 40px}
@media (max-width:900px){.footer .wrap{grid-template-columns:1fr 1fr}}
@media (max-width:560px){.footer .wrap{grid-template-columns:1fr}}
.footer h2{color:#fff;font-size:.85rem;letter-spacing:.14em;margin-bottom:1rem}
.footer a{color:#ffffff;text-decoration:none}
.footer a:hover{color:#fff;text-decoration:underline}
.footer ul{list-style:none;padding:0;margin:0;display:grid;gap:8px}
.footer .contact li{display:flex;gap:10px;align-items:flex-start}
.footer .contact svg{width:16px;height:16px;flex:none;margin-top:4px;color:var(--orange)}
.footer .brand img{height:64px;margin-bottom:12px}
.footer .social{display:flex;gap:10px}
.footer .social a{width:38px;height:38px;border-radius:50%;background:rgba(255,255,255,.08);display:grid;place-items:center}
.footer .social a:hover{background:var(--orange)}
.footer .social svg{width:18px;height:18px;fill:#fff}
.footer .bottom{border-top:1px solid rgba(255,255,255,.18);text-align:center;padding:16px 24px;font-size:.78rem;color:#d6eef1}
.footer .swam{margin-top:14px;background:#fff;border-radius:4px;padding:6px;display:inline-block}
.footer .swam img{height:46px;width:auto}

/* content pages */
.prose{max-width:760px}
.prose p{color:#3b4650}
.prose h2{margin-top:2rem}
.svc-block{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center;padding:56px 0;border-top:1px solid var(--line)}
.svc-block:first-child{border-top:0}
.svc-block.flip .pic{order:2}
.svc-block .pic{border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow)}
.svc-block .pic img{aspect-ratio:4/3;object-fit:cover;width:100%}
@media (max-width:900px){.svc-block{grid-template-columns:1fr;gap:26px}.svc-block.flip .pic{order:0}}
.feature-list{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
@media (max-width:900px){.feature-list{grid-template-columns:1fr 1fr}}
@media (max-width:560px){.feature-list{grid-template-columns:1fr}}
/* Seven plates do not divide by three or by two, so the last row is left holding one
   tile against the left edge. Centre a lone trailing tile instead: at three columns
   it moves into the middle one, at two it spans the pair and is held to a single
   column's width (22px gap, so half of it is 11). One column has no short row. */
.feature-list > :last-child:nth-child(3n+1){grid-column:2}
@media (max-width:900px){
  .feature-list > :last-child:nth-child(3n+1){grid-column:auto}
  .feature-list > :last-child:nth-child(2n+1){grid-column:1/-1;width:calc(50% - 11px);justify-self:center}
}
@media (max-width:560px){
  .feature-list > :last-child:nth-child(2n+1){grid-column:auto;width:auto}
}
.feature{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:26px 24px}
.feature svg{width:34px;height:34px;color:var(--orange);margin-bottom:12px}
.feature h3{font-size:1.1rem}
.feature p{color:var(--muted);font-size:.93rem;margin:0}
.quote{border-left:4px solid var(--orange);padding:6px 0 6px 22px;font-size:1.25rem;font-family:var(--head);text-transform:uppercase;color:var(--navy);max-width:720px;margin:1.6rem 0}

/* team */
.team{display:flex;flex-wrap:wrap;justify-content:center;gap:28px 24px}
.member{flex:0 1 calc(25% - 18px);min-width:200px}
@media (max-width:900px){.member{flex-basis:calc(50% - 12px)}}
.member{text-align:center}
.member .ph{aspect-ratio:1;border-radius:var(--r);overflow:hidden;background:var(--bg-2);margin-bottom:14px}
.member .ph img{width:100%;height:100%;object-fit:cover}
.member .ph.blank{display:grid;place-items:center;color:var(--navy);font-family:var(--head);font-size:2.6rem;font-weight:600;background:linear-gradient(160deg,#e9eef2,#d7e0e6)}
.member b{display:block;font-family:var(--head);text-transform:uppercase;letter-spacing:.06em;color:var(--navy);font-size:1.05rem}
.member span{font-size:.82rem;color:var(--orange);text-transform:uppercase;letter-spacing:.1em;font-family:var(--head);font-weight:600}
.member p{font-size:.88rem;color:var(--muted);margin-top:10px;text-align:left}

/* service area */
.towns-head{font-size:.82rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin:0 0 .9rem}
.towns{display:grid;grid-template-columns:repeat(4,1fr);gap:10px 24px;list-style:none;padding:0;margin:0 0 1.6rem}
@media (max-width:700px){.towns{grid-template-columns:repeat(2,1fr)}}
.towns li{padding-left:26px;position:relative;font-family:var(--head);text-transform:uppercase;letter-spacing:.06em;color:var(--navy);font-weight:600}
.towns li svg{position:absolute;left:0;top:4px;width:16px;height:16px;color:var(--orange)}
.map{border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow);aspect-ratio:16/10}
.map iframe{width:100%;height:100%;border:0;display:block}

/* forms */
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form-grid .full{grid-column:1/-1}
@media (max-width:640px){.form-grid{grid-template-columns:1fr}}
.form-grid fieldset{border:0;margin:0;padding:0;min-width:0}
.form-grid legend{display:block;padding:0;font-size:.8rem;text-transform:uppercase;letter-spacing:.08em;font-family:var(--head);color:var(--navy);font-weight:600;margin-bottom:6px}
label{display:block;font-size:.8rem;text-transform:uppercase;letter-spacing:.08em;font-family:var(--head);color:var(--navy);font-weight:600;margin-bottom:6px}
input,select,textarea{width:100%;font:inherit;padding:12px 14px;border:1px solid #c9d2d9;border-radius:4px;background:#fff;color:var(--ink)}
input:focus,select:focus,textarea:focus{outline:2px solid var(--orange);outline-offset:0;border-color:var(--orange)}
textarea{min-height:140px;resize:vertical}
.form-note{font-size:.82rem;color:var(--muted)}
.form-msg{display:none;padding:14px 16px;border-radius:4px;margin-top:14px;font-size:.95rem}
.form-msg.ok{display:block;background:#e6f6ee;color:#14603a}
.form-msg.err{display:block;background:#fdecec;color:#8f1d1d}
.hp{position:absolute;left:-9999px;opacity:0;height:0;width:0}
/* auto-fit, not a fixed three: the email card came off on 2026-09-04 and a hard
   three-column rule would have left the pair sitting in two thirds of the row. */
.contact-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:22px;margin-bottom:48px}
@media (max-width:900px){.contact-cards{grid-template-columns:1fr}}
.contact-card{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:26px;display:flex;gap:16px;align-items:flex-start}
.contact-card svg{width:30px;height:30px;color:var(--orange);flex:none}
.contact-card b{display:block;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;color:var(--navy)}
.contact-card a,.contact-card span{color:var(--ink)}
.panel{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:36px;box-shadow:var(--shadow)}
@media (max-width:640px){.panel{padding:24px 18px}}
.tabs{display:flex;gap:8px;margin-bottom:28px;flex-wrap:wrap}
.tab{font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.85rem;font-weight:600;padding:10px 18px;border:2px solid var(--navy);border-radius:3px;background:#fff;color:var(--navy);cursor:pointer}
.tab.active{background:var(--navy);color:#fff}
.radio-row{display:flex;gap:18px;flex-wrap:wrap;padding-top:6px}
.radio-row label{display:inline-flex;align-items:center;gap:6px;text-transform:none;letter-spacing:0;font-family:var(--body);font-weight:400;font-size:.95rem;color:var(--ink)}
.radio-row input{width:auto}
.badge{display:inline-block;background:#fff3ea;color:var(--orange-2);font-family:var(--head);text-transform:uppercase;letter-spacing:.1em;font-size:.72rem;padding:4px 10px;border-radius:999px;font-weight:600}
.faq details{border-top:1px solid var(--line);padding:16px 0}
.faq details:last-child{border-bottom:1px solid var(--line)}
.faq summary{cursor:pointer;font-family:var(--head);text-transform:uppercase;letter-spacing:.04em;color:var(--navy);font-size:1.05rem;font-weight:600;list-style:none;display:flex;justify-content:space-between;gap:12px}
.faq summary::after{content:"+";color:var(--orange);font-size:1.4rem;line-height:1}
.faq details[open] summary::after{content:"\2013"}
.faq p{color:var(--muted);margin:10px 0 0}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}

/* ===== v2: Class A repositioning ===== */
.hero .rotator{font-family:var(--head);text-transform:uppercase;font-size:clamp(1.1rem,2vw,1.5rem);letter-spacing:.12em;color:var(--navy);margin:0 0 1rem;min-height:1.6em;font-weight:600}
.hero .rotator .w{display:inline-block;color:var(--orange);animation:wfade .5s ease}
@keyframes wfade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.cred-bar{background:var(--navy-2);color:#fff}
.cred-bar .wrap{display:flex;flex-wrap:wrap;justify-content:center;gap:0}
.cred{display:flex;align-items:center;gap:10px;padding:16px 22px;font-family:var(--head);text-transform:uppercase;letter-spacing:.1em;font-size:.78rem;font-weight:600;border-left:1px solid rgba(255,255,255,.12)}
.cred:first-child{border-left:0}
.cred svg{width:22px;height:22px;color:var(--orange)}
@media (max-width:700px){.cred{padding:12px 14px;border-left:0;flex:1 1 45%}}
.stats-row{display:grid;grid-template-columns:repeat(5,1fr);gap:18px}
@media (max-width:900px){.stats-row{grid-template-columns:repeat(3,1fr)}}
@media (max-width:560px){.stats-row{grid-template-columns:repeat(2,1fr)}}
.stat-tile{background:#fff;border:1px solid var(--line);border-top:4px solid var(--orange);border-radius:var(--r);padding:24px 18px;text-align:center}
.stat-tile b{display:block;font-family:var(--head);font-size:2.4rem;color:var(--navy);line-height:1;font-weight:600}
.stat-tile b.tbd{color:#c3ccd3}
.stat-tile span{display:block;font-family:var(--head);text-transform:uppercase;letter-spacing:.1em;font-size:.74rem;color:var(--muted);margin-top:8px;font-weight:600}
.why{display:grid;grid-template-columns:1fr 1fr;gap:56px;align-items:center}
@media (max-width:900px){.why{grid-template-columns:1fr;gap:30px}}
.why-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
/* Three tiles over two columns leave the last one against the left edge. It runs the
   full width of the grid instead - which suits the one that is left down there, "one
   contract, one warranty, one number to call", being the shortest of the three and
   the one that reads as the conclusion of the other two. */
.why-grid > :last-child:nth-child(2n+1){grid-column:1/-1}
.why-tile{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:22px 20px}
.why-tile svg{width:30px;height:30px;color:var(--orange);margin-bottom:8px}
.why-tile b{display:block;font-family:var(--head);text-transform:uppercase;letter-spacing:.06em;color:var(--navy);font-size:1.05rem}
.why-tile p{margin:6px 0 0;color:var(--muted);font-size:.9rem}
.logos{display:grid;grid-template-columns:repeat(6,1fr);gap:14px}
@media (max-width:900px){.logos{grid-template-columns:repeat(3,1fr)}}
@media (max-width:560px){.logos{grid-template-columns:repeat(2,1fr)}}
.logo-slot{aspect-ratio:5/3;border:1px dashed #c3ccd3;border-radius:var(--r);display:grid;place-items:center;text-align:center;padding:12px;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.78rem;color:#7b8892;background:#fff;font-weight:600}
.logo-slot img{max-height:60px;width:auto;filter:grayscale(1);opacity:.75}
.testimonials{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
@media (max-width:900px){.testimonials{grid-template-columns:1fr}}
.testimonial{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:28px 26px;position:relative}
.testimonial::before{content:"\201C";position:absolute;top:8px;left:18px;font-family:Georgia,serif;font-size:4rem;color:#f5d3bd;line-height:1}
.testimonial p{position:relative;color:#3b4650;font-size:.97rem}
.testimonial .who{font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.78rem;color:var(--navy);font-weight:600}
.testimonial .who span{display:block;color:var(--orange)}
.testimonial .stars{color:var(--orange);letter-spacing:2px;font-size:.9rem;margin-bottom:8px}
.cap-list{columns:2;column-gap:32px;list-style:none;padding:0;margin:0 0 1.4rem}
.cap-list li{break-inside:avoid;padding:6px 0 6px 26px;position:relative;border-bottom:1px solid var(--line);font-size:.95rem}
.cap-list li::before{content:"";position:absolute;left:0;top:13px;width:14px;height:14px;border-radius:50%;background:var(--orange)}
.cap-list li::after{content:"";position:absolute;left:4px;top:16px;width:6px;height:3px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg)}
@media (max-width:560px){.cap-list{columns:1}}
.trades{display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0;list-style:none}
.trades li{background:var(--bg-2);border:1px solid var(--line);border-radius:999px;padding:6px 14px;font-size:.85rem;font-family:var(--head);text-transform:uppercase;letter-spacing:.06em;color:var(--navy);font-weight:600}
.pcard{background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;display:grid;grid-template-columns:1.1fr 1fr}
.pcard+.pcard{margin-top:28px}
.pcard .ph{position:relative;min-height:320px}
.pcard .ph img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.pcard .info{padding:32px 34px}
.pcard .info h3{font-size:1.5rem}
.pcard dl{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;margin:16px 0 20px;font-size:.93rem}
.pcard dt{font-family:var(--head);text-transform:uppercase;letter-spacing:.1em;font-size:.72rem;color:var(--orange);font-weight:600;padding-top:3px}
.pcard dd{margin:0;color:#3b4650}
.pcard dd.tbd{color:#9aa7b0;font-style:italic}
@media (max-width:900px){.pcard{grid-template-columns:1fr}.pcard .ph{min-height:240px}.pcard .info{padding:24px 22px}}
.ba{position:relative;aspect-ratio:4/3;overflow:hidden;border-radius:var(--r);user-select:none}
.ba img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.ba .after{clip-path:inset(0 0 0 var(--x,50%))}
.ba input{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:ew-resize;margin:0}
.ba .handle{position:absolute;top:0;bottom:0;left:var(--x,50%);width:3px;background:#fff;pointer-events:none;box-shadow:0 0 0 1px rgba(0,0,0,.2)}
.ba .handle::after{content:"\2039\203A";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;border-radius:50%;background:var(--orange);color:#fff;display:grid;place-items:center;font-size:1.3rem;letter-spacing:2px}
.ba .tag{position:absolute;top:12px;padding:4px 10px;background:rgba(7,147,161,.9);color:#fff;font-family:var(--head);text-transform:uppercase;letter-spacing:.1em;font-size:.7rem;border-radius:3px}
.ba .tag.l{left:12px}.ba .tag.r{right:12px}
.safety-list{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
@media (max-width:700px){.safety-list{grid-template-columns:1fr}}
.note-preview{background:#fff8e6;border:1px dashed #e2c36b;color:#7a5a00;font-size:.82rem;padding:8px 12px;border-radius:4px;margin:0 0 16px}
.footer .wrap{grid-template-columns:1.4fr 1.2fr 1fr}
.portal-links{display:none}
@media (max-width:980px){.portal-links{display:flex;flex-wrap:wrap;gap:6px 18px;border-top:1px solid var(--line);margin-top:6px}}
.portal-links a{padding:8px 0!important;font-size:.8rem!important;color:var(--muted)!important}
/* residential photo gallery (portrait tiles) */
.pgrid{display:grid;grid-template-columns:repeat(5,1fr);gap:14px}
@media (max-width:1100px){.pgrid{grid-template-columns:repeat(4,1fr)}}
@media (max-width:800px){.pgrid{grid-template-columns:repeat(3,1fr)}}
@media (max-width:520px){.pgrid{grid-template-columns:repeat(2,1fr)}}
.pgrid a{display:block;aspect-ratio:3/4;overflow:hidden;border-radius:var(--r);background:var(--bg-2)}
.pgrid img{width:100%;height:100%;object-fit:cover;transition:transform .5s}
.pgrid a:hover img{transform:scale(1.04)}
.pgrid a.wide{grid-column:span 2;aspect-ratio:auto}
@media (max-width:980px){.topbar .right{display:none}.topbar .left span{white-space:nowrap}}
/* video hero */
.hero-video{background:var(--navy);color:#fff;min-height:640px;display:flex;align-items:center}
.hero-video .bgvid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.hero-video .veil{position:absolute;inset:0;z-index:1;background:linear-gradient(90deg,rgba(6,110,120,.9) 0%,rgba(6,110,120,.75) 38%,rgba(6,110,120,.35) 70%,rgba(6,110,120,.25) 100%)}
.hero-video .wrap{position:relative;z-index:2;width:100%}
.hero-video .copy{max-width:640px;padding:90px 0 150px}
.hero-video h1{color:#fff}
.hero-video .rotator{color:#fff}
.hero-video .lead{color:#eaf6f8}
.hero-video .mtn{z-index:3}
@media (max-width:900px){.hero-video{min-height:0}.hero-video .copy{padding:56px 0 120px}.hero-video .veil{background:linear-gradient(180deg,rgba(6,110,120,.88),rgba(6,110,120,.7))}}
@media (prefers-reduced-motion:reduce){.hero-video .bgvid{display:none}.hero-video{background:var(--navy) url(../img/hero-cville-poster.jpg) center/cover}}
/* footer: responsive override (the 5-col rule above was beating the media queries) */
@media (max-width:900px){.footer .wrap{grid-template-columns:1fr 1fr;gap:28px 24px;padding:44px 24px 32px}.footer .brand{grid-column:1/-1}.footer .brand div{max-width:none}}
@media (max-width:560px){.footer .wrap{grid-template-columns:1fr 1fr;gap:24px 18px}.footer .brand{grid-column:1/-1}.footer .bottom{font-size:.74rem;line-height:1.6}.footer .bottom{padding:14px 18px}}
@media (max-width:380px){.footer .wrap{grid-template-columns:1fr}}
/* phone-width polish */
@media (max-width:560px){
  .stats-row .stat-tile:last-child:nth-child(odd){grid-column:1/-1}
  .why-grid{grid-template-columns:1fr}
  .cred{font-size:.72rem}
  .contact-cards{gap:12px}
  .hero-video .copy{padding:48px 0 110px}
  h1{font-size:2.3rem}
  .section{padding:56px 0}
  .pcard dl{grid-template-columns:1fr;gap:2px 0}.pcard dt{margin-top:8px}
}
/* bigger logo */
.brand img{height:74px}
.header .wrap{min-height:100px}
.footer .brand img{height:84px;margin-bottom:14px}
@media (max-width:980px){.brand img{height:60px}.header .wrap{min-height:84px}.nav{max-height:calc(100vh - 100px)}}
/* fewer icons on the home and commercial pages: keep the industry pictograms, drop the decorative ones */
.page-index .cred svg,.page-commercial .cred svg,.page-index .card .icon,.page-index .why-tile svg,.page-commercial .feature svg{display:none}
.page-index .card .body{padding-top:24px}
.cred{gap:0}
/* logo: larger still (SVG cropped to artwork) */
.brand img{height:88px}
.header .wrap{min-height:110px}
.footer .brand img{height:96px}
@media (max-width:980px){.brand img{height:66px}.header .wrap{min-height:88px}.nav{max-height:calc(100vh - 88px)}}

/* teal-background tweaks */
.hero-inner .eyebrow,.hero-video .eyebrow,.cta-band .eyebrow{color:#f8b348}
.hero-video .rotator .w{color:#f8b348}
.footer h2{color:#fff}
.cred{border-left-color:rgba(255,255,255,.25)}
/* fewer icons, site-wide: keep social icons, button arrows and the nav caret only */
.industry svg,.feature svg,.why-tile svg,.cred svg,.card .icon,.contact-card svg,.footer .contact svg,.stat svg,.towns li svg{display:none}
.card .body{padding-top:24px}
.towns li{padding-left:14px}.towns li::before{content:"";position:absolute;left:0;top:10px;width:6px;height:6px;border-radius:50%;background:var(--orange)}
.industry{padding:22px 10px}
/* plainer list bullets */
.checks li{padding-left:18px}.checks li::before{width:7px;height:7px;top:9px;left:2px}.checks li::after{display:none}
.cap-list li{padding-left:18px}.cap-list li::before{width:7px;height:7px;top:16px;left:2px}.cap-list li::after{display:none}
/* room for the taller ridge line */
.mtn{height:clamp(64px,13.2vw,190px)!important}
.hero-inner .wrap{padding-bottom:215px}
.cta-band .wrap{padding-bottom:215px}
.hero-video .copy{padding-bottom:225px}
.hero .copy{padding-bottom:225px}
@media (max-width:900px){.hero-inner .wrap{padding-bottom:110px}.cta-band .wrap{padding-bottom:110px}.hero-video .copy{padding-bottom:110px}}
.footer .swam img{height:46px}
/* static tagline with a rolling highlight (reads correctly when static) */
.tagline{font-family:var(--head);text-transform:uppercase;font-size:clamp(1rem,1.7vw,1.35rem);letter-spacing:.12em;margin:0 0 1rem;font-weight:600;color:rgba(255,255,255,.55)}
/* The four words were links into the service pages until 2026-09-04 and are plain
   spans now: the hero has two buttons and they are the only things in it to press.
   Hovering a word still holds that job's photograph on screen, and the sliding
   .tagline-rule below is the only underline any of them gets. */
.tagline span{font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;transition:color .4s}
.tagline span.on{color:#f8b348}
.tagline span:hover{color:#f8b348}
/* the rule below the tagline doubles as the underline for the highlighted word:
   main.js sets its width and offset to match, and it slides between words. It falls
   back to the plain 56px bar when the tagline wraps or JS is off. */
.tagline-rule{margin-top:.6rem;transition:width .55s cubic-bezier(.4,0,.2,1),transform .55s cubic-bezier(.4,0,.2,1)}
@media (prefers-reduced-motion:reduce){.tagline-rule{transition:none}}
/* The sentence under the tagline. It is the one piece of running prose in the hero,
   so it is set in the body face rather than the condensed display one - four uppercase
   words above it and a button below, and this is the line that has to be read rather
   than scanned. 600px, not the copy column's 640: the before/after panel opens at the
   end of the headline, and the blurb is the only line long enough to reach it. */
.hero-blurb{max-width:600px;margin:1.1rem 0 1.6rem;font-size:clamp(1rem,1.15vw,1.08rem);
  line-height:1.62;color:rgba(255,255,255,.92)}
/* ---- Trusted By: the brand grid --------------------------------------------
   Sixteen owner and brand logos used to ride the 3D ring together with the
   contractors. A ring shows three faces at a time and takes two minutes to bring
   the rest round, which is the wrong object for a list whose only job is to be
   scanned - so these are a plate that can be read at a glance, and the ring below
   kept the contractors, where a carousel says the true thing: this is a sample.
   Cell grounds stay white in every mode, dark included: a third-party logo is
   artwork drawn for a light background and inverting the plate under it is how you
   get a black rectangle where a wordmark used to be. */
.logo-grid{display:grid;grid-template-columns:repeat(var(--lcols,5),1fr);gap:14px}
.logo-cell{margin:0;background:#fff;border:1px solid var(--line);border-radius:var(--r);
  min-height:106px;padding:18px 14px;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:8px}
/* MONOCHROME. Sixteen logos in sixteen brand palettes is sixteen things competing
   for the eye, and none of them is the one we are selling - the wall says "these
   buildings", not "look at these colours". Greyed, they read as one set. contrast()
   pulls back the mid-greys a pure desaturate leaves flat, so a two-tone mark does not
   collapse into one value. */
.logo-cell img{max-height:52px;max-width:100%;object-fit:contain;
  filter:grayscale(1) contrast(1.08)}
/* except in high contrast, which exists to make things legible rather than tidy */
html[data-a11y-contrast="high"] .logo-cell img{filter:none}
.logo-cell figcaption{font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;
  font-size:.66rem;line-height:1.35;color:var(--muted);text-align:center}
@media (max-width:900px){.logo-grid{grid-template-columns:repeat(3,1fr)}}
@media (max-width:560px){
  .logo-grid{grid-template-columns:repeat(2,1fr);gap:12px}
  /* fifteen cells over two columns leave one against the left edge; centre it */
  .logo-grid > :last-child:nth-child(2n+1){grid-column:1/-1;width:calc(50% - 6px);justify-self:center}
}
/* The trademark notice, which sits at the foot of the page rather than under either
   wall. It is the sentence that keeps a wall of other people's marks from reading as
   a list of sponsors: muted, because it is not a selling line, but never hidden -
   this is the one piece of copy on the page whose whole job is to be found by
   somebody looking for it. The band is tight; it is a footnote, not a section.

   IT IS TEAL, not white. On white it was a bright sliver between the call-to-action
   band above it and the footer below - both of which are --surface-teal - and a thin
   pale stripe across the foot of the page reads as something broken rather than as a
   notice. Taking the same teal makes the bottom of the page one block with the
   notice inside it, which is where a legal footnote belongs. The ink is #cfe9ec:
   4.59:1 on that ground, which clears AA at this size, where the --teal it carried
   on white would have been unreadable. The surface itself is set in the audited
   teal-surface list further down rather than here, so the ground and the type that
   goes on it stay in the one place they were measured together. */
.mark-note-band{padding-top:34px;padding-bottom:34px}
.mark-note{max-width:780px;margin:0 auto;text-align:center;
  font-size:.78rem;line-height:1.6;color:#cfe9ec}

/* 3D logo ring */
.ring-wrap{perspective:1400px;height:260px;display:flex;align-items:center;justify-content:center;overflow:hidden}
.ring{position:relative;width:220px;height:140px;transform-style:preserve-3d;animation:spin 28s linear infinite}
.ring:hover{animation-play-state:paused}
.ring .face{position:absolute;inset:0;margin:0;background:#fff;border:1px solid var(--line);border-radius:var(--r);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:14px;backface-visibility:hidden;transform:rotateY(calc(var(--i) * 360deg / var(--n))) translateZ(calc(110px / tan(3.14159rad / var(--n)) + 40px))}
.ring .face img{max-height:64px;max-width:170px;object-fit:contain}
.ring .face figcaption{font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.7rem;color:var(--muted)}
@keyframes spin{from{transform:rotateY(0)}to{transform:rotateY(-360deg)}}
@media (prefers-reduced-motion:reduce){.ring{animation:none}}
.ring-wrap{overflow:visible;height:320px}
.ring{width:250px;height:150px}
.ring .face{transform:rotateY(calc(var(--i) * 360deg / var(--n))) translateZ(calc(125px / tan(3.14159rad / var(--n)) + 60px))}
/* ring v2: JS-driven rotation (auto + drag + wheel + arrows) */
.ring{animation:none!important;transition:none;will-change:transform;cursor:grab;touch-action:pan-y}
.ring.dragging{cursor:grabbing}
.ring-wrap{position:relative;user-select:none}
@media (max-width:700px){.ring{width:200px;height:120px}.ring .face{transform:rotateY(calc(var(--i) * 360deg / var(--n))) translateZ(calc(100px / tan(3.14159rad / var(--n)) + 50px))}.ring-wrap{height:260px}}
/* no arrows: the ring is drag, wheel and auto-rotate only, so nothing is reserved
   below it and the block is 90px shorter than it was */
.ring-wrap{height:260px;padding-bottom:0}
/* mobile audit fixes: ring must never widen the page; roomier tap targets */
.ring-wrap{width:100vw;margin-left:calc(50% - 50vw);overflow:hidden}
html,body{overflow-x:clip}
.footer ul{gap:0}.footer ul li{padding:5px 0}.footer .contact li{padding:5px 0}
.card .more{display:inline-block;padding:8px 0}
.crumbs a{display:inline-block;padding:6px 0}
.portal-links a{padding:10px 0!important}
/* phones: the 3D ring becomes a flat swipe strip (a 14-face ring is wider than the screen) */
@media (max-width:700px){
  .ring-wrap{height:auto;padding:0 0 12px;perspective:none;overflow:visible;width:auto;margin:0}
  .ring{transform:none!important;width:auto;height:auto;display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;padding:6px 24px 14px;scrollbar-width:none;cursor:default;touch-action:pan-x pan-y}
  .ring::-webkit-scrollbar{display:none}
  .ring .face{position:relative;inset:auto;transform:none!important;flex:0 0 58%;aspect-ratio:5/3;scroll-snap-align:center;backface-visibility:visible}
  .ring .face img{max-height:56px;max-width:80%}
}
/* photo hero, no ridge */
.hero-video .bgimg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 30%;z-index:0}
.hero-photo .copy{padding:110px 0 120px}
.hero-inner .wrap{padding-bottom:90px}
.cta-band .wrap{padding-bottom:80px}
@media (max-width:900px){.hero-photo .copy{padding:64px 0 72px}.hero-inner .wrap{padding-bottom:64px}.cta-band .wrap{padding-bottom:64px}}
@media (min-width:701px){.ring-wrap{padding-top:16px;height:290px}}

/* ================= ring v3: sharp logos + bokeh depth of field ================= */
/* Why the logos looked blurry: faces were laid out at 250x150 and then magnified
   ~2.2x by the 1400px perspective (radius ~769px), so every front-facing logo was
   an upscaled raster. Fix is two-part: a longer perspective (less magnification)
   and 2x supersampling - each face is laid out at double size and drawn back down
   with scale(.5), so the rasterized layer carries more detail than the projection
   asks for. Geometry is unchanged: the rendered card is still 250x150 at the same
   radius, so translateZ keeps the 125px half-width. */
@media (min-width:701px){
  .ring-wrap{perspective:2600px}
  .ring{width:500px;height:300px}
  .ring .face{padding:28px;border-width:2px;border-radius:12px;gap:0;
    transform:rotateY(calc(var(--i) * 360deg / var(--n))) translateZ(calc(125px / tan(3.14159rad / var(--n)) + 60px)) scale(.5)}
  .ring .face img{max-height:128px;max-width:340px}
  .ring .face figcaption{font-size:1.4rem}
}
/* A ring face with no artwork behind it: the contractor's name set in the site's
   own display face. Thirteen of the fourteen have no logo file in the repo, and a
   wordmark in our type is the honest thing to put there - better than an empty
   card, and not a mark we invented on somebody else's behalf. Sized twice over for
   the same reason figcaption jumps from .7rem to 1.4: at 701px and up the ring lays
   every face out at double scale and draws it back down with scale(.5). The ink is
   --navy-ink rather than the display teal, because on the phone strip this is small
   text and #0793a1 on white is the 3.69:1 that only display type may use. */
.ring .face .wordmark{font-family:var(--head);text-transform:uppercase;letter-spacing:.05em;
  font-weight:600;font-size:1.02rem;line-height:1.2;color:var(--navy-ink);
  text-align:center;text-wrap:balance;padding:0 4px}
@media (min-width:701px){.ring .face .wordmark{font-size:2.04rem;padding:0 10px}}
/* .sub is the name printed UNDER a symbol-only mark, not instead of one. It is a
   caption rather than the face's subject, so it takes about half the wordmark's size
   and none of its weight in the layout: the artwork stays the thing being looked at. */
.ring .face .wordmark.sub{font-size:.82rem;font-weight:500;letter-spacing:.1em}
@media (min-width:701px){.ring .face .wordmark.sub{font-size:1.1rem}}
/* Inner wrapper carries the depth-of-field filter so the .face transform stays a
   pure 3D transform - putting a filter on the transformed element itself flattens
   it out of the preserve-3d context in some engines. */
.ring .face{display:flex;align-items:center;justify-content:center}
.f-in{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;width:100%;height:100%;
  transition:filter .3s linear,opacity .3s linear}
/* only the 3D ring animates the filter, so only promote a layer there */
@media (min-width:701px){.f-in{gap:16px;will-change:filter,opacity}}
@media (prefers-reduced-motion:reduce){.f-in{transition:none}}

/* bokeh: soft out-of-focus light drifting behind the ring */
.ring-wrap{isolation:isolate}
.ring-wrap::before,.ring-wrap::after{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;background-repeat:no-repeat}
.ring-wrap::before{
  background-image:
    radial-gradient(circle,rgba(7,147,161,.34) 0 40%,rgba(7,147,161,0) 70%),
    radial-gradient(circle,rgba(244,136,71,.30) 0 42%,rgba(244,136,71,0) 72%),
    radial-gradient(circle,rgba(248,179,72,.34) 0 40%,rgba(248,179,72,0) 70%),
    radial-gradient(circle,rgba(7,147,161,.22) 0 44%,rgba(7,147,161,0) 74%);
  background-size:360px 360px,240px 240px,300px 300px,200px 200px;
  background-position:4% 16%,80% 8%,66% 76%,20% 82%;
  animation:bokeh-a 30s ease-in-out infinite alternate}
.ring-wrap::after{
  background-image:
    radial-gradient(circle,rgba(255,255,255,.9) 0 46%,rgba(255,255,255,0) 72%),
    radial-gradient(circle,rgba(248,179,72,.24) 0 44%,rgba(248,179,72,0) 72%),
    radial-gradient(circle,rgba(7,147,161,.20) 0 46%,rgba(7,147,161,0) 74%),
    radial-gradient(circle,rgba(244,136,71,.20) 0 44%,rgba(244,136,71,0) 72%);
  background-size:140px 140px,170px 170px,120px 120px,190px 190px;
  background-position:34% 6%,10% 64%,92% 54%,52% 94%;
  animation:bokeh-b 38s ease-in-out infinite alternate}
@keyframes bokeh-a{from{transform:translate3d(-2%,-1.5%,0) scale(1)}to{transform:translate3d(3%,2%,0) scale(1.08)}}
@keyframes bokeh-b{from{transform:translate3d(2%,1%,0) scale(1.05)}to{transform:translate3d(-3%,-2%,0) scale(.96)}}
@media (prefers-reduced-motion:reduce){.ring-wrap::before,.ring-wrap::after{animation:none}}

/* ================= stats row: centered, and sized to how many tiles there are ================= */
/* Was repeat(5,1fr) against four tiles, which parked the whole row one column
   left of centre. Column count now comes from the markup. */
.stats-row{justify-content:center;margin-left:auto;margin-right:auto;grid-template-columns:repeat(var(--cols,5),1fr)}
@media (max-width:900px){.stats-row{grid-template-columns:repeat(3,1fr)}}
@media (max-width:560px){.stats-row{grid-template-columns:repeat(2,1fr)}}
.stats-row .stat-tile{min-width:0}

/* ================= photo provenance ================= */
.photo-fig{margin:0}
.photo-credit{display:block;font-size:.74rem;line-height:1.45;color:var(--muted);margin:10px 2px 0;text-align:left}
.photo-credit b{font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.7rem;color:var(--navy);font-weight:600}
.photo-credit .loc{display:block}
.center .photo-credit,.pgrid .photo-credit{text-align:center}
/* An unfilled credit has to be impossible to miss before launch. */
.photo-credit.tbd{color:#b3541e;background:#fff5ec;border:1px dashed var(--orange);border-radius:3px;padding:4px 8px;display:inline-block}
.photo-credit.tbd b{color:#b3541e}
.pgrid figure{margin:0;display:flex;flex-direction:column}
.pgrid .photo-credit{margin-top:8px;font-size:.7rem}
.member .photo-credit{text-align:center;margin-top:8px}
/* the Our Vision photo credits its client with a logo lockup */
.client-credit{display:flex;align-items:center;gap:12px;margin:14px 2px 0}
.client-credit img{height:26px;width:auto;max-width:150px;object-fit:contain;flex:0 0 auto}
.client-credit span{font-size:.74rem;color:var(--muted);line-height:1.4}
.client-credit b{display:block;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.72rem;color:var(--navy);font-weight:600}
/* American Heart Association credential inside a .feature tile. .aha-mark is the slot
   for the AHA-approved artwork (site/img/logos/aha.png); .aha-mark-text is the
   licence-safe wordmark-free fallback that ships until that file arrives. */
.aha-mark{display:block;height:34px;width:auto;max-width:170px;object-fit:contain;margin:14px 0 0}
.aha-mark-text{display:inline-block;margin:14px 0 0;padding:6px 12px;background:var(--bg-2);border:1px solid var(--line);border-radius:999px;font-family:var(--head);text-transform:uppercase;letter-spacing:.06em;font-size:.72rem;font-weight:600;color:var(--navy-ink);line-height:1.3}
/* wrapping .pic in a <figure> moves the flip ordering up one level */
.svc-block.flip>.photo-fig{order:2}
@media (max-width:900px){.svc-block.flip>.photo-fig{order:0}}

/* ================= service area map ================= */
/* The map sits on satellite imagery now, so everything drawn over it is light with a
   dark halo rather than dark ink on a pale panel. paint-order:stroke puts the halo
   behind the glyph instead of eating into it - without it the 2.6px stroke thins the
   letterforms at 19px. */
.map-svg{background:#1d2a24;aspect-ratio:1200/800}
.area-map{width:100%;height:100%;display:block}
.sam-ring{fill:none;stroke:rgba(255,255,255,.34);stroke-width:1.5;stroke-dasharray:5 7}
.sam-reach{fill:rgba(7,147,161,.14);stroke:rgba(255,255,255,.72);stroke-width:2.5;stroke-dasharray:12 9}
.sam-primary{fill:rgba(7,147,161,.24);stroke:#6ee4ee;stroke-width:2.5}
.sam-dot{fill:#22d3e0;stroke:#04262b;stroke-width:2.5}
.sam-hq-halo{fill:rgba(244,136,71,.35)}
.sam-hq{fill:var(--orange);stroke:#fff;stroke-width:3}
.sam-lbl{font-family:var(--head);font-size:19px;font-weight:600;letter-spacing:.05em;fill:#fff;text-transform:uppercase;stroke:#04262b;stroke-width:2.6px;paint-order:stroke fill;stroke-linejoin:round}
.sam-lbl-hq{font-size:26px;letter-spacing:.07em;fill:#ffd9bd;stroke-width:3.4px}
.sam-scale line{stroke:#fff;stroke-width:2}
.sam-scalelbl,.sam-credit{font-family:var(--head);font-size:16px;fill:rgba(255,255,255,.88);letter-spacing:.1em;text-transform:uppercase}
.sam-credit{font-size:14px;letter-spacing:.06em;fill:rgba(255,255,255,.72);text-transform:none}
.sam-n path{fill:#fff}
.sam-n text{font-family:var(--head);font-size:20px;fill:#fff;text-anchor:middle;font-weight:600}
.map-legend{list-style:none;padding:0;margin:16px 0 0;display:flex;flex-wrap:wrap;gap:9px 20px;font-size:.8rem;color:var(--muted)}
.map-legend li{display:flex;align-items:center;gap:8px}
.map-legend span{width:16px;height:16px;border-radius:50%;flex:0 0 auto}
.map-legend .k-hq{background:var(--orange);box-shadow:0 0 0 3px rgba(244,136,71,.25)}
.map-legend .k-dot{width:11px;height:11px;background:#0e9aa7;box-shadow:0 0 0 2px #04262b}
.map-legend .k-primary{background:rgba(7,147,161,.24);border:2px solid #0d8d99;border-radius:3px}
.map-legend .k-reach{background:rgba(7,147,161,.10);border:2px dashed #5b6770;border-radius:3px}
.map-legend .k-ring{background:none;border:2px dashed #8a949b}

/* ================= hero scrim ================= */
/* The inner-hero photo sat at opacity .32 under a .95 teal gradient, so roughly 2% of
   the image survived on the copy side and 14% on the far side. Lift the photo and
   reshape the gradient: heavy where the headline and lead sit (out to ~52%, which is
   where the 640px lead column ends), falling away across the right of the frame.
   Worst case measured against a pure-white photo is 4.6:1 behind the lead text. */
/* Photo now runs at full strength - all the dimming is done by the gradient, which
   falls to .12 by the right edge so the far side of the frame is essentially the
   photograph. The copy keeps its backing out to ~46%, where the 640px lead column
   ends. A text-shadow carries the rest, which is what lets the scrim come down this
   far without the headline going soft against a bright sky. */
.hero-inner .bg img{opacity:1}
.hero-inner .bg::after{background:linear-gradient(90deg,rgba(6,110,120,.92) 0%,rgba(6,110,120,.86) 46%,rgba(6,110,120,.44) 72%,rgba(6,110,120,.12) 100%)}
.hero-inner h1,.hero-inner .lead,.hero-inner .crumbs{text-shadow:0 1px 14px rgba(3,38,42,.55),0 1px 3px rgba(3,38,42,.45)}
/* pure white rather than the old tinted #eaf6f8: it buys enough contrast to hold AA
   at .86 scrim, where the tinted colour needed .89 and a visibly darker frame */
.hero-inner .lead{font-size:1.2rem;font-weight:500;color:#fff}
/* narrow screens: the copy runs the full width, so the scrim has to as well */
@media (max-width:900px){
  .hero-inner .bg::after{background:linear-gradient(180deg,rgba(6,110,120,.86) 0%,rgba(6,110,120,.90) 100%)}
}

/* ============================================================================
   THE RESIDENTIAL HEADLINE, IRIDESCENT

   "Let us bring your colors to life." is the one line on the site that is about
   colour itself, so it is the one place a sheen earns its keep: the headline is
   painted with an oil-slick gradient clipped to the letterforms, and the slick
   travels through the words as the page is scrolled. main.js publishes the
   scroll position of the headline as --irid, 0 to 1, on the h1 itself - the same
   arrangement as --glass-x on the header, and for the same reason: a property on
   :root would invalidate style for the whole document on every frame.

   FOUR THINGS HOLD IT UP.

   1. THE GROUND IS WHAT PAYS FOR THE COLOUR. The contrast of light type is set
      by what is behind it, so the way to a saturated slick is to take the ground
      down, not to take the chroma out. Sampled off hero-residential.jpg where
      the headline actually falls - the left 46% of the frame, upper middle - and
      measured at the 95th-percentile pixel, which is the brightest place the
      line realistically sits:

                                    on the old .92 teal   on this scrim
        #ffffff                           5.2:1              13.1:1
        #63e6ff  cyan                     3.5:1               8.9:1
        #b78bff  violet                   2.0:1               5.1:1
        #ff7ad9  magenta                  2.2:1               5.6:1
        #ffd166  gold                     3.6:1               9.1:1
        #5ef2a8  green                    3.6:1               9.2:1

      Every stop clears 4.5:1 on the deepened scrim and the two cool hues clear
      it by the least, which is why violet and magenta are the narrowest bands in
      the gradient. On the standard scrim this palette measures 2:1 and must not
      be used - that is what ties .hero-sheen and .irid to one build argument.

   2. THE SCRIM BECOMES A FILTER. Clipped text is transparent, and a text-shadow
      under transparent glyphs shows through them - the letters would fill with
      their own halo. drop-shadow filters the painted result instead, so the same
      two shadows land where they did.

   3. IT IS ENTIRELY INSIDE @supports, AND OUT OF THE MODES THAT EXIST TO REMOVE
      THIS SORT OF THING. Without background-clip:text the declaration block never
      applies and the headline is white, which is what it was. Readable and high
      contrast are excluded for the same reason they drop the cursive, and forced
      colours takes it off entirely: a system palette is a system palette.

   4. STOPPING ANIMATION PARKS IT, IT DOES NOT REMOVE IT. Motion is what the
      switch is for; the sheen itself is not motion. main.js stops writing --irid
      and the fallback in the position below holds the slick mid-line, so the
      headline still reads as painted rather than snapping back to flat white.
   ============================================================================ */
/* The scrim under the copy column, deepened. This is not decoration, it is the
   headroom the palette is spent from: the contrast of light type is set by what
   is behind it, so saturated stops are bought by taking the ground down rather
   than by taking the colour out. The right-hand stops are untouched, so the far
   side of the frame is still essentially the photograph. */
.hero-inner.hero-sheen .bg::after{
  background:linear-gradient(90deg,rgba(3,44,50,.95) 0%,rgba(3,44,50,.92) 46%,
                                   rgba(6,110,120,.44) 72%,rgba(6,110,120,.12) 100%)}
@media (max-width:900px){
  .hero-inner.hero-sheen .bg::after{
    background:linear-gradient(180deg,rgba(3,44,50,.93) 0%,rgba(3,44,50,.95) 100%)}
}

@supports ((-webkit-background-clip:text) or (background-clip:text)){
  html:not([data-a11y-readable]):not([data-a11y-contrast="high"]) .hero-inner h1.irid{
    /* 300% wide, so there are two full widths of slick to travel through the
       line before the last stop arrives at the right edge */
    background-image:linear-gradient(100deg,
      #ffffff 0%,#63e6ff 10%,#b78bff 22%,#ff7ad9 34%,#ffd166 46%,
      #5ef2a8 58%,#63e6ff 70%,#ffffff 82%,#b78bff 100%);
    background-size:300% 100%;background-repeat:no-repeat;
    background-position:calc(var(--irid,.35) * 100%) 0;
    -webkit-background-clip:text;background-clip:text;
    color:transparent;-webkit-text-fill-color:transparent;
    text-shadow:none;
    filter:drop-shadow(0 1px 3px rgba(3,38,42,.45))
           drop-shadow(0 1px 14px rgba(3,38,42,.55))}
  /* The accent span carries its own orange, which would paint over the slick as
     an opaque word in the middle of it. It stands down to the gradient and keeps
     the cursive face, which is what distinguishes it now. */
  html:not([data-a11y-readable]):not([data-a11y-contrast="high"]) .hero-inner h1.irid .accent{
    color:inherit;-webkit-text-fill-color:transparent}
  /* held still, not taken away - see point 4 */
  html[data-a11y-motion] .hero-inner h1.irid{background-position:35% 0}
  @media (prefers-reduced-motion:reduce){
    .hero-inner h1.irid{background-position:35% 0}
  }
  @media (forced-colors:active){
    .hero-inner h1.irid,.hero-inner h1.irid .accent{
      background:none;color:CanvasText;-webkit-text-fill-color:currentColor;filter:none}
  }
}

/* nav logo rides 5px high in the header */
.header .brand img{transform:translateY(-5px)}

/* home hero: the photo cross-fades with the tagline word. Layers are stacked, only the
   active one is opaque; the first carries .on from the markup so it shows without JS. */
.hero-video .bgimg{opacity:0;transition:opacity .9s ease-in-out}
.hero-video .bgimg.on{opacity:1}
@media (prefers-reduced-motion:reduce){.hero-video .bgimg{transition:none}}

/* The Restorations panel is a PAIR of frames, split down the middle, where the other
   three are one photograph each. It is a .bgimg like them - same stack, same
   cross-fade, same z-index - so the cycle in main.js never has to know which kind of
   layer it is holding; only the inside of this one is different. */
/* IT SITS ABOVE THE VEIL, AND IT SITS TO THE RIGHT OF THE COPY. Both halves of that
   sentence are the same decision, arrived at the hard way.

   The veil is a left-to-right scrim - .92 at the left edge, .8 at 38%, .45 by 70% -
   and the copy column runs from about 2% to 53% of the hero. Run this panel
   full-bleed at 50/50 and the "before" frame lands entirely inside that dark end
   while the "after" frame sits out in the light one: one half a teal ghost, the
   other clean, which is not a comparison, it is two unrelated pictures. Nor can a
   scrim be tucked under the copy instead - anything dark enough to carry white
   display type across a 640px column is dark enough to cover the before frame,
   because they want the same piece of the hero.

   So the pair does not go under the copy at all. It sits in the right 42%, past
   where the headline ends, both frames above the veil and neither of them dimmed,
   with the left of the hero left as the brand ground the veil was already making of
   it. The copy keeps the exact scrim it has always had; nothing about the other
   three panels changes. Raising this one is unconditional and free: an inactive
   layer is opacity 0, so being above the veil costs nothing until it is showing. */
/* WHERE IT STARTS is measured off the headline, not guessed. The pair opens where the
   type stops - past the end of the LONGER of the two headline lines, so neither line
   runs under a photograph - and stops 44px short of the page edge, so the gap on the
   right reads as a margin rather than a crop.
   The anchor is arithmetic on the copy column: the column's left edge is
   (100% - 1180px)/2 + 24px - the wrap centred at its max width, past its padding -
   and the end of "expertly delivered." lands 9.26 headline-em further along. Both
   terms scale, so the seam holds as the h1 grows with the viewport: at 1440px it is
   769px in, which is where the type actually ends. 100%, not 100vw, because 100vw
   counts the scrollbar the wrap does not get to use.
   RE-MEASURE THIS IF THE HEADLINE CHANGES. It is 9.26em because of the words that
   are in it; a longer line would run under the "before" frame. */
.hero-video .bgsplit{display:flex;justify-content:flex-end;align-items:stretch;z-index:2;
  left:calc((100% - var(--max))/2 + 24px + 9.26 * clamp(2.4rem,5.2vw,4.2rem));right:44px;
  width:auto}
/* Half the panel each, which is the panel's own width now rather than a share of the
   hero. The old min(21%,430px) cap did that job when the pair was measured against
   the whole hero; the anchor above bounds it instead. */
.hero-video .bgsplit img{flex:1 1 50%;width:50%;height:100%;
  min-width:0;object-fit:cover;object-position:center 45%}
/* the outer edge fades into the ground rather than cutting against it */
.hero-video .bgsplit img:first-child{
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 13%);
          mask-image:linear-gradient(90deg,transparent 0,#000 13%)}
.hero-video .bgsplit::after{content:"";position:absolute;top:0;bottom:0;
  left:50%;margin-left:-1px;width:2px;background:rgba(255,255,255,.72);box-shadow:0 0 16px rgba(0,0,0,.4)}
/* Labels either side of the divider, at the top of the frames. They are above the
   veil with the photographs, so they carry only enough ground to hold their own. */
.hero-video .ba-tag{position:absolute;top:clamp(22px,3.5vw,42px);
  font-family:var(--head);text-transform:uppercase;letter-spacing:.14em;font-size:.7rem;font-weight:600;
  color:#fff;background:rgba(4,63,71,.82);border:1px solid rgba(255,255,255,.3);
  padding:5px 12px;border-radius:3px;white-space:nowrap}
.hero-video .ba-tag.l{right:calc(50% + 14px)}
.hero-video .ba-tag.r{left:calc(50% + 14px)}

/* Under 1200px the copy is a fixed 640px against a shrinking hero and there is no
   clear right-hand end left to put the pair in, so it goes back to a plain 50/50
   full-bleed UNDER the veil, with the other three. That is not a downgrade: below
   900px the veil is top-to-bottom rather than left-to-right, so both frames are
   dimmed by the same amount and the two are still being compared like with like -
   which was the one thing the full-width layout could not do up here. */
@media (max-width:1199px){
  .hero-video .bgsplit{z-index:0;justify-content:stretch;left:0;right:0;width:100%}
  .hero-video .bgsplit img:first-child{-webkit-mask-image:none;mask-image:none}
  .hero-video .ba-tag.l{right:auto;left:50%;transform:translateX(calc(-100% - 14px))}
  .hero-video .ba-tag.r{left:calc(50% + 14px)}
}

/* ============================================================================
   ACCESSIBILITY
   1. Focus, skip link and screen-reader helpers
   2. The accessibility panel (text size, spacing, contrast, colour vision, ...)
   3. The AI chat widget
   Everything here is self-contained: no third-party script, no CDN, no font
   download. The panel writes data-* attributes on <html> and the rules below
   react to them, so nothing rewrites the DOM behind a screen reader's back.
   ============================================================================ */

/* ---- 1. focus, skip link, sr-only ---------------------------------------- */
.sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.skip-link{position:absolute;left:8px;top:-60px;z-index:200;background:var(--orange);color:#fff;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.85rem;padding:12px 18px;border-radius:0 0 4px 4px;transition:top .15s}
.skip-link:focus{top:0;text-decoration:none}
:focus-visible{outline:3px solid var(--focus,#b4551c);outline-offset:2px;border-radius:2px}
.hero-video :focus-visible,.hero-inner :focus-visible,.cta-band :focus-visible,.footer :focus-visible,.topbar :focus-visible{outline-color:#ffd9bd}
main:focus{outline:none}
/* the mouse-driven ring on inputs stays, but keyboard focus gets the stronger one */
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--focus,#b4551c);outline-offset:1px}

/* ---- 2. accessibility panel ---------------------------------------------- */
.a11y-open{position:fixed;left:18px;bottom:18px;z-index:120;width:52px;height:52px;border-radius:50%;border:2px solid #fff;background:var(--navy);color:#fff;display:grid;place-items:center;cursor:pointer;box-shadow:0 6px 20px rgba(0,0,0,.28);padding:0}
.a11y-open svg{width:26px;height:26px;display:block}
.a11y-open:hover{background:var(--ink)}
.a11y-panel{position:fixed;left:18px;bottom:82px;z-index:121;width:330px;max-width:calc(100vw - 36px);max-height:min(76vh,640px);overflow-y:auto;overscroll-behavior:contain;background:#fff;color:var(--ink);border:1px solid var(--line);border-radius:8px;box-shadow:0 18px 50px rgba(0,0,0,.28);padding:18px 18px 20px;font-size:.9rem}
.a11y-panel[hidden]{display:none}
.a11y-panel h2{font-size:1.05rem;margin:0 0 2px;color:var(--ink)}
.a11y-panel .a11y-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:12px}
.a11y-panel .a11y-note{font-size:.76rem;color:var(--muted);margin:0;line-height:1.45}
.a11y-close{border:1px solid var(--line);background:var(--bg-2);color:var(--ink);border-radius:4px;width:30px;height:30px;font-size:1.1rem;line-height:1;cursor:pointer;flex:0 0 auto}
.a11y-close:hover{background:var(--line)}
.a11y-group{border:0;margin:0 0 14px;padding:0}
.a11y-group legend{font-family:var(--head);text-transform:uppercase;letter-spacing:.09em;font-size:.72rem;color:var(--muted);font-weight:600;padding:0;margin-bottom:7px}
.a11y-row{display:flex;flex-wrap:wrap;gap:6px}
.a11y-row button{flex:1 1 auto;min-width:44px;font:inherit;font-size:.82rem;padding:8px 6px;border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:4px;cursor:pointer;text-align:center}
.a11y-row button:hover{background:var(--bg-2)}
.a11y-row button[aria-pressed="true"]{background:var(--navy);border-color:var(--navy);color:#fff;font-weight:600}
.a11y-col{display:grid;gap:6px}
.a11y-col button{text-align:left;padding:9px 10px}
.a11y-col button[aria-pressed="true"]::after{content:"\2713";float:right;font-weight:700}
.a11y-reset{width:100%;margin-top:4px;font:inherit;font-size:.82rem;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;padding:10px;border:2px solid var(--navy);background:#fff;color:var(--navy);border-radius:4px;cursor:pointer}
.a11y-reset:hover{background:var(--navy);color:#fff}
@media (max-width:560px){.a11y-panel{left:10px;right:10px;width:auto;bottom:76px}.a11y-open{left:12px;bottom:12px;width:46px;height:46px}}
@media print{.a11y-open,.a11y-panel,.sp-chat-open,.sp-chat{display:none!important}}

/* --- panel effects. All keyed off <html data-*>, set by the panel and restored
   from localStorage by an inline boot script before first paint. --- */
html[data-a11y-font="1"]{font-size:112.5%}
html[data-a11y-font="2"]{font-size:125%}
html[data-a11y-font="3"]{font-size:140%}
html[data-a11y-font="4"]{font-size:160%}
html[data-a11y-spacing="1"] body,html[data-a11y-spacing="1"] p,html[data-a11y-spacing="1"] li,html[data-a11y-spacing="1"] dd{line-height:1.9}
html[data-a11y-spacing="1"] p,html[data-a11y-spacing="1"] li{margin-bottom:1.3em}
html[data-a11y-spacing="2"] body,html[data-a11y-spacing="2"] p,html[data-a11y-spacing="2"] li,html[data-a11y-spacing="2"] dd{line-height:2.2;letter-spacing:.06em;word-spacing:.16em}
html[data-a11y-spacing="2"] p,html[data-a11y-spacing="2"] li{margin-bottom:1.6em}
/* WCAG 1.4.12 wants text to survive these; heights that were fixed for one line must give */
html[data-a11y-spacing] .industry,html[data-a11y-spacing] .btn,html[data-a11y-spacing] .stat-tile{height:auto}

html[data-a11y-font] .btn,html[data-a11y-font] .industry,html[data-a11y-font] .nav a{white-space:normal}

/* readable font: drops the condensed uppercase display face for a plain sans */
html[data-a11y-readable] body{--head:"Verdana","DejaVu Sans","Segoe UI",system-ui,sans-serif;--body:"Verdana","DejaVu Sans","Segoe UI",system-ui,sans-serif}
html[data-a11y-readable] h1,html[data-a11y-readable] h2,html[data-a11y-readable] h3,html[data-a11y-readable] h4,
html[data-a11y-readable] .eyebrow,html[data-a11y-readable] .btn,html[data-a11y-readable] .industry,html[data-a11y-readable] .nav a{text-transform:none;letter-spacing:normal}
html[data-a11y-readable] h1,html[data-a11y-readable] h2{line-height:1.2}

/* link highlighting */
html[data-a11y-links] a:not(.btn):not(.brand):not(.skip-link){text-decoration:underline!important;text-underline-offset:2px;text-decoration-thickness:2px;background:#fff8d6;color:#2b1a00!important;box-shadow:0 0 0 2px #fff8d6}
html[data-a11y-links] .footer a:not(.btn),html[data-a11y-links] .topbar a,html[data-a11y-links] .cta-band a:not(.btn),html[data-a11y-links] .hero-video a:not(.btn),html[data-a11y-links] .hero-inner a:not(.btn){background:#231a00;color:#ffe9a8!important;box-shadow:0 0 0 2px #231a00}

/* big cursor */
html[data-a11y-cursor],html[data-a11y-cursor] *{cursor:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'><path d='M8 4l26 18-11 2 6 13-5 2-6-13-10 8z' fill='%23fff' stroke='%23000' stroke-width='2.5' stroke-linejoin='round'/></svg>") 6 4,auto!important}
html[data-a11y-cursor] a,html[data-a11y-cursor] button,html[data-a11y-cursor] a *,html[data-a11y-cursor] button *{cursor:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'><path d='M18 4c2 0 3 1 3 3v14l3-4c1-2 4-2 5 0l8 12c2 3 2 6 1 9l-2 5H18l-8-11c-1-2 0-5 3-5l2 2V7c0-2 1-3 3-3z' fill='%23fff' stroke='%23000' stroke-width='2.5' stroke-linejoin='round'/></svg>") 14 3,pointer!important}

/* reading guide */
.a11y-guide{position:fixed;left:0;right:0;height:44px;z-index:119;pointer-events:none;border-top:3px solid rgba(0,0,0,.85);border-bottom:3px solid rgba(0,0,0,.85);background:rgba(255,238,120,.18);display:none}
html[data-a11y-guide] .a11y-guide{display:block}

/* stop animation: the same switch the OS reduced-motion setting throws */
html[data-a11y-motion] *,html[data-a11y-motion] *::before,html[data-a11y-motion] *::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
html[data-a11y-motion] .hero-video .bgimg{opacity:0}
html[data-a11y-motion] .hero-video .bgimg.on{opacity:1}

/* colour vision correction. The filter goes on the page regions rather than on
   <html> or <body>: a filter makes its element a containing block, which would
   break position:fixed for the panel, the chat and the sticky header. */
body>.topbar,body>.header,body>main,body>.footer{filter:var(--a11y-cvd,none)}

/* contrast modes */
html[data-a11y-contrast="high"]{--ink:#000;--muted:#1a1a1a;--line:#000;--bg:#fff;--bg-2:#fff;--navy:#00404a;--navy-2:#00404a;--navy-3:#00404a;--teal:#00404a;--link:#00404a;--orange:#8f4212;--orange-2:#6d3009;--orange-ink:#8f4212;--focus:#000}
html[data-a11y-contrast="high"] body{background:#fff;color:#000}
html[data-a11y-contrast="high"] .hero-inner .bg img,html[data-a11y-contrast="high"] .hero-video .bgimg,html[data-a11y-contrast="high"] .hero-inner .bg::after,html[data-a11y-contrast="high"] .hero-video .veil{display:none}
html[data-a11y-contrast="high"] .hero-video,html[data-a11y-contrast="high"] .hero-inner,html[data-a11y-contrast="high"] .cta-band,html[data-a11y-contrast="high"] .footer,html[data-a11y-contrast="high"] .topbar,html[data-a11y-contrast="high"] .cred-bar{background:#00303a!important;color:#fff}
html[data-a11y-contrast="high"] .footer,html[data-a11y-contrast="high"] .footer a,html[data-a11y-contrast="high"] .footer .bottom,html[data-a11y-contrast="high"] .topbar a,html[data-a11y-contrast="high"] .cta-band p,html[data-a11y-contrast="high"] .hero-video .lead,html[data-a11y-contrast="high"] .hero-inner .lead{color:#fff!important}
html[data-a11y-contrast="high"] .hero-video .eyebrow,html[data-a11y-contrast="high"] .hero-inner .eyebrow,html[data-a11y-contrast="high"] .cta-band .eyebrow,html[data-a11y-contrast="high"] .accent{color:#ffd98a!important}
html[data-a11y-contrast="high"] .btn-primary{background:#8f4212;border-color:#fff;color:#fff}
html[data-a11y-contrast="high"] .card,html[data-a11y-contrast="high"] .panel,html[data-a11y-contrast="high"] .feature,html[data-a11y-contrast="high"] .active,html[data-a11y-contrast="high"] .industry,html[data-a11y-contrast="high"] .stat-tile,html[data-a11y-contrast="high"] input,html[data-a11y-contrast="high"] select,html[data-a11y-contrast="high"] textarea{border:2px solid #000!important;background:#fff}
html[data-a11y-contrast="high"] .industries{background:#000;gap:2px}
html[data-a11y-contrast="high"] a:not(.btn){text-decoration:underline}

html[data-a11y-contrast="dark"]{--ink:#f2f5f7;--muted:#c2ccd3;--line:#3b464e;--bg:#12181c;--bg-2:#1b2329;--navy:#4fd0dd;--navy-2:#0e3f45;--navy-3:#0e3f45;--teal:#4fd0dd;--link:#7fdce7;--orange:#ffa668;--orange-2:#ffbb8a;--orange-ink:#ffa668;--focus:#ffd9bd;--shadow:0 10px 30px rgba(0,0,0,.5)}
html[data-a11y-contrast="dark"] body{background:#12181c;color:#f2f5f7}
html[data-a11y-contrast="dark"] .header,html[data-a11y-contrast="dark"] .panel,html[data-a11y-contrast="dark"] .card,html[data-a11y-contrast="dark"] .industry,html[data-a11y-contrast="dark"] .split .text{background:#1b2329}
html[data-a11y-contrast="dark"] .split .text.on-bg{background:#161d22}
html[data-a11y-contrast="dark"] input,html[data-a11y-contrast="dark"] select,html[data-a11y-contrast="dark"] textarea{background:#0e1418;color:#f2f5f7;border-color:#3b464e}
html[data-a11y-contrast="dark"] .hero-video .veil{background:linear-gradient(90deg,rgba(4,40,45,.94) 0%,rgba(4,40,45,.86) 45%,rgba(4,40,45,.6) 100%)}
html[data-a11y-contrast="dark"] .hero-inner .bg::after{background:linear-gradient(90deg,rgba(4,40,45,.95) 30%,rgba(4,40,45,.7) 100%)}
html[data-a11y-contrast="dark"] .cta-band,html[data-a11y-contrast="dark"] .footer,html[data-a11y-contrast="dark"] .topbar,html[data-a11y-contrast="dark"] .cred-bar,html[data-a11y-contrast="dark"] .mark-note-band{background:#0e3f45}
html[data-a11y-contrast="dark"] .btn-primary{background:#ffa668;border-color:#ffa668;color:#141414}
html[data-a11y-contrast="dark"] .btn-outline{color:#4fd0dd;border-color:#4fd0dd}
html[data-a11y-contrast="dark"] .btn-outline-orange{background:transparent;color:#ffa668;border-color:#ffa668}
html[data-a11y-contrast="dark"] .a11y-panel,html[data-a11y-contrast="dark"] .sp-chat{background:#1b2329;border-color:#3b464e;color:#f2f5f7}
html[data-a11y-contrast="dark"] .a11y-row button,html[data-a11y-contrast="dark"] .a11y-close{background:#12181c;color:#f2f5f7;border-color:#3b464e}
html[data-a11y-contrast="dark"] .a11y-row button[aria-pressed="true"]{background:#4fd0dd;color:#0e1418;border-color:#4fd0dd}
html[data-a11y-contrast="dark"] .mtn{filter:invert(1) hue-rotate(180deg) saturate(.4) brightness(.35)}

/* ---- 3. AI chat -----------------------------------------------------------
   The disclosure is not decoration: every surface says plainly that the replies
   come from an automated assistant, and the human routes (phone, bid form) sit
   inside the widget rather than behind it. Nothing here auto-opens.            */
.sp-chat-open{position:fixed;right:18px;bottom:18px;z-index:120;display:inline-flex;align-items:center;gap:9px;padding:12px 18px 12px 14px;border:2px solid #fff;border-radius:999px;background:var(--navy);color:#fff;font-family:var(--head);text-transform:uppercase;letter-spacing:.07em;font-size:.8rem;font-weight:600;cursor:pointer;box-shadow:0 6px 20px rgba(0,0,0,.28)}
.sp-chat-open svg{width:20px;height:20px;flex:0 0 auto}
.sp-chat-open:hover{background:var(--ink)}
.sp-chat-open .sp-ai-tag{background:#fff;color:var(--navy);border-radius:3px;padding:2px 5px;font-size:.66rem;letter-spacing:.08em}
.sp-chat{position:fixed;right:18px;bottom:82px;z-index:121;width:380px;max-width:calc(100vw - 36px);height:min(70vh,560px);background:#fff;border:1px solid var(--line);border-radius:8px;box-shadow:0 18px 50px rgba(0,0,0,.28);display:flex;flex-direction:column;overflow:hidden}
.sp-chat[hidden]{display:none}
.sp-chat-hd{background:var(--navy);color:#fff;padding:13px 14px;display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex:0 0 auto}
.sp-chat-hd b{font-family:var(--head);text-transform:uppercase;letter-spacing:.06em;font-size:.92rem;display:block}
.sp-chat-hd span{font-size:.74rem;color:#dff2f5;display:block;margin-top:2px}
.sp-chat-x{border:1px solid rgba(255,255,255,.5);background:transparent;color:#fff;border-radius:4px;width:28px;height:28px;font-size:1.1rem;line-height:1;cursor:pointer;flex:0 0 auto}
.sp-chat-x:hover{background:rgba(255,255,255,.16)}
.sp-ai-banner{background:#fff5e8;border-bottom:1px solid #f0d5b4;color:#6b3d13;font-size:.76rem;line-height:1.45;padding:9px 14px;flex:0 0 auto}
.sp-ai-banner b{font-weight:700}
.sp-chat-log{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;padding:14px;display:flex;flex-direction:column;gap:10px;background:var(--bg-2)}
.sp-msg{max-width:86%;padding:9px 12px;border-radius:10px;font-size:.88rem;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}
.sp-msg.ai{background:#fff;border:1px solid var(--line);border-bottom-left-radius:3px;align-self:flex-start}
.sp-msg.me{background:var(--navy);color:#fff;border-bottom-right-radius:3px;align-self:flex-end}
.sp-msg.sys{align-self:center;background:transparent;color:var(--muted);font-size:.76rem;text-align:center;max-width:100%}
.sp-msg .sp-who{display:block;font-family:var(--head);text-transform:uppercase;letter-spacing:.08em;font-size:.64rem;opacity:.75;margin-bottom:3px}
.sp-chat-form{flex:0 0 auto;border-top:1px solid var(--line);padding:10px 12px 12px;background:#fff}
.sp-otp{margin:0 0 9px;padding:8px 11px;border-radius:4px;background:#fff5e8;border:1px solid #f0d5b4;color:#6b3d13;font-size:.8rem;line-height:1.45}
.sp-otp-x{font:inherit;font-size:.78rem;background:none;border:0;padding:0;color:#6b3d13;text-decoration:underline;cursor:pointer}
.sp-chat-in{display:flex;gap:8px;align-items:flex-end}
.sp-chat-in textarea{flex:1 1 auto;resize:none;min-height:42px;max-height:110px;padding:9px 10px;font-size:.88rem;line-height:1.4}
.sp-chat-send{flex:0 0 auto;border:0;background:var(--orange);color:#fff;border-radius:4px;width:42px;height:42px;display:grid;place-items:center;cursor:pointer}
.sp-chat-send svg{width:18px;height:18px}
.sp-chat-send:hover{background:var(--orange-2)}
.sp-chat-send:disabled{opacity:.5;cursor:default}
.sp-chat-foot{margin:8px 0 0;font-size:.72rem;color:var(--muted);line-height:1.45}
.sp-chat-foot a{text-decoration:underline}
.sp-typing{display:inline-flex;gap:3px;align-items:center}
.sp-typing i{width:5px;height:5px;border-radius:50%;background:var(--muted);animation:spdot 1.1s infinite}
.sp-typing i:nth-child(2){animation-delay:.15s}.sp-typing i:nth-child(3){animation-delay:.3s}
@keyframes spdot{0%,60%,100%{opacity:.25}30%{opacity:1}}
@media (prefers-reduced-motion:reduce){.sp-typing i{animation:none;opacity:.6}}
@media (max-width:560px){.sp-chat{right:10px;left:10px;width:auto;bottom:74px;height:min(74vh,520px)}.sp-chat-open{right:12px;bottom:12px;padding:10px 14px 10px 12px;font-size:.74rem}}

/* the same conversation embedded in the Contact page, so it is reachable without
   hunting for a floating button */
.sp-chat-inline{border:1px solid var(--line);border-radius:var(--r);overflow:hidden;background:#fff}
.sp-chat-inline .sp-chat{position:static;width:auto;max-width:none;height:460px;border:0;border-radius:0;box-shadow:none;display:flex}
.sp-chat-inline .sp-chat-x{display:none}

/* ============================================================================
   WCAG AA CONTRAST PASS
   The brand palette was built for large display type and fails AA where it is
   used for ordinary text. Measured on the original colours:

     white on the orange button   2.48:1   needs 4.5   <- the main CTA, every page
     orange .eyebrow on white     2.48:1   needs 4.5
     the .accent word on teal     1.48:1   needs 3.0
     teal link text on white      3.69:1   needs 4.5
     white text on brand teal     3.69:1   needs 4.5   <- footer, CTA band, top bar
     industry labels on white     3.69:1   needs 4.5

   Nothing below changes hue, only depth: each colour is the lightest value in
   the same family that clears the threshold. The originals are in the comments,
   so reverting is an edit to this one block.

   --orange keeps its original value throughout: it is still correct for rules,
   icons, borders and fills, which carry no text. Only the tokens used FOR text
   and for the surfaces text sits on are darkened.
   ============================================================================ */
:root{
  --teal:#056b74;        /* was #0793a1 - inline link text        3.69 -> 6.25 */
  --orange-ink:#b4551c;  /* orange text on light backgrounds      2.48 -> 4.93 */
  --orange-btn:#bd571c;  /* orange fill under a white label       2.48 -> 4.62 */
  --orange-btn-2:#a34d18;/* its hover                                    5.78 */
  --surface-teal:#057078;/* teal fill under normal-size white text 3.69 -> 5.84 */
  --veil-rgb:4,86,94;    /* was 6,110,120 - the hero scrim         3.69 -> 8.41 */
  --accent-dark:#ffb07a; /* the .accent word on a teal surface     1.48 -> 4.70 */
  --eyebrow-dark:#ffe2b8;/* was #f8b348 - eyebrow on a teal surface 3.21 -> 4.68 */
  --focus:#b4551c;       /* focus ring, also needs 3:1 against white      4.93 */
}
/* orange TEXT */
.eyebrow,.accent,.card .more,.member span,.faq summary::after,
.testimonial .who span,.testimonial .stars,.pcard dt,
.nav a:hover,.nav li.active>a,.btn-outline-orange{color:var(--orange-ink)}
.btn-outline-orange{border-color:var(--orange-ink)}
/* orange FILLS that sit under white text or a white glyph */
.btn-primary,.btn-outline-orange:hover,.skip-link,.sp-chat-send,
.ba .handle::after,.footer .social a:hover{background:var(--orange-btn)}
.btn-primary{border-color:var(--orange-btn)}
.btn-primary:hover,.sp-chat-send:hover{background:var(--orange-btn-2);border-color:var(--orange-btn-2)}
input:focus,select:focus,textarea:focus{outline-color:var(--focus);border-color:var(--focus)}
/* teal surfaces that carry normal-size white text */
.topbar,.cred-bar,.cta-band,.footer,.mark-note-band{background:var(--surface-teal)}
.topbar{color:#fff}
.hero-inner .bg::after{background:linear-gradient(90deg,rgba(var(--veil-rgb),.92) 0%,rgba(var(--veil-rgb),.86) 46%,rgba(var(--veil-rgb),.5) 72%,rgba(var(--veil-rgb),.2) 100%)}
.hero-video .veil{background:linear-gradient(90deg,rgba(var(--veil-rgb),.92) 0%,rgba(var(--veil-rgb),.8) 38%,rgba(var(--veil-rgb),.45) 70%,rgba(var(--veil-rgb),.3) 100%)}
@media (max-width:900px){
  .hero-video .veil{background:linear-gradient(180deg,rgba(var(--veil-rgb),.9),rgba(var(--veil-rgb),.76))}
  .hero-inner .bg::after{background:linear-gradient(180deg,rgba(var(--veil-rgb),.88) 0%,rgba(var(--veil-rgb),.92) 100%)}
}
/* accents sitting ON those surfaces read the other way round: they have to get
   lighter, not darker, to pull away from the teal */
.hero-inner .eyebrow,.hero-video .eyebrow,.cta-band .eyebrow{color:var(--eyebrow-dark)}
.hero-inner .accent,.hero-video .accent,.cta-band .accent,.hero .rotator .w{color:var(--accent-dark)}
.footer,.cta-band p,.hero-video .lead,.hero-inner .lead{color:#f0f8fa}
.footer .bottom{color:#e4f3f5}
.project .cap span{color:#f2fbfc}
/* small labels that were using the display teal at body sizes */
.industry{color:var(--teal)}

/* --- part two: --navy was doing three jobs at once ---------------------------
   It was the display-heading colour, the colour of every small uppercase label,
   AND the fill behind white text. At #0793a1 that is 3.69:1, which clears AA for
   display type (needs 3.0) and fails it for everything else (needs 4.5). Split
   into three tokens so each job gets a value that passes:
     --navy       #0793a1  unchanged: h1/h2 display type, icon and graphic fills
     --navy-ink   #056b74  every text use below display size          -> 6.25:1
     --surface-teal        the fills that carry white text            -> 5.84:1  */
:root{ --navy-ink:#056b74; }
h3,h4,.btn-outline,.btn-outline-light:hover,.nav a,.stat,.industry,.quote,.member b,
.towns li,label,.contact-card b,.tab,.faq summary,.hero .rotator,.why-tile b,
.testimonial .who,.trades li,.photo-credit b,.client-credit b,
.a11y-reset,.sp-chat-open .sp-ai-tag{color:var(--navy-ink)}
.btn-outline:hover,.card .icon,.tab.active,.a11y-open,
.a11y-row button[aria-pressed="true"],.a11y-reset:hover,.sp-chat-open,
.sp-chat-hd,.sp-msg.me{background:var(--surface-teal)}
.a11y-row button[aria-pressed="true"]{border-color:var(--surface-teal)}
/* the hero's own background is the scrim colour, not the bright teal: it is what
   shows through the veil and what shows if the photo never loads */
.hero-inner,.hero-video{background:#04565e}
@media (prefers-reduced-motion:reduce){.hero-video{background:#04565e url(../img/hero-cville-poster.jpg) center/cover}}
.tagline{color:rgba(255,255,255,.82)}   /* was .55 -> 3.0:1 on the scrim; now 6.4 */
.tagline span.on,.tagline span:hover{color:var(--accent-dark)}
.crumbs,.crumbs a{color:#ffe2c9}

/* ---- nav mega menu under the accessibility panel's modes --------------------
   The panel is a header surface, so it follows .header rather than the page
   background. Its link colour is --ink and its group headings --muted, both of
   which the contrast modes already retune, so only the surfaces, the display
   faces and the motion switch need saying here. The dark-mode nav colour is
   fixed at the same time: --navy-ink is not redefined for dark, so the top-level
   labels and the burger sheet were dark teal on dark grey. */
html[data-a11y-contrast="dark"] .nav{background:#1b2329}
html[data-a11y-contrast="dark"] .mega{background:#1b2329;border-top-color:#3b464e}
html[data-a11y-contrast="dark"] .nav>li>a:not(.btn),html[data-a11y-contrast="dark"] .nav>li>button{color:var(--navy)}
html[data-a11y-contrast="dark"] .nav .mega a{color:var(--ink)}
html[data-a11y-contrast="dark"] .nav .mega-h{color:var(--muted)}
html[data-a11y-contrast="high"] .mega{border-top:2px solid #000;box-shadow:none}
html[data-a11y-font] .nav>li>button{white-space:normal}
html[data-a11y-readable] .nav>li>button,html[data-a11y-readable] .mega-h,
html[data-a11y-readable] label,html[data-a11y-readable] legend,html[data-a11y-readable] .tab,
html[data-a11y-readable] .crumbs,html[data-a11y-readable] .tagline,html[data-a11y-readable] .stat,
html[data-a11y-readable] .cred{text-transform:none;letter-spacing:normal}
/* Stop animation also has to clear the delay that holds a closed panel visible,
   or its links stay in the tab order for a quarter of a second after it shuts */
html[data-a11y-motion] .mega,html[data-a11y-motion] .nav li.open>.mega{transition:none!important}

/* ============================================================================
   HOME HERO: the hand-lettered word in the headline
   Yellowtail is a sign-painter's brush script - one weight, an even heavy stroke,
   working letterforms - so it holds in white over the photo scrim, where the
   hairlines of Great Vibes, Sacramento or Allura would break up and vanish.
   It cannot take Oswald's metrics. The h1's uppercase transform would kill the
   joins, its .01em tracking would pull the connected letters apart, and its
   line-height:1.05 is cut to the cap height, which leaves a script's ascenders
   sitting on the edge of the line box. All three are overridden below.
   Measured against the real font files: Oswald caps top out at .810em, and the
   script at 1.25em puts its x-height at .49em (60% of the cap height, where a
   script wants to be) and its b/t ascenders at .92em, just clear of the caps and
   still inside the 1.14 line box. Scoped to .hero-photo: the home hero only.
   ============================================================================ */
.hero-photo h1{line-height:1.14;text-shadow:0 1px 14px rgba(3,38,42,.45),0 1px 3px rgba(3,38,42,.35)}
/* Two shapes. A .script span inside an h1 is one hand-lettered word among caps, so it
   steps up 1.25em to match the cap height it sits beside. h1.script is the whole
   headline in the face: it keeps the h1's own clamp, but a brush script needs room for
   its ascenders and descenders, so the line-height opens from Oswald's 1.05 and the
   .01em tracking that Oswald wants goes to 0 - a script is already spaced by its
   letterforms. Kaushan Script ships one weight; inheriting the h1's 600 would make the
   browser synthesise a smeared fake bold. */
h1 .script,h1.script{font-family:var(--script);font-weight:400;letter-spacing:0;text-transform:none}
/* 1.55em, not the 1.25em a brush face took: Pinyon Script is a hairline
   copperplate hand on a small x-height and long swashed ascenders, and reads
   about a quarter of a size small beside caps set at the same font-size. */
h1 .script{display:inline-block;font-size:1.55em;line-height:.95;padding-right:.1em;vertical-align:baseline}
h1.script{line-height:1.3;font-size:clamp(2.7rem,6vw,4.9rem)}
.hero-inner h1{line-height:1.14}
/* font-weight:400 because Yellowtail ships one weight - inheriting the h1's 600
   makes the browser synthesise a smeared fake bold. padding-right because the exit
   stroke of the last letter overruns its advance width by .083em. */
/* readable font: the panel trades the condensed display face for a plain sans, so
   the script goes with it - it is the least legible thing on the page for the
   visitor who just asked for legible. */
html[data-a11y-readable] .hero-photo h1{line-height:1.2;text-shadow:none}
/* Readable and high contrast are both chosen for legibility. A brush script is the
   least legible thing on the page, so it goes with the condensed display face rather
   than surviving the setting that exists to remove it. font:inherit also hands the
   word back the panel's letter-spacing, so data-a11y-spacing is not overridden. */
html[data-a11y-readable] h1 .script,html[data-a11y-contrast="high"] h1 .script{font:inherit;letter-spacing:inherit;padding-right:0;display:inline;vertical-align:baseline}
html[data-a11y-readable] h1.script,html[data-a11y-contrast="high"] h1.script{font-family:var(--head);letter-spacing:.01em;text-transform:uppercase;font-size:clamp(2.4rem,5.2vw,4.2rem);line-height:1.05}
/* a fine script over a photo needs the scrim doing more work than a brush face did */
.hero-photo h1.script{text-shadow:0 2px 18px rgba(3,38,42,.6),0 1px 4px rgba(3,38,42,.5)}
html[data-a11y-contrast="high"] .hero-photo h1{text-shadow:none}

/* ================= cred bar: two offices ================= */
/* The location credential names Charlottesville and Richmond now. Set in Oswald 600 at
   .78rem with .1em tracking that is one glyph wider than "Charlottesville, Virginia",
   so the four items still measure roughly 820px against the 1132px content box and
   nothing moves on a wide screen. The band between the 700px phone layout and the 980px
   nav breakpoint is where the row gets close, and a .cred that wraps to a second line
   keeps its left divider and sits off centre - that is the failure to avoid, so the
   gaps tighten there. It also buys headroom for the accessibility panel's large-text
   settings, which scale the root font up to 160%. Below 700px the row is already a 2x2
   grid (flex:1 1 45%) and the label wraps inside its own tile: centre it so the second
   line does not hang left of the first. */
@media (max-width:1040px){.cred{padding:16px 14px;letter-spacing:.07em}}
@media (max-width:700px){.cred{justify-content:center;text-align:center;line-height:1.35}}


/* ============================================================================
   THE HOME PAGE: SOFTER TYPE, REVEALS, AND THE KALEIDOSCOPE BAND

   Everything from here down is scoped to body.page-index, which is the live home
   page and nothing else. The eleven style-*.html previews render exactly the same
   markup under body.page-style-<slug>, so none of this reaches them, and no inner
   page is touched either.

   Three parts, in order:

     1. THE DISPLAY FACE      unconditional
     2. THE REVEALS           only once js/style-motion.js is running (html.sp-motion)
     3. THE KALEIDOSCOPE BAND only once js/kaleidoscope.js has compiled and linked
                              the shader (.kaleido-on on the section)

   Parts 2 and 3 are additive and gated on a class the browser only ever sees if
   the module behind it is actually working. Script blocked, no WebGL2, a driver
   that rejects the shader, a module that throws - in every one of them not a
   single selector below matches, and the page is the page it was before any of
   it: the same layout and the same copy, in a softer face, standing still.
   ============================================================================ */

/* ---- 1. THE DISPLAY FACE ---------------------------------------------------
   Oswald is a condensed grotesk cut for signage - flat sides, tight fit, no
   curves it can avoid. Barlow Semi Condensed is the same measure with rounded
   terminals, open counters and low stroke contrast, so it softens the page
   without moving anything on it: every heading keeps its width, every authored
   <br> still breaks where it did, and the nav row still fits at 980px.

   Oswald stays in the stack behind it on purpose. The shared <head> fetches it
   for every page anyway, so a Barlow request that fails falls back to a face
   that is certainly already there rather than dropping to Arial Narrow.

   Barlow sets a smaller cap height than Oswald and reads a shade lighter at the
   same weight, so h2 and h3 step up one weight and the caps take a little more
   tracking to hold their presence. h1 is deliberately left alone: on this page
   it is the cursive, which ships a single weight and would be synthesised into a
   smeared fake bold by anything above 400.

   :not([data-a11y-readable]) is load-bearing. The accessibility panel's Readable
   setting swaps both families for Verdana on `html[data-a11y-readable] body`,
   which is the same specificity as this rule and earlier in the file - without
   the guard, this would quietly win and the visitor who asked for a plain face
   would keep the display one.                                                 */
html:not([data-a11y-readable]) body.page-index{
  --head:"Barlow Semi Condensed","Oswald","Barlow Condensed","Arial Narrow",sans-serif}
body.page-index h2,body.page-index h3{font-weight:700}
body.page-index h2{letter-spacing:.015em}

/* Cursive beside the caps. The hero headline is already set in the script face
   whole; this is the one other place on the page that takes it - the "Family
   owned. Charlottesville based." heading over the aerial, where the sentence is
   about the family and not about the work. h1 .script already exists for the
   inner-page headlines; this extends the same two rules to an h2 and is scoped
   to this page so the previews, which set their own display faces, do not
   inherit a cursive they were not designed around.
   font-weight:400 for the same reason as the h1: one weight in the file. The
   size step is smaller than the h1's 1.25em because an h2 is set at a third of
   the size, where a script's x-height needs proportionally less correction. */
body.page-index h2 .script{font-family:var(--script);font-weight:400;letter-spacing:0;
  text-transform:none;display:inline-block;font-size:1.64em;line-height:.9;
  padding-right:.08em;vertical-align:baseline}
/* Readable and high contrast both exist to make the page legible, and a cursive
   is the least legible thing on it, so it goes with the display face exactly as
   the h1's does. */
html[data-a11y-readable] body.page-index h2 .script,
html[data-a11y-contrast="high"] body.page-index h2 .script{
  font:inherit;letter-spacing:inherit;padding-right:0;display:inline;vertical-align:baseline}

/* ---- 1b. THE HERO HEADLINE --------------------------------------------------
   Both lines in the hero's own face. The headline used to lead with "Building
   better" in the cursive, which needed a span, a size correction against the
   caps beside it, an opened line-height for the ascender loops and a three-layer
   scrim for the hairline strokes. With the script out of the hero all four go
   with it: the h1 is an ordinary h1, so the display face, the uppercase
   transform, the caps size, the base 1.14 line-height and the lighter scrim
   measured for caps all arrive from the rules above with nothing restated here.
   The weight is the one thing left. Barlow Semi Condensed is loaded at 500, 600
   and 700 for this page and the headline takes the heaviest - it is the only
   thing on the page set against a photograph at that size.
   The cursive is not gone from the site: the inner-page headlines still carry it.
   It is out of this page altogether - out of the hero, and out of the
   "Headquartered in / Charlottesville, Virginia." heading over the aerial.    */
.page-index .hero-photo h1{font-weight:700}

/* ---- 2. THE REVEALS --------------------------------------------------------
   js/style-motion.js supplies the hooks and nothing else: it adds html.sp-motion
   once it is live, wraps each word of the headings in .sp-w > .sp-wi so they can
   be addressed one at a time, marks each grid .sp-group with --sp-i on its
   children, marks the standalone blocks .sp-rise, and adds .in to whichever of
   them has come into view. Every hidden starting state below is gated on
   html.sp-motion, so nothing here can leave content stuck invisible.

   This page is not one of the expressive previews and is not styled like one.
   The numbers are deliberately smaller than style-motion.css's defaults - a 16px
   lift instead of 34, .62s instead of .9 - so the page settles rather than
   performs. Nothing rotates, nothing scales, nothing is taken apart a letter at
   a time, and every block moves exactly once.                                 */
body.page-index{
  --sp-ease:cubic-bezier(.16,1,.3,1);
  --sp-dur:.62s;
  --sp-stagger:48ms;
  --sp-rise:16px;
}

/* Geometry, not motion: these hold whenever the spans exist. A split word is two
   boxes so the clip and the travel can run independently, and the padding /
   negative-margin pair keeps the clip off the descenders of g, p and y. nowrap
   stops a word that does not fit its column breaking in the middle. */
.page-index .sp-w{display:inline-block;vertical-align:top;white-space:nowrap}
.page-index [data-sp-split="words"] .sp-w{overflow:hidden;padding-bottom:.14em;margin-bottom:-.14em}
.page-index .sp-wi{display:inline-block}
/* THE CURSIVE IS THE EXCEPTION, and it is why the headline here is split by word
   and not by character. A script hangs its ascender loops and descenders well
   outside the line box, and its letters are joined - clip the box and the loops
   are sheared off mid-animation; split the letters and every join is broken. So
   any word inside a .script keeps its overflow and is revealed by part 2b below
   with opacity instead of a clip. */
.page-index .script .sp-w{overflow:visible;padding-bottom:0;margin-bottom:0}

/* 2a. blocks. A .sp-rise is the trigger, not the thing that moves: its children
   step in one after another. Grids and split headings are excluded because they
   choreograph themselves off --sp-i, and running both would compound the delays
   into a stall. The tagline rule is excluded too - see 2c. */
html.sp-motion .page-index .sp-rise > *:not(.sp-group):not([data-sp-split]):not(.tagline-rule){
  opacity:0;transform:translate3d(0,var(--sp-rise),0);
  transition:opacity var(--sp-dur) var(--sp-ease),transform var(--sp-dur) var(--sp-ease)}
/* Each :not() is worth a point of specificity, so the revealed state has to carry
   all three or the hidden state outranks it and the element never appears. */
html.sp-motion .page-index .sp-rise.in > *:not(.sp-group):not([data-sp-split]):not(.tagline-rule){
  opacity:1;transform:none}
html.sp-motion .page-index .sp-rise > :nth-child(2){transition-delay:60ms}
html.sp-motion .page-index .sp-rise > :nth-child(3){transition-delay:120ms}
html.sp-motion .page-index .sp-rise > :nth-child(4){transition-delay:180ms}
html.sp-motion .page-index .sp-rise > :nth-child(n+5){transition-delay:240ms}

/* 2b. words. The default is a slide out of the clipped line box, which is what
   the uppercase display face wants. The cursive gets the other treatment: it
   fades up out of a blur, which reads as ink settling and needs no clip. */
html.sp-motion .page-index [data-sp-split="words"] .sp-wi{
  transform:translate3d(0,105%,0);
  transition:transform .7s var(--sp-ease) calc(var(--sp-i,0) * 46ms)}
html.sp-motion .page-index .in [data-sp-split="words"] .sp-wi,
html.sp-motion .page-index .in[data-sp-split="words"] .sp-wi{transform:none}
html.sp-motion .page-index .script .sp-wi{
  opacity:0;transform:translate3d(0,.2em,0);filter:blur(7px);
  transition:opacity .66s var(--sp-ease) calc(var(--sp-i,0) * 60ms),
             transform .66s var(--sp-ease) calc(var(--sp-i,0) * 60ms),
             filter .66s var(--sp-ease) calc(var(--sp-i,0) * 60ms)}
html.sp-motion .page-index .in .script .sp-wi,
html.sp-motion .page-index .in.script .sp-wi{opacity:1;transform:none;filter:none}

/* 2c. the hero, which runs its own order rather than the generic cascade: the
   eyebrow, then the headline word by word, then the tagline, the rule under it
   and the buttons. .hero .copy is the .sp-rise and it is in view at load, so the
   whole sequence starts on the first frame the engine runs and is over in about
   a second and a quarter. */
html.sp-motion .page-index .hero .copy > .eyebrow{transition-delay:40ms}
html.sp-motion .page-index .hero .copy > .btn-row{transition-delay:.6s}
html.sp-motion .page-index .hero .tagline > *{transition-delay:calc(.4s + var(--sp-i,0) * 60ms)}
/* The headline fades up out of a blur, word by word, on one run of delays that
   crosses the line break. The clip the other headings use stays off here. It was
   off originally because it would have sheared the cursive's ascender loops, and
   with the script gone it is kept because the blur is what the hero does - the
   tagline words and the eyebrow arrive the same way, and a clipped slide in the
   middle of them would read as a second effect. */
html.sp-motion .page-index .hero-photo h1 .sp-w{overflow:visible;padding-bottom:0;margin-bottom:0}
html.sp-motion .page-index .hero-photo h1 .sp-wi{
  opacity:0;transform:translate3d(0,.2em,0);filter:blur(7px);
  transition:opacity .66s var(--sp-ease) calc(.12s + var(--sp-i,0) * 60ms),
             transform .66s var(--sp-ease) calc(.12s + var(--sp-i,0) * 60ms),
             filter .66s var(--sp-ease) calc(.12s + var(--sp-i,0) * 60ms)}
html.sp-motion .page-index .hero .copy.in h1 .sp-wi{opacity:1;transform:none;filter:none}
/* The rule under the tagline is driven by main.js, which measures it in pixels
   and slides it under the highlighted word, so it is never given a transform
   here - and its own width/transform transition has to be restated, because the
   shorthand in 2a would otherwise replace it and the underline would jump from
   word to word instead of travelling. */
html.sp-motion .page-index .hero .copy > .tagline-rule{opacity:0;
  transition:opacity var(--sp-dur) var(--sp-ease) .5s,
             width .55s cubic-bezier(.4,0,.2,1),transform .55s cubic-bezier(.4,0,.2,1)}
html.sp-motion .page-index .hero .copy.in > .tagline-rule{opacity:1}

/* 2d. grids. The container is what is observed, so a row of cards runs off one
   wave rather than four separate triggers, and keeps its order even when the
   whole grid is already on screen. */
html.sp-motion .page-index .sp-group > *{
  opacity:0;transform:translate3d(0,var(--sp-rise),0);
  transition:opacity var(--sp-dur) var(--sp-ease) calc(var(--sp-i,0) * var(--sp-stagger)),
             transform var(--sp-dur) var(--sp-ease) calc(var(--sp-i,0) * var(--sp-stagger))}
html.sp-motion .page-index .sp-group.in > *{opacity:1;transform:none}

/* 2e. the short orange rule under a section heading draws itself out from the
   edge it is aligned to. scaleX, never width: .tagline-rule is the same element
   with a width main.js owns, and this way the two can never meet. Excluded here
   as well, for the same reason. */
html.sp-motion .page-index .sp-rise > .rule:not(.tagline-rule){
  transform:translate3d(0,var(--sp-rise),0) scaleX(.1);transform-origin:left center}
html.sp-motion .page-index .center .sp-rise > .rule:not(.tagline-rule),
html.sp-motion .page-index .sp-rise.center > .rule:not(.tagline-rule){transform-origin:center}
html.sp-motion .page-index .sp-rise.in > .rule:not(.tagline-rule){transform:none}

/* 2f. standing still. The engine already reveals everything at once and stops
   its scroll loop under either reduced-motion switch; these clear the
   transitions so the last frame does not slide into place as the setting is
   turned on mid-page. */
@media (prefers-reduced-motion:reduce){
  html.sp-motion .page-index .sp-rise > *,html.sp-motion .page-index .sp-group > *,
  html.sp-motion .page-index .sp-wi{
    opacity:1!important;transform:none!important;filter:none!important;transition:none!important}
}
html[data-a11y-motion].sp-motion .page-index .sp-rise > *,
html[data-a11y-motion].sp-motion .page-index .sp-group > *,
html[data-a11y-motion].sp-motion .page-index .sp-wi{
  opacity:1!important;transform:none!important;filter:none!important;transition:none!important}
/* Large text and letter-spacing both reflow the page, and a heading clipped to
   its own line box is the one thing that cannot take a taller line. */
html[data-a11y-spacing] .page-index .sp-w,
html[data-a11y-font] .page-index .sp-w{overflow:visible;padding-bottom:0;margin-bottom:0}

/* ---- 3. THE KALEIDOSCOPE BAND ----------------------------------------------
   A raymarched Shader Park field behind "Purpose-built to work with national
   GCs". It is a section ground in its own right, not a wash laid over a
   photograph: the band is repainted a deep teal and the shader is the light in
   it. Scroll-driven, so the figure tumbles and morphs from torus to lattice as
   the band comes up the page, which means it behaves the same under a keyboard,
   on a phone and for a visitor who never moves a pointer over it.

   Four decisions, the first three inherited from the preview that worked them out:

   1. IT SITS BEHIND THE PLATES, NOT BEHIND THE TYPE. The six .feature tiles
      become opaque deep-ink plates, so they read as panels lit from behind and
      the field shows in the gutters between them and in the margins either side
      of the 1180px column.

   2. THE FIELD IS KEPT OFF THE HEADING. The canvas is masked to nothing across
      the top third, and a radial of the band's own ground - sized to the
      .section-head box itself, so it grows with the copy rather than with a
      fixed fraction of a section whose height depends on it - sits under the
      heading and the lead.

   3. SCREEN, NEVER MULTIPLY. mix-blend-mode:screen over a dark ground can only
      ADD light, so no output of the shader can darken a plate or a label. The
      ground is flat rather than a gradient for the same reason the radial exists:
      a flat colour is one the scrim can match exactly, with no seam.

   4. IT IS ADDITIVE, NEVER REQUIRED. The canvas ships at opacity 0 and
      js/kaleidoscope.js adds .kaleido-on to the section only once WebGL2 has
      compiled and linked the shader. Without that class every rule below is
      inert and the band is the plain white section it was.

   Contrast, worst case, with the shader blown out to white under the plates:
   the canvas is at .82, so the ground behind a plate cannot exceed about
   rgb(210,214,216); the plate is .8 opaque over it, which lands at about
   rgb(44,68,73), and #cfe6ea on that is 7.3:1. On the flat ground with no
   shader the same text is 12:1. Both clear AA for body copy either way.      */
.page-index .section:has(> .kaleidoscope-canvas){position:relative;overflow:hidden;isolation:isolate}
.page-index .kaleidoscope-canvas{
  position:absolute;inset:0;width:100%;height:100%;display:block;z-index:0;
  opacity:0;                       /* the script raises this once the shader links */
  pointer-events:none;             /* the plates and their links stay clickable */
  transition:opacity 1.4s ease;
  /* Edge fades only. This used to hold the field off the top third so the heading
     could sit on flat ground; the heading now sits on the field itself and is
     handled by the vignette below, so all that is left here is keeping the
     canvas from cutting hard against the sections above and below. */
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 7%,#000 93%,transparent 100%);
          mask-image:linear-gradient(180deg,transparent 0,#000 7%,#000 93%,transparent 100%)}
/* :has() is well supported now, but the whole effect hangs on this, so the
   class the script adds establishes the same context the old way. */
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on{
  position:relative;overflow:hidden;isolation:isolate;background:#043f47}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on > .wrap{position:relative;z-index:2}
/* .6 and not the .82 the previews use, and pulled down rather than up in
   saturation and brightness. Screen blending over a ground this dark takes the
   shader's brightest passes almost to white, and at the preview's settings the
   lower half of the band - which is where the figure blooms - washed out to
   pastel and stopped reading as one deep teal band with a light in it. */
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .kaleidoscope-canvas{
  opacity:.6;mix-blend-mode:screen;filter:saturate(.9) brightness(.86)}

/* the type on the band, in the tokens the contrast audit already measured for
   white-on-teal surfaces */
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on h2{color:#fff}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .eyebrow{color:var(--eyebrow-dark)}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .lead{color:#e6f4f6}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .rule{background:var(--accent-dark)}

/* Under the heading: a vignette that DARKENS rather than a patch that repaints.
   The earlier version filled this box with the band's own #043f47, which read as
   a flat teal panel laid over the field and cut the sculpture in half. Black at
   .42 falling to nothing leaves the figure visible right through the heading and
   only takes the luminance down under it.

   It is still doing real work. White on the field at its brightest - the shader
   blown out and screened over the ground - measures 2.7:1, which fails even the
   3:1 that display type is allowed. Under this vignette the same worst case is
   6.2:1, and the lead paragraph beneath it, which is normal-size text and wants
   4.5:1, clears with room. The ellipse is sized to the box itself so it grows
   with the copy instead of tracking a fraction of a section whose height depends
   on that copy, and the far stop is black at zero alpha rather than
   `transparent`, which would interpolate through grey. */
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .section-head{position:relative}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .section-head::after{
  content:"";position:absolute;inset:-110px -16%;z-index:-1;pointer-events:none;
  background:radial-gradient(58% 58% at 50% 50%,rgba(0,0,0,.42) 0 54%,rgba(0,0,0,0) 100%)}
/* and the type carries its own scrim, the way the headline over the hero photo does */
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on h2,
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .lead{
  text-shadow:0 1px 12px rgba(2,20,24,.55),0 1px 3px rgba(2,20,24,.45)}

/* the plates */
/* .5 rather than .8: half the field comes through each plate. The backdrop blur
   in the glass block below is what makes that readable - it averages the moving
   colour under the plate before any text is set over it. Worst case, with the
   shader blown out behind a plate, the body copy measures 5.2:1 and the heading
   6.8:1; on the ordinary ground both are far higher. */
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .feature{
  background:rgba(2,30,35,.5);border-color:rgba(255,255,255,.22);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.12),0 10px 30px rgba(0,16,20,.28)}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .feature h3{color:#fff}
html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .feature p{color:#cfe6ea}
/* No icon rule here on purpose: the line pictograms in .feature, .why-tile, .cred
   and the rest are turned off site-wide further up this file ("fewer icons,
   site-wide"), and a colour for one that never renders would only read as a rule
   that had stopped working. */

/* A raymarcher is fragment-bound, so on a phone it costs more than it gives, and
   the six plates stack into one column there with almost no gutter for it to
   show through. The band keeps its deep teal ground and its plates - what goes
   is the field, not the design. */
@media (max-width:700px){
  .page-index .kaleidoscope-canvas{display:none}
  .page-index .section.kaleido-on .section-head::after{display:none}
}
/* Stop animation and the OS setting are handled in the renderer, which draws one
   still frame and holds it - a motionless image rather than nothing. The fade-in
   is cancelled here so even that does not move. */
@media (prefers-reduced-motion:reduce){.page-index .kaleidoscope-canvas{transition:none}}
html[data-a11y-motion] .page-index .kaleidoscope-canvas{transition:none}
/* High contrast flattens the page to solid panels and hard borders; a shimmering
   colour field is the opposite of what that mode is for, so the band goes back to
   being an ordinary white section. Every rule above is already guarded, and this
   removes the canvas itself. */
html[data-a11y-contrast="high"] .page-index .kaleidoscope-canvas{display:none}
/* The colour-vision correction puts a filter on body > main, which makes every
   descendant its own backdrop root and stops mix-blend-mode reaching the section
   ground. Compositing normally is correct there, just flatter. */
html[style*="--a11y-cvd"] .page-index .section.kaleido-on .kaleidoscope-canvas{
  mix-blend-mode:normal;opacity:.5}

/* ============================================================================
   GLASS SURFACES

   Five surfaces stop being opaque panels and become frosted plates - the sticky
   header, the mega menu, the burger sheet, the accessibility panel and the chat
   window - plus, on the home page, the six plates over the kaleidoscope.
   Everything else on the site is unchanged.

   THREE THINGS HOLD THIS UP.

   1. IT IS ENTIRELY INSIDE @supports. Without backdrop-filter a translucent
      header is not a softer header, it is running text with a photograph
      showing through it, so a browser that cannot blur keeps the solid white
      panels it has today. Both the prefixed and unprefixed properties are
      tested: Safari shipped this behind -webkit- and still needs it written.

   2. THE ALPHA IS SET BY CONTRAST, NOT BY TASTE. The nav labels are --navy-ink
      #056b74 at .82rem 600, which is normal-size text and wants 4.5:1. The
      worst backdrop on the site is the deep teal of the kaleidoscope band
      passing under the header, #043f47. White at .86 over that composites to
      about rgb(217,227,228), where #056b74 measures 4.8:1. At .78 it would be
      4.2:1 and would fail, which is why these are not the .72 a dark design
      can afford. The blur helps rather than hurts: it averages the backdrop,
      so a blown-out highlight is pulled back toward the mean before any text
      is set over it.

   3. THE SELECTORS ARE PLAIN CLASSES, DELIBERATELY. style.css loads before a
      style-*.css, so anything here that outranked a preview's own .header rule
      would win across files and repaint headers those pages had already
      designed. At one class each, a preview's rule is later and equal and
      keeps its page. The two accessibility modes are the exception and are
      written at attribute strength below, because they must override this.
   ============================================================================ */
:root{
  /* THE HEADER'S ALPHA IS A DESIGN DECISION WITH A MEASURABLE COST, so it is a
     token and the numbers are here rather than buried.

     The nav labels are --navy-ink #056b74. For that teal to clear 4.5:1 the bar
     has to composite to about rgb(218,218,218) or lighter WHEREVER it sits, and
     the darkest thing that passes under it is the kaleidoscope band at #043f47.
     Getting there from #043f47 means conditioning the backdrop until almost
     nothing of it survives - at which point it is not glass, it is a white bar.
     Very translucent, teal labels, and AA cannot all three be true.

     .38 is the translucent end of that trade, and the labels are darkened to
     pay for it. Sampled off the rendered page rather than modelled - the glass
     composites to rgb(185,202,204) over the kaleidoscope band and rgb(165,217,217)
     over the hero photograph, which are the two darkest things that pass under
     the bar:

                          over the band   over the photo   over white
       --navy-ink #056b74     3.68:1          4.02:1          ~7.0:1
       --glass-nav-ink        5.16:1          5.66:1          7.78:1

     #056b74 is the site's audited teal and it misses AA on glass this thin, by
     enough to matter and not by enough to be worth abandoning the colour for.
     #04525a is the same hue two steps down: it clears 4.5:1 everywhere the bar
     can go, and against the logo and the orange button it still reads as the
     brand teal rather than as black. Only the top-level nav labels take it -
     --navy-ink keeps doing its other twenty jobs untouched.

     To go back to a solid bar instead, raise the alpha to .86 and drop
     --glass-nav-ink to var(--navy-ink); that combination measures 4.8:1. */
  --glass-nav:rgba(255,255,255,.38);
  --glass-nav-ink:#04525a;
  /* Frosted glass scatters internally, so a real pane over a dark object reads
     far lighter than the object. brightness and contrast together are what
     stand in for that: they lift the backdrop and compress its range, so the bar
     stays a bar instead of turning into a dark hole over the dark bands. */
  --glass-nav-cond:blur(28px) saturate(185%) brightness(1.5) contrast(.84);
  /* The other two layers of the pane, as tokens for the same reason --glass-nav
     is one: the header and the mega panel both draw them, and a style-*.css that
     repaints the bar in its own colour has to be able to hand all three back at
     once. Setting --glass-nav:transparent alone would leave a white sheen
     travelling over a header the preview had already designed. */
  --glass-sweep:linear-gradient(100deg,
      rgba(255,255,255,0) calc(var(--glass-x,.28) * 100% - 34%),
      rgba(255,255,255,.30) calc(var(--glass-x,.28) * 100% - 8%),
      rgba(255,255,255,.46) calc(var(--glass-x,.28) * 100%),
      rgba(255,255,255,.30) calc(var(--glass-x,.28) * 100% + 8%),
      rgba(255,255,255,0) calc(var(--glass-x,.28) * 100% + 34%));
  --glass-lip:linear-gradient(180deg,
      rgba(96,214,232,.16) 0%,rgba(255,190,120,.13) 100%);
  --glass-panel:rgba(255,255,255,.9);     /* burger sheet, a11y panel, chat    */
  --glass-blur:saturate(180%) blur(16px);
  --glass-edge:rgba(255,255,255,.55);     /* the lit top edge of a plate       */
}
@supports ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){

  /* ---- the header, as a pane of glass ------------------------------------
     Three things separate glass from a blurred rectangle, and the bar gets all
     three. THICKNESS: a lit top edge and a darker under edge, so it reads as a
     slab seen edge-on rather than a flat wash. REFLECTION: a specular sweep that
     travels as the page moves, driven by --glass-x, which main.js writes on this
     element (and only on this element - the same property on :root would
     invalidate style for the whole document every frame). REFRACTION: a band
     along the bottom lip where the pane is effectively thicker, carrying its own
     heavier blur and a faint two-sided colour split, which is what dispersion
     looks like at the edge of real glass.

     Both pseudo-elements sit at z-index:-1. Inside a stacking context the
     element's own background paints first, then negative-z children, then the
     in-flow content - so they land between the glass and the nav without
     .header .wrap having to be positioned. That matters: positioning the wrap
     would make it the containing block for the burger sheet, which is
     position:absolute and has to span the viewport, not the 1180px column.

     THE FILTER IS ON ::before, NOT ON .header. An element with a backdrop-filter
     is a backdrop root for everything inside it: its descendants can only sample
     what has been painted into it, not the page underneath. With the filter on
     the header itself the mega panel was inside that root, so its own blur
     resolved against nothing and it fell back to its alpha - a flat white plate
     hanging off a glass bar, which is exactly what it looked like. Moving the
     filter one level down to the sweep layer leaves the header an ordinary
     stacking context, and the panel, the burger sheet and anything else that
     hangs out of the bar sample the page like any other plate.
     The tint travels with the filter for the same reason the pseudo-elements sit
     at z-index:-1: the element's own background paints before them, so a tint
     left on .header would sit under the blur and be blurred and brightened along
     with the page. It is the layer's background-color instead, and the two
     contrast modes below repaint that layer rather than the header. */
  .header{background:transparent;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.75),
               inset 0 -1px 0 rgba(7,147,161,.20),
               0 1px 0 rgba(7,147,161,.14),
               0 10px 34px rgba(7,147,161,.10)}
  /* the sweep. Two stops of white either side of a moving centre, so the
     highlight has a soft leading and trailing edge instead of a hard band. */
  .header::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
    background-color:var(--glass-nav);
    -webkit-backdrop-filter:var(--glass-nav-cond);backdrop-filter:var(--glass-nav-cond);
    background-image:var(--glass-sweep)}
  /* The lip, where the pane is effectively thicker and the light through it
     splits.

     NO SECOND backdrop-filter HERE, deliberately. The first cut gave this band
     its own blur on top of the header's, and that reads as exactly what it is:
     a strip of visibly different glass with a seam along the top. A filter
     cannot be ramped - it is either applied to a pixel or it is not - so no
     amount of masking hides the step in translucency at its boundary. The
     dispersion is a tint instead, cool falling to warm, faded in from nothing,
     which has no boundary to see. Nothing white in it either: white here was
     what read as a strip, and the blur only made it flatter.

     The under edge follows the same logic. A pane lit from above is darker along
     its bottom lip, not brighter, so the inset line there is teal where the top
     edge gets white. */
  .header::after{content:"";position:absolute;left:0;right:0;bottom:0;height:20px;
    z-index:-1;pointer-events:none;
    -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0%,#000 100%);
            mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0%,#000 100%);
    background-image:var(--glass-lip)}
  /* A white halo under the labels. It is not a substitute for the contrast
     measured on the token above - shadows earn nothing under WCAG - but it is
     what keeps the teal from dissolving into the busiest parts of a photograph. */
  .header .nav>li>a,.header .nav>li>button,.header .brand{
    text-shadow:0 1px 2px rgba(255,255,255,.85),0 0 10px rgba(255,255,255,.55)}
  .nav>li>a:not(.btn),.nav>li>button{color:var(--glass-nav-ink)}
  .nav a.btn{text-shadow:none}

  /* ---- the mega panel, which is the same pane -----------------------------
     The panel is not a second glass with its own recipe: it is the bar carried
     further down the page, so it takes the bar's tokens unchanged - the same
     --glass-nav alpha, the same --glass-nav-cond conditioning, the same lit top
     edge and teal under edge, the same travelling sweep and the same dispersion
     lip along the bottom. A panel mixed a different thickness reads as a second
     sheet stuck to the first, which is what a thicker, flatter plate hanging off
     a thin bar looked like.
     The sweep needs nothing passed to it: --glass-x is written to .header and
     custom properties inherit, so the panel's highlight sits at the same point
     in the same 100deg travel as the bar's and the two read as one surface.
     The inks come with the recipe. At this alpha the panel cannot carry --muted
     on its group headings - that is 2:1 over the darkest thing on the site - so
     they take --glass-nav-ink, the token the bar's own labels are audited on at
     5.16:1, and the links keep --ink, which is darker still. The white halo is
     the bar's too: not contrast under WCAG, but what keeps a letterform from
     dissolving into the busiest part of a photograph.
     Below 980px none of this applies. There the panel is not a panel - it is a
     block of links inside the burger sheet, which is frosted already, and a
     second plate over it would be the seam this section exists to avoid. */
  @media (min-width:981px){
    .mega{background:var(--glass-nav);
      -webkit-backdrop-filter:var(--glass-nav-cond);backdrop-filter:var(--glass-nav-cond);
      border-top-color:var(--glass-edge);
      box-shadow:inset 0 1px 0 rgba(255,255,255,.75),
                 inset 0 -1px 0 rgba(7,147,161,.20),
                 0 16px 34px rgba(7,147,161,.13),0 40px 80px rgba(31,41,51,.06)}
    /* the sweep and the lip, the bar's rules verbatim against the panel's box */
    .mega::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
      background-image:var(--glass-sweep)}
    .mega::after{content:"";position:absolute;left:0;right:0;bottom:0;height:20px;
      z-index:-1;pointer-events:none;
      -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0%,#000 100%);
              mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0%,#000 100%);
      background-image:var(--glass-lip)}
    .mega-h{color:var(--glass-nav-ink)}
    .nav .mega a,.mega-h{
      text-shadow:0 1px 2px rgba(255,255,255,.85),0 0 10px rgba(255,255,255,.55)}
  }

  /* the two floating overlays */
  .a11y-panel,.sp-chat{background:var(--glass-panel);
    -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
    border-color:rgba(255,255,255,.6);
    box-shadow:inset 0 1px 0 var(--glass-edge),0 18px 50px rgba(0,0,0,.22)}

  /* the burger sheet, which is the nav at phone width */
  @media (max-width:980px){
    .nav{background:var(--glass-panel);
      -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur)}
  }

  /* The six plates over the kaleidoscope, which is the one place on the site
     where there is something worth seeing through glass. Held to a 10px blur
     rather than the 16 the chrome uses: this backdrop is a raymarcher redrawing
     every frame, so unlike the others it cannot be cached between frames and
     the cost is paid six times per plate row. Below 700px the canvas is already
     off and the blur has nothing to do, so it comes off with it. */
  @media (min-width:701px){
    html:not([data-a11y-contrast="high"]) .page-index .section.kaleido-on .feature{
      -webkit-backdrop-filter:saturate(115%) blur(16px);
      backdrop-filter:saturate(115%) blur(16px)}
  }

  /* Dark mode repaints these surfaces to #1b2329 further up the file, at one
     attribute and one class. These match that so the mode keeps its glass
     rather than dropping back to flat panels. */
  html[data-a11y-contrast="dark"] .header::before{background-color:rgba(21,28,33,.86)}
  html[data-a11y-contrast="dark"] .header{box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 1px 0 var(--line),0 8px 28px rgba(0,0,0,.4)}
  @media (min-width:981px){
    html[data-a11y-contrast="dark"] .mega{background:rgba(21,28,33,.86)}
  }
  html[data-a11y-contrast="dark"] .a11y-panel,
  html[data-a11y-contrast="dark"] .sp-chat{background:rgba(27,35,41,.9);
    border-color:#3b464e;box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 18px 50px rgba(0,0,0,.5)}
  @media (max-width:980px){
    html[data-a11y-contrast="dark"] .nav{background:rgba(27,35,41,.94)}
  }

  /* High contrast flattens the page to solid fills and hard borders. Glass is
     the opposite of what that mode is for, so every surface above goes back to
     the panel it was. */
  html[data-a11y-contrast="high"] .header,
  html[data-a11y-contrast="high"] .mega,
  html[data-a11y-contrast="high"] .a11y-panel,
  html[data-a11y-contrast="high"] .sp-chat,
  html[data-a11y-contrast="high"] .nav{
    background:#fff;-webkit-backdrop-filter:none;backdrop-filter:none;
    box-shadow:none}
  html[data-a11y-contrast="high"] .header{background:#fff;box-shadow:0 2px 0 #000}
  /* the tint, the blur, the sweep and the dispersion band all come off with it */
  html[data-a11y-contrast="high"] .header::before,
  html[data-a11y-contrast="high"] .header::after,
  html[data-a11y-contrast="high"] .mega::before,
  html[data-a11y-contrast="high"] .mega::after{
    background:none;-webkit-backdrop-filter:none;backdrop-filter:none}
  html[data-a11y-contrast="high"] .mega-h,
  html[data-a11y-contrast="high"] .nav .mega a{text-shadow:none}
}

/* ============================================================================
   THE TOP EDGE OF THE CALL-TO-ACTION BAND

   The band's top edge is a gradient: its own colour fading up out of the page
   above it, so the last section on every page hands over to the band instead of
   meeting it at a line. It was a mountain range - the real skyline west of
   Charlottesville, 322 points of it - and the reasoning that shaped that is what
   shapes this.

   IT IS THE BAND'S OWN COLOUR, NOT A NEUTRAL FADE. The point of the ridge was
   that it read as the edge of the band rather than as a graphic sitting on it,
   and a fade only does that if it is made of the thing it belongs to - which is
   why the end colour is restated for each contrast mode below. A band repainted
   in one mode with a fade left behind in the light teal would detach from it
   exactly as a teal ridge would.

   IT HANGS ABOVE THE BOX, LIKE THE RIDGE DID. Same anchor: bottom at 100% minus
   two pixels. Sitting it exactly on the band's top edge looks right at a device
   pixel ratio of 1 and opens a hairline of page background at any fractional
   ratio or browser zoom, because the two edges round to different device pixels.
   The overlap costs nothing: the bottom two pixels of the gradient are the band's
   colour at full strength.

   THE HEIGHT IS TALLER THAN THE RIDGE'S AND STILL FITS. A silhouette can stop
   dead at its peaks; a fade needs room or it reads as a band with a blurred edge
   rather than as a transition. It runs to 88px against the 80px of bottom
   padding every .section carries, and the top third of it is under 10% alpha, so
   what reaches into the copy above is nothing the eye resolves.
   ============================================================================ */
/* the band clips its own bottom divider on the variation pages; the gradient
   hangs off the top and needs that clip off. What it was guarding is the 2px the
   .mtn sits proud of the bottom edge, which the footer covers anyway. */
.cta-band{overflow:visible}
.cta-band::before{content:"";position:absolute;left:0;right:0;bottom:calc(100% - 2px);
  height:clamp(48px,6vw,88px);z-index:1;pointer-events:none;
  background-image:linear-gradient(180deg,transparent 0%,var(--cta-fade) 100%)}
:root{--cta-fade:var(--surface-teal)}
html[data-a11y-contrast="dark"]{--cta-fade:#0e3f45}
html[data-a11y-contrast="high"]{--cta-fade:#00303a}
/* Print puts the band on white with no fill behind it, so a teal haze hanging
   over the previous page's copy is only a waste of toner. */
@media print{.cta-band::before{display:none}}
