/* ==========================================================================
   TM Construction, A Palmetto State Builder LLC
   Shared stylesheet for every page.
   Built from tm-construction-design-package.md. Palette section 4,
   type section 5, signature element section 6, vector layer section 18.
   ========================================================================== */

/* --- 1. Tokens ----------------------------------------------------------- */

:root{
  /* Palette, sampled from the approved hero footage. Section 4. */
  --canvas:#0F1C2B;              /* deep navy, the dominant field */
  --canvas-light:#F5F3EF;        /* warm off white, never pure white */
  --panel:#16273A;               /* raised surfaces on dark */
  --panel-light:#FFFFFF;         /* cards on the light sections */
  --accent:#C49A5C;              /* gold: CTA, rules, rare emphasis */
  --accent-hover:#B0873F;
  --accent-muted:rgba(196,154,92,.18);
  --accent-line:rgba(196,154,92,.28);
  --text-primary:#FFFFFF;
  --text-secondary:#C2CDD8;
  --text-primary-light:#132030;
  --text-secondary-light:#4E5C68;

  /* Hairlines. Interactive borders get their own stronger value, because
     hairline colours fail the 3:1 interface floor. */
  --rule-dark:rgba(194,205,216,.16);
  --rule-light:rgba(19,32,48,.12);
  --border-interactive-dark:rgba(194,205,216,.42);
  --border-interactive-light:rgba(19,32,48,.38);

  /* Type */
  --display:"Archivo","Arial Narrow",system-ui,sans-serif;
  --body:"Source Sans 3",system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:"JetBrains Mono",ui-monospace,"SFMono-Regular",Consolas,monospace;

  /* Motion. Two curves, used everywhere. Nothing snaps. */
  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-out:cubic-bezier(.16,.84,.44,1);
  --dur:.5s;
  --dur-slow:.9s;

  /* Text shadow for type over live footage. Three layers. */
  --tshadow:0 1px 2px rgba(5,5,10,.95),0 3px 12px rgba(5,5,10,.78),0 10px 44px rgba(5,5,10,.8);

  /* Spacing */
  --gutter:clamp(20px,5vw,64px);
  --maxw:1280px;
  --section-y:clamp(64px,9vw,132px);
}

/* --- 2. Reset and base --------------------------------------------------- */

*,*::before,*::after{box-sizing:border-box}

html,body{
  overflow-x:hidden;            /* fallback first */
  overflow-x:clip;              /* the one that actually holds */
}

html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}

body{
  margin:0;
  background:var(--canvas);
  color:var(--text-primary);
  font-family:var(--body);
  font-size:clamp(16px,1.05vw,17.5px);
  line-height:1.6;
  font-weight:400;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

img,video,svg{max-width:100%;display:block}
img{height:auto}

a{color:inherit;text-decoration:none}

h1,h2,h3,h4{
  font-family:var(--display);
  font-weight:800;
  text-transform:uppercase;
  letter-spacing:-.015em;
  line-height:1.03;
  margin:0;
  text-wrap:balance;
}

p{margin:0}

/* Size text blocks in ch on the TEXT element itself, never a container:
   a ch resolves against the font the element inherits. */
.lede{font-size:clamp(17px,1.35vw,20px);line-height:1.55;max-width:62ch}
.body-w{max-width:68ch}

/* `p{margin:0}` above is the site-wide reset, so every context that runs
   prose has to state its own gap: `.arealist p + p` is 7px, `.faq .a p + p`
   is 12px. `.body-w` never stated one, so the two-paragraph argument body on
   a service page and EVERY PARAGRAPH OF EVERY BLOG POST ran with no gap at
   all. 15px is a hair under one line of the 16px/25.6px body, which is the
   usual prose measure and sits between the two rules above.

   ⚠ `:not(.mt)` IS NOT COSMETIC, IT PROTECTS /about/. That page spaces its
   four `.body-w` paragraphs with an explicit `class="mt"`, which resolves to
   44px at 1440. `.body-w p + p` is (0,1,2) and `.mt` is (0,1,0), so without
   the exclusion this rule would WIN and silently collapse that block from
   44px to 15px. Anything that opts into a gap by hand keeps it. */
.body-w p + p:not(.mt){margin-top:15px}

/* Headings inside a blog post body. `h1,h2,h3,h4{margin:0}` above is the
   site-wide reset and `.post` never overrode it, so every heading in all five
   posts sat FLUSH against the prose above and below it — the same class of
   bug as the paragraph gap, found at the same time.

   Top scales with the heading, which is clamp(27px, .., 46px): 46px of air at
   1440 and 30px at 375. Bottom is a flat 14px, just under the 15px paragraph
   gap, so a heading sits closer to the text it owns than to the text it
   follows. That asymmetry is the whole point; a symmetric margin reads as a
   heading floating between two blocks rather than introducing one.

   `.post h2` is (0,1,1) and beats the (0,0,1) type reset. ⚠ SCOPE: `.post`
   is used in exactly one place, `build-blog.mjs`, on the post body div.
   `class="poster"` on the homepage is a DIFFERENT class and is untouched —
   CSS matches whole tokens, so it cannot be caught by this, but a careless
   grep for 'class="post' finds it and that is worth knowing.

   There are no lists, h3s or h4s inside `.post`: every `<ul>` and `<li>` on
   a blog page belongs to the nav or the footer, outside this block. Checked
   rather than assumed. A post that later uses one will need its own rule. */
.post h2{margin:clamp(30px,3.4vw,46px) 0 14px}

/* An icon leading a card heading. Added 2026-09-23 for the homepage
   commercial section, which is the only place on the site an icon sits above
   a heading rather than inside a button or a list marker.

   Sized in px, NOT em, on purpose: these five sit above five headings of
   different lengths and have to agree with EACH OTHER, not with the type
   under them. An em size would make the icon jump whenever an h3 wrapped.

   The colour is set here rather than inherited. `.band-light` paints card
   text with the dark primary, so a `currentColor` stroke would come out
   charcoal; `--accent-hover` is the darker gold the same band already uses
   for `.kicker`, which is the one that holds contrast on the light canvas. */
.cardicon{
  display:block;width:28px;height:28px;margin-bottom:16px;
  color:var(--accent-hover);stroke-width:1.6;
}
.narrow{max-width:54ch}

/* --- 3. Skip link and focus --------------------------------------------- */

.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--accent);color:#1A1206;
  padding:14px 22px;font-family:var(--mono);font-size:13px;
  border-radius:0 0 6px 0;
}
.skip:focus{left:0}

:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
  border-radius:2px;
}

/* --- 4. The environment layer (section 18) -------------------------------
   One fixed background behind everything so scrolling feels like moving
   through a place, not past stacked sections. 90 second cycle. */

.env{
  position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:
    radial-gradient(120% 80% at 18% 0%,rgba(196,154,92,.09),transparent 62%),
    radial-gradient(90% 70% at 88% 100%,rgba(196,154,92,.055),transparent 60%),
    var(--canvas);
}
.env::after{
  content:"";position:absolute;inset:-12%;
  background:radial-gradient(46% 38% at 30% 34%,rgba(196,154,92,.10),transparent 70%);
  animation:envdrift 96s var(--ease) -34s infinite alternate;
  will-change:transform;
}
@keyframes envdrift{
  0%  {transform:translate3d(-3%,-2%,0) scale(1)}
  100%{transform:translate3d(4%,3%,0) scale(1.12)}
}

/* Fine grain, one repeating SVG, no network request. */
.grain{
  position:fixed;inset:0;z-index:-1;pointer-events:none;opacity:.055;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.6'/%3E%3C/svg%3E");
}

/* --- 5. Layout ----------------------------------------------------------- */

.wrap{width:min(100% - (var(--gutter) * 2),var(--maxw));margin-inline:auto}

main{display:block}

section{position:relative}

.band{padding-block:var(--section-y)}
/* A band carrying one image and no copy sits closer to what is above it. */
.band-tight{padding-block:clamp(30px,4vw,56px)}
.band-light{background:var(--canvas-light);color:var(--text-primary-light)}
.band-light h1,.band-light h2,.band-light h3,.band-light h4{color:var(--text-primary-light)}
.band-light .muted{color:var(--text-secondary-light)}
.muted{color:var(--text-secondary)}

.kicker{
  font-family:var(--mono);font-size:12px;font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent);margin:0 0 14px;
}
.band-light .kicker{color:var(--accent-hover)}   /* darker gold, contrast on light */

h1{font-size:clamp(34px,5.6vw,68px)}
h2{font-size:clamp(27px,3.6vw,46px)}
h3{font-size:clamp(19px,1.7vw,25px);letter-spacing:-.005em}
h4{font-size:clamp(15px,1.1vw,17px);letter-spacing:.02em}

/* --- 6. The layer line: the signature element (section 6) ----------------
   A 1px gold line down the left edge of every page that draws itself on
   scroll, spurring into mono-labelled ticks at each major section.
   Remove it and the site loses its argument. That is the test. */

.layerline{
  position:fixed;left:max(14px,calc((100vw - var(--maxw)) / 2 - 34px));
  top:0;bottom:0;width:1px;z-index:3;pointer-events:none;
}
.layerline i{
  position:absolute;inset:0;display:block;
  background:linear-gradient(180deg,transparent,var(--accent-line) 6%,var(--accent-line) 94%,transparent);
  transform-origin:top;
  transform:scaleY(var(--draw,0));
}
.tick{
  position:absolute;left:0;display:flex;align-items:center;gap:9px;
  opacity:var(--on,0);transition:opacity .5s var(--ease);
}
.tick::before{
  content:"";width:16px;height:1px;background:var(--accent);opacity:.7;
}
.tick span{
  font-family:var(--mono);font-size:10px;font-weight:500;
  letter-spacing:.2em;color:var(--accent);white-space:nowrap;opacity:.85;
}
/* No room for it on narrow or short screens. Decorations never collide. */
@media (max-width:1180px),(max-height:560px){.layerline{display:none}}

/* --- 7. Navigation ------------------------------------------------------- */

.nav{
  position:sticky;top:0;z-index:60;
  background:rgba(15,28,43,.82);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--rule-dark);
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease);
}
.nav-in{display:flex;align-items:center;gap:clamp(14px,2vw,30px);min-height:70px}

.brand{display:flex;align-items:center;gap:11px;flex:0 0 auto;padding-block:10px}
.brand svg{width:34px;height:34px;flex:0 0 auto}
/* The brand mark is the South Carolina silhouette from the real logo. It is
   wider than it is tall, so it is sized on WIDTH with height auto; forcing it
   into a 34px square would squash the state. */
.brand .markimg{width:38px;height:auto;flex:0 0 auto;display:block}
/* The nav logo is the full stacked lockup (shape, TM, CONSTRUCTION, tagline) as one
   transparent PNG, 2026-10-10, at the client's request. Sized on HEIGHT so the nav bar
   stays the same; the footer still uses the small mark plus text. */
.brand .lockup{height:54px;width:auto;display:block}
@media (max-width:760px){.brand .lockup{height:44px}}
.brand b{
  font-family:var(--display);font-weight:800;font-size:19px;
  letter-spacing:.01em;line-height:1;display:block;color:#fff;
}
.brand small{
  display:block;font-family:var(--mono);font-size:8.5px;font-weight:500;
  letter-spacing:.19em;color:var(--accent);margin-top:3px;
}

.navlinks{display:flex;align-items:center;gap:clamp(10px,1.5vw,22px);margin-left:auto}
.navlinks > a,.navlinks .drop > button{
  font-size:14px;font-weight:600;letter-spacing:.01em;color:var(--text-secondary);
  background:none;border:0;font-family:inherit;cursor:pointer;
  padding:9px 2px;position:relative;
  transition:color var(--dur) var(--ease);
}
.navlinks > a:hover,.navlinks .drop > button:hover,
.navlinks > a[aria-current="page"]{color:#fff}
.navlinks > a::after{
  content:"";position:absolute;left:0;right:0;bottom:2px;height:1.5px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur) var(--ease);
}
.navlinks > a:hover::after,.navlinks > a[aria-current="page"]::after{transform:scaleX(1)}

/* Services dropdown, all twelve */
.drop{position:relative}
.drop > button::after{content:"";display:inline-block;width:5px;height:5px;margin-left:7px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform var(--dur) var(--ease)}
.drop[data-open="true"] > button::after{transform:translateY(1px) rotate(225deg)}
.dropmenu{
  position:absolute;top:calc(100% + 10px);left:50%;translate:-50% 0;
  min-width:430px;padding:14px;border-radius:12px;
  background:var(--panel);border:1px solid var(--border-interactive-dark);
  box-shadow:0 28px 70px rgba(3,8,14,.6);
  display:grid;grid-template-columns:1fr 1fr;gap:2px;
  opacity:0;visibility:hidden;transform:translateY(-7px);
  transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease),visibility var(--dur);
}
.drop[data-open="true"] .dropmenu{opacity:1;visibility:visible;transform:none}
.dropmenu a{
  padding:9px 12px;border-radius:7px;font-size:13.5px;font-weight:600;
  color:var(--text-secondary);transition:background var(--dur) var(--ease),color var(--dur) var(--ease);
}
.dropmenu a:hover{background:rgba(196,154,92,.13);color:#fff}

.navcta{display:flex;align-items:center;gap:12px;flex:0 0 auto}
.tel{
  font-family:var(--mono);font-size:13px;font-weight:500;color:var(--accent);
  white-space:nowrap;transition:color var(--dur) var(--ease);
}
.tel:hover{color:#fff}

.navtoggle{
  display:none;background:none;border:1px solid var(--border-interactive-dark);
  border-radius:8px;width:44px;height:44px;cursor:pointer;
  align-items:center;justify-content:center;flex:0 0 auto;margin-left:auto;
}
.navtoggle span{display:block;width:19px;height:1.5px;background:#fff;position:relative}
.navtoggle span::before,.navtoggle span::after{
  content:"";position:absolute;left:0;width:19px;height:1.5px;background:#fff;
  transition:transform var(--dur) var(--ease)}
.navtoggle span::before{top:-6px}
.navtoggle span::after{top:6px}

@media (max-width:1000px){
  .navtoggle{display:flex}
  .navlinks{
    position:fixed;inset:70px 0 auto;margin:0;flex-direction:column;align-items:stretch;
    gap:0;padding:14px var(--gutter) 26px;background:var(--panel);
    border-bottom:1px solid var(--rule-dark);
    max-height:calc(100dvh - 70px);overflow-y:auto;
    transform:translateY(-8px);opacity:0;visibility:hidden;
    transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease),visibility var(--dur);
  }
  body[data-nav="open"] .navlinks{opacity:1;visibility:visible;transform:none}
  .navlinks > a,.navlinks .drop > button{
    width:100%;text-align:left;padding:14px 0;font-size:16px;
    border-bottom:1px solid var(--rule-dark);
  }
  .navlinks > a::after{display:none}
  .drop{width:100%}
  .dropmenu{
    position:static;translate:none;min-width:0;width:100%;
    grid-template-columns:1fr;background:none;border:0;box-shadow:none;
    padding:0 0 0 12px;transform:none;
    display:none;opacity:1;visibility:visible;
  }
  .drop[data-open="true"] .dropmenu{display:grid}
  .navcta .tel{display:none}
}

/* Under 560px the brand plus the toggle plus a button does not fit, and the
   button was being clipped off the edge. The fixed call bar at the bottom
   already carries Call and Free estimate, so the nav button stands down. */
@media (max-width:560px){
  .navcta{display:none}
  .brand b{font-size:16.5px}
  .brand small{font-size:7.5px;letter-spacing:.16em}
  .brand svg{width:29px;height:29px}
}

/* --- 8. Buttons ---------------------------------------------------------- */

.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-family:var(--body);font-weight:600;font-size:14.5px;letter-spacing:.01em;
  padding:13px 24px;border-radius:8px;border:1px solid transparent;cursor:pointer;
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease),
             border-color var(--dur) var(--ease),transform var(--dur) var(--ease);
  text-align:center;
}
.btn-primary{background:var(--accent);color:#1A1206;border-color:var(--accent)}
.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover);transform:translateY(-1px)}
.btn-ghost{background:transparent;color:#fff;border-color:var(--border-interactive-dark)}
.btn-ghost:hover{border-color:var(--accent);color:var(--accent);transform:translateY(-1px)}
.band-light .btn-ghost{color:var(--text-primary-light);border-color:var(--border-interactive-light)}
.band-light .btn-ghost:hover{border-color:var(--accent-hover);color:var(--accent-hover)}
.btn-sm{padding:10px 18px;font-size:13.5px}

/* A shadowed button label reads as grime. */
.hero .btn{text-shadow:none}

/* Touch targets. 44px minimum on anything a thumb has to hit.
   Never swap an element's display type without re-declaring its alignment:
   a bare inline-flex left-aligns button labels and shrinks full-width rows. */
@media (pointer:coarse){
  .btn{min-height:44px}
  .navlinks > a,.dropmenu a,.tel{min-height:44px;display:inline-flex;align-items:center}

  /* Standalone call-to-action links, not inline in a sentence. */
  .tlink{min-height:44px}

  /* Footer link lists. The gap goes to zero and the height moves into the
     links themselves, so the column height barely changes but every row is
     actually tappable. */
  .foot ul{gap:0}
  .foot ul a{min-height:44px;display:flex;align-items:center}
  .foot ul li.muted{padding-block:11px}

  /* The brand mark in the header and footer. */
  .brand{min-height:44px}

  /* Filter tabs on the gallery. */
  .galtabs button{min-height:44px}

  /* Breadcrumbs are standalone navigation, not inline prose. */
  .crumbs a{min-height:44px;display:inline-flex;align-items:center}
}

/* Inline links inside a running sentence are deliberately NOT forced to 44px.
   Stretching them breaks the line box and the text around them, and WCAG
   exempts links inline in a block of text for exactly that reason. */

.arrow{width:14px;height:14px;flex:0 0 auto;transition:transform var(--dur) var(--ease)}
.btn:hover .arrow,.tlink:hover .arrow{transform:translateX(3px)}

.tlink{
  display:inline-flex;align-items:center;gap:8px;
  font-weight:600;font-size:14.5px;color:var(--accent);
  transition:color var(--dur) var(--ease);
}
.tlink:hover{color:#fff}
.band-light .tlink{color:var(--accent-hover)}
.band-light .tlink:hover{color:var(--text-primary-light)}

/* --- 9. Section furniture ------------------------------------------------ */

.hdr{margin-bottom:clamp(30px,4vw,54px);max-width:70ch}
.hdr h2 + .lede{margin-top:18px}
/* A short line that sits between an h2 and its lede, for the one place a
   heading has a tagline under it rather than going straight to body copy.
   Added 2026-09-23 for the homepage "Why choose TM Construction?" block.

   It is a <p>, not a heading, on purpose: the three numbered points below it
   are h3s under that h2, and slipping a real h3 in here would sit at the
   same level as them and read as a fourth sibling to a screen reader.

   NOT GOLD, and that is the whole reason it is declared rather than reusing
   .kicker. This block sits on .band-light, where the light-band gold
   --accent-hover measures about 3.29:1 and fails the 4.5 small-text bar.
   Design package section 4 is explicit that gold never carries text on the
   light sections. The display face and the tracking do the work instead. */
.subhead{
  margin-top:14px;font-family:var(--display);font-weight:700;
  font-size:clamp(14.5px,1.35vw,18px);letter-spacing:.06em;
  text-transform:uppercase;line-height:1.3;
}
.hdr .subhead + .lede{margin-top:16px}

.rule{
  height:1px;border:0;margin:0;
  background:linear-gradient(90deg,var(--accent),transparent 62%);
  transform:scaleX(var(--draw,0));transform-origin:left;
  transition:transform 1.1s var(--ease);
}
.band-light .rule{background:linear-gradient(90deg,var(--accent-hover),transparent 62%)}

/* The horizon divider, hand-drawn SVG that draws left to right on entry. */
.horizon{display:block;width:100%;height:26px;overflow:visible}
.horizon path{
  fill:none;stroke:var(--accent);stroke-width:1;opacity:.42;
  stroke-dasharray:var(--len,1400);stroke-dashoffset:var(--len,1400);
  transition:stroke-dashoffset 1.5s var(--ease);
}
.in .horizon path{stroke-dashoffset:0}

/* --- 10. Cards ----------------------------------------------------------- */

.grid{display:grid;gap:clamp(14px,1.6vw,22px)}
.g2{grid-template-columns:repeat(2,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}
@media (max-width:900px){.g3,.g4{grid-template-columns:repeat(2,1fr)}}
@media (max-width:620px){.g2,.g3,.g4{grid-template-columns:1fr}}

.card{
  position:relative;border-radius:13px;overflow:hidden;
  background:var(--panel);border:1px solid var(--rule-dark);
  transition:border-color var(--dur) var(--ease),transform var(--dur) var(--ease);
}
.card:hover{border-color:var(--accent-line);transform:translateY(-3px)}
.card-media{position:relative;aspect-ratio:16/10;overflow:hidden;background:#0B1622}
.card-media img{width:100%;height:100%;object-fit:cover;
  transition:transform 1.1s var(--ease)}
.card:hover .card-media img{transform:scale(1.045)}
.card-media::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,transparent 42%,rgba(9,17,27,.72));
}
.card-body{padding:20px 22px 24px}
.card-body h3{margin-bottom:8px}
/* :not(.kicker) IS LOAD BEARING, and it is here rather than in a more
   specific rule further down on purpose.

   An eyebrow in a card is a <p class="kicker">, so this rule used to match
   it. `.card-body p` is specificity (0,1,1) and `.kicker` is (0,1,0), so
   this one won and the eyebrow rendered in --text-secondary GREY instead of
   gold. Caught by reading the computed colour, not by looking at it: at
   11px the two are easy to mistake for each other on navy.

   The fix excludes the eyebrow here instead of restating gold in the
   .card-body .kicker rule below. Restating it would have won this fight and
   then lost a different one, because `.band-light .kicker` swaps to
   --accent-hover on light bands and a later, equally specific override
   would have blocked that swap. Excluding it leaves the shared .kicker
   rules in charge of colour, which is where that decision belongs.

   No existing element changes: nothing else in a .card-body carries
   .kicker, so every other card paragraph matches exactly as before. */
.card-body p:not(.kicker){font-size:14.5px;color:var(--text-secondary);max-width:44ch}
.card-body .tlink{margin-top:14px}
/* THE SPLIT-PATH CARDS OPT OUT OF THE 44ch MEASURE, added 2026-09-23.

   The 44ch cap above is right for the services grid, which is 3-up and
   whose cards are about 410px wide at 1440: the cap barely engages there.
   The split path is 2-up and its cards are 629px, so the same cap left the
   body text at 317px, FIFTY FOUR PERCENT of the space available to it, and
   wrapping to four and six lines inside a half-empty card.

   Scoped to #paths so the seven services tiles and the two other .card-body
   users on this page keep the 44ch measure they were designed around. It is
   an ID selector and therefore specificity (1,0,1), which beats the
   (0,2,1) rule above cleanly; that is deliberate, not laziness.

   ⚠ THIS DELIBERATELY EXCEEDS THE COMFORTABLE READING MEASURE. At 1440 the
   full body width is about 80ch, above the usual 45 to 75. It is fine HERE
   because these are two and three line paragraphs, where a long line costs
   nothing; do not copy this rule onto a block of running text. */
#paths .card-body p:not(.kicker){max-width:none}
/* An eyebrow inside a card. Added 2026-09-23 for the homepage split path,
   which is the first and so far only place a .kicker sits in a .card-body.

   Two things are dialled down from the section-level .kicker rather than
   inherited. The size drops 12px to 11px because this one sits above an h3
   in a compact card, not above an h2 at the top of a band. And the bottom
   margin drops 14px to 9px, because .card-body h3 already carries an 8px
   margin-bottom: at the shared 14px the eyebrow floated further from its own
   heading than the heading sat from its body copy, which reads as the
   eyebrow belonging to the image above instead of to the words below it.

   COLOUR IS DELIBERATELY ABSENT FROM THIS RULE. It is settled by the shared
   .kicker rules, which is what the :not(.kicker) on .card-body p above
   exists to allow. .card is --panel, so --accent applies and measures
   5.88:1, over the 4.5 small-text bar, and a card on a light band would
   still pick up the --accent-hover swap from .band-light .kicker. Do not
   add a colour here; fix the thing shadowing it instead. */
.card-body .kicker{font-size:11px;margin-bottom:9px}

/* The call-to-action tile in the services grid. Same .card shell as its
   neighbours, so it inherits their radius, border, hover lift and timing and
   cannot drift away from them. What changes is the fill: no media block, no
   body copy, and the accent carrying it instead of a photograph.

   It stretches to the row rather than setting its own height. Grid items
   default to align-items:stretch, so this tile is exactly as tall as the
   card beside it whatever that card's copy does, which is why there is no
   aspect-ratio or min-height here to fall out of sync. */
.card-cta{
  display:flex;align-items:center;justify-content:center;
  padding:clamp(26px,3.2vw,44px) 26px;
  text-align:center;
  background:var(--accent-muted);
  border-color:var(--accent-line);
}
.card-cta-in{display:flex;flex-direction:column;align-items:center;gap:15px}
.card-cta-t{
  font-family:var(--display);font-weight:800;text-transform:uppercase;
  font-size:clamp(18px,1.85vw,24px);line-height:1.15;letter-spacing:-.01em;
  color:var(--accent);
  max-width:12ch;          /* on the text element in its own font, see line 92 */
}
.card-cta .arrow{width:24px;height:24px;color:var(--accent)}

/* Hover and focus are the same gesture here, because the whole tile is the
   control. On the service cards the control is the small link inside, so the
   card takes the hover and the link takes the focus ring; this tile has to do
   both jobs itself, or a keyboard user gets the ring with none of the change
   that tells a mouse user it is live.

   NO transform:translateY here, and that is deliberate rather than an
   oversight. `.rise.in .r{transform:none}` is three classes and beats
   `.card:hover` at two, so the lift on .card has been inert on every card
   inside a .rise grid for as long as both have existed: the JS adds `in` and
   `done` and never removes `rise`, so that rule matches forever. Writing a
   translate here would put a declaration in the file that does nothing and
   would drift away from the neighbours it is supposed to match. If the lift
   is ever wanted back, fix it once on .card by riding the value on a custom
   property that the entrance rule re-declares, the way .proofcard handles
   its tilt, and this tile will come along with it. */
.card-cta:hover,.card-cta:focus-visible{border-color:var(--accent)}
.card-cta:hover .card-cta-t,.card-cta:focus-visible .card-cta-t{color:#fff}
.card-cta:hover .arrow,.card-cta:focus-visible .arrow{color:#fff;transform:translateX(4px)}

.band-light .card{background:var(--panel-light);border-color:var(--rule-light)}
.band-light .card-body p{color:var(--text-secondary-light)}

/* --- 11. Forms ----------------------------------------------------------- */

.form{display:grid;gap:15px}
.field{display:grid;gap:7px}
.field label{
  font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.15em;text-transform:uppercase;color:var(--accent);
}
.band-light .field label{color:var(--accent-hover)}
.field input,.field textarea{
  font-family:var(--body);font-size:16px;color:#fff;
  background:rgba(255,255,255,.045);
  border:1px solid var(--border-interactive-dark);border-radius:9px;
  padding:13px 15px;width:100%;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease);
}
.field textarea{min-height:118px;resize:vertical}
.field select{
  font-family:var(--body);font-size:16px;color:#fff;
  background:rgba(255,255,255,.045);
  border:1px solid var(--border-interactive-dark);border-radius:9px;
  padding:13px 15px;width:100%;
}
.field select:focus{outline:none;border-color:var(--accent);background:rgba(255,255,255,.07)}
.field select:invalid{color:rgba(194,205,216,.5)}
.field select option{color:#14202c;background:#fff}
.field input::placeholder,.field textarea::placeholder{color:rgba(194,205,216,.5)}
.field input:focus,.field textarea:focus{
  outline:none;border-color:var(--accent);background:rgba(255,255,255,.07)}
.band-light .field input,.band-light .field textarea{
  color:var(--text-primary-light);background:#fff;border-color:var(--border-interactive-light)}
/* Was #8A96A1, which measured 3.02:1 on white and failed the 4.5 floor for
   text. Raised to --text-secondary-light on 2026-09-19: 6.87:1, already in
   the palette, and it reaches every light-band form at once. Do not lighten
   it back. The .formwhite block below uses #6B7480 for the same reason and
   also passes; two values, both over the bar. */
.band-light .field input::placeholder,.band-light .field textarea::placeholder{color:var(--text-secondary-light)}

.form .btn{width:100%;margin-top:4px}
.formnote{font-size:13px;color:var(--text-secondary);margin-top:2px}
/* Empty until site.js puts a failure message in it. */
.formnote:empty{display:none}
.band-light .formnote{color:var(--text-secondary-light)}

/* The form's own heading, matching the section headline beside it. It borrows
   that headline's size only; the display font, weight, tracking and uppercase
   all come from the shared h1-h4 rule, so the two cannot drift apart. */
.formhead{font-size:clamp(27px,3.6vw,46px);margin-bottom:6px}

.formdone{
  display:none;padding:26px;border-radius:12px;text-align:center;
  border:1px solid var(--accent-line);background:var(--accent-muted);
}
.formdone h3{margin-bottom:8px}
.formdone p{font-size:15px;color:var(--text-secondary)}
.band-light .formdone p{color:var(--text-secondary-light)}
form[data-sent="true"] .form{display:none}
form[data-sent="true"] .formdone{display:block}

/* The builder portrait beside the estimate form. Cropped to a landscape band
   so the column does not become a tall tower next to the form. */
.formportrait{
  margin:0 0 26px;border-radius:13px;overflow:hidden;
  border:1px solid var(--rule-dark);max-width:520px;
}
.formportrait img{
  width:100%;aspect-ratio:5/4;object-fit:cover;object-position:50% 22%;display:block;
}
.band-light .formportrait{border-color:var(--rule-light)}

/* Optional call-time preference. Pre-set to Anytime so it never blocks. */
.whenfield{border:0;margin:2px 0 0;padding:0;min-width:0}
.whenfield legend{
  font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.15em;
  text-transform:uppercase;color:var(--accent);padding:0;margin-bottom:9px;
}
.band-light .whenfield legend{color:var(--accent-hover)}
.whenopts{display:flex;flex-wrap:wrap;gap:8px}
.whenopts label{position:relative;cursor:pointer}
.whenopts input{position:absolute;opacity:0;width:100%;height:100%;left:0;top:0;margin:0;cursor:pointer}
.whenopts span{
  display:inline-flex;align-items:center;justify-content:center;
  padding:9px 15px;border-radius:100px;font-size:13.5px;
  border:1px solid var(--border-interactive-dark);color:var(--text-secondary);
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease),background var(--dur) var(--ease);
}
.whenopts input:checked + span{
  border-color:var(--accent);color:var(--accent);background:var(--accent-muted);
}
.whenopts input:focus-visible + span{outline:2px solid var(--accent);outline-offset:3px}
.band-light .whenopts span{border-color:var(--border-interactive-light);color:var(--text-secondary-light)}
.band-light .whenopts input:checked + span{border-color:var(--accent-hover);color:var(--accent-hover)}
.whenday{margin-top:13px}
.whenday input[type=date]{color-scheme:dark}

/* --- 11c. White form controls on a dark band -----------------------------
   A modifier for the homepage estimate form. The same .field and .whenopts
   markup is generated into every service page and the contact page, so this
   is scoped to .formwhite and changes nothing else.

   TWO MEASURED CONSTRAINTS SHAPE EVERY VALUE BELOW, and neither is obvious:

   1. The placeholder does not reuse the light-band grey. #6B7480 is
      4.74:1 and still reads as muted. Do not lighten it back. (The light
      band itself was #8A96A1 at 3.02:1 until 2026-09-19; it is now
      --text-secondary-light at 6.87:1, so both pass.)

   2. Gold cannot do the work it does on navy. --accent on white is 2.58:1,
      under even the 3:1 boundary floor, so it is unusable here as either
      text or a border. --accent-hover is 3.29:1 and carries the selected
      outline; the selected LABEL stays dark rather than turning gold,
      because gold text on a light ground is design package section 4's one
      hard prohibition. That is why the selected pill is signalled by ring
      plus wash plus weight instead of by colouring the words. */

.formwhite .field input,
.formwhite .field textarea,
.formwhite .field select{
  color:var(--text-primary-light);
  background:var(--panel-light);
  border-color:var(--border-interactive-light);
}
.formwhite .field select:invalid{color:#6B7480}
.formwhite .field input::placeholder,
.formwhite .field textarea::placeholder{color:#6B7480}
/* The field's own white edge against the navy band is 17.19:1, so the border
   is definition rather than the thing making the control findable. Focus
   still gets a real 2px ring: accent-hover at 3.29:1 on white, doubled by an
   inset shadow so it does not shift the layout. */
.formwhite .field input:focus,
.formwhite .field textarea:focus{
  background:var(--panel-light);
  border-color:var(--accent-hover);
  box-shadow:inset 0 0 0 1px var(--accent-hover);
}

.formwhite .whenopts span{
  background:var(--panel-light);
  border-color:var(--border-interactive-light);
  color:var(--text-secondary-light);
}
.formwhite .whenopts input:checked + span{
  background:var(--accent-muted);
  border-color:var(--accent-hover);
  box-shadow:inset 0 0 0 1px var(--accent-hover);
  color:var(--text-primary-light);
  font-weight:600;
}
.formwhite .whenopts input:focus-visible + span{
  outline:2px solid var(--accent-hover);outline-offset:3px;
}
.band-light .whenday input[type=date]{color-scheme:light}
@media (pointer:coarse){.whenopts span{min-height:44px}}

/* Honeypot, off-screen but not display:none so bots still fill it. */
.hp{position:absolute;left:-5000px;width:1px;height:1px;overflow:hidden}

/* --- 12. Footer ---------------------------------------------------------- */

.ctaband{
  background:linear-gradient(180deg,#132538,#0D1826);
  border-top:1px solid var(--accent-line);
  border-bottom:1px solid var(--rule-dark);
  padding-block:clamp(46px,6vw,78px);
}
.ctaband .inner{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:28px}
.ctaband h2{max-width:18ch}
.ctaband .acts{display:flex;flex-wrap:wrap;gap:12px}

/* The final CTA, built around the office portrait so the promise of one
   person and one number has a face attached to it. */
.ctaperson{padding-block:0;overflow:hidden}
.ctagrid{
  display:grid;grid-template-columns:minmax(0,.78fr) minmax(0,1fr);
  gap:clamp(28px,5vw,72px);align-items:end;
}
.ctaportrait{margin:0;align-self:end}
.ctaportrait img{
  width:100%;height:auto;display:block;
  max-height:clamp(360px,44vw,560px);object-fit:cover;object-position:50% 18%;
  border-radius:14px 14px 0 0;
}
.ctacopy{padding-block:clamp(50px,6vw,86px)}
.ctacopy h2{max-width:16ch}
/* The 46ch cap was lifted 2026-09-23. It predates the client's current CTA
   copy, which is four sentences rather than one, and at 46ch that ran to
   NINE lines in a column using 65 percent of the space beside the portrait.

   ⚠ IT SAYS `max-width:none`, NOT NOTHING. Simply deleting the declaration
   is not enough and was the first attempt: the base `.lede` rule caps at
   62ch, so the paragraph stopped at 599px of the 679px available, 88 percent
   and still four lines short of filling the column. `none` is what actually
   releases it.

   THIS RULE REACHES BOTH PAGES AND THAT IS THE POINT. `.ctacopy` exists in
   exactly two places, the homepage and /homeowners/, which are the two
   pages carrying this block. Those two are kept word for word identical by
   hand, and the usual risk is changing one and forgetting the other; a
   change made HERE cannot fall out of step, because there is only one rule.
   Do not replace this with per-page inline widths.

   The colour is unchanged and stays on this rule. Full width works out at
   roughly 57ch at 1440, inside the comfortable reading measure, so unlike
   the #paths card override further up this one needs no caveat. */
.ctacopy .lede{color:var(--text-secondary);max-width:none}
.ctacopy .acts{display:flex;flex-wrap:wrap;gap:12px}
.ctahours{
  margin-top:20px;font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-secondary);opacity:.85;
}
@media (max-width:820px){
  .ctagrid{grid-template-columns:1fr;gap:0}
  .ctaportrait{order:2;max-width:420px;margin-inline:auto;width:100%}
  .ctaportrait img{max-height:clamp(300px,66vw,420px);border-radius:14px 14px 0 0}
  .ctacopy{order:1;padding-block:clamp(46px,9vw,64px) clamp(26px,5vw,36px)}
}

/* --- The service-area block -------------------------------------------
   Small print above the footer naming the towns TM genuinely covers. Real
   places in a real service area, phrased per page, so it reads as useful
   detail rather than a keyword dump. */
.arealist{
  background:#0C1725;
  border-top:1px solid var(--rule-dark);
  padding-block:clamp(26px,3.4vw,40px);
  font-size:12.5px;line-height:1.65;
}
.arealist h2{
  font-family:var(--mono);font-weight:500;font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent);margin-bottom:12px;line-height:1.5;
}
/* Runs the full width of the row on purpose. This is fine print, so long
   lines are fine and the block stays short instead of a tall narrow column. */
.arealist p{color:var(--text-secondary);opacity:.72;max-width:none}
/* The client's service-area sentence, added 2026-09-23. It leads the block,
   so it is lifted out of the fine print around it: full opacity and a size
   up, while the town lists underneath keep theirs. It stays --text-secondary
   rather than going gold, because at this size gold would be the fourth
   different gold on one screen and it has to sit under a gold h2 already. */
.arealist .arealead{
  opacity:1;color:var(--text-secondary);
  font-size:14px;margin-bottom:10px;
}
/* Small print, but not punishing on a phone. 12.5px is genuinely hard to read
   at arm's length; 13.5px still reads as fine print and stays legible. */
@media (max-width:768px){
  .arealist{font-size:13.5px}
  .arealist p{opacity:.8}
}
.arealist p + p{margin-top:7px}
.arealist strong{color:var(--text-secondary);font-weight:600}
.arealist a{color:var(--accent);opacity:.9}
.arealist a:hover{opacity:1;text-decoration:underline}

.foot{padding-block:clamp(46px,6vw,72px) 30px;font-size:14px}
.footgrid{display:grid;grid-template-columns:1.5fr 1fr 1fr 1.2fr;gap:clamp(24px,3vw,48px)}
@media (max-width:900px){.footgrid{grid-template-columns:1fr 1fr}}
@media (max-width:560px){.footgrid{grid-template-columns:1fr}}
.foot .foot-h{
  font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.17em;
  color:var(--accent);margin:0 0 15px;text-transform:uppercase;line-height:1.4;
}
.foot ul{list-style:none;margin:0;padding:0;display:grid;gap:9px}
.foot a{color:var(--text-secondary);transition:color var(--dur) var(--ease)}
.foot a:hover{color:#fff}
.foot .brand{margin-bottom:14px}
.footnote{
  margin-top:clamp(34px,4vw,52px);padding-top:22px;border-top:1px solid var(--rule-dark);
  display:flex;flex-wrap:wrap;gap:14px;justify-content:space-between;
  font-size:13.5px;color:var(--text-secondary);
}
/* This is the AI imagery disclosure. It has to be readable, so it does not
   get to be decorative small print. */
.disclose{font-size:13.5px;color:var(--text-secondary);opacity:.82;margin-top:12px;max-width:52ch}
.band-light .disclose{color:var(--text-secondary-light)}

/* Mono micro-labels get a little bigger on phones. 10px uppercase mono is a
   fine desktop device and unreadable at arm's length. */
@media (max-width:768px){
  .prooffoot,.ctahours{font-size:11.5px}
  .trust-inline li{font-size:12px}
}

/* --- 13. Mobile call bar (section 15) -----------------------------------
   Click to call is the priority on phones. Appears after the hero. */

.callbar{
  position:fixed;left:0;right:0;bottom:0;z-index:70;display:none;gap:9px;
  padding:10px 12px calc(10px + env(safe-area-inset-bottom));
  background:rgba(11,21,33,.94);backdrop-filter:blur(12px);
  border-top:1px solid var(--accent-line);
  transform:translateY(110%);transition:transform .45s var(--ease);
}
.callbar .btn{flex:1}
body[data-past-hero="true"] .callbar{transform:none}
@media (max-width:767px){.callbar{display:flex}body{padding-bottom:0}}

/* --- 14. Entrances -------------------------------------------------------
   IntersectionObserver adds .in. Start and end states are prefixed with the
   container class so a later rule cannot silently cancel them. */

.rise .r{opacity:0;transform:translateY(16px);
  transition:opacity var(--dur-slow) var(--ease),transform var(--dur-slow) var(--ease)}
.rise.in .r{opacity:1;transform:none}
.rise.in .r:nth-child(2){transition-delay:.07s}
.rise.in .r:nth-child(3){transition-delay:.14s}
.rise.in .r:nth-child(4){transition-delay:.21s}
.rise.in .r:nth-child(5){transition-delay:.28s}
.rise.in .r:nth-child(6){transition-delay:.35s}

/* Retire the stagger once the entrance is done, or every hover on the later
   siblings lags forever. :nth-child counts as a class, so this selector has
   to match the delay rules it retires. */
.rise.done.in .r:nth-child(2),.rise.done.in .r:nth-child(3),
.rise.done.in .r:nth-child(4),.rise.done.in .r:nth-child(5),
.rise.done.in .r:nth-child(6),.rise.done .r{transition-delay:0s !important}

/* --- 15. One living element per section, whisper level ------------------- */

.pulse{position:relative}
.pulse::after{
  content:"";position:absolute;inset:-30% -18%;pointer-events:none;z-index:-1;
  background:radial-gradient(closest-side,rgba(196,154,92,.19),transparent 72%);
  opacity:0;
}
.live .pulse::after{animation:breathe 7.5s var(--ease) -2.4s infinite}
@keyframes breathe{0%,100%{opacity:.24}50%{opacity:.62}}

.motes{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:0}
.motes i{
  position:absolute;width:3px;height:3px;border-radius:50%;
  background:var(--accent);opacity:.3;
}
.live .motes i{animation:drift 26s linear infinite}
@keyframes drift{
  0%{transform:translate3d(0,26px,0);opacity:0}
  14%{opacity:.34}
  86%{opacity:.28}
  100%{transform:translate3d(16px,-120px,0);opacity:0}
}

/* Pause everything off-screen and on hidden tabs. animation-play-state is
   not inherited, so it has to hit every element and pseudo-element. */
body.paused *,body.paused *::before,body.paused *::after{animation-play-state:paused !important}

/* --- 16. The scrub hero (index.html only) -------------------------------- */

/* 440vh, not the 400vh in design package section 7. The title card adds a
   fourth beat to a fixed scroll budget, and at 400vh band one could not hold
   five full-opacity flicks without running into band two. Progress is
   normalised, so a taller hero changes no band range and no video sync: it
   only buys every beat more pixels. Measured, not guessed: at 400vh band one
   scored 4 flicks against a 5+ gate, at 440vh it scores 5. */
.hero{position:relative;height:440vh}
.stage{
  position:sticky;top:0;height:100vh;height:100dvh;overflow:hidden;
  background:#0A131E;
}
.stage video{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .7s var(--ease);
  will-change:transform;transform:translateZ(0);
}
.stage.video-ready video{opacity:1}
.poster{
  position:absolute;inset:0;background-size:cover;background-position:center;
  transition:opacity .7s var(--ease);
}
.stage.video-ready .poster{opacity:0}
.stage.video-failed video{display:none}

/* Base scrim, always on, so no frame is ever raw behind the page.
   The linear pass darkens the LEFT CAPTION LANE only and dies out well
   before the action lane, which lives centre right. Tuned against the
   worst-frame audit, not by eye. */
.scrim{
  position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(97deg,
      rgba(6,10,16,.90) 0%,
      rgba(6,10,16,.84) 26%,
      rgba(6,10,16,.60) 40%,
      rgba(6,10,16,.22) 53%,
      rgba(6,10,16,0) 66%),
    radial-gradient(ellipse 120% 90% at 50% 45%,rgba(10,10,18,0) 35%,rgba(10,10,18,.62) 100%);
}

/* The opening title card. Centred, and therefore sitting in the one place
   the base scrim deliberately does NOT darken: the linear pass covers the
   left caption lane and is fully out by 66%, and the radial is transparent
   until 35%. So this carries its own scrim rather than borrowing one. */
.htitle{
  position:absolute;left:50%;top:50%;translate:-50% -50%;z-index:2;
  /* Sized in px, NOT ch: ch resolves against THIS element's font-size, not
     the display size of the name inside it, so a ch width lands far too
     narrow, leaves "CONSTRUCTION" overflowing as one unwrappable word, and
     breaks the subline mid-sentence. The subline gets its own ch cap below,
     where ch is measured in the right font. */
  width:min(92vw,980px);text-align:center;
  /* Deliberately NOT opacity:var(--t). The fade rides the text children and
     the scrim below rides --ts, which holds until the text is nearly gone.
     Fading both together drops the background out from under the text at the
     exact moment the footage brightens, which measured 2.38:1 against a 3.5
     floor. The bands never hit this because the base scrim on the left lane
     does not fade with them. */
  text-shadow:var(--tshadow);
}
/* Denser and tighter than the bands' scrim, because this one sits in the
   centre over the lit roof rather than in the darkened left lane. Measured
   at 2.29:1 with a softer ramp, which failed the 3.5 floor. */
.htitle::before{
  content:"";position:absolute;inset:-85% -14%;pointer-events:none;z-index:-1;
  opacity:var(--ts,0);
  background:radial-gradient(ellipse 50% 52% at 50% 50%,
    rgba(5,5,10,.95) 0%,rgba(5,5,10,.92) 40%,rgba(5,5,10,.70) 64%,rgba(5,5,10,0) 88%);
}
.htitle-name{
  font-family:var(--display);font-weight:800;
  font-size:clamp(30px,5.4vw,72px);line-height:1.02;
  text-transform:uppercase;letter-spacing:-.018em;
  color:var(--text-primary);
  /* Hug the glyphs. As a full-width block its box spans the whole 980px
     even though the word is far narrower, which puts bare sunlit roof
     inside the measured text box and reads as a legibility failure that
     no visitor would ever see. */
  width:fit-content;margin-inline:auto;
  /* Rides --tk so the card settles on load instead of appearing flat.
     Defaults to 0 like the bands' own opacity: if site.js never runs the
     hero is empty over the poster, which is the established no-JS state
     here. Defaulting these to 1 instead would paint the card, then have
     JS hide it and animate it back in, which reads as a glitch. */
  opacity:calc(var(--t,0) * var(--tk,0));
  transform:translateY(calc((1 - var(--tk,0)) * 18px));
}
/* The logo inside the h1. 520px wide on desktop, 80vw on phones, and never wider
   than its own 1041px so it is never upscaled. The h1 keeps .htitle-name's fade
   and rise (the --t and --tk custom properties), so the logo behaves exactly as
   the written name did. The soft shadow is cheap insurance on bright frames; the
   dark scrim behind the card does most of the work. */
.htitle-name{margin-top:0;margin-bottom:0}
.htitle-logo{
  display:block;width:min(520px,80vw);max-width:1041px;height:auto;margin-inline:auto;
  filter:drop-shadow(0 2px 14px rgba(5,5,10,.55));
}
/* Phones and reduced motion get the static hero instead of the video hero, so the logo
   leads it too, left aligned with the text below. */
.statichero .htitle-logo{margin-inline:0;margin-bottom:clamp(14px,3vw,22px)}
.htitle-sub{
  margin-top:clamp(12px,1.4vw,20px);
  /* Capped here, in its own font, so it sets as one line on a wide screen
     and breaks sensibly rather than at "Over 25 / years". */
  width:fit-content;max-width:46ch;margin-inline:auto;
  font-size:clamp(14px,1.18vw,18.5px);color:#E7EDF3;
  /* Trails the name by a beat, same device as the bands' .sub. */
  --tks:clamp(0,(var(--tk,0) - .34) * 1.9,1);
  opacity:calc(var(--t,0) * var(--tks));
  transform:translateY(calc((1 - var(--tks)) * 12px));
}
/* A gold hairline between the two, the one accent this card spends. */
.htitle-sub::before{
  content:"";display:block;width:clamp(34px,4vw,56px);height:1px;
  margin:0 auto clamp(12px,1.4vw,18px);
  background:var(--accent);opacity:calc(.75 * var(--tks));
  transform:scaleX(var(--tks));
}

.hband{
  position:absolute;left:var(--gutter);top:50%;translate:0 -50%;
  /* Was min(40ch,36vw). The 40ch resolved against THIS element's 16px font,
     not the 46px headline inside it, so the column was about 320px rather
     than the left third section 7 describes, and 40ch always won. The short
     original captions wrapped to two lines and hid it; longer copy wrapped
     to six. Capped in px so the cap means what it says. */
  width:min(500px,38vw);z-index:2;opacity:0;
  text-shadow:var(--tshadow);
}
/* Per-band scrim: rides the band's opacity, deepens with --k, dies out by
   76% so the corners stay live. Dim, never flatten. */
.hband::before{
  content:"";position:absolute;inset:-22% -30% -22% -60%;pointer-events:none;z-index:-1;
  opacity:calc(.34 + .66 * var(--k,1));
  background:radial-gradient(ellipse 84% 74% at 46% 50%,
    rgba(5,5,10,.80) 0%,rgba(5,5,10,.66) 52%,rgba(5,5,10,0) 84%);
}
.hband h2{font-size:clamp(27px,3.4vw,46px);margin-bottom:16px}
.hband p{font-size:clamp(15.5px,1.25vw,19px);color:#E7EDF3;max-width:32ch}

/* Split text spans for the entrances */
.w{display:inline-block;white-space:pre}
.c{display:inline-block;will-change:transform,opacity}

/* (a) rise from below with a soft mask wipe. Band 1, per section 7. */
.e-rise .w{
  --kc:clamp(0,(var(--k,0) - var(--th,0)) * 2.8,1);
  opacity:var(--kc);
  transform:translateY(calc((1 - var(--kc)) * 26px));
}
/* (b) slide-in from the left, staggered */
.e-slide .w{
  --kc:clamp(0,(var(--k,0) - var(--th,0)) * 2.6,1);
  opacity:var(--kc);
  transform:translateX(calc((1 - var(--kc)) * -34px));
}
/* (c) letter-by-letter fade up */
.e-fade .c{
  --kc:clamp(0,(var(--k,0) - var(--th,0)) * 3.1,1);
  opacity:var(--kc);
  transform:translateY(calc((1 - var(--kc)) * 12px));
}
.hband .sub{
  --ks:clamp(0,(var(--k,0) - .58) * 3.4,1);
  opacity:var(--ks);
  transform:translateY(calc((1 - var(--ks)) * 12px));
}

/* Masked reveals need descender room or g, y and p lose their tails. */
.mask{overflow:hidden;padding-block:.14em;margin-block:-.14em}

.hudring{
  position:absolute;right:calc(var(--gutter) + 4px);bottom:calc(var(--gutter) + 4px);
  z-index:3;width:48px;height:48px;color:var(--accent);
}
.hudring circle{transition:stroke-dashoffset .25s linear}
.cue{
  position:absolute;left:50%;bottom:26px;translate:-50% 0;z-index:3;
  font-family:var(--mono);font-size:10px;letter-spacing:.22em;
  color:var(--accent);opacity:.8;display:flex;flex-direction:column;
  align-items:center;gap:8px;
}
.cue svg{width:13px;height:13px;animation:nudge 2.6s var(--ease) infinite}
@keyframes nudge{0%,100%{transform:translateY(0)}50%{transform:translateY(5px)}}
@media (max-height:560px){.cue,.hudring{display:none}}

/* The static hero, for the five gates. A designed layout, not an apology. */
.statichero{display:none;position:relative;min-height:82vh;overflow:hidden}
.statichero .bg{position:absolute;inset:0;background-size:cover;background-position:center}
.statichero .bg::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(9,17,27,.42),rgba(9,17,27,.88))}
.statichero .inner{position:relative;z-index:2;padding-block:clamp(70px,14vw,120px)}
/* The kicker is NOT gold here, and this is the one place it is not.
   Measured over the photograph at the three phone sizes the mobile suite
   uses, accent gold came out 3.31:1, 3.62:1 and 3.10:1. The kicker is 12px
   mono, which is small text and wants 4.5:1, so it failed at every size and
   failed even the hero captions' 3.5 floor at two of them. This is not new
   and was not caused by the copy change: the old "Conway, South Carolina"
   measured identically, because the brightest pixel behind it is in the same
   place whatever the words are. Secondary on the same background measures
   5.30, 5.80 and 4.96. Section 4's rule is to compute every pairing rather
   than eyeball it, and this is what computing it says. Gold is still in this
   hero, on the filled CTA where it carries dark text. */
.statichero .kicker{color:var(--text-secondary)}
/* The page h1 is the visually hidden structural one, so the static hero's
   display heading is an h2. Target both. */
/* 20ch, not 16ch. The ch is on the TEXT element in its own display font
   here, which is the correct use, but 16ch was sized for the old short
   headline and the client's longer one broke to five ragged lines. */
.statichero h1,.statichero h2{max-width:20ch;margin-bottom:18px}
.statichero p{max-width:44ch;color:#E7EDF3;margin-bottom:26px}
.statichero .lede{margin-bottom:28px}

/* THE FIVE STATIC-HERO GATES. These five strings must stay character for
   character identical to the GATES array in site.js. */
@media (max-width:720px){
  .hero{display:none}.statichero{display:block}
}
@media (orientation:portrait) and (max-width:1024px){
  .hero{display:none}.statichero{display:block}
}
@media (orientation:portrait) and (pointer:coarse){
  .hero{display:none}.statichero{display:block}
}
@media (orientation:landscape) and (pointer:coarse) and (max-height:560px){
  .hero{display:none}.statichero{display:block}
}
@media (prefers-reduced-motion:reduce){
  .hero{display:none}.statichero{display:block}
}

/* --- 17. Interior page hero --------------------------------------------- */

.phero{position:relative;overflow:hidden;background:#0A131E}
.phero .bg{position:absolute;inset:0}
.phero .bg img{width:100%;height:100%;object-fit:cover}
.phero .bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(9,17,27,.93) 26%,rgba(9,17,27,.66) 62%,rgba(9,17,27,.42));
}
.phero .inner{position:relative;z-index:2;padding-block:clamp(66px,10vw,124px)}
.phero h1{max-width:17ch}
.phero .lede{margin-top:20px;color:#E7EDF3}
.phero .acts{display:flex;flex-wrap:wrap;gap:12px;margin-top:30px}
.phero .pills{display:flex;flex-wrap:wrap;gap:10px 22px;margin-top:30px}
.phero .pills li{
  list-style:none;font-family:var(--mono);font-size:11.5px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--text-secondary);
  display:flex;align-items:center;gap:8px;
}
.phero .pills li::before{
  content:"";width:5px;height:5px;background:var(--accent);border-radius:50%;flex:0 0 auto}

.crumbs{
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  padding-block:16px;color:var(--text-secondary-light);
}
.crumbs a{color:var(--text-secondary-light)}
.crumbs a:hover{color:var(--accent-hover)}
.crumbs span{opacity:.5;margin-inline:8px}

/* --- 18. Trust strip ----------------------------------------------------- */

.trust{border-block:1px solid var(--rule-dark);background:rgba(9,17,27,.55)}
.trust ul{
  list-style:none;margin:0;padding:20px 0;display:flex;flex-wrap:wrap;
  gap:14px clamp(20px,4vw,58px);justify-content:center;align-items:baseline;
}
.trust li{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--text-secondary);display:flex;align-items:baseline;gap:9px;
}
.trust li::before{content:"";width:5px;height:5px;background:var(--accent);
  border-radius:50%;flex:0 0 auto;translate:0 -2px}

/* Founder names. These are h2 because the section has no heading above them
   any more, so an h3 would skip a level in the outline. Sized like an h3. */
.name-h{font-size:clamp(19px,1.7vw,25px);letter-spacing:-.005em}

/* Inline credential row, under the team block */
.trust-inline li{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-secondary);display:flex;align-items:baseline;gap:9px;
}
.trust-inline li::before{
  content:"";width:5px;height:5px;background:var(--accent);border-radius:50%;
  flex:0 0 auto;translate:0 -2px;
}
.band-light .trust-inline li{color:var(--text-secondary-light)}

/* --- 18b. The years block, homeowners page ------------------------------- */

.yearsplit{
  display:grid;grid-template-columns:1.05fr .95fr;
  gap:clamp(30px,5vw,72px);align-items:center;
}
@media (max-width:900px){
  .yearsplit{grid-template-columns:1fr}
  .yearsplit .figure{max-width:460px}
}

.bignum{display:flex;align-items:baseline;gap:16px;margin-top:clamp(20px,3vw,30px)}
/* The homepage years band only. EVERY rule here is scoped to #years because
   .bignum is shared with the /homeowners/ years block, which is left aligned
   and baseline aligned and must not move.
   align-items:center sits the heading on the figure's vertical centre instead
   of down on the digits' baseline; justify-content:center puts the pair in
   the middle of the page rather than against the left gutter. */
#years .bignum{align-items:center;justify-content:center}
.bignum b{
  font-family:var(--display);font-weight:800;line-height:.82;
  font-size:clamp(76px,11vw,148px);letter-spacing:-.045em;
  color:var(--accent-hover);display:block;
}
.bignum b span{font-size:.46em;vertical-align:super;letter-spacing:0}
.bignum em{
  font-style:normal;font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:var(--text-secondary-light);
  max-width:9ch;line-height:1.6;
}

/* --- 18c. Slideshow ------------------------------------------------------
   Crossfading project images. Opacity only, pauses off screen and on hover,
   and reduced motion gets the first slide with working dots and no autoplay. */

.slideshow{position:relative;margin:0;border-radius:13px;overflow:hidden;
  border:1px solid var(--rule-dark)}
.band-light .slideshow{border-color:var(--rule-light)}
.slides{position:relative;aspect-ratio:4/5;background:#0B1622}
.slide{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .9s var(--ease);
}
.slide.is-on{opacity:1}
.slides::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,transparent 52%,rgba(9,17,27,.82));
}
.slidecap{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:18px 20px 18px;
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:#fff;
}
.slidedots{
  position:absolute;top:14px;right:14px;z-index:3;display:flex;gap:7px;
}
.slidedots button{
  width:8px;height:8px;padding:0;border-radius:50%;cursor:pointer;
  border:1px solid rgba(255,255,255,.72);background:transparent;
  transition:background var(--dur) var(--ease),transform var(--dur) var(--ease);
}
.slidedots button[aria-selected="true"]{background:var(--accent);border-color:var(--accent);transform:scale(1.25)}
/* On touch the buttons become a full 44px target while the visible dot stays
   small: the dot is drawn as ::before inside a transparent hit area. */
@media (pointer:coarse){
  .slidedots{gap:0;top:4px;right:4px}
  .slidedots button{
    width:44px;height:44px;border:0;background:transparent;
    display:grid;place-items:center;transform:none;
  }
  .slidedots button::before{
    content:"";width:9px;height:9px;border-radius:50%;
    border:1px solid rgba(255,255,255,.72);background:transparent;
    transition:background var(--dur) var(--ease),transform var(--dur) var(--ease);
  }
  .slidedots button[aria-selected="true"]{background:transparent;transform:none}
  .slidedots button[aria-selected="true"]::before{
    background:var(--accent);border-color:var(--accent);transform:scale(1.25);
  }
}

/* --- 18d. Compact scheduler, inside a CTA -------------------------------- */

.sched{margin-top:22px;max-width:46ch}
.sched > summary{
  list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:9px;
  font-family:var(--mono);font-size:11px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--accent);padding:8px 0;
}
.sched > summary::-webkit-details-marker{display:none}
.sched > summary::after{
  content:"";width:7px;height:7px;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:translateY(-2px) rotate(45deg);
  transition:transform var(--dur) var(--ease);
}
.sched[open] > summary::after{transform:translateY(1px) rotate(225deg)}
.sched > summary:hover{color:#fff}
.schedbody{padding-top:14px;display:grid;gap:11px}
.schedrow{display:grid;grid-template-columns:1fr 1fr;gap:11px}
@media (max-width:620px){.schedrow{grid-template-columns:1fr}}
.sched .field input{padding:11px 13px;font-size:15px}
.sched .whenfield legend{font-size:10px;margin-bottom:7px}
.sched .whenopts span{padding:7px 13px;font-size:12.5px}
.sched .btn{width:100%}
@media (pointer:coarse){.sched > summary{min-height:44px}}

/* --- 18e. The free estimate house picker --------------------------------
   The house is drawn, not photographed, so every zone is a real control:
   focusable, labelled, keyboard operable and crisp at any size. */

.pickgrid{display:grid;grid-template-columns:1.15fr .85fr;gap:clamp(24px,3.4vw,54px);align-items:start}
@media (max-width:1000px){.pickgrid{grid-template-columns:1fr}}

/* The sticky lives on the COLUMN, not on the house alone. With it on the
   house, the pinned house painted straight over the chip row below it,
   because the chips are its sibling and scroll up behind it. */
.housecol{position:sticky;top:88px}
/* Sticky is pointless, and starts clipping, once the column is taller than
   the window. */
@media (max-width:1000px),(max-height:940px){.housecol{position:static}}

.housewrap{
  border-radius:14px;overflow:hidden;
  background:#BFE0F2;
  border:1px solid var(--rule-light);
  box-shadow:0 18px 44px -26px rgba(19,32,48,.4);
}
.house{display:block;width:100%;height:auto}

.house .zone{cursor:pointer;outline:none}
.house .zone .hit{fill:transparent;stroke:none}
.house .zone .fillable{
  transition:fill .3s var(--ease),stroke .3s var(--ease),opacity .3s var(--ease);
}
.house .zone:hover .fillable{fill:rgba(196,138,42,.34);stroke:#9A6B18}
.house .zone:focus-visible .hit{stroke:#9A6B18;stroke-width:3;stroke-dasharray:5 4}
.house .zone.is-on .fillable{fill:rgba(214,150,44,.55);stroke:#8A5E12;stroke-width:2.5}
.house .zonelabel{
  font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.09em;
  text-transform:uppercase;fill:#16324A;paint-order:stroke;
  stroke:rgba(255,255,255,.9);stroke-width:3.5;stroke-linejoin:round;
  opacity:0;pointer-events:none;transition:opacity .3s var(--ease);
}
.house .zone:hover .zonelabel,
.house .zone:focus-visible .zonelabel,
.house .zone.is-on .zonelabel{opacity:1}
.house .zonetick{opacity:0;transition:opacity .3s var(--ease)}
.house .zone.is-on .zonetick{opacity:1}

.househint{
  padding:13px 18px;border-top:1px solid var(--rule-dark);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-secondary);display:flex;align-items:center;gap:9px;flex-wrap:wrap;
}
.househint b{color:var(--accent);font-weight:500}

/* the parallel chip list, for anyone who would rather not poke a drawing */
.chiprow{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.chip{
  font-family:var(--body);font-size:13.5px;cursor:pointer;
  padding:9px 15px;border-radius:100px;background:transparent;
  border:1px solid var(--border-interactive-dark);color:var(--text-secondary);
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease),background var(--dur) var(--ease);
}
.chip:hover{border-color:var(--accent);color:var(--accent)}
.chip.is-on{border-color:var(--accent);background:var(--accent-muted);color:var(--accent)}
.chip.is-on::before{content:"\2713\00a0"}
@media (pointer:coarse){.chip{min-height:44px}}

.picked{margin-top:26px;display:grid;gap:12px}
.pickpanel{
  border:1px solid var(--rule-dark);border-left:2px solid var(--accent);
  border-radius:11px;padding:15px 16px 17px;background:var(--panel);
}
.pickpanel .ph{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}
.pickpanel h3{font-size:15px}
/* These live outside .field, so they need the field treatment explicitly. */
.pickpanel textarea,.pickpanel input{
  font-family:var(--body);font-size:15px;color:#fff;width:100%;
  background:rgba(255,255,255,.045);
  border:1px solid var(--border-interactive-dark);border-radius:9px;
  padding:11px 13px;min-height:76px;resize:vertical;
}
.pickpanel textarea::placeholder{color:rgba(194,205,216,.5)}
.pickpanel textarea:focus{outline:none;border-color:var(--accent);background:rgba(255,255,255,.07)}
.dropbtn{
  background:none;border:0;cursor:pointer;padding:6px;
  font-family:var(--mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--text-secondary);transition:color var(--dur) var(--ease);
}
.dropbtn:hover{color:var(--accent)}
@media (pointer:coarse){.dropbtn{min-height:44px;display:inline-flex;align-items:center}}

.pickempty{
  border:1px dashed var(--border-interactive-dark);border-radius:11px;
  padding:22px 20px;color:var(--text-secondary);font-size:14.5px;margin-top:26px;
}

/* --- Light variants, so the picker can sit on the warm canvas ----------- */
.band-light .househint{
  background:#fff;border-top-color:var(--rule-light);color:var(--text-secondary-light);
}
.band-light .househint b{color:var(--accent-hover)}
.band-light .chip{border-color:var(--border-interactive-light);color:var(--text-primary-light);background:#fff}
.band-light .chip:hover{border-color:var(--accent-hover);color:var(--accent-hover)}
.band-light .chip.is-on{border-color:var(--accent-hover);background:var(--accent-muted);color:var(--accent-hover)}
.band-light .pickempty{
  border-color:var(--border-interactive-light);color:var(--text-secondary-light);background:#fff;
}
.band-light .pickpanel{
  background:var(--panel-light);border-color:var(--rule-light);border-left-color:var(--accent-hover);
}
.band-light .pickpanel h3{color:var(--text-primary-light)}
.band-light .pickpanel textarea,.band-light .pickpanel input{
  color:var(--text-primary-light);background:#FBFAF8;border-color:var(--border-interactive-light);
}
.band-light .pickpanel textarea::placeholder{color:var(--text-secondary-light)}
.band-light .pickpanel textarea:focus{border-color:var(--accent-hover);background:#fff}
.band-light .dropbtn{color:var(--text-secondary-light)}
.band-light .dropbtn:hover{color:var(--accent-hover)}

/* The form column, given its own white card so it reads as the welcoming
   half of the page rather than more dark field. */
.formcard{
  background:var(--panel-light);border:1px solid var(--rule-light);
  border-radius:14px;padding:clamp(22px,2.4vw,32px);
  box-shadow:0 18px 44px -28px rgba(19,32,48,.34);
}
.formcard .kicker{color:var(--accent-hover)}
.formcard h2{color:var(--text-primary-light)}

/* --- The big phone number banner ---------------------------------------- */
.telbanner{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:clamp(20px,3vw,44px)}
.bigtel{
  display:inline-block;font-family:var(--display);font-weight:800;
  font-size:clamp(30px,4.4vw,58px);letter-spacing:-.03em;line-height:1;
  color:var(--accent-hover);white-space:nowrap;
  transition:color var(--dur) var(--ease),transform var(--dur) var(--ease);
}
.bigtel:hover{color:var(--text-primary-light);transform:translateY(-1px)}
.telbanner .sub{
  margin-top:10px;font-family:var(--mono);font-size:10.5px;letter-spacing:.15em;
  text-transform:uppercase;color:var(--text-secondary-light);
}
@media (pointer:coarse){.bigtel{min-height:44px}}

/* --- The booking calendar ------------------------------------------------ */
.calgrid{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(20px,2.6vw,40px);align-items:start}
@media (max-width:820px){.calgrid{grid-template-columns:1fr}}

.calbox{
  background:var(--panel-light);border:1px solid var(--rule-light);
  border-radius:14px;padding:clamp(16px,1.8vw,24px);
  box-shadow:0 18px 44px -28px rgba(19,32,48,.34);
}
.calhead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
.calhead b{font-family:var(--display);font-weight:800;text-transform:uppercase;
  font-size:clamp(15px,1.3vw,19px);letter-spacing:-.005em;color:var(--text-primary-light)}
.calnav{display:flex;gap:6px}
.calnav button{
  width:36px;height:36px;border-radius:9px;cursor:pointer;
  border:1px solid var(--border-interactive-light);background:#fff;
  color:var(--text-primary-light);display:grid;place-items:center;font-size:15px;line-height:1;
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease);
}
.calnav button:hover:not(:disabled){border-color:var(--accent-hover);color:var(--accent-hover)}
.calnav button:disabled{opacity:.35;cursor:not-allowed}
@media (pointer:coarse){.calnav button{width:44px;height:44px}}

.caldow{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin-bottom:6px}
.caldow span{
  text-align:center;font-family:var(--mono);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--text-secondary-light);padding-bottom:4px;
}
.caldays{display:grid;grid-template-columns:repeat(7,1fr);gap:4px}
.day{
  aspect-ratio:1;min-height:38px;border-radius:9px;cursor:pointer;
  border:1px solid transparent;background:#F7F5F1;
  color:var(--text-primary-light);font-family:var(--body);font-size:14.5px;
  display:grid;place-items:center;
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease),color var(--dur) var(--ease);
}
.day.pad{background:none;border:0;cursor:default}
.day:hover:not(:disabled):not(.pad){background:var(--accent-muted);border-color:var(--accent-hover)}
.day:disabled{background:transparent;color:#B6B0A6;cursor:not-allowed}
.day.is-today{border-color:var(--border-interactive-light);font-weight:600}
.day.is-on{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff;font-weight:600}
.day:focus-visible{outline:2px solid var(--accent-hover);outline-offset:2px}
/* Seven columns inside a phone-width card makes each cell about 40px, which
   is under the touch minimum. min-height wins over the square aspect. */
@media (pointer:coarse){.day{min-height:44px}}

.slotpanel{display:grid;gap:9px}
.slotpanel .lab{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--accent-hover);margin-bottom:2px;
}
.slotwait{
  border:1px dashed var(--border-interactive-light);border-radius:11px;
  padding:26px 20px;text-align:center;color:var(--text-secondary-light);font-size:14px;
}
.slot{
  width:100%;padding:13px 16px;border-radius:10px;cursor:pointer;text-align:left;
  border:1px solid var(--border-interactive-light);background:#fff;
  color:var(--text-primary-light);font-family:var(--body);font-size:14.5px;
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease),color var(--dur) var(--ease);
}
.slot:hover{border-color:var(--accent-hover);color:var(--accent-hover)}
.slot[aria-pressed="true"]{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff;font-weight:600}
@media (pointer:coarse){.slot{min-height:44px}}

.chosenline{
  margin-top:16px;padding:13px 16px;border-radius:10px;
  background:var(--accent-muted);border:1px solid var(--accent-line);
  font-size:14.5px;color:var(--text-primary-light);
}
.chosenline b{color:var(--accent-hover)}

/* --- The in person visit scheduler -------------------------------------- */
.visitgrid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media (max-width:720px){.visitgrid{grid-template-columns:1fr}}
.slotrow{display:flex;flex-wrap:wrap;gap:8px}
.slotrow label{position:relative;cursor:pointer}
.slotrow input{position:absolute;opacity:0;inset:0;width:100%;height:100%;margin:0;cursor:pointer}
/* Dark by default, because this scheduler sits on the navy band. The light
   variant is there for if it is ever moved onto the warm canvas. */
.slotrow span{
  display:inline-flex;align-items:center;justify-content:center;
  padding:10px 16px;border-radius:100px;font-size:13.5px;
  border:1px solid var(--border-interactive-dark);color:var(--text-secondary);background:transparent;
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease),background var(--dur) var(--ease);
}
.slotrow input:checked + span{border-color:var(--accent);background:var(--accent-muted);color:var(--accent)}
.slotrow input:focus-visible + span{outline:2px solid var(--accent);outline-offset:3px}
.band-light .slotrow span{border-color:var(--border-interactive-light);color:var(--text-primary-light);background:#fff}
.band-light .slotrow input:checked + span{border-color:var(--accent-hover);background:var(--accent-muted);color:var(--accent-hover)}
@media (pointer:coarse){.slotrow span{min-height:44px}}

/* --- 19. Stat band ------------------------------------------------------- */

.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(16px,2vw,30px)}
@media (max-width:820px){.stats{grid-template-columns:repeat(2,1fr)}}
.stat b{
  display:block;font-family:var(--display);font-weight:800;
  font-size:clamp(32px,4vw,54px);color:var(--accent);line-height:1;
  letter-spacing:-.02em;
}
.stat span{
  display:block;font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--text-secondary);margin-top:11px;
}
.stat small{display:block;font-size:13.5px;color:var(--text-secondary);margin-top:9px;max-width:26ch}
.band-light .stat span,.band-light .stat small{color:var(--text-secondary-light)}

/* --- 19b. The proof cluster ---------------------------------------------
   Floating credibility cards beside the three trust points, so that side of
   the section is not blank. Drift is transform and opacity only, negative
   delays so they are mid cycle at first paint, and it pauses off screen. */

.trustsplit{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(30px,5vw,72px);align-items:center}
@media (max-width:940px){.trustsplit{grid-template-columns:1fr}}

.proofwrap{position:relative}
/* The min-height lives HERE, not on the wrapper. Absolutely positioned cards
   use percentage `top`, which resolves against this element's height, and a
   percentage height on a child never resolves against a parent's min-height:
   it collapses to zero and every card stacks at the top. */
.proofcluster{
  list-style:none;margin:0;padding:0;position:relative;
  /* Tall enough that the longest card (pc-4) clears the bottom, otherwise it
     overlaps the source line underneath. */
  min-height:clamp(560px,58vw,700px);
}
.prooffoot{
  margin-top:16px;font-family:var(--mono);font-size:10px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--text-secondary-light);opacity:.8;
}
@media (max-width:940px){.prooffoot{margin-top:14px}}

.proofcard{
  --rot:0deg;
  position:absolute;width:min(262px,66%);
  background:var(--panel-light);
  border:1px solid var(--rule-light);
  border-left:2px solid var(--accent);
  border-radius:12px;padding:18px 20px 20px;
  box-shadow:0 18px 44px -20px rgba(19,32,48,.34);
  will-change:transform;
}
.proofcard b{
  display:block;font-family:var(--display);font-weight:800;
  font-size:clamp(22px,2.3vw,30px);letter-spacing:-.02em;line-height:1;
  color:var(--text-primary-light);
}
.proofcard span{
  display:block;margin-top:9px;font-size:13.5px;line-height:1.45;
  color:var(--text-secondary-light);
}
.proofcard .stars{
  color:var(--accent-hover);font-size:13px;letter-spacing:.2em;margin-bottom:9px;
}

/* Staggered so they read as a scattered pile rather than a list.
   The tilt rides a custom property because the generic entrance rule sets
   `transform:none`, which would otherwise win on specificity and flatten
   every card the moment it animated in. */
.pc-1{top:0;left:2%;z-index:4;width:min(196px,50%);--rot:-1.8deg}
.pc-2{top:11%;right:0;z-index:3;--rot:1.7deg}
.pc-3{top:44%;left:0;z-index:2;--rot:-1.1deg}
.pc-4{top:55%;right:0;z-index:1;width:min(330px,84%);--rot:1.3deg}

.rise .r.proofcard{opacity:0;transform:translateY(16px) rotate(var(--rot))}
.rise.in .r.proofcard{opacity:1;transform:rotate(var(--rot))}

@media (max-width:940px){
  .proofwrap{min-height:0;margin-top:clamp(30px,5vw,44px)}
  .proofcluster{display:grid;grid-template-columns:1fr 1fr;gap:14px;height:auto}
  .proofcard{position:static;width:auto;transform:none !important;animation:none !important}
}
@media (max-width:520px){.proofcluster{grid-template-columns:1fr}}

.live .pc-1{animation:floaty 13s var(--ease) -1.2s infinite alternate}
.live .pc-2{animation:floaty 15s var(--ease) -6.4s infinite alternate}
.live .pc-3{animation:floaty 12s var(--ease) -3.8s infinite alternate}
.live .pc-4{animation:floaty 16s var(--ease) -9.1s infinite alternate}
@keyframes floaty{
  from{translate:0 0}
  to  {translate:0 -11px}
}

/* THE CLEAN REVIEW STACK, added 2026-09-23 at the client's direction. It
   replaces the scattered overlapping pile above on the homepage.

   .proofcluster and .pc-1 to .pc-4 ARE NOW UNUSED BY ANY PAGE. They are kept
   rather than deleted because the floating treatment is a real component
   that took work and the client may want it back; nothing else on the site
   references them, so they cost only bytes. Delete them only if you are
   sure. The same applies to the `floaty` keyframes and the `.live .pc-N`
   animation hooks.

   This reuses .proofcard and .quote-card for the card's own look, so a
   change to the card shell still reaches both treatments, and overrides only
   what the pile needed and a list does not: the absolute positioning, the
   fixed width, and the tilt. --rot is reset to 0deg rather than the tilt
   being removed, because the shared entrance rule
   `.rise .r.proofcard` composes rotate(var(--rot)) into its transform; unset
   it and that rule resolves to rotate() with no argument and the entrance
   silently stops working. The shadow is lightened because cards in a list
   sit flat rather than piled, and the pile's heavier shadow reads as dirt
   once nothing is overlapping. */
.reviewstack{
  list-style:none;margin:0;padding:0;
  display:grid;gap:clamp(12px,1.4vw,16px);
}
.reviewstack .proofcard{
  position:static;width:auto;--rot:0deg;
  box-shadow:0 10px 28px -20px rgba(19,32,48,.30);
}

/* Quote card variant, for when real reviews arrive */
.quote-card blockquote{
  margin:0;font-size:14.5px;line-height:1.55;color:var(--text-primary-light);
}
.quote-card cite{
  display:flex;align-items:center;gap:10px;margin-top:14px;font-style:normal;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--text-secondary-light);
}

/* --- 20. Numbered blocks ------------------------------------------------- */

.steps{counter-reset:s;display:grid;gap:clamp(18px,2.4vw,34px)}
.step{position:relative;padding-left:64px}
.step::before{
  counter-increment:s;content:counter(s,decimal-leading-zero);
  position:absolute;left:0;top:2px;
  font-family:var(--mono);font-size:13px;font-weight:500;color:var(--accent);
  letter-spacing:.08em;
}
.step::after{
  content:"";position:absolute;left:26px;top:9px;width:22px;height:1px;
  background:var(--accent);opacity:.45;
}
.step h3{margin-bottom:9px}
.step p{color:var(--text-secondary);max-width:52ch}
.band-light .step p{color:var(--text-secondary-light)}

/* --- 20b. The process flow ----------------------------------------------
   A drawn spine with four nodes. Deliberately NOT the numbered-list
   skeleton used elsewhere on the page. */

.flow{
  position:relative;
  display:grid;grid-template-columns:repeat(4,1fr);
  gap:clamp(16px,2.2vw,34px);
  padding-top:14px;
}

/* The spine. Rides the same --draw variable the rest of the site's drawn
   lines use, so the existing observer animates it with no extra JS. */
/* Two classes, so this beats `.band-light .rule`, which otherwise fades the
   spine out to transparent at 62 percent and leaves the last node stranded. */
.flow .flowline{
  position:absolute;top:27px;left:6%;right:6%;height:1px;margin:0;
  background:var(--accent-hover);
  opacity:.42;z-index:0;
}

.flownode{position:relative;z-index:1;padding-right:6px}

.flowdot{
  display:grid;place-items:center;
  width:30px;height:30px;border-radius:50%;
  background:var(--canvas-light);
  border:1.5px solid var(--accent-hover);
  margin-bottom:20px;position:relative;
}
.flowdot i{
  font-family:var(--mono);font-style:normal;font-size:10.5px;font-weight:500;
  letter-spacing:.04em;color:var(--accent-hover);
}
.flowimg{
  margin:0 0 18px;border-radius:11px;overflow:hidden;
  border:1px solid var(--rule-light);background:#E9E5DE;
}
.flowimg img{
  width:100%;aspect-ratio:4/3;object-fit:cover;display:block;
  transition:transform 1.1s var(--ease);
}
.flownode:hover .flowimg img{transform:scale(1.04)}

.flownode h3{margin-bottom:9px;font-size:clamp(16px,1.25vw,19px)}
.flownode p{font-size:14.5px;color:var(--text-secondary-light);max-width:30ch}

.flowchip{
  display:inline-block;margin-top:15px;
  font-family:var(--mono);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--accent-hover);
  border:1px solid var(--accent-line);border-radius:100px;
  padding:6px 12px;background:var(--accent-muted);
}

/* Stack to a vertical spine on narrow screens. */
@media (max-width:860px){
  .flow{grid-template-columns:1fr;gap:0;padding-top:0;padding-left:44px}
  .flow .flowline{
    top:10px;bottom:24px;left:14px;right:auto;width:1px;height:auto;
    transform:scaleY(var(--draw,0));transform-origin:top;
  }
  .flownode{padding-bottom:30px}
  .flowdot{position:absolute;left:-44px;top:0;margin:0}
  .flownode p{max-width:46ch}
}

/* --- 20c. The swimlane -------------------------------------------------
   What TM does above the line, what the homeowner does below it, across four
   phases. A different shape from the numbered list and the process flow, so
   no two of the three read as the same template. */

.lanes{
  display:grid;grid-template-columns:repeat(4,1fr);
  grid-template-rows:1fr auto auto 1fr;
  column-gap:clamp(12px,1.6vw,22px);row-gap:0;
  position:relative;padding-top:34px;
}
.lanehead{
  position:absolute;top:0;font-family:var(--mono);font-size:10.5px;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;margin:0;
}
.lanehead-us{left:0;color:var(--accent)}
.lanehead-you{right:0;color:var(--text-secondary)}
.lanehead span{display:inline-flex;align-items:center;gap:8px}
.lanehead-us span::before{content:"";width:16px;height:1px;background:var(--accent)}
.lanehead-you span::after{content:"";width:16px;height:1px;background:var(--text-secondary);opacity:.6}

/* display:contents dissolves the column wrapper so all four columns share ONE
   grid. Without it each column is its own grid, the 1fr rows resolve to
   different heights, and the axis sits at a different y in every column. */
.lanecol{display:contents}
.lanecard.up{grid-row:1}
.lanenode{grid-row:2}
.lanephase{grid-row:3}
.lanecard.down{grid-row:4}

/* One line spanning every column, behind the nodes. */
.laneaxis{
  grid-row:2;grid-column:1 / -1;align-self:center;
  height:1px;background:var(--accent);opacity:.3;
}

.lanecard{
  background:var(--panel);border:1px solid var(--rule-dark);
  border-radius:11px;padding:17px 18px 19px;
}
.lanecard.up{border-top:2px solid var(--accent);margin-bottom:16px;align-self:end}
.lanecard.down{border-bottom:2px solid var(--border-interactive-dark);margin-top:16px;align-self:start}
.lanecard h3{font-size:clamp(14.5px,1.1vw,16.5px);margin-bottom:7px}
.lanecard p{font-size:13.5px;color:var(--text-secondary);line-height:1.55}
.lanecard.down h3{color:var(--text-secondary)}

.lanenode{
  width:11px;height:11px;border-radius:50%;
  background:var(--canvas);border:1.5px solid var(--accent);
  justify-self:center;position:relative;z-index:2;
}
.lanephase{
  text-align:center;font-family:var(--mono);font-size:10px;font-weight:500;
  letter-spacing:.17em;text-transform:uppercase;color:var(--accent);
  margin:9px 0 0;
}

.lanefoot{
  margin-top:clamp(26px,3vw,38px);padding-top:20px;
  border-top:1px solid var(--rule-dark);
  font-size:14px;color:var(--text-secondary);max-width:74ch;
}

@media (max-width:900px){
  .lanes{grid-template-columns:1fr;grid-template-rows:none;row-gap:26px;padding-top:0}
  .lanehead,.laneaxis{display:none}
  /* Back to a normal block per phase, so each column reads as one unit. */
  .lanecol{display:block;margin-bottom:26px}
  .lanecard.up,.lanenode,.lanephase,.lanecard.down{grid-row:auto}
  .lanenode{display:none}
  .lanephase{
    text-align:left;margin:0 0 12px;order:-1;
    display:flex;align-items:center;gap:9px;
  }
  .lanephase::before{content:"";width:18px;height:1px;background:var(--accent)}
  .lanecard.up{margin-bottom:10px;align-self:auto}
  .lanecard.down{margin-top:0;align-self:auto}
  .lanecard.up::before,.lanecard.down::before{
    display:block;font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;
    text-transform:uppercase;margin-bottom:8px;
  }
  .lanecard.up::before{content:"TM Construction";color:var(--accent)}
  .lanecard.down::before{content:"You";color:var(--text-secondary);opacity:.75}
}

/* --- 21. FAQ ------------------------------------------------------------- */

.faq{border-top:1px solid var(--rule-light)}
.band:not(.band-light) .faq{border-color:var(--rule-dark)}
.faq details{border-bottom:1px solid var(--rule-light)}
.band:not(.band-light) .faq details{border-color:var(--rule-dark)}
.faq summary{
  list-style:none;cursor:pointer;padding:22px 44px 22px 0;position:relative;
  font-family:var(--display);font-weight:700;text-transform:uppercase;
  font-size:clamp(15px,1.25vw,18px);letter-spacing:-.005em;
  transition:color var(--dur) var(--ease);
}
.faq summary::-webkit-details-marker{display:none}
.faq summary:hover{color:var(--accent)}
.band-light .faq summary:hover{color:var(--accent-hover)}
.faq summary::after{
  content:"";position:absolute;right:6px;top:50%;width:9px;height:9px;
  border-right:1.5px solid var(--accent);border-bottom:1.5px solid var(--accent);
  transform:translateY(-70%) rotate(45deg);transition:transform var(--dur) var(--ease);
}
.faq details[open] summary::after{transform:translateY(-30%) rotate(225deg)}
.faq .a{padding:0 44px 24px 0;color:var(--text-secondary)}
.band-light .faq .a{color:var(--text-secondary-light)}
.faq .a p + p{margin-top:12px}

/* --- 22. Quotes ---------------------------------------------------------- */

.quote{
  padding:26px 26px 28px;border-radius:13px;
  background:var(--panel-light);border:1px solid var(--rule-light);
}
.quote .stars{color:var(--accent-hover);font-size:14px;letter-spacing:.18em;margin-bottom:14px}
.quote blockquote{margin:0;font-size:15.5px;line-height:1.62;color:var(--text-primary-light)}
.quote figcaption{
  margin-top:16px;display:flex;align-items:center;gap:11px;
  font-family:var(--mono);font-size:11.5px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--text-secondary-light);
}
.avatar{
  width:34px;height:34px;border-radius:50%;flex:0 0 auto;
  display:grid;place-items:center;background:var(--accent-muted);
  border:1px solid var(--accent-line);color:var(--accent-hover);
  font-family:var(--display);font-weight:800;font-size:13px;
}

/* --- 22b. The one interactive moment: open the wall (section 10.1) -------
   The visitor presses and holds and the wall opens layer by layer. Each
   layer is a real construction layer, labelled in mono as it is exposed. */

.wall{
  --wall-h:clamp(280px,42vw,430px);
  display:grid;grid-template-columns:1.15fr .85fr;gap:clamp(26px,4vw,58px);
  align-items:center;
}
@media (max-width:860px){.wall{grid-template-columns:1fr}}

.wpad{
  position:relative;height:var(--wall-h);border-radius:14px;overflow:hidden;
  border:1px solid var(--border-interactive-dark);background:#0B1622;
  cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;
  transition:border-color var(--dur) var(--ease);
}
.wpad:active{cursor:grabbing}
.wpad:hover{border-color:var(--accent-line)}

/* Four stacked layers. Each slides away as its own --k rises.
   Siding is the OUTERMOST skin, so it has to paint on top and leave first.
   Without the explicit z-index the last element in the DOM (framing) sits on
   top and the wall peels inside out. */
.wl{
  position:absolute;inset:0;
  transform:translateX(calc(var(--k,0) * -101%));
  transition:transform .05s linear;
  will-change:transform;
}
.wl-1{z-index:4}
.wl-2{z-index:3}
.wl-3{z-index:2}
.wl-4{z-index:1}
.wl::after{content:"";position:absolute;inset:0;
  box-shadow:inset -22px 0 34px -22px rgba(0,0,0,.85)}

/* Siding: vertical board and batten, the outermost skin */
.wl-1{background:
  repeating-linear-gradient(90deg,#22384C 0 34px,#1B2E40 34px 41px);}
/* House wrap: a flat sheet */
.wl-2{background:linear-gradient(180deg,#2A4257,#22384C)}
/* Sheathing: horizontal panel joints */
.wl-3{background:
  repeating-linear-gradient(0deg,#33506A 0 58px,#2B4459 58px 61px),#33506A}
/* Framing: the studs themselves, the last thing standing */
.wl-4{background:
  repeating-linear-gradient(90deg,transparent 0 62px,rgba(196,154,92,.30) 62px 74px),
  linear-gradient(180deg,#12202F,#0D1826);}

.wlabels{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.wlabel{
  display:flex;align-items:baseline;gap:12px;padding:13px 0;
  border-bottom:1px solid var(--rule-dark);
  opacity:calc(.32 + .68 * var(--on,0));
  transition:opacity .45s var(--ease);
}
.wlabel b{
  font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.18em;
  color:var(--accent);flex:0 0 auto;width:2.4em;
}
.wlabel span{font-size:14.5px;color:var(--text-secondary)}
.band-light .wlabel span{color:var(--text-primary-light)}
.band-light .wlabel{border-bottom-color:var(--rule-light)}
.wlabel em{
  margin-left:auto;font-style:normal;font-family:var(--mono);font-size:10px;
  letter-spacing:.14em;color:var(--accent);
  opacity:var(--on,0);transition:opacity .45s var(--ease);
}

.whint{
  font-family:var(--mono);font-size:11px;letter-spacing:.19em;text-transform:uppercase;
  color:var(--accent);margin-top:20px;display:flex;align-items:center;gap:9px;
}
.whint::before{content:"";width:18px;height:1px;background:var(--accent);opacity:.65}

.wpayoff{
  margin-top:22px;font-family:var(--display);font-weight:700;text-transform:uppercase;
  font-size:clamp(15px,1.4vw,20px);letter-spacing:-.005em;color:#fff;
  opacity:var(--on,0);transform:translateY(calc((1 - var(--on,0)) * 10px));
  transition:opacity .6s var(--ease),transform .6s var(--ease);
}

/* --- 23. Utilities ------------------------------------------------------- */

.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.center{text-align:center;margin-inline:auto}
.mt{margin-top:clamp(26px,3.4vw,44px)}
.split{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(28px,4vw,70px);align-items:center}
.split.rev > :first-child{order:2}
/* Top-align the two columns instead of centring them, for the one split
   where the shorter column should start level with the taller column's
   photograph rather than floating in the middle of it. It changes nothing
   about the row's height, which is set by the taller column either way. */
.split-top{align-items:start}
@media (max-width:900px){.split{grid-template-columns:1fr}.split.rev > :first-child{order:0}}
.figure{border-radius:13px;overflow:hidden;border:1px solid var(--rule-dark)}
.band-light .figure{border-color:var(--rule-light)}
.figure img{width:100%;aspect-ratio:4/3;object-fit:cover}

/* --- 23c. The gallery lightbox ------------------------------------------
   Click a project to see it large. Built so a multi-photo project needs no
   new CSS: the arrows and the counter are hidden attributes that JS clears
   only when a card holds more than one photo.
   ------------------------------------------------------------------------ */

/* The whole thumbnail is the target. A transparent button laid over the media
   keeps the markup valid: a <button> cannot legally contain the media div. */
.galcard .card-media{position:relative}
.gopen{
  position:absolute;inset:0;width:100%;height:100%;
  background:none;border:0;padding:0;margin:0;cursor:zoom-in;
  border-radius:inherit;
}
.gopen:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.galcard .card-media::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(10,18,28,0) 60%,rgba(10,18,28,.34) 100%);
  opacity:0;transition:opacity .3s var(--ease);
}
.galcard:hover .card-media::after,
.galcard:focus-within .card-media::after{opacity:1}

.lbox{position:fixed;inset:0;z-index:120;display:grid;place-items:center;padding:clamp(14px,3vw,40px)}
.lbox[hidden]{display:none}
.lbox-back{position:absolute;inset:0;background:rgba(6,11,18,.86);backdrop-filter:blur(3px)}
.lbox-panel{
  position:relative;z-index:1;max-width:min(1100px,100%);width:100%;
  display:flex;flex-direction:column;gap:12px;
  animation:lboxin .28s var(--ease) both;
}
@keyframes lboxin{from{opacity:0;transform:scale(.97)}to{opacity:1;transform:none}}
.lbox-stage{position:relative;display:flex;align-items:center;justify-content:center}
.lbox-img{
  max-width:100%;max-height:min(74vh,780px);
  width:auto;height:auto;display:block;
  border-radius:12px;background:var(--panel);
  box-shadow:0 30px 80px rgba(0,0,0,.5);
}
.lbox-cap{
  color:var(--text-secondary);font-size:15px;line-height:1.5;
  text-align:center;margin:0;max-width:72ch;margin-inline:auto;
}
.lbox-count{
  font-family:var(--mono);font-size:12px;letter-spacing:.14em;
  color:var(--text-secondary);text-align:center;margin:0;
}
.lbox-count[hidden]{display:none}

.lbox-btn{
  display:grid;place-items:center;cursor:pointer;
  width:46px;height:46px;border-radius:50%;
  background:rgba(12,20,31,.72);border:1px solid var(--accent-line);
  color:#fff;transition:background .25s var(--ease), border-color .25s var(--ease);
}
.lbox-btn[hidden]{display:none}
.lbox-btn svg{width:22px;height:22px}
.lbox-btn:hover{background:rgba(12,20,31,.95);border-color:var(--accent)}
.lbox-btn:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.lbox-close{position:absolute;top:-8px;right:-4px;z-index:2}
.lbox-prev{position:absolute;left:10px}
.lbox-next{position:absolute;right:10px}
@media (max-width:620px){
  .lbox-prev{left:4px}
  .lbox-next{right:4px}
  .lbox-btn{width:42px;height:42px}
}
/* Touch targets, without changing the layout. */
@media (pointer:coarse){.lbox-btn{min-width:44px;min-height:44px}}

body.lbox-open{overflow:hidden}

/* --- 23c2. The service estimate modal -----------------------------------
   One per service page, opened by the "Get a <service> estimate" link.

   It deliberately REUSES the lightbox's shell conventions rather than
   inventing a second overlay: same fixed full-screen layer, same dark
   backdrop, same scroll lock via a body class. What it does not reuse is
   .lbox-panel, which is sized for a photograph. A form wants a narrow
   column with real padding and its own scroll when a phone keyboard eats
   the viewport.

   The focus trap, Escape handling and focus return are NOT here. They live
   in one shared helper in site.js that the lightbox and this both call, so
   the two cannot drift apart. */
.fmodal{position:fixed;inset:0;z-index:130;display:grid;place-items:center;padding:clamp(12px,3vw,40px)}
.fmodal[hidden]{display:none}
.fmodal-back{position:absolute;inset:0;background:rgba(6,11,18,.86);backdrop-filter:blur(3px)}
.fmodal-panel{
  position:relative;z-index:1;width:min(100%,520px);
  max-height:min(92vh,860px);overflow:auto;
  background:var(--panel);border:1px solid var(--rule-dark);border-radius:16px;
  padding:clamp(22px,3vw,34px);
  box-shadow:0 40px 90px -30px rgba(0,0,0,.7);
}
.fmodal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
.fmodal-head h2{font-size:clamp(20px,2.2vw,27px);max-width:20ch}
.fmodal-close{
  flex:0 0 auto;display:grid;place-items:center;width:34px;height:34px;
  border-radius:9px;border:1px solid var(--border-interactive-dark);
  background:transparent;color:var(--text-secondary);cursor:pointer;
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease);
}
.fmodal-close:hover{border-color:var(--accent);color:var(--accent)}
.fmodal-close:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.fmodal-close svg{width:18px;height:18px}
@media (pointer:coarse){.fmodal-close{width:44px;height:44px}}
.fmodal-note{
  margin-top:14px;font-family:var(--mono);font-size:11px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--accent);
}
.fmodal .form{margin-top:20px}

/* White fields on the modal's dark panel. The values are copied from the
   .band-light variants rather than invented, so a field in here looks like a
   field on any light band.

   SCOPED TO .field ON PURPOSE. The project-type radios are inputs too, but
   they live in .ptypes and are visually hidden behind their pills; catching
   them here would paint a white box behind every option.

   The ::placeholder half is not optional. Without it the fields keep the
   dark-panel placeholder, rgba(194,205,216,.5), which on white measures
   about 1.3:1 and is effectively invisible. It uses --text-secondary-light,
   the same value every light-band form uses since 2026-09-19, measuring
   6.87:1 on white. */
.fmodal .field input,
.fmodal .field textarea{
  color:var(--text-primary-light);background:#fff;
  border-color:var(--border-interactive-light);
}
.fmodal .field input::placeholder,
.fmodal .field textarea::placeholder{color:var(--text-secondary-light)}
.fmodal .field input:focus,
.fmodal .field textarea:focus{border-color:var(--accent-hover);background:#fff}

/* The project-type choice. Same pill language as the best-time-to-call
   radios, so a visitor meets one control style, not two. */
.ptypes{display:grid;gap:9px;margin-top:2px}
.ptypes label{display:block;cursor:pointer}
.ptypes input{position:absolute;opacity:0;pointer-events:none}
/* White pills, matching the fields above them. The selected state does NOT
   reinvent anything: it is the treatment .formwhite already uses for the
   best-time-to-call pills on white, because this is the same problem and it
   was solved once already. See the note above .formwhite.

   THE SELECTED STATE IS NOT COLOUR ALONE, which matters because on white the
   old darker fill is gone. Four cues, three of them independent of colour:
     - the ring doubles from 1px to 2px, via an inset shadow so nothing shifts
     - the label goes to weight 600
     - a check mark appears at the right hand end
     - and, carrying none of the load by itself, a faint gold wash
   Any one of the first three identifies the selection with colour ignored.

   The label stays DARK when selected rather than turning gold. Gold text on a
   light ground is design package section 4's one hard prohibition, and
   --accent on white measures 2.58:1.

   min-height is unconditional here, not just under (pointer:coarse). These
   are the primary control in a modal and 44px is the floor either way. */
.ptypes span{
  display:block;position:relative;
  padding:13px 46px 13px 16px;min-height:44px;border-radius:10px;
  background:var(--panel-light);
  border:1px solid var(--border-interactive-light);
  color:var(--text-primary-light);
  font-size:14.5px;transition:border-color var(--dur) var(--ease),
    background var(--dur) var(--ease),box-shadow var(--dur) var(--ease);
}
.ptypes label:hover span{border-color:var(--accent-hover)}
/* NO WASH. --accent-muted is rgba(196,154,92,.18), and setting it as the
   background REPLACES the white rather than tinting it, so it composites over
   the navy panel behind the pill and the selected row renders dark grey
   instead of pale gold. Caught in a screenshot, not in the numbers.

   The pill therefore stays white when selected and the state is carried by
   ring, weight and mark alone, which is what this reads as anyway now that
   the dark fill is gone. If a tint is ever wanted it has to be an OPAQUE
   value, roughly #F4EDE2, not a translucent one. */
.ptypes input:checked + span{
  background:var(--panel-light);
  border-color:var(--accent-hover);
  box-shadow:inset 0 0 0 1px var(--accent-hover);
  color:var(--text-primary-light);
  font-weight:600;
}
/* Drawn with two borders on a rotated box rather than a glyph, so it needs no
   font and inherits no line-height surprises. */
.ptypes input:checked + span::after{
  content:"";position:absolute;right:18px;top:50%;
  width:13px;height:7px;margin-top:-6px;
  border-left:2px solid var(--accent-hover);
  border-bottom:2px solid var(--accent-hover);
  transform:rotate(-45deg);
}
/* accent-hover, not accent: 3.29:1 on white against 2.58:1. */
.ptypes input:focus-visible + span{outline:2px solid var(--accent-hover);outline-offset:3px}

/* The opener is a button so it announces as one, but it has to read as the
   .tlink it replaced. */
button.tlink{background:none;border:0;padding:0;font-family:inherit;cursor:pointer}

/* Scroll lock while any modal or popup is open. The body rule alone NEVER held: html and body
   both carry overflow-x (section 1), and once html has an overflow value of its own the body's
   overflow stops propagating to the viewport, so the page kept scrolling behind every modal under
   a mouse wheel or a finger. Found by the pre-launch checks 2026-10-10. Locking html is what
   actually stops the viewport; :has() keeps the JS (three call sites) unchanged. */
body.fmodal-open{overflow:hidden}
html:has(body.fmodal-open){overflow:hidden}

/* --- 23d. The commercial video overlay ----------------------------------
   The homepage commercial is preload="none" with a poster, so before anyone
   presses play the page is showing a still. It read as a photograph. This
   sits over the poster and says video: a scrim, a large gold play control,
   the brand mark kept quiet, and a line of the services the spot names.

   The overlay IS the button, so a click anywhere on the frame starts it and
   the whole thing goes, handing the frame to the video's native controls.
   Nothing here introduces a colour or an image: the scrim is --canvas at
   alpha, the control is --accent, the mark is the same logo-mark.png the
   nav and footer use. */

.vwrap{position:relative}

.vplay{
  position:absolute;inset:0;z-index:2;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:clamp(14px,2vw,22px);
  width:100%;padding:0;border:0;cursor:pointer;
  font:inherit;color:var(--text-primary);text-align:center;
  /* --canvas (#0F1C2B) at alpha. Deeper at the edges so the mono line and
     the mark stay legible over whatever the poster frame happens to be. */
  background:
    radial-gradient(ellipse at center,rgba(15,28,43,.34) 0%,rgba(15,28,43,.66) 100%),
    linear-gradient(rgba(15,28,43,.22),rgba(15,28,43,.52));
  transition:opacity .3s ease,visibility 0s linear .3s;
}

/* Once it is playing the overlay is gone, not merely transparent: no click
   target left over the native controls. */
.vwrap.is-playing .vplay{
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .3s ease,visibility 0s linear .3s;
}

.vplay .vmark{
  width:clamp(52px,6vw,72px);height:auto;
  opacity:.62;
}

.vplay .vbtn{
  display:grid;place-items:center;
  width:clamp(74px,9vw,116px);aspect-ratio:1;
  border-radius:50%;
  background:var(--accent);
  box-shadow:0 10px 30px rgba(15,28,43,.42);
  transition:background .22s ease,transform .22s ease,box-shadow .22s ease;
}
/* The triangle is drawn, not an image, and it is optically centred: a
   centred triangle reads as sitting left of centre inside a circle. */
.vplay .vbtn::before{
  content:"";
  width:0;height:0;
  margin-left:12%;
  border-style:solid;
  border-width:calc(clamp(74px,9vw,116px) * .17) 0 calc(clamp(74px,9vw,116px) * .17) calc(clamp(74px,9vw,116px) * .29);
  border-color:transparent transparent transparent var(--canvas);
}
.vplay:hover .vbtn,
.vplay:focus-visible .vbtn{
  background:var(--accent-hover);
  transform:translateY(-3px) scale(1.04);
  box-shadow:0 16px 38px rgba(15,28,43,.52);
}
.vplay:focus-visible{outline:2px solid var(--accent);outline-offset:-4px;border-radius:2px}

.vplay .vsvcs{
  max-width:min(82%,640px);
  font-family:var(--mono);font-size:clamp(8.5px,1.05vw,10.5px);font-weight:500;
  letter-spacing:.2em;text-transform:uppercase;
  color:var(--text-secondary);opacity:.68;
  line-height:1.9;
}

@media (max-width:560px){
  .vplay .vsvcs{letter-spacing:.14em}
}
/* The whole frame is the target, so there is nothing to enlarge for touch. */

/* --- 24. Reduced motion, honoured completely ----------------------------- */

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;transition-delay:0s !important;
    scroll-behavior:auto !important;
  }
  /* Show every choreographed element in its finished state. */
  .rise .r{opacity:1;transform:none}
  .horizon path{stroke-dashoffset:0}
  /* The lightbox still opens and closes, it just does not animate in. */
  .lbox-panel{animation:none}
  .rule{transform:scaleX(1)}
  .layerline i{transform:scaleY(1)}
  .tick{opacity:1}
  .hband{opacity:1}
  .hband .w,.hband .c,.hband .sub{opacity:1;transform:none}
  /* The title card resolves settled. The five gates hide .hero under
     reduced motion anyway, so this is the belt to that braces. */
  .htitle::before{opacity:1}
  .htitle-name,.htitle-sub{opacity:1;transform:none}
  .htitle-sub::before{opacity:.75;transform:none}
  .env::after{transform:none}
  /* Positional transforms are re-applied rather than blanket-cleared, so
     nothing gets thrown off screen. */
  .hband{translate:0 -50%}
  .htitle{translate:-50% -50%}
  .dropmenu{translate:-50% 0}
  .callbar{transform:none}
  @media (max-width:1000px){.dropmenu{translate:none}}
}

/* ===========================================================================
   Assembly scroll section on /homeowners/, 2026-10-10. "Every trade. One
   builder." A pinned stage scrubs house-assembly.mp4 as the visitor scrolls.

   THREE MODES, and the first is the default so nothing depends on JS:
     1. no JS, phones under 768px, or prefers-reduced-motion: a still photo
        under the headline, no pinning, normal flow.
     2. .asm-on (added by initAssembly in site.js when the media query below
        matches): the section becomes 250vh tall, its stage sticks for the
        150vh of extra height, and the video replaces the still.
   The background is the video's own edge colour (sampled, #E4D6C7) and the
   video's four edges are masked to fade into it, so there is no visible box.
   =========================================================================== */
.asm{background:#E4D6C7;position:relative}
.asm-stage{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:clamp(14px,2.4vh,28px);padding:clamp(56px,9vh,96px) 20px clamp(40px,7vh,72px);
}
.asm h2{
  color:var(--text-primary-light);text-align:center;margin:0;max-width:none;
  font-size:clamp(22px,2.5vw,36px);align-self:stretch;width:100%;
}
.asm-fig{
  margin:0;width:min(92vw,980px);aspect-ratio:4/3;position:relative;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 11%,#000 89%,transparent),linear-gradient(to bottom,transparent,#000 7%,#000 93%,transparent);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(to right,transparent,#000 11%,#000 89%,transparent),linear-gradient(to bottom,transparent,#000 7%,#000 93%,transparent);
  mask-composite:intersect;
}
.asm-fig img,.asm-fig video{display:block;width:100%;height:100%;object-fit:cover}
.asm-video{display:none}
@media (min-width:768px) and (prefers-reduced-motion:no-preference){
  /* 300vh = 100vh stage + 150vh of scrub + 50vh HOLD on the finished frame */
  .asm.asm-on{height:300vh}
  .asm.asm-on .asm-stage{
    position:sticky;top:0;height:100vh;box-sizing:border-box;
    padding-top:clamp(84px,12vh,110px);padding-bottom:clamp(20px,4vh,40px);
  }
  .asm.asm-on .asm-fig{width:min(92vw,980px,calc((100vh - 220px) * 1.3333))}
  .asm.asm-on .asm-video{display:block}
  .asm.asm-on .asm-still{display:none}
}

/* Free-estimate house picker, 2026-10-10: the heading now sits at the top of the
   LEFT column so the Your Project panel starts level with it. On desktop the
   panel stays pinned while the visitor scrolls past the house; if it is taller
   than the viewport it scrolls inside itself so the form stays reachable.
   Phones (<=1000px) stack as before. */
@media (min-width:1001px){
  .pickgrid .formcard{position:sticky;top:92px;align-self:start;max-height:calc(100vh - 112px);overflow-y:auto}
}

/* ===========================================================================
   Homepage opening section, 2026-10-10. Full viewport height under the nav, the
   logo and subline over a muted looping background video, with a dark overlay.
   The poster is a real <img> underneath (a <video> poster is invisible while the
   video fades in), and the video fades in over it only once it is playing.
   =========================================================================== */
.open{
  position:relative;overflow:hidden;background:#05080e;
  min-height:calc(100vh - 75px);min-height:calc(100svh - 75px);
  display:grid;place-items:center;
}
.open-poster,.open-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.open-video{opacity:0;transition:opacity .9s var(--ease)}
.open-video.is-on{opacity:1}
.open::after{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:
    radial-gradient(ellipse 58% 62% at 50% 50%,rgba(5,5,10,.60) 0%,rgba(5,5,10,.40) 55%,rgba(5,5,10,0) 100%),
    linear-gradient(rgba(5,8,14,.52),rgba(5,8,14,.52));
}
.open-in{position:relative;z-index:2;width:min(92vw,980px);padding:clamp(28px,6vh,64px) 20px;text-align:center;text-shadow:var(--tshadow)}
.open-logo{margin:0}
.open-sub{
  margin:clamp(12px,1.4vw,20px) auto 0;width:fit-content;max-width:46ch;
  font-size:clamp(14px,1.18vw,18.5px);color:#E7EDF3;
}
.open-sub::before{
  content:"";display:block;width:clamp(34px,4vw,56px);height:1px;
  margin:0 auto clamp(12px,1.4vw,18px);background:var(--accent);opacity:.75;
}

/* ===========================================================================
   Homepage estimate popup, 2026-10-10. It is a .fmodal, so the panel, close
   button, white fields and backdrop all come from the service-page modal. This
   adds the select, the intro and call lines, and on phones turns it into a
   bottom sheet that slides up instead of a centred box.
   =========================================================================== */
.fmodal .field select{
  font-family:var(--body);font-size:16px;width:100%;padding:13px 15px;
  color:var(--text-primary-light);background:#fff;
  border:1px solid var(--border-interactive-light);border-radius:9px;
}
.fmodal .field select:invalid{color:var(--text-secondary-light)}
.fmodal .field select:focus{outline:none;border-color:var(--accent-hover)}
.fmodal.pop .fmodal-head .kicker{margin-bottom:8px}
.fmodal.pop .fmodal-head h2{max-width:none}
.pop-text{margin-top:14px;color:var(--text-secondary);font-size:15.5px;line-height:1.5}
.pop-call{margin-top:14px;text-align:center;font-size:13.5px;color:var(--text-secondary)}
.pop-call a{color:var(--accent);text-decoration:underline;text-underline-offset:3px}
@media (max-width:640px){
  .fmodal.pop{place-items:end center;padding:0}
  .fmodal.pop .fmodal-panel{
    width:100%;max-height:92vh;max-height:92svh;border-radius:18px 18px 0 0;
  }
}

/* ---------------------------------------------------------------------------
   Popup, second pass 2026-10-10: no bounce. The backdrop fades in over .4s; the
   panel fades in while rising 16px over .5s on a soft ease-out. Closing reverses
   it a little faster. The transition that runs is the one on the DESTINATION
   state, so the slower values live on .is-in (opening) and the quicker ones on
   the base state (closing). initPopup adds .is-in a frame after unhiding and
   removes it before re-hiding.
   --------------------------------------------------------------------------- */
.fmodal.pop .fmodal-back{opacity:0;transition:opacity .28s ease-in}
.fmodal.pop.is-in .fmodal-back{opacity:1;transition:opacity .4s ease-out}
.fmodal.pop .fmodal-panel{
  opacity:0;transform:translateY(16px);
  transition:opacity .3s ease-in,transform .3s ease-in;
}
.fmodal.pop.is-in .fmodal-panel{
  opacity:1;transform:none;
  transition:opacity .5s cubic-bezier(.22,.8,.3,1),transform .5s cubic-bezier(.22,.8,.3,1);
}
@media (prefers-reduced-motion:reduce){
  .fmodal.pop .fmodal-back,.fmodal.pop .fmodal-panel,
  .fmodal.pop.is-in .fmodal-back,.fmodal.pop.is-in .fmodal-panel{transition:none}
}
/* Compact: inputs about 40px, small labels, tight spacing, so the whole offer
   fits one screen at 1366x768 with no scrolling. Phones may scroll inside the sheet. */
.fmodal.pop .fmodal-panel{width:min(100%,468px);padding:18px 22px 16px}
.fmodal.pop .fmodal-head h2{font-size:clamp(18px,1.7vw,22px);line-height:1.12}
.fmodal.pop .fmodal-head .kicker{margin-bottom:6px;font-size:10.5px}
.fmodal.pop .pop-text{margin-top:8px;font-size:14px;line-height:1.4}
.fmodal.pop .form{margin-top:12px;gap:9px}
.fmodal.pop .field{gap:4px}
.fmodal.pop .field label{font-size:10px;letter-spacing:.12em}
.fmodal.pop .field input,.fmodal.pop .field select{height:40px;padding:0 12px;font-size:15px}
.fmodal.pop .field textarea{min-height:0;height:auto;padding:8px 12px;font-size:15px;line-height:1.35;resize:none}
.pop-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fmodal.pop .btn{margin-top:2px;padding-block:11px;font-size:13px}
.pop-fine{margin-top:2px;text-align:center;font-size:11.5px;line-height:1.35;color:var(--text-secondary)}
.fmodal.pop .pop-call{margin-top:2px;font-size:12.5px}
@media (max-width:640px){
  .pop-row{grid-template-columns:1fr}
  .fmodal.pop .fmodal-panel{padding:18px 18px 18px}
}

/* ===========================================================================
   Step by step reveal on /homeowners/ (2026-10-10). .stepflow replaces .rise on
   that one list: every step is hidden until initSteps() in site.js marks it
   .is-shown as it comes about 80% of the way up the screen, and it STAYS shown.
   Fade plus a 20px rise over .6s on a soft ease-out; the gold rule beside the
   number draws in left to right a beat later. The stagger comes from a per-step
   --d set by the script. Reduced motion shows everything with no transition.
   =========================================================================== */
.stepflow .step{
  opacity:0;transform:translateY(20px);
  transition:opacity .6s cubic-bezier(.22,.8,.3,1) var(--d,0s),transform .6s cubic-bezier(.22,.8,.3,1) var(--d,0s);
}
.stepflow .step::after{
  transform:scaleX(0);transform-origin:left center;
  transition:transform .6s cubic-bezier(.22,.8,.3,1) calc(var(--d,0s) + .18s);
}
.stepflow .step.is-shown{opacity:1;transform:none}
.stepflow .step.is-shown::after{transform:scaleX(1)}
@media (prefers-reduced-motion:reduce){
  .stepflow .step,.stepflow .step::after{opacity:1;transform:none;transition:none}
}


/* --- Trade showcase over the top of the scroll hero (2026-10-10) -------- */
.tshow{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  display:flex;align-items:center;justify-content:center;
  background:radial-gradient(ellipse at center,rgba(10,19,30,.45) 0%,rgba(10,19,30,.78) 62%,rgba(10,19,30,.92) 100%);
  opacity:var(--o,1);will-change:opacity;
}
.tshow-frame{position:relative;display:inline-block;line-height:0;transform:scale(var(--s,1));will-change:transform}
.tshow img{
  display:block;width:auto;height:auto;max-width:min(86vw,1400px);max-height:84vh;object-fit:contain;
  box-shadow:0 0 90px 46px rgba(10,19,30,.7);
}
/* The button rides the frame, so it sits at 63% of the IMAGE at any size and fades and scales with it.
   The layer is pointer-events:none; only the button takes clicks, and the layer goes visibility:hidden
   once the image is gone, so it cannot be clicked then. */
.tshow-cta{
  position:absolute;left:50%;top:63%;translate:-50% 0;pointer-events:auto;line-height:1.2;
  font-size:clamp(11px,1.45vw,17px);padding:.8em 1.6em;white-space:nowrap;
}
.tshow-still{display:none;margin:0;background:#0A131E;padding:clamp(18px,4vw,40px) var(--gutter)}
.tshow-still img{display:block;width:100%;max-width:900px;height:auto;margin-inline:auto}
@media (max-width:720px){.tshow-still{display:block}}
@media (orientation:portrait) and (max-width:1024px){.tshow-still{display:block}}
@media (orientation:portrait) and (pointer:coarse){.tshow-still{display:block}}
@media (orientation:landscape) and (pointer:coarse) and (max-height:560px){.tshow-still{display:block}}
@media (prefers-reduced-motion:reduce){.tshow-still{display:block}}
.tshow-still-cta{margin:20px 0 0;text-align:center}


/* --- Free guide popup, 2026-10-10 ---------------------------------------------------------
   A .fmodal.pop (same fade and rise, same navy panel). .guide widens it to two columns: a tilted
   cover on the left, copy and form on the right. Phones stack it with a small cover on top. */
.fmodal.guide .fmodal-panel{width:min(100%,780px);padding:22px 26px 20px;position:relative}
.fmodal.guide .fmodal-close{position:absolute;top:12px;right:12px;z-index:2}
.guide-grid{display:grid;grid-template-columns:minmax(0,260px) 1fr;gap:clamp(18px,3vw,32px);align-items:center}
.guide-cover{display:grid;place-items:center;padding:6px 4px}
.guide-cover img{
  display:block;width:100%;max-width:240px;height:auto;border-radius:3px;
  transform:rotate(-3.5deg);
  box-shadow:0 18px 38px rgba(0,0,0,.55),0 4px 10px rgba(0,0,0,.4);
}
.guide-main .kicker{margin-bottom:6px;font-size:10.5px}
.guide-main h2{font-size:clamp(22px,2.3vw,30px);line-height:1.1;max-width:none}
.fmodal.guide .pop-text{margin-top:10px;font-size:14.5px;line-height:1.45}
.fmodal.guide .form{margin-top:14px;gap:9px}
.fmodal.guide .btn{margin-top:4px;width:100%;justify-content:center}
.guide-no{
  display:block;margin:8px auto 0;background:none;border:0;padding:4px 8px;cursor:pointer;
  font:inherit;font-size:12.5px;color:var(--text-secondary);text-decoration:underline;text-underline-offset:3px;
}
.guide-no:hover{color:var(--accent)}
.fmodal.guide .formdone{text-align:center;padding:10px 0}
.fmodal.guide .formdone h3{margin-bottom:16px}
.fmodal.guide .pop-call .btn{margin-top:8px;width:auto;display:inline-flex}
@media (max-width:640px){
  .fmodal.guide .fmodal-panel{padding:16px 16px 14px;max-height:96svh;overflow:auto}
  .guide-grid{grid-template-columns:1fr;gap:10px}
  .guide-cover{padding:0}
  .guide-cover img{max-width:min(104px,22vh);transform:rotate(-3deg)}
  .guide-main{text-align:center}
  .guide-main h2{font-size:21px}
  .fmodal.guide .pop-text{font-size:13px;line-height:1.38;margin-top:6px}
  .fmodal.guide .form{margin-top:8px;gap:7px;text-align:left}
  .fmodal.guide .field input{height:38px}
}
