/* ============================================================
   CWS Electrical, v2.0 "Signal" design system
   Single source of truth for all 6 pages. Built to Website/DESIGN.md.
   Dark ground, mark-led, blue only. No orange. No em/en dashes.
   ============================================================ */

/* ---------- tokens ---------- */
:root{
  --accent:#5FB3F9;        /* brand blue: hero keyword, mark, focus, links */
  --accent-bright:#7DD3FC;  /* highlights, gradient top-stop on the mark */
  --primary:#0F4C81;        /* deep brand blue, gradient base */
  --cta:#0369A1;            /* primary button / call pill */
  --cta-hover:#075985;      /* CTA hover */
  --ink:#05080F;            /* near-black ground */
  --ink-2:#0B1220;          /* raised dark panel */
  --line:#1E2A3C;           /* borders, schematic grid */
  --muted:#8A9AB2;          /* labels / disclaimer-only greys */
  --text:#E8EEF6;           /* primary body on dark, clears 4.5:1 */
  --text-2:#B9C6D8;         /* secondary body, clears 4.5:1 */
  --font-display:'Archivo',system-ui,sans-serif;
  --font-body:'Inter',system-ui,sans-serif;
  --ease:cubic-bezier(.22,1,.36,1);
  --wrap:1280px;
}

/* ---------- reset ---------- */
*{margin:0;padding:0;box-sizing:border-box}
/* text-size-adjust: iOS Safari inflates text in some blocks on its own, which can
   resurrect a font-size:0 label and shove a centred icon off-centre. Pin it. */
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{
  font-family:var(--font-body);background:var(--ink);color:var(--text);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden;line-height:1.6;
}
img{max-width:100%;display:block}
a{color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:4px}

.wrap{max-width:var(--wrap);margin:0 auto;padding-left:clamp(20px,5vw,72px);padding-right:clamp(20px,5vw,72px)}
.section{position:relative;z-index:2;padding-top:clamp(56px,9vw,108px);padding-bottom:clamp(56px,9vw,108px)}
.section--tight{padding-top:clamp(40px,6vw,72px);padding-bottom:clamp(40px,6vw,72px)}
.panel{background:var(--ink-2)}

/* ---------- atmosphere (bounded, electrical-specific) ---------- */
.bg{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.bg .glow-a{position:absolute;top:-22%;right:-12%;width:62vw;height:62vw;
  background:radial-gradient(circle,rgba(15,76,129,.34),rgba(15,76,129,0) 64%);
  filter:blur(14px);animation:breathe 11s var(--ease) infinite}
.bg .glow-b{position:absolute;bottom:-26%;left:-14%;width:48vw;height:48vw;
  background:radial-gradient(circle,rgba(95,179,249,.1),rgba(95,179,249,0) 66%);
  filter:blur(16px);animation:breathe 14s var(--ease) infinite reverse}
.bg .grid{position:absolute;inset:0;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),
                   linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:68px 68px;opacity:.32;
  -webkit-mask-image:radial-gradient(ellipse 90% 80% at 60% 32%,#000 30%,transparent 78%);
          mask-image:radial-gradient(ellipse 90% 80% at 60% 32%,#000 30%,transparent 78%)}
.bg .grain{position:absolute;inset:0;opacity:.05;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
@keyframes breathe{0%,100%{transform:scale(1);opacity:.72}50%{transform:scale(1.06);opacity:.86}}

/* ---------- typography ---------- */
h1,h2,h3{font-family:var(--font-display);letter-spacing:-.02em}
h1{font-weight:900;letter-spacing:-.038em;line-height:.93;font-size:clamp(2.85rem,7.4vw,5.5rem)}
h2{font-weight:800;line-height:1.02;font-size:clamp(1.9rem,3.4vw,2.9rem)}
h3{font-weight:700;line-height:1.1;font-size:clamp(1.2rem,1.8vw,1.5rem);letter-spacing:-.01em}
p{max-width:48ch}
.lead{font-family:var(--font-display);font-weight:700;letter-spacing:-.01em;line-height:1.2;
  font-size:clamp(1.05rem,2.3vw,1.7rem);color:var(--text-2);max-width:none}
.body{font-size:clamp(1rem,1.25vw,1.16rem);line-height:1.64;color:var(--text-2)}
.tnum{font-variant-numeric:tabular-nums}

.spec-label{display:inline-flex;align-items:center;gap:11px;font-family:var(--font-body);
  font-size:.74rem;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  color:var(--accent-bright);font-variant-numeric:tabular-nums}
.spec-label .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 0 4px rgba(95,179,249,.16);animation:pulseDot 2.6s var(--ease) infinite}
@keyframes pulseDot{0%,100%{box-shadow:0 0 0 4px rgba(95,179,249,.16)}
  50%{box-shadow:0 0 0 7px rgba(95,179,249,.04)}}

.accent{color:var(--accent)}
.section-head{max-width:60ch;margin-bottom:clamp(32px,5vw,56px)}
.section-head h2{margin:18px 0 16px}

/* ---------- nav ---------- */
nav{position:sticky;top:0;z-index:50;height:68px;display:flex;align-items:center;
  justify-content:space-between;padding:0 clamp(20px,5vw,72px);
  background:rgba(5,8,15,.72);backdrop-filter:blur(14px);
  border-bottom:1px solid rgba(30,42,60,.7)}
.brand{display:flex;align-items:center;gap:13px;font-family:var(--font-display);
  font-weight:900;letter-spacing:-.02em;font-size:1.12rem;color:#fff;text-decoration:none;
  white-space:nowrap}
.mark-mini{display:flex;flex-direction:column;align-items:flex-end;gap:3px;width:26px}
.mark-mini i{height:4px;border-radius:2px;background:var(--accent);display:block}
.mark-mini i:nth-child(1){width:100%}
.mark-mini i:nth-child(2){width:78%}
.mark-mini i:nth-child(3){width:56%}
.mark-mini i:nth-child(4){width:38%}

.navlinks{display:none;gap:32px;font-size:.9rem;font-weight:500;color:#9aa9bd}
.navlinks a{text-decoration:none;color:inherit;position:relative;transition:color .25s}
.navlinks a::after{content:"";position:absolute;left:0;bottom:-6px;width:100%;height:1.5px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .28s var(--ease)}
.navlinks a:hover{color:#fff}
.navlinks a:hover::after{transform:scaleX(1)}
.navlinks a[aria-current="page"]{color:#fff}
.navlinks a[aria-current="page"]::after{transform:scaleX(1)}

.nav-right{display:flex;align-items:center;gap:12px}
.call-pill{display:inline-flex;align-items:center;gap:9px;background:var(--cta);color:#fff;
  text-decoration:none;font-weight:600;font-size:.9rem;padding:10px 18px;border-radius:9px;
  font-variant-numeric:tabular-nums;
  transition:transform .25s var(--ease),box-shadow .25s var(--ease),background .25s}
.call-pill svg{width:16px;height:16px}
@media (hover:hover) and (pointer:fine){
  .call-pill:hover{background:var(--cta-hover);transform:translateY(-1px);
    box-shadow:0 10px 26px rgba(3,105,161,.4)}
}

/* menu button, deliberately distinct from the four-bar mark */
.menu-btn{display:inline-flex;align-items:center;gap:8px;background:rgba(255,255,255,.04);
  border:1.5px solid #2b3c54;color:#fff;border-radius:10px;height:44px;padding:0 14px;
  font-family:var(--font-body);font-weight:600;font-size:.86rem;cursor:pointer;
  transition:border-color .25s,background .25s}
.menu-btn svg{width:18px;height:18px}
.menu-btn:hover{border-color:var(--accent)}
/* 960, not 900: at exactly 900 the long brand plus six links plus the call pill
   ran 5px past the viewport. The links need the extra room before they switch on. */
@media(min-width:960px){.navlinks{display:flex}.menu-btn{display:none}}
/* below the desktop nav: the long brand must be able to wrap so it never shoves
   the call and menu buttons off-screen. min-width:0 lets the flex item shrink. */
@media(max-width:959px){
  .brand{white-space:normal;min-width:0;line-height:1.14;font-size:1rem;max-width:56vw}
  .nav-right{flex:none}
}
/* narrower: call pill and menu collapse to one-tap icon buttons */
@media(max-width:560px){
  /* client note 2026-07-20: the call button was too small on mobile.
     Bigger target, brighter fill, and it stays the loudest thing in the bar. */
  /* client note 2026-07-28: the call button read as misaligned on a real handset.
     Two causes, both fixed here. (1) call was 60x60 / radius 14 next to a menu at
     50x50 / radius 10, so the pair looked off even though both were centred; menu
     now matches at 60. (2) the 0 8px 22px glow cleared the 60px button by 8px and
     bled ~14px past the nav border into the hero, so the button read as floating
     out of the bar; tightened so the glow stays inside the bar. */
  .call-pill{font-size:0;gap:0;width:60px;height:60px;padding:0;justify-content:center;
    border-radius:14px;box-shadow:0 3px 10px rgba(3,105,161,.38);position:relative}
  /* icon centred by absolute placement rather than by a collapsed text node, so it
     cannot drift if the font-size:0 phone label ever gets a real size back. */
  .call-pill svg{width:28px;height:28px;stroke-width:2.2;position:absolute;inset:0;margin:auto}
  .menu-btn{font-size:0;gap:0;width:60px;height:60px;padding:0;justify-content:center;
    border-radius:14px}
  .menu-btn svg{width:24px;height:24px}
  .nav-right{gap:10px}
  /* the taller call target needs the bar to grow with it, or it crowds the edges */
  nav{height:76px;padding-left:14px;padding-right:14px}
  .brand{font-size:.86rem;gap:8px;max-width:44vw}
}

/* ---------- mobile sheet (the genuinely unbuilt piece) ---------- */
.sheet{position:fixed;inset:0;z-index:90;background:rgba(5,8,15,.97);
  backdrop-filter:blur(8px);display:flex;flex-direction:column;
  padding:clamp(20px,6vw,32px);
  opacity:0;visibility:hidden;transform:translateY(-8px);
  transition:opacity .28s var(--ease),transform .28s var(--ease),visibility .28s}
.sheet.open{opacity:1;visibility:visible;transform:none}
.sheet-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}
.sheet-close{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:10px;border:1.5px solid #2b3c54;
  background:rgba(255,255,255,.04);color:#fff;cursor:pointer}
.sheet-close svg{width:20px;height:20px}
.sheet-call{display:flex;align-items:center;justify-content:center;gap:10px;
  background:linear-gradient(180deg,var(--cta),#025684);color:#fff;text-decoration:none;
  font-weight:700;font-size:1.05rem;padding:18px;border-radius:12px;margin-bottom:20px;
  font-variant-numeric:tabular-nums;box-shadow:0 12px 30px rgba(3,105,161,.34)}
.sheet-call svg{width:18px;height:18px}
/* reset the nav{} bleed: .sheet-links is a <nav>, so it inherits the sticky bar
   styles (height:68px, align-items:center, bar background). Shed them here so the
   links are full-width, left-aligned, 56px-tall rows. */
.sheet-links{display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;
  height:auto;position:static;padding:0;background:none;border:0}
.sheet-links a{text-decoration:none;color:var(--text);font-family:var(--font-display);
  font-weight:700;font-size:1.35rem;min-height:56px;display:flex;align-items:center;
  border-bottom:1px solid var(--line);
  opacity:0;transform:translateY(8px);
  transition:opacity .3s var(--ease),transform .3s var(--ease)}
.sheet.open .sheet-links a{opacity:1;transform:none}
.sheet.open .sheet-links a:nth-child(1){transition-delay:.06s}
.sheet.open .sheet-links a:nth-child(2){transition-delay:.10s}
.sheet.open .sheet-links a:nth-child(3){transition-delay:.14s}
.sheet.open .sheet-links a:nth-child(4){transition-delay:.18s}
.sheet.open .sheet-links a:nth-child(5){transition-delay:.22s}
.sheet.open .sheet-links a:nth-child(6){transition-delay:.26s}
.sheet-links a[aria-current="page"]{color:var(--accent)}

/* ---------- buttons ---------- */
.cta-row{display:flex;flex-wrap:wrap;gap:14px}
.btn{display:inline-flex;align-items:center;gap:10px;text-decoration:none;font-weight:700;
  font-size:1.02rem;padding:16px 28px;border-radius:11px;min-height:44px;cursor:pointer;
  border:0;font-family:var(--font-body);
  transition:transform .25s var(--ease),box-shadow .3s var(--ease),background .25s,border-color .25s}
.btn-primary{background:linear-gradient(180deg,var(--cta),#025684);color:#fff;
  box-shadow:0 12px 30px rgba(3,105,161,.34)}
.btn-primary svg{width:17px;height:17px;transition:transform .3s var(--ease)}
.btn-ghost{background:rgba(255,255,255,.02);color:#fff;border:1.5px solid #2b3c54;
  font-variant-numeric:tabular-nums}
.btn-ghost svg{width:17px;height:17px;color:var(--accent)}
@media (hover:hover) and (pointer:fine){
  .btn-primary:hover{transform:translateY(-2px);box-shadow:0 18px 42px rgba(3,105,161,.5)}
  .btn-primary:hover svg{transform:translateX(4px)}
  .btn-ghost:hover{border-color:var(--accent);transform:translateY(-2px);
    box-shadow:0 12px 30px rgba(95,179,249,.18)}
}
.btn:active,.call-pill:active,.menu-btn:active,.qa-q:active,.sheet-close:active,.sheet-call:active{transform:scale(.97);transition-duration:.12s}

/* ---------- hero (mark-led) ---------- */
.hero{position:relative;z-index:2}
.hero-inner{max-width:var(--wrap);margin:0 auto;display:grid;grid-template-columns:1fr;
  gap:clamp(40px,6vw,64px);align-items:center;
  padding:clamp(48px,9vw,108px) clamp(20px,5vw,72px) clamp(40px,7vw,88px)}
.hero-inner .left{max-width:64ch;min-width:0}
/* two-column at tablet+ so the big mark lines up beside the heading; single column below (mark hidden) */
@media(min-width:700px){
  /* the heading column has to hold "CONDITIONING" set in caps at the top of the
     clamp, or the word overruns the column and collides with the mark. The
     subhead keeps its own 48ch measure, so widening this does not hurt the copy. */
  .hero-inner{grid-template-columns:1.6fr 1fr;align-items:start}
  /* size the heading off its own column, not the viewport: "CONDITIONING" is the
     widest word and at 7.4vw it overran the column and hit the mark. cqw keeps it
     as large as the column can actually hold, at every width. */
  .hero-inner .left{max-width:none;container-type:inline-size}
  .hero h1 .l{font-size:clamp(2.85rem,12.2cqw,5.5rem)}
  /* mark tracks the top of the heading, not the middle of the block */
  .mark-wrap{margin-top:1px}
}
.hero .eyebrow{margin-bottom:22px}
.hero h1{margin-bottom:26px}
.hero h1 .l{display:block;text-transform:uppercase}
/* caps run wider than title case; step the hero heading down on small phones and let the long eyebrow wrap so nothing overflows */
@media(max-width:560px){
  .hero h1 .l{font-size:clamp(2.1rem,8.6vw,2.85rem)}
  .hero .eyebrow{white-space:normal}
  /* client note 2026-07-28: quote and call were missing a shared row by a couple of
     pixels and wrapping, which left dead space to the right of each. Trimming the
     28px side padding to 18 and the row gap to 10 buys ~45px at 393 wide, and
     flex-grow spends the leftover on the buttons instead of leaving a ragged edge.
     Wrap is deliberately still allowed: below ~340 they cannot share a row, and
     stacking full-width beats overflowing. Hero only, so .photo-cta and the About
     close-out keep their centred desktop treatment. */
  .hero .cta-row{gap:10px}
  .hero .cta-row .btn{flex:1 1 auto;justify-content:center;padding-left:18px;padding-right:18px}
}
.hero h1 .sub{display:block;font-size:clamp(1.05rem,2.3vw,1.7rem);font-weight:700;
  letter-spacing:-.01em;color:var(--text-2);margin-top:18px}
.hero .subhead{font-size:clamp(1rem,1.25vw,1.16rem);line-height:1.64;max-width:48ch;
  color:var(--text-2);margin-bottom:34px}

/* on-load rise (CSS-driven, runs without JS) */
.rise{opacity:0;animation:rise .9s var(--ease) forwards}
.rise.d1{animation-delay:.08s}
.rise.d2{animation-delay:.18s}
.rise.d3{animation-delay:.32s}
.rise.d4{animation-delay:.44s}
.rise.d5{animation-delay:.54s}
@keyframes rise{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}

/* ---------- the four-bar mark, architectural scale ---------- */
.mark-wrap{position:relative;display:flex;justify-content:center;align-items:center;
  min-height:clamp(240px,40vw,460px)}
.mark{position:relative;display:flex;flex-direction:column;align-items:flex-end;
  gap:clamp(14px,2vw,26px);width:min(100%,440px)}
.mark::after{content:"";position:absolute;right:-22px;top:4px;bottom:4px;width:1px;
  background:linear-gradient(180deg,transparent,rgba(95,179,249,.5),transparent)}
.bar{position:relative;height:clamp(40px,6.4vw,68px);border-radius:14px;overflow:hidden;
  transform:scaleX(0);transform-origin:right center;
  background:linear-gradient(270deg,var(--accent-bright) 0%,var(--accent) 24%,var(--primary) 100%);
  box-shadow:0 14px 40px rgba(3,105,161,.3),inset 0 1px 0 rgba(255,255,255,.28),
    inset 0 -2px 6px rgba(2,30,60,.5);
  animation:grow .9s var(--ease) forwards}
.bar:nth-child(1){width:100%;animation-delay:.30s}
.bar:nth-child(2){width:78%;animation-delay:.42s}
.bar:nth-child(3){width:56%;animation-delay:.54s}
.bar:nth-child(4){width:38%;animation-delay:.66s}
.bar:nth-child(1)::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,transparent 20%,rgba(255,255,255,.55) 48%,transparent 76%);
  transform:translateX(-120%);animation:sweep 5.5s var(--ease) 1.6s infinite}
@keyframes grow{to{transform:scaleX(1)}}
/* deliberate current pulse: travels quickly, then a long rest, so it reads as a discrete charge, not a constant glint */
@keyframes sweep{0%{transform:translateX(-120%)}14%,100%{transform:translateX(120%)}}
@media(min-width:960px){.mark{transform:translateX(8%)}}
/* On phones the stacked mark owns a whole dead section below the CTAs and does no
   work there (the nav mini-mark keeps brand presence). Drop it so the trust band
   rises into first paint. Kept at 700px+ where it renders at architectural scale. */
@media(max-width:699px){.mark-wrap{display:none}}
@media (hover:hover) and (pointer:fine){
  .mark-wrap:hover .bar{box-shadow:0 18px 52px rgba(95,179,249,.42),
    inset 0 1px 0 rgba(255,255,255,.34),inset 0 -2px 6px rgba(2,30,60,.5)}
}

/* ---------- photo-hero variant (interior page tops) ---------- */
.photo-hero{position:relative;z-index:2;min-height:clamp(380px,56vw,560px);
  display:flex;align-items:flex-end;overflow:hidden;border-bottom:1px solid var(--line);
  background:var(--ink-2)}
.photo-hero img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 28%;z-index:-2;opacity:0;transition:opacity .5s var(--ease)}
.photo-hero img.loaded{opacity:1}
.photo-hero::after{content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(5,8,15,.45) 0%,rgba(5,8,15,.72) 55%,rgba(5,8,15,.96) 100%)}
/* until a real photo loads, drop the scrim so the placeholder spec stays readable */
.photo-hero:not(.has-img)::after{background:linear-gradient(180deg,rgba(5,8,15,.32),rgba(5,8,15,.58))}
.photo-hero .img-ph{z-index:-2;border-radius:0;border-left:0;border-right:0;border-top:0}
.photo-hero .ph-inner{max-width:var(--wrap);margin:0 auto;width:100%;
  padding:clamp(40px,7vw,72px) clamp(20px,5vw,72px)}
.photo-hero h1{font-size:clamp(2.4rem,6vw,4.4rem);margin:16px 0 0;max-width:18ch}
.photo-hero .lead{max-width:52ch;margin-top:16px;color:var(--text-2)}
.photo-caption{display:inline-flex;align-items:center;gap:8px;font-size:.78rem;
  color:var(--text-2);margin-top:18px}
.photo-caption::before{content:"";width:14px;height:1px;background:var(--accent)}

/* photo-cta: full-bleed closing CTA over a darkened photo.
   Scrim fades to solid --ink top AND bottom so it melts into the page
   instead of reading as a floating band. */
.photo-cta{min-height:clamp(340px,42vw,460px);align-items:center;justify-content:center;
  text-align:center;border-bottom:0}
.photo-cta::after{background:linear-gradient(180deg,
  var(--ink) 0%, rgba(5,8,15,.74) 26%, rgba(5,8,15,.72) 58%, var(--ink) 100%)}
.photo-cta:not(.has-img)::after{background:linear-gradient(180deg,
  var(--ink), rgba(5,8,15,.55), var(--ink))}
.photo-cta .ph-inner{max-width:720px;text-align:center;
  padding-top:clamp(48px,8vw,84px);padding-bottom:clamp(48px,8vw,84px)}
.photo-cta .spec-label{justify-content:center}
.photo-cta h2{margin:14px auto 0}
.photo-cta .lead{max-width:52ch;margin:16px auto 0;color:#cdd8e8}
.photo-cta .cta-row{justify-content:center;margin-top:30px}

/* ---------- image slots + swap-ready placeholders ----------
   Drop a correctly named JPG into assets/photos/ and main.js reveals it over
   the placeholder. No file yet = the labelled placeholder stays. See the
   image manifest for the full slot list, subjects, and dimensions. */
.img-slot{position:relative;display:block;overflow:hidden;border-radius:14px;
  aspect-ratio:var(--ar,4/3);border:1px solid var(--line);background:var(--ink-2)}
.img-slot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .5s var(--ease)}
.img-slot img.loaded{opacity:1}
.img-ph{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:9px;text-align:center;padding:22px;
  border:1.5px dashed rgba(95,179,249,.38);border-radius:inherit;
  background:repeating-linear-gradient(-45deg,transparent 0 11px,rgba(95,179,249,.035) 11px 22px)}
.img-ph svg{width:28px;height:28px;color:var(--accent);opacity:.65}
.img-ph .n{font-family:var(--font-body);font-weight:600;font-size:.78rem;letter-spacing:.07em;
  text-transform:uppercase;color:var(--accent-bright);font-variant-numeric:tabular-nums}
.img-ph .s{color:var(--text-2);font-size:.88rem;line-height:1.4;max-width:34ch}
.img-ph .d{color:var(--muted);font-size:.74rem;font-variant-numeric:tabular-nums;letter-spacing:.04em}
/* once the real photo has loaded, drop the placeholder entirely (both hero + slot variants) */
.has-img .img-ph{display:none}

/* ---------- trust band (DEFAULT VISIBLE) ---------- */
.trust{position:relative;z-index:2;border-top:1px solid rgba(30,42,60,.7);
  border-bottom:1px solid rgba(30,42,60,.7);background:rgba(11,18,32,.4)}
.trust-inner{max-width:var(--wrap);margin:0 auto;padding:clamp(22px,3vw,30px) clamp(20px,5vw,72px);
  display:grid;grid-template-columns:1fr 1fr;gap:22px 18px}
@media(min-width:760px){.trust-inner{grid-template-columns:repeat(4,1fr)}}
.tcell .k{display:flex;align-items:center;gap:7px;font-size:.68rem;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted);font-weight:600;margin-bottom:6px}
.tcell .k::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--accent)}
.tcell .v{font-family:var(--font-display);font-weight:700;font-size:clamp(1rem,1.5vw,1.28rem);
  color:#fff;letter-spacing:-.01em;font-variant-numeric:tabular-nums}

/* ---------- numbered reasoning rows ---------- */
.reasons{display:grid;gap:0;border-top:1px solid var(--line)}
.reason{display:grid;grid-template-columns:auto 1fr;gap:clamp(18px,4vw,40px);
  align-items:start;padding:clamp(26px,4vw,40px) 0;border-bottom:1px solid var(--line)}
.reason .n{font-family:var(--font-display);font-weight:800;font-variant-numeric:tabular-nums;
  font-size:clamp(1.6rem,3vw,2.4rem);color:var(--accent);line-height:1}
.reason h3{margin-bottom:10px}
.reason p{color:var(--text-2)}

/* ---------- services index (editorial rows) ---------- */
.svc-index{border-top:1px solid var(--line)}
.svc-row{display:grid;grid-template-columns:1fr;gap:12px;
  padding:clamp(26px,3.6vw,44px) 0;border-bottom:1px solid var(--line)}
@media(min-width:760px){
  .svc-row{grid-template-columns:minmax(200px,30%) 1fr;gap:clamp(28px,6vw,72px)}
}
.svc-row h3{margin:0}
.svc-detail .body{margin:0}
.svc-caps{margin-top:16px;color:var(--text-2);font-size:.9rem;line-height:1.75}

/* ---------- generic two-column copy block ---------- */
.split{display:grid;grid-template-columns:1fr;gap:clamp(28px,5vw,56px);align-items:center}
@media(min-width:880px){.split{grid-template-columns:1fr 1fr}}
/* .split--flip moves the figure to the right column on desktop only, so two
   stacked splits do not share an identical arrangement. Mobile keeps DOM
   order, which puts the image first. */
@media(min-width:880px){.split--flip .img-slot{order:2}}
.split--photo img{border-radius:14px;border:1px solid var(--line);
  width:100%;height:100%;max-height:520px;object-fit:cover}
.prose p+p{margin-top:16px}

/* ---------- service-area list ---------- */
/* Plain listed set, deliberately NOT card-styled. Client 2026-07-26: the old
   bordered chips read as buttons and invited taps that went nowhere. Matches
   .cov-legend-list on Service Areas so both pages read as one component. */
.area-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:11px 20px;
  list-style:none;margin-top:14px}
.area-list li{display:flex;align-items:center;gap:10px;font-weight:500;color:#fff}
.area-list li::before{content:"";width:7px;height:7px;border-radius:50%;
  background:var(--accent);box-shadow:0 0 6px rgba(95,179,249,.6);flex:none}
.note{background:rgba(95,179,249,.05);border:1px solid var(--line);border-radius:12px;
  padding:clamp(18px,3vw,24px);color:var(--text-2);margin-top:28px}

/* ---------- featured reviews (Home only) ----------
   Real Google reviews, hardcoded. Scope-locked 2026-05-12: static section,
   4 to 5 hand-picked, no widget, no subscription, no ongoing cost.
   Multi-column rather than grid so the ragged quote lengths pack without
   leaving a stretched half-row. Stars are --accent, NOT gold: blue-only is
   a hard brand lock and gold would read as orange. */
.reviews{columns:3;column-gap:clamp(16px,2vw,22px)}
@media(max-width:1000px){.reviews{columns:2}}
@media(max-width:640px){.reviews{columns:1}}
.review{break-inside:avoid;-webkit-column-break-inside:avoid;
  display:inline-block;width:100%;background:var(--ink-2);
  border:1px solid var(--line);border-radius:12px;
  padding:clamp(18px,2.4vw,24px);margin:0 0 clamp(16px,2vw,22px)}
.review-stars{display:flex;gap:3px;margin-bottom:13px}
.review-stars svg{width:15px;height:15px;color:var(--accent);fill:currentColor}
.review blockquote{color:var(--text);font-size:.97rem;line-height:1.62;
  margin-bottom:15px}
.review figcaption{font-family:var(--font-display);font-weight:700;
  font-size:.92rem;color:#fff;letter-spacing:-.01em}

/* ---------- FAQ accordion ---------- */
.faq{border-top:1px solid var(--line);max-width:820px}
.qa{border-bottom:1px solid var(--line)}
.qa-q{width:100%;text-align:left;background:none;border:0;cursor:pointer;color:#fff;
  font-family:var(--font-display);font-weight:700;font-size:clamp(1.05rem,1.8vw,1.32rem);
  letter-spacing:-.01em;padding:clamp(20px,3vw,28px) 44px clamp(20px,3vw,28px) 0;position:relative;
  display:block}
.qa-q .ic{position:absolute;right:6px;top:50%;width:18px;height:18px;color:var(--accent);
  transform:translateY(-50%);transition:transform .3s var(--ease)}
.qa[open] .qa-q .ic{transform:translateY(-50%) rotate(45deg)}
.qa-a{overflow:hidden;color:var(--text-2);line-height:1.64;
  max-height:0;opacity:0;transition:max-height .35s var(--ease),opacity .3s var(--ease),padding .3s}
.qa[open] .qa-a{max-height:480px;opacity:1;padding-bottom:clamp(20px,3vw,28px)}
.qa-a p{max-width:62ch}
/* JS-off fallback: native details disclosure shows content */
.no-js .qa-a{max-height:none;opacity:1}
.no-js .qa[open] .qa-a{padding-bottom:clamp(20px,3vw,28px)}

/* ---------- contact ---------- */
.contact-grid{display:grid;grid-template-columns:1fr;gap:clamp(28px,5vw,48px)}
@media(min-width:880px){.contact-grid{grid-template-columns:.9fr 1.1fr}}
.call-card{background:var(--ink-2);border:1px solid var(--line);border-radius:14px;
  padding:clamp(24px,4vw,36px)}
.call-card .big{font-family:var(--font-display);font-weight:900;letter-spacing:-.02em;
  font-size:clamp(1.8rem,4vw,2.6rem);color:#fff;text-decoration:none;display:inline-block;
  margin:8px 0 4px;font-variant-numeric:tabular-nums}
.call-card .row{display:flex;flex-direction:column;gap:6px;margin-top:22px;
  padding-top:22px;border-top:1px solid var(--line)}
.call-card a{color:var(--accent);text-decoration:none;font-weight:500}
.hours{color:var(--text-2);font-size:.95rem;margin-top:6px}
/* On the stacked layout the call card leads with the phone-number CTA, not a large
   portrait (mobile priority is the call). Photo returns in the 2-col desktop layout. */
@media(max-width:879px){.call-card .img-slot{display:none}}

form{display:grid;gap:16px}
.field{display:grid;gap:7px}
.field label{font-size:.8rem;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.field input,.field select,.field textarea{background:var(--ink-2);border:1.5px solid var(--line);
  border-radius:10px;padding:13px 15px;color:var(--text);font-family:var(--font-body);font-size:1rem;
  transition:border-color .2s,box-shadow .2s var(--ease)}
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--accent);outline:none;
  box-shadow:0 0 0 3px rgba(95,179,249,.14)}
.field textarea{min-height:120px;resize:vertical}
/* quote form send states. The honeypot is off-screen rather than display:none
   so it still posts, and it carries no label a real person would ever see. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
/* required markers, added 2026-07-30. The asterisk alone cannot say "one of
   these two", so .form-legend carries the rule in words and the phone and email
   inputs point at it with aria-describedby. Marker is aria-hidden so a screen
   reader gets the sentence, not a bare star. */
.req{color:var(--accent);font-weight:700}
.form-legend{margin:-4px 0 2px;color:var(--muted);font-size:.88rem;line-height:1.6}
.form-note{margin-top:4px;color:#9fb0c6;font-size:.92rem;line-height:1.6}
.form-note:empty{display:none}
.form-note.is-error{color:#FFB4B4}
.quote-form.is-sending button[type=submit]{opacity:.6;pointer-events:none}
.form-done{background:var(--ink-2);border:1px solid var(--line);border-radius:14px;
  padding:clamp(24px,4vw,36px)}
.form-done h3{margin:14px 0 10px}

/* ---------- footer ---------- */
footer{position:relative;z-index:2;background:var(--ink-2);border-top:1px solid var(--line);
  padding:clamp(48px,7vw,72px) 0 clamp(28px,4vw,40px);margin-top:clamp(40px,6vw,72px)}
.foot-grid{max-width:var(--wrap);margin:0 auto;padding:0 clamp(20px,5vw,72px);
  display:grid;grid-template-columns:1fr;gap:clamp(28px,4vw,44px)}
@media(min-width:760px){.foot-grid{grid-template-columns:1.4fr 1fr 1fr}}
.foot-brand .brand{margin-bottom:14px}
.foot-brand p{color:var(--text-2);font-size:.95rem;max-width:42ch}
.foot-col h4{font-family:var(--font-body);font-size:.74rem;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted);font-weight:600;margin-bottom:14px}
.foot-col a,.foot-col p{display:block;color:var(--text-2);text-decoration:none;font-size:.95rem;
  margin-bottom:9px;max-width:none}
.foot-col a:hover{color:var(--accent)}
.foot-col .small{font-size:.85rem}
.foot-bottom{max-width:var(--wrap);margin:clamp(36px,5vw,52px) auto 0;
  padding:24px clamp(20px,5vw,72px) 0;border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between;
  color:var(--muted);font-size:.82rem}
.foot-bottom .tnum{font-variant-numeric:tabular-nums}

/* ---------- reveal as progressive enhancement (JS only) ---------- */
.js .reveal{opacity:0;transform:translateY(16px);
  transition:opacity .7s var(--ease),transform .7s var(--ease)}
.js .reveal.in{opacity:1;transform:none}
.js .reveal.d1{transition-delay:.08s}
.js .reveal.d2{transition-delay:.16s}
.js .reveal.d3{transition-delay:.24s}

/* ---------- reduced motion: full stop ---------- */
@media(prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important;scroll-behavior:auto!important}
  .rise,.reveal{opacity:1!important;transform:none!important}
  .bar{transform:scaleX(1)!important}
  .tcell{opacity:1!important;transform:none!important}
}

/* ---------- print: never ship blank (reveal/rise/bars forced visible) ---------- */
@media print{
  .js .reveal,.rise{opacity:1!important;transform:none!important}
  .bar{transform:scaleX(1)!important}
  .covmap .pin{opacity:1!important;transform:none!important}
}

/* ---------- coverage circuit (Service Areas) ---------- */
.covmap-layout{display:grid;gap:clamp(24px,4vw,52px);align-items:center;
  margin-top:clamp(28px,5vw,44px)}
@media(min-width:820px){
  .covmap-layout{grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr)}
}
.covmap-figure{position:relative}
.covmap-svg{display:block;width:100%;max-width:400px;height:auto;
  margin-inline:auto;font-family:Inter,system-ui,sans-serif}

/* the trace = one line down the valley */
.cov-trace{stroke:rgba(95,179,249,.38);stroke-width:2;stroke-linecap:round}

/* the traveling current (echoes the hero current-sweep: quick charge, long rest) */
/* offset-path MUST stay character-identical to the .cov-trace `d` in
   service-areas.html. They diverged until 2026-07-27: the trace started at
   M232,44 (Coffs Harbour) but the dot started at M238,92, so the dot could
   never reach the top pin. Client caught it. Edit both or neither. */
.cov-current{fill:var(--accent-bright);opacity:0;
  filter:drop-shadow(0 0 6px rgba(125,211,252,.9));
  offset-path:path("M232,44 C214,64 150,70 150,96 C150,120 236,116 236,140 C236,166 250,166 250,190 C250,214 224,214 224,238 C224,262 176,262 176,286 C176,310 244,310 244,334 C244,356 170,358 170,380 C170,410 252,420 252,450 C252,478 192,456 192,484");
  offset-distance:0%}
/* Travel pace, revised 2026-07-28. Was 11s x 32% = 3.5s of travel on var(--ease).
   Two changes: the travel window is now 45% of a 14s cycle (6.3s, near double), and
   the timing function is linear rather than the ease-out. Ease-out front-loads the
   speed, so the dot used to bolt out of Coffs Harbour and crawl into South West
   Rocks, which is what read as "too quick" even after the first slowdown. Linear
   is also the honest curve for a current sweep: current does not accelerate. */
.js .covmap.in .cov-current{animation:covCurrent 14s linear 1s infinite}
@keyframes covCurrent{
  0%{offset-distance:0%;opacity:0}
  2%{opacity:1}
  45%{offset-distance:100%;opacity:1}
  50%,100%{offset-distance:100%;opacity:0}
}

/* boundary near Urunga */
.cov-bound{stroke:var(--muted);stroke-width:1.4;stroke-dasharray:5 5;opacity:.65}
.cov-bound-lbl{fill:var(--muted);font-size:11px;font-weight:600;letter-spacing:.02em}

/* excluded: dim, hollow, off the line */
.cov-ring{fill:none;stroke:var(--muted);stroke-width:1.6;opacity:.7}
.cov-xlbl{fill:var(--muted);font-size:12px;font-weight:500;opacity:.82}

/* nodes on the line */
.cov-pins .pin circle{fill:var(--accent);filter:drop-shadow(0 0 6px rgba(95,179,249,.6))}
.cov-pins .pin text{fill:#fff;font-size:13px;font-weight:600}
.cov-pins .pin.cov-soft text{fill:var(--accent);font-style:italic;font-weight:500;opacity:.9}

/* Urunga edge node: hollow, sits on the boundary */
.pin-edge circle{fill:var(--ink-2);stroke:var(--muted);stroke-width:1.8}
.pin-edge text{fill:var(--muted);font-size:12px;font-weight:600}

/* nodes visible by default (no-JS safe); JS hides then pops in on reveal */
.js .covmap .pin{opacity:0;transform:scale(.2);
  transform-box:fill-box;transform-origin:center;
  transition:opacity .3s var(--ease),transform .3s var(--ease);
  transition-delay:calc(var(--d) * 70ms)}
.js .covmap.in .pin{opacity:1;transform:scale(1)}

/* legend: covered towns beside the diagram, unboxed */
.covmap-legend{align-self:center}
.cov-legend-list{list-style:none;margin:18px 0 0;padding:0;
  display:grid;grid-template-columns:1fr 1fr;gap:11px 20px}
.cov-legend-list li{display:flex;align-items:center;gap:10px;
  font-size:1rem;color:#fff;font-weight:500}
.cov-legend-list li::before{content:"";width:7px;height:7px;border-radius:50%;
  background:var(--accent);box-shadow:0 0 6px rgba(95,179,249,.6);flex:none}
.note-inline{margin-top:24px;color:#9fb0c6;font-size:.95rem;line-height:1.62;max-width:44ch}

@media(prefers-reduced-motion:reduce){
  .covmap .pin{opacity:1!important;transform:none!important}
  .cov-current{display:none!important}
}
@media(max-width:420px){
  .cov-pins .pin text,.cov-xlbl,.pin-edge text{font-size:12.5px}
  .cov-legend-list{gap:10px 14px}
}

/* ============================================================
   About, editorial spread (scoped .about-ed). Built to DESIGN.md.
   Reuses tokens, .reveal, .rise, .photo-hero, .photo-caption, .img-slot,
   .spec-label, .lead, .btn. No new @keyframes, so the existing
   reduced-motion / print full-stop rules cover every animated element.
   ============================================================ */

/* 1. hero: bigger, quieter, with a magazine standfirst */
.about-ed .ed-hero{min-height:clamp(440px,64vw,640px)}
/* client request 2026-07-26 (#844): the seated four-up replaces the walking frame.
   All four sit centred in this crop, so the old right-of-centre bias is gone.
   At 375px only ~51% of the width shows, and centred keeps all four inside it. */
.about-ed .ed-hero img{object-position:50% 28%}
@media(max-width:700px){.about-ed .ed-hero img{object-position:50% 24%}}
.about-ed .ed-hero .standfirst{font-family:var(--font-body);font-weight:400;
  font-size:clamp(1.05rem,1.7vw,1.35rem);line-height:1.55;color:var(--text);
  max-width:52ch;margin-top:20px}

/* 2. opening pull-line: oversized display, set off asymmetric */
.about-ed .pl-grid{display:grid;gap:16px}
@media(min-width:880px){
  .about-ed .pl-grid{grid-template-columns:minmax(120px,1fr) minmax(0,2.5fr);
    align-items:start;gap:clamp(28px,5vw,72px)}
}
.about-ed .pl-kicker{padding-top:10px}
.about-ed .pull-line{font-family:var(--font-display);font-weight:900;
  letter-spacing:-.03em;line-height:1;font-size:clamp(2.3rem,6vw,4.4rem);
  max-width:18ch;color:#fff}
.about-ed .pull-line .accent{color:var(--accent)}

/* 3. narrative spread: asymmetric 2-col, drop-cap, pull-quote */
.about-ed .narr{display:grid;gap:clamp(32px,5vw,56px)}
@media(min-width:900px){
  .about-ed .narr{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
    align-items:start;gap:clamp(40px,6vw,80px)}
  .about-ed .narr-photo{position:sticky;top:92px}
}
.about-ed .narr-prose .body+.body{margin-top:18px}
/* family portrait (DESIGN.md §7 dark-ground treatment): the frame is bright sand
   and pale sky sitting on --ink, so seat it with one bounded ink fade top and
   base. Same technique as .photo-cta::after, one layer, gated on .has-img so the
   placeholder stays readable until the real file loads. */
.about-ed .narr-photo .img-slot.has-img::after{content:"";position:absolute;
  inset:0;pointer-events:none;border-radius:inherit;
  background:linear-gradient(180deg,rgba(5,8,15,.20) 0%,rgba(5,8,15,0) 24%,
    rgba(5,8,15,0) 60%,rgba(5,8,15,.44) 100%)}
.about-ed .pullquote{font-family:var(--font-display);font-weight:800;
  letter-spacing:-.02em;line-height:1.03;font-size:clamp(1.95rem,4.5vw,3.3rem);
  color:#fff;margin:clamp(28px,4vw,44px) 0;padding-left:clamp(18px,2vw,26px);
  border-left:3px solid var(--accent);max-width:none}
.about-ed .pullquote small{display:block;font-family:var(--font-body);
  font-weight:500;font-size:clamp(.92rem,1.1vw,1.02rem);letter-spacing:0;
  color:var(--text-2);margin-top:16px;line-height:1.55;max-width:40ch}

/* 4. credentials as marginalia */
.about-ed .creds{display:grid;gap:clamp(28px,4vw,44px)}
@media(min-width:880px){
  .about-ed .creds{grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
    align-items:start;gap:clamp(36px,6vw,80px)}
  .about-ed .creds-head{position:sticky;top:92px}
}
.about-ed .creds-head .lead{margin-top:16px;max-width:34ch}
.about-ed .cred-list{border-top:1px solid var(--line)}
.about-ed .cred{display:grid;grid-template-columns:1fr;gap:6px;
  padding:clamp(16px,2.4vw,22px) 0;border-bottom:1px solid var(--line)}
@media(min-width:560px){
  .about-ed .cred{grid-template-columns:minmax(130px,.46fr) 1fr;
    gap:clamp(16px,3vw,32px);align-items:baseline}
}
.about-ed .cred dt{font-family:var(--font-display);font-weight:700;
  font-size:1.02rem;color:#fff;letter-spacing:-.01em}
.about-ed .cred dd{color:var(--text-2);font-size:.95rem;line-height:1.58;
  max-width:46ch}

/* 6. warm close, calm and centred */
.about-ed .ed-close{border-top:1px solid var(--line);text-align:center}
.about-ed .ed-close .spec-label{justify-content:center}
.about-ed .ed-close h2{margin:16px auto 0;max-width:20ch}
.about-ed .ed-close .lead{margin:16px auto 0;max-width:44ch}
.about-ed .ed-close .cta-row{justify-content:center;margin-top:30px}

/* ============================================================
   DUCTED DIAGRAM (Services). Added 2026-07-28, built to
   ducted-diagram-SPEC_2026-07-28.md.

   Deliberate sibling of the coverage circuit above, not a new component:
   the layout, legend and note reuse .covmap-layout / .covmap-figure /
   .covmap-legend / .cov-legend-list / .note-inline as-is. No .cov* rule
   was modified to make this fit.

   SELF-CONTAINED AND REVERTIBLE. Deleting this block plus the one
   <section class="... ductmap ..."> in services.html returns the site to
   its previous state. Nothing else needs touching.
   ============================================================ */
/* 620, not the flat version's 520: the isometric drawing is wider (aspect 1.68
   against 1.53) and needs the extra width to land at the same visual weight. */
.ductmap-svg{display:block;width:100%;max-width:620px;height:auto;
  margin-inline:auto;font-family:Inter,system-ui,sans-serif}

/* the plan itself stays quiet so the mechanical parts carry the eye */
.duct-floor{fill:rgba(11,18,32,.62);stroke:var(--line);stroke-width:2}
.duct-wall{fill:none;stroke:var(--line);stroke-width:1.2}
/* extruded wall cards for the isometric drawing. No thickness and no shading:
   a low-alpha fill and the same --line stroke as the floor, so the plan still
   stays quiet. */
.duct-wall-face{fill:rgba(30,42,60,.5);stroke:var(--line);stroke-width:1.2;
  stroke-linejoin:round}
/* .duct-wall is unused since the diagram went isometric on 2026-07-28. Kept
   because it is part of this revertible block, not because anything renders it. */

/* zoning tints, added 2026-08-05. One blue, five strengths carried on --t per
   room, so the whole set is retuned by editing the five inline values in
   services.html rather than by touching this rule. Same blue as .duct-run so
   the zones read as part of the system, not a second colour on the drawing. */
.duct-zone{fill:rgba(95,179,249,var(--t));stroke:none}

/* room labels, restored 2026-08-05 after the Tranche A geometry made them
   clear. Lighter than .duct-out-lbl on purpose: these name the space, they are
   not parts of the system, so they sit under the equipment labels in the
   reading order. */
.duct-room-lbl{fill:var(--muted);font-size:12px;font-weight:500;opacity:.82}

/* ducting: same weight and blue as .cov-trace on the coverage map */
.duct-run{fill:none;stroke:rgba(95,179,249,.38);stroke-width:2;stroke-linecap:round}
/* return air trunk: same blue, drawn heavier. On a real system the return is
   the larger duct, so this is both the honest weight and what keeps it from
   reading as a sixth supply branch. */
.duct-return{fill:none;stroke:rgba(95,179,249,.38);stroke-width:3.4;stroke-linecap:round}

/* refrigerant: outdoor unit to indoor unit only, never to an outlet.
   Dashed grey, matching .cov-bound, so it never reads as ducting. */
.duct-refrig{fill:none;stroke:var(--muted);stroke-width:1.4;stroke-dasharray:5 5;opacity:.65}

/* the two units, plus the return air grille. .duct-unit is the top face of each
   box; .duct-unit-face is a side face, at marginally lower alpha so the box
   reads as a box. That 4.5 points of alpha is the only place any face is
   treated differently from another, and it is not lighting. */
.duct-unit{fill:rgba(95,179,249,.10);stroke:var(--accent);stroke-width:1.5}
.duct-unit-face{fill:rgba(95,179,249,.055);stroke:var(--accent);stroke-width:1.5;
  stroke-linejoin:round}
.duct-fan{fill:none;stroke:var(--accent);stroke-width:1.2;opacity:.5}
/* condenser coil on the outdoor unit's wall-facing side, added 2026-08-04 when
   the unit was rotated flush to the wall. Same weight as the grille slats. */
.duct-fin{fill:none;stroke:var(--accent);stroke-width:1;opacity:.45}
.duct-unit-lbl{fill:#fff;font-size:10px;font-weight:700;letter-spacing:.04em}
.duct-out-lbl{fill:var(--muted);font-size:10px;font-weight:600}
.duct-grille{fill:none;stroke:var(--muted);stroke-width:1.3;opacity:.85}
.duct-grille-slat{fill:none;stroke:var(--muted);stroke-width:1;opacity:.6}

/* ceiling outlets: same node treatment as .cov-pins .pin circle, with a
   faint ring so it reads as air spreading into the room */
.duct-outlet circle{fill:var(--accent);filter:drop-shadow(0 0 6px rgba(95,179,249,.6))}
.duct-outlet .duct-spread{fill:none;stroke:var(--accent);stroke-width:1.2;
  opacity:.22;filter:none}

/* drop lines: each ceiling outlet down to its room floor. Standard axonometric
   device. It is what stops a floating outlet reading as unattached to any room,
   and it is what makes the height legible at 350px. */
.duct-drop{fill:none;stroke:var(--accent);stroke-width:1;opacity:.2;
  stroke-dasharray:3 4}

/* outlets visible by default (no-JS safe); JS hides then pops them in on
   reveal, same 70ms stagger as the coverage map pins */
.js .ductmap .duct-outlet{opacity:0;transform:scale(.2);
  transform-box:fill-box;transform-origin:center;
  transition:opacity .3s var(--ease),transform .3s var(--ease);
  transition-delay:calc(var(--d) * 70ms)}
.js .ductmap.in .duct-outlet{opacity:1;transform:scale(1)}

/* Airflow. Three dots, not five: three reads as distribution without turning
   into a light show. Linear, not ease-out, for the same reason the coverage
   dot was changed on 2026-07-28: ease-out front-loads the speed and reads as
   "too quick". Air does not accelerate either. Delays are staggered so the
   three never fire in unison, and the gaps keep something moving most of the
   cycle without it ever feeling busy.
   Each dot's offset-path is written inline in services.html, on the line
   under its duct `d`. That is on purpose. See the note on .cov-current. */
.duct-flow{fill:var(--accent-bright);opacity:0;offset-distance:0%;
  filter:drop-shadow(0 0 6px rgba(125,211,252,.9))}
.js .ductmap.in .duct-flow{animation:ductFlow 12s linear infinite}
.js .ductmap.in .duct-flow-2{animation-delay:1.2s}
.js .ductmap.in .duct-flow-3{animation-delay:4.6s}
.js .ductmap.in .duct-flow-5{animation-delay:8.2s}
@keyframes ductFlow{
  0%{offset-distance:0%;opacity:0}
  3%{opacity:1}
  33%{offset-distance:100%;opacity:1}
  38%,100%{offset-distance:100%;opacity:0}
}

@media(prefers-reduced-motion:reduce){
  .ductmap .duct-outlet{opacity:1!important;transform:none!important}
  .duct-flow{display:none!important}
}
/* print: never ship blank, and never ship a stray airflow dot */
@media print{
  .ductmap .duct-outlet{opacity:1!important;transform:none!important}
  .duct-flow{display:none!important}
}
@media(max-width:420px){
  .duct-room-lbl{font-size:13px}
}
/* ==================== END DUCTED DIAGRAM ==================== */
