/* ============================================================================
   American Countertops & Design — home v2 foundation
   ----------------------------------------------------------------------------
   This file is the single source of layout truth. Section authors consume it;
   they do not extend it at the bottom.

   HOUSE RULES (breaking one of these is what broke v1)
   1. No ID selectors. If a section needs to differ, it needs a modifier class.
   2. No !important, with exactly one exception: the reduced-motion kill switch.
   3. No property is declared twice for the same selector. One rule, one place.
   4. No "compaction" block at the end of the file. There is no end-of-file tail.
   5. Every two-column .split stretches. A split photo NEVER uses
      height:auto + aspect-ratio + max-height above 900px. That combination is
      what left the empty band above and below the photo in v1.
   6. Palette is white + green + navy. No gold, no olive, no third-party hex.

   TABLE OF CONTENTS
    01 Fonts
    02 Tokens
    03 Reset and base elements
    04 Type scale
    05 Utilities
    06 Motion system
    07 Surfaces and section rhythm
    08 Drawing devices (rack band, datum rule, plan band, plate caption)
    09 Header, topbar, nav, call bar
    10 Buttons
    11 Hero
    12 Split system  <-- the parity rule
    13 Cards, grids, galleries
    14 Icons and badges
    15 Fact strip and mobile jump bar
    16 Process rail
    17 Before / after comparator
    18 Tables
    19 FAQ
    20 Lead form
    21 Service area map
    22 City chips
    23 Footer
    24 Print
   ========================================================================== */


/* 01 — FONTS ================================================================
   Montserrat only, self hosted. Bebas Neue is gone, and so are the two
   preconnects and the render blocking Google Fonts stylesheet.
   The document head must preload 400 and 800 (800 carries the H1 and the H2s).
   ========================================================================== */
@font-face{font-family:'Montserrat';src:url(Montserrat-400.woff2) format('woff2');
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Montserrat';src:url(Montserrat-600.woff2) format('woff2');
  font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Montserrat';src:url(Montserrat-700.woff2) format('woff2');
  font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:'Montserrat';src:url(Montserrat-800.woff2) format('woff2');
  font-weight:800;font-style:normal;font-display:swap}


/* 02 — TOKENS ===============================================================
   Nothing below this block invents a colour, a radius or a shadow.
   ========================================================================== */
:root{
  /* surface */
  --white:#FFFFFF;
  --bg:#FFFFFF;
  --bg-alt:#ECF1F8;          /* L*~94 — the light band actually reads as a band */
  --bg-dark:#192850;
  --navy-lift:#22366A;
  --navy-deep:#132043;

  /* ink */
  --ink:#192850;
  --ink-soft:#46506B;
  --ink-faint:#5B6680;

  /* lines */
  --line:#D6DDE9;
  --line-soft:#E6EBF3;
  --line-strong:#7F8DA9;     /* 3.34:1 on white — resting border of every input */

  /* green: five jobs, five values, never improvised */
  --green-surface:#00A652;   /* surface and vector only. Never behind white text */
  --green:#00883F;           /* primary button at rest — 4.57:1 */
  --green-ink:#00753A;       /* button hover, link on white, focus ring — 5.82:1 */
  --green-deep:#005A2C;      /* link hover */
  --green-on-navy:#5FD99A;   /* 8.13:1 — TEXT and LINK on navy only */
  --green-wash:#E6F4EC;      /* light badge fill */

  /* geometry */
  --r-sm:6px;
  --r-md:12px;
  --miter:10px;
  --wrap:1180px;
  --pad:clamp(20px,4vw,32px);
  --sec-pad:clamp(56px,7vw,104px);
  --gap-split:clamp(28px,4vw,64px);
  --header-h:86px;

  /* split media guard rails — see section 12 */
  --fill-min:clamp(360px,34vw,460px);
  --fill-max:720px;

  /* elevation by role, one navy shadow colour */
  --e1:0 1px 2px rgba(25,40,80,.05),0 3px 10px -5px rgba(25,40,80,.12);
  --e2:0 2px 4px rgba(25,40,80,.05),0 14px 32px -16px rgba(25,40,80,.22);
  --e3:0 4px 10px rgba(25,40,80,.06),0 30px 64px -28px rgba(25,40,80,.30);

  /* motion */
  --ease-stone:cubic-bezier(.2,.7,.3,1);
  --ease-swap:cubic-bezier(.62,.04,.3,1);
  --ease-snap:cubic-bezier(.3,.1,.2,1);

  /* surface textures */
  --mesh:repeating-linear-gradient(0deg,rgba(25,40,80,.055) 0 1px,transparent 1px 72px),
         repeating-linear-gradient(90deg,rgba(25,40,80,.055) 0 1px,transparent 1px 72px);
  --vein:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cg fill='none' stroke='%23fff' stroke-width='1' stroke-opacity='.05'%3E%3Cpath d='M-20 38C28 8 70 68 112 42s62-28 108-4'/%3E%3Cpath d='M-20 106C38 88 62 138 118 118s72-12 122 6'/%3E%3Cpath d='M-20 166C30 148 82 184 142 164'/%3E%3C/g%3E%3C/svg%3E");
}
@media(max-width:899px){:root{--header-h:72px}}


/* 03 — RESET AND BASE =======================================================*/
*{box-sizing:border-box}
[hidden]{display:none}
html{font-size:112.5%;                       /* 1rem = 18px, audience is 45+ */
  scroll-behavior:smooth;
  scroll-padding-top:clamp(84px,11vh,112px);
  overflow-x:clip;
  -webkit-text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--ink);
  font-family:'Montserrat',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  font-size:1rem;line-height:1.66;-webkit-font-smoothing:antialiased}
img,video,svg{max-width:100%;display:block}
img,video{height:auto}
figure{margin:0}
strong,b{font-weight:600}
p{margin:0 0 .95em;max-width:62ch}
ul,ol{margin:0 0 1.2em;padding-left:1.3em;max-width:62ch}
li{margin-bottom:.45em}
a{color:var(--green-ink);text-underline-offset:3px}
a:hover{color:var(--green-deep)}

/* Focus is visible on everything, including textarea and summary, which had
   no indicator at all in v1. On navy the ring is white (14.6:1); the light
   green stays reserved for text and links. */
:is(a,button,input,select,textarea,summary,details,[tabindex]):focus-visible{
  outline:3px solid var(--green-ink);outline-offset:2px;border-radius:2px}
:is(section.dark,.hero.photo-hero,.plan-band,footer) :is(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
  outline-color:var(--white)}

::selection{background:var(--green-surface);color:var(--white)}

/* Anchors never land under the sticky header. */
:where(section[id],[id]){scroll-margin-top:110px}


/* 04 — TYPE SCALE ===========================================================
   Three named levels with real daylight between them. v1 rendered fourteen
   identical 36.9px H2s, so nothing could be emphasised.
   Line height follows MEASURE, it is not inherited from body.
   ========================================================================== */
h1,h2,h3,h4,.ui{font-family:inherit;font-weight:800;color:var(--ink);text-wrap:balance;margin:0}
h1{font-size:clamp(2.05rem,4.7vw,3.15rem);line-height:1.04;letter-spacing:-.03em}
.sec-head h2{font-size:clamp(1.9rem,2.6vw,2.45rem);line-height:1.08;letter-spacing:-.022em}
.sec-head.lead h2{font-size:clamp(2.35rem,3.55vw,2.85rem);line-height:1.05;letter-spacing:-.028em}
.sec-head.quiet h2{font-size:clamp(1.45rem,1.9vw,1.72rem);line-height:1.15;letter-spacing:-.01em;
  font-weight:700}
h3{font-size:1.24rem;line-height:1.22;letter-spacing:-.008em}
h4{font-size:1.04rem;line-height:1.3}

.lede{font-size:1.13rem;line-height:1.5;max-width:56ch;color:var(--ink-soft)}
.fine{font-size:.85rem;line-height:1.55;color:var(--ink-faint);max-width:72ch}

/* annotation voice — the technical-drawing register the page never had */
.note{font-weight:700;font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-faint);line-height:1.3}
.sec-eyebrow{display:flex;align-items:center;gap:10px;font-weight:800;font-size:.72rem;
  letter-spacing:.2em;text-transform:uppercase;color:var(--ink-faint);margin:0 0 10px}
.sec-eyebrow::before{content:"";flex:0 0 22px;height:1px;background:var(--green-surface)}

.sec-head{margin:0 0 clamp(30px,3.6vw,52px);max-width:100%}
/* 26ch, not 22ch. At 22ch a title like "In-House Fabrication and Professional
   Countertop Installation" broke into five short lines stacked on the left
   margin, which is what made every section read as the same block again. */
.sec-head h2{max-width:26ch}
.sec-head.lead h2{max-width:20ch}
.sec-head .sec-sub{margin:.8em 0 0;font-size:1.06rem;line-height:1.55;color:var(--ink-soft);
  max-width:60ch}
.sec-head > :last-child{margin-bottom:0}

/* numerals that must line up */
.tabular{font-variant-numeric:tabular-nums}


/* 05 — UTILITIES ============================================================*/
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--pad);
  position:relative;z-index:1}
.wrap.narrow{max-width:860px}

/* Reserved: .sr-only is the accessibility utility. The showroom section owns
   the .sr-* prefix for everything ELSE, but must never author .sr-only. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

/* the mitred corner — the brand shape. clip-path eats the border, so the
   stroke comes back as an inset shadow. */
.miter{clip-path:polygon(0 0,calc(100% - var(--miter)) 0,100% var(--miter),100% 100%,0 100%)}
.miter-tl{clip-path:polygon(var(--miter) 0,100% 0,100% 100%,0 100%,0 var(--miter))}

.stack{display:flex;flex-direction:column;gap:var(--stack,18px)}
.stack > *{margin-block:0}

/* opt-in only. Do NOT put this on a section that contains sticky media. */
.cv{content-visibility:auto;contain-intrinsic-size:auto 900px}


/* 06 — MOTION SYSTEM ========================================================
   Kill switch first. `animation:none` cancels the animation and returns the
   element to its declared state — which in an entrance system is opacity:0.
   `.01ms` keeps animationend firing and keeps the content on screen.
   ========================================================================== */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;
    animation-iteration-count:1!important;
    transition-duration:.01ms!important;
    scroll-behavior:auto!important}
}

/* One reveal API: data-anim="rise" | "slide".
   Every hidden initial state lives inside no-preference, so the worst case is
   "appears without animating", never "does not appear". */
@media(prefers-reduced-motion:no-preference){
  .js [data-anim]{opacity:0;will-change:opacity,transform}
  .js [data-anim="rise"]{transform:translateY(18px)}
  .js [data-anim="slide"]{transform:translateX(var(--from,24px))}
  .js [data-anim].is-in{opacity:1;transform:none;will-change:auto;
    transition:opacity 420ms linear var(--d,0ms),
               transform 520ms var(--ease-stone) var(--d,0ms)}
  /* nothing that is already in the first viewport on load may animate */
  .js [data-anim].no-anim{opacity:1;transform:none;transition:none}

  /* first-fold settle: transform only. Opacity is locked at 1 above the fold
     so the LCP is never delayed. The hero <img> never animates at all. */
  .js .fold-in{animation:fold-settle 480ms var(--ease-stone) both;
    animation-delay:calc(90ms + var(--i,0) * 70ms)}
}
@keyframes fold-settle{from{transform:translateY(14px)}to{transform:none}}

/* A horizontal entrance is the one motion a one-column phone layout cannot
   afford: the column is already the full width of the screen, so the resting
   translateX makes the document wider than the viewport until the observer
   fires. Measured at 390px: scrollWidth 394 against clientWidth 390, from the
   single data-anim="slide" that carries a positive --from. Under 900px a slide
   simply becomes a rise; nothing else about the API changes. */
@media(prefers-reduced-motion:no-preference) and (max-width:899px){
  .js [data-anim="slide"]{transform:translateY(18px)}
}

/* the datum rule draws itself from its origin tick */
@media(prefers-reduced-motion:no-preference){
  .js .datum{transform:scaleX(0);transform-origin:0 50%}
  .js .datum.is-in{transform:none;transition:transform 420ms linear}
}


/* 07 — SURFACES AND SECTION RHYTHM ==========================================
   Three surfaces with meaning: paper, drafting board, stone.
   Budget for the whole page: at most THREE surface changes. Two sections that
   argue the same point stay on the same surface and are joined by .datum-seam.
   ========================================================================== */
section{padding:var(--sec-pad) 0;position:relative}
section.pillar{--sec-pad:clamp(72px,8.5vw,128px)}
section.tight{--sec-pad:clamp(30px,4vw,56px)}
section.flush{--sec-pad:0px}

/* 2 — drafting board */
section.alt{background:var(--bg-alt)}
section.alt::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--mesh);background-size:72px 72px;opacity:.5}

/* 3 — stone */
section.dark{color:#E6EAF2;
  background:radial-gradient(120% 90% at 18% 0%,var(--navy-lift) 0%,var(--bg-dark) 58%,var(--navy-deep) 100%)}
section.dark::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--vein);background-repeat:repeat}
section.dark :is(h1,h2,h3,h4){color:var(--white)}
section.dark :is(p,li){color:#DCE3F0}
section.dark .lede{color:#E3E8F2}
section.dark .fine{color:#B9C2D6}
section.dark .sec-eyebrow{color:rgba(255,255,255,.72)}
section.dark .sec-eyebrow::before{flex-basis:34px;height:2px}
section.dark .note{color:rgba(255,255,255,.66)}
section.dark a:not(.btn){color:var(--green-on-navy)}
section.dark a:not(.btn):hover{color:var(--white)}

/* A light card inside a dark section always takes dark ink. Without this the
   form heading rendered white on white in v1 — 1.00:1, invisible. */
:is(section.dark,.hero.photo-hero) :is(.card,.mcard,.formcard,.tablewrap,.panel){color:var(--ink)}
:is(section.dark,.hero.photo-hero) :is(.card,.mcard,.formcard,.panel) :is(h2,h3,h4){color:var(--ink)}
:is(section.dark,.hero.photo-hero) :is(.card,.mcard,.formcard,.panel) :is(p,li){color:var(--ink-soft)}
:is(section.dark,.hero.photo-hero) :is(.card,.mcard,.formcard,.panel) .fine{color:var(--ink-faint)}
:is(section.dark,.hero.photo-hero) :is(.card,.mcard,.formcard,.panel) a:not(.btn){color:var(--green-ink)}
:is(section.dark,.hero.photo-hero) :is(.card,.mcard,.formcard,.panel) .sec-eyebrow{color:var(--ink-faint)}


/* 08 — DRAWING DEVICES ======================================================*/

/* Datum rule — the ONLY divider on the page. It is the origin mark of the
   whole system: a 64px green segment, the rest of the line at 22%, and a tick
   standing on the zero. Geometry mirrors assets/svg/device-datum-rule.svg. */
.datum{display:block;position:relative;height:14px;width:100%;
  max-width:var(--datum-w,220px);margin:0 0 clamp(16px,2vw,24px);color:var(--ink);
  background-image:
    linear-gradient(var(--green-surface),var(--green-surface)),
    linear-gradient(color-mix(in srgb,currentColor 22%,transparent),
                    color-mix(in srgb,currentColor 22%,transparent));
  background-size:64px 2px,100% 2px;
  background-position:0 6px,0 6px;
  background-repeat:no-repeat}
.datum::before{content:"";position:absolute;left:0;top:1px;width:2px;height:12px;
  background:var(--green-surface)}
section.dark .datum{color:var(--white)}

/* full-bleed variant used between two blocks of the same surface */
.datum-seam{--datum-w:100%;margin:clamp(34px,5vw,64px) 0 0}

/* Plan band — navy ritual break, 160px, no H2, no photo, no new claim. */
.plan-band{background:var(--bg-dark);color:#E6EAF2;position:relative;overflow:hidden;
  min-height:160px;display:flex;align-items:center}
.plan-band::before{content:"";position:absolute;left:0;right:0;top:50%;height:1px;
  background:repeating-linear-gradient(90deg,rgba(255,255,255,.10) 0 14px,transparent 14px 24px)}
.plan-band .wrap{display:flex;flex-wrap:wrap;align-items:center;gap:14px 24px;
  width:100%;padding-block:28px}

/* Plate caption — the drafting title block. Only where the payload is already
   true: #about and #showroom. Never on a client job photo. */
.plate{position:absolute;left:0;right:0;bottom:0;z-index:2;display:flex;flex-wrap:wrap;
  align-items:center;justify-content:space-between;gap:6px 18px;
  padding:11px clamp(14px,2vw,22px);background:rgba(25,40,80,.90);color:var(--white)}
.plate::before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;
  background:var(--green-surface)}
.plate > span{font-weight:800;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase}
.plate > span + span{font-weight:700;color:rgba(255,255,255,.76);letter-spacing:.1em}
.has-plate{position:relative}


/* 09 — HEADER, TOPBAR, NAV, CALL BAR ========================================*/
.topbar{background:var(--bg-dark);color:#E6EAF2;font-size:.82rem}
.topbar .wrap{display:flex;justify-content:space-between;align-items:center;gap:10px 18px;
  flex-wrap:wrap;padding-block:8px}
.topbar a{color:var(--green-on-navy);text-decoration:none;font-weight:600;
  display:inline-block;padding:9px 8px;margin:-9px -8px}
.topbar a:hover{color:var(--white)}
@media(max-width:899px){.topbar{display:none}}   /* 110px of address before the H1 is indefensible on a phone */

/* Type selector, so it must exclude a section heading written as <header>.
   Without the :not(), <header class="sec-head"> becomes a second sticky
   white bar with a border, mid-section. */
header:not(.sec-head){position:sticky;top:0;z-index:50;background:var(--bg);
  border-bottom:1px solid var(--line);transition:box-shadow 220ms var(--ease-snap)}
header.stuck{box-shadow:var(--e1)}
.hd{display:flex;align-items:center;justify-content:space-between;gap:18px;
  min-height:var(--header-h)}
/* The nav is the only part of the row allowed to give ground; the logo and the
   two buttons keep their size. Without min-width:0 a flex item refuses to
   shrink below its content and the whole row pushes past the wrap. */
nav.main{min-width:0}
.logo{display:flex;align-items:center;text-decoration:none;flex:0 0 auto}
.logo img{height:48px;width:auto;transform-origin:left center;
  transition:transform 260ms var(--ease-snap)}
header.stuck .logo img{transform:scale(.88)}   /* transform, never height: height reflows the page every scroll */
@media(max-width:600px){.logo img{height:44px}}

/* --- The phone box beside the CTA -------------------------------------------
   Renato, 19/09/2026: "o numero do telefone movido para um box ao lado do Free
   Estimate e que os dois ficassem flashing ou com algum efeito na cor".

   Where the number lives, by width, so it is never in two places at once:
     >=1280px  header box (.hd-phone)          .tb-phone hidden
     900-1279  topbar (.tb-phone)              .hd-phone hidden - the header row
                                               has no width for a second button
     <900px    the fixed callbar at the bottom .hd-phone and the topbar both gone

   The effect is a SLOW pulse, ~2s a cycle, not a flash. Anything above 3 cycles
   per second is a photosensitivity trigger; this reads as "look here" without
   going near that. It animates box-shadow and transform only - never opacity of
   the label, which would make the text unreadable at the bottom of the cycle,
   and never a property that reflows. The two buttons run half a cycle apart:
   alternating catches the eye better than both blinking together, and it does
   not turn the header into a shop sign. */
.hd-actions{display:flex;align-items:center;gap:10px;flex:0 0 auto}

/* Scoped with .hd-actions on purpose: a bare .hd-phone is one class, the same
   as .btn, and .btn{display:inline-flex} is declared further down the file, so
   it would win the cascade and the number would show in the header AND in the
   topbar at the same time. Two classes settle it by specificity, not by order. */
.hd-actions .hd-phone{display:none;font-size:.86rem;letter-spacing:0;padding:10px 14px;
  min-height:44px;align-items:center;white-space:nowrap}
/* 1380, nao 1280 - Paulo, 19/09/2026. A 1280 o box entrava mas a linha do
   header passava de 1180px (a largura do .wrap) e o cabecalho ficava mais
   largo que o resto do site. Medido: logo 48px + 6 itens de menu + os dois
   botoes so cabem no wrap a partir de ~1380. */
/* 1280. Abaixo disso o numero fica na topbar. A largura util do header nao
   depende da viewport (o .wrap trava em 1180, logo 1116 uteis), entao o que
   decide nao e a tela e sim a soma logo + 6 itens de menu + os dois botoes.
   Medido a 1500 com a caixa: 1112 de 1116. A caixa foi compactada para abrir
   folga real em vez de caber por um pixel. */
@media(min-width:1280px){.hd-actions .hd-phone{display:inline-flex}}
@media(min-width:1280px){.tb-phone{display:none}}

@media(prefers-reduced-motion:no-preference){
  .hd-actions .btn{animation:cta-attention 2000ms var(--ease-stone) infinite}
  .hd-actions .hd-phone{animation-delay:1000ms}
}
@keyframes cta-attention{
  0%,100%{transform:translateY(0);box-shadow:0 0 0 0 rgba(0,166,82,0)}
  45%    {transform:translateY(-2px);box-shadow:0 0 0 6px rgba(0,166,82,.22)}
  70%    {transform:translateY(0);box-shadow:0 0 0 10px rgba(0,166,82,0)}
}
/* Hover and keyboard focus own the button: the ambient pulse steps aside so the
   user's own intent is what the element is reporting. */
.hd-actions .btn:is(:hover,:focus-visible){animation:none}

.read-bar{position:absolute;left:0;right:0;bottom:-1px;height:3px;
  background:var(--green-surface);transform:scaleX(var(--read,0));transform-origin:0 50%}

nav.main{display:flex;gap:2px;flex-wrap:nowrap}
nav.main a{font-weight:600;font-size:.84rem;color:var(--ink);text-decoration:none;
  padding:10px;border-radius:var(--r-sm);white-space:nowrap;
  transition:background 180ms var(--ease-snap),color 180ms var(--ease-snap)}
nav.main a:hover{background:var(--bg-alt);color:var(--green-deep)}
nav.main a[aria-current="page"]{background:var(--green);color:var(--white)}
/* 900-1079px was a broken band: below 900 the burger takes over, above 1080 the
   row has room, but in between the seven links at full padding need 855px of a
   821px row and ran off the right edge of the viewport. Same links, tighter
   gutters and a slightly smaller mark — measured, not guessed. */
@media(min-width:900px) and (max-width:1079px){
  nav.main a{padding-inline:6px;font-size:.8rem}
  .logo img{height:50px}
}
.hd .cta{display:none}
@media(min-width:1040px){.hd .cta{display:inline-flex}}

.menu-btn{display:inline-flex;align-items:center;gap:7px;font-family:inherit;font-weight:700;
  font-size:.9rem;background:none;border:1px solid var(--line);border-radius:var(--r-sm);
  padding:11px 15px;min-height:44px;cursor:pointer;color:var(--ink)}
@media(min-width:900px){.menu-btn{display:none}}
@media(max-width:899px){
  nav.main{display:none;width:100%;flex-direction:column;gap:0;padding-bottom:10px}
  nav.main.open{display:flex}
  nav.main a{padding:13px 6px;border-bottom:1px solid var(--line-soft)}
  .hd{flex-wrap:wrap;padding-block:6px}
}

/* Call and Text are different numbers — never merge them. */
.callbar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:grid;
  grid-template-columns:1fr 1fr;gap:8px;background:var(--bg-dark);
  border-top:2px solid var(--green-surface);
  padding:8px 8px calc(8px + env(safe-area-inset-bottom))}
.callbar a{display:flex;align-items:center;justify-content:center;gap:8px;min-height:50px;
  font-weight:800;font-size:1rem;text-decoration:none;border-radius:var(--r-sm);line-height:1}
.callbar a.cb-call{background:var(--green);color:var(--white)}
.callbar a.cb-text{background:var(--white);color:var(--green-ink)}
.callbar a:active{filter:brightness(.94)}
@media(max-width:899px){body{padding-bottom:calc(76px + env(safe-area-inset-bottom))}}
@media(min-width:900px){.callbar{display:none}}


/* 10 — BUTTONS ==============================================================
   In v1 this was the only element on the page with no transition, and the
   hover LIGHTENED it: contrast fell from 4.57:1 to 3.19:1. It now darkens.
   ========================================================================== */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-family:inherit;font-weight:700;font-size:1rem;line-height:1.1;
  padding:14px 24px;min-height:48px;border-radius:var(--r-sm);
  text-decoration:none;cursor:pointer;position:relative;overflow:hidden;
  background:var(--green);color:var(--white);border:2px solid var(--green);
  transition:background 180ms var(--ease-snap),border-color 180ms var(--ease-snap),
             color 180ms var(--ease-snap),transform 180ms var(--ease-snap),
             box-shadow 180ms var(--ease-snap)}
.btn::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.22) 50%,transparent 62%);
  transform:translateX(-120%)}
@media(hover:hover){
  .btn:hover{background:var(--green-ink);border-color:var(--green-ink);color:var(--white);
    transform:translateY(-2px);box-shadow:0 10px 22px -12px rgba(0,117,58,.85)}
  .btn:hover::after{transform:translateX(120%);transition:transform 600ms var(--ease-swap)}
}
.btn:active{transform:translateY(1px) scale(.988);transition-duration:60ms}
/* [hidden] in the reset has the same specificity as .btn and loses on
   source order, which showed Back + Continue + Submit at the same time in
   both three-step forms. One rule, in the section that owns .btn. */
.btn[hidden]{display:none}

.btn-ghost{background:transparent;color:var(--green-ink);border-color:var(--green-ink)}
.btn-ghost:hover{background:var(--green-ink);color:var(--white);border-color:var(--green-ink)}
.btn-dark{background:var(--ink);border-color:var(--ink);color:var(--white)}
.btn-dark:hover{background:var(--navy-deep);border-color:var(--navy-deep)}
.btn-full{width:100%}
.btn-row{display:flex;gap:12px;flex-wrap:wrap;margin:1.5em 0 0}
.btn-row:last-child{margin-bottom:0}

:is(section.dark,.hero.photo-hero,.plan-band) .btn-ghost{color:var(--white);
  border-color:rgba(255,255,255,.55);background:transparent}
:is(section.dark,.hero.photo-hero,.plan-band) .btn-ghost:hover{background:var(--white);
  color:var(--ink);border-color:var(--white)}
:is(section.dark,.hero.photo-hero) :is(.card,.formcard,.panel) .btn-ghost{
  color:var(--green-ink);border-color:var(--green-ink);background:transparent}


/* 11 — HERO ================================================================= */
.hero{position:relative;overflow:hidden;background:var(--bg-dark);
  min-height:clamp(600px,78vh,760px);display:flex;align-items:center;
  padding:clamp(34px,5vw,60px) 0}
.hero > img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:60% 58%;z-index:0}
/* The veil over the photograph is BLACK, not navy — Paulo, 18/09/2026. It only
   darkens; it must not tint. The navy gradient under the copy is a different
   element (.hero .col-copy below) and stays navy on purpose: that one is the
   brand surface the text sits on, this one is just exposure. */
.hero::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,.42) 0%,rgba(0,0,0,.06) 26%,rgba(0,0,0,.58) 100%)}
.hero > .wrap{position:relative;z-index:2;width:100%}
.hero :is(h1,.ticks li){color:var(--white)}
.hero .lede{color:#E3E8F2}
.hero .fine{color:#C6CFE0}

/* registration marks — two opposite corners, never a frame */
.hero-marks{position:absolute;inset:24px;z-index:1;pointer-events:none}
.hero-marks::before,.hero-marks::after{content:"";position:absolute;width:26px;height:26px;
  border:2px solid rgba(255,255,255,.42)}
.hero-marks::before{left:0;top:0;border-right:0;border-bottom:0}
.hero-marks::after{right:0;bottom:0;border-left:0;border-top:0}

.hero-grid{display:grid;grid-template-columns:1fr;gap:clamp(24px,3.4vw,44px);align-items:center}
@media(min-width:1081px){
  .hero-grid{grid-template-columns:minmax(520px,1fr) minmax(360px,404px)}
}

/* the text plate — the copy gets its own surface instead of darkening the
   whole photograph */
.hero .col-copy{position:relative;margin-left:calc(var(--pad) * -1);
  padding:clamp(30px,3vw,46px) clamp(38px,4.4vw,72px) clamp(30px,3vw,46px) var(--pad);
  background:linear-gradient(104deg,rgba(25,40,80,.86) 0%,rgba(25,40,80,.70) 62%,rgba(25,40,80,0) 100%);
  backdrop-filter:blur(2px) saturate(1.06);border-left:3px solid var(--green-surface)}
@media(max-width:1080px){.hero .col-copy{margin-left:0;padding-inline:clamp(18px,4vw,28px);
  border-radius:var(--r-md)}}
.hero .col-copy .datum{color:var(--white)}

.ticks{list-style:none;padding:0;margin:1.2em 0 0;max-width:56ch}
.ticks li{position:relative;padding-left:32px;margin-bottom:.5em;font-weight:600;
  font-size:.96rem;line-height:1.5}
.ticks li::before{content:"";position:absolute;left:0;top:.28em;width:18px;height:18px;
  background:var(--green-surface);
  clip-path:polygon(0 0,14px 0,18px 4px,18px 18px,0 18px)}
.ticks li::after{content:"";position:absolute;left:4.5px;top:.62em;width:9px;height:5px;
  border-left:2px solid var(--white);border-bottom:2px solid var(--white);
  transform:rotate(-45deg)}
@media(prefers-reduced-motion:no-preference){
  .js .ticks[data-anim] li::before{animation:tick-set 260ms var(--ease-stone) both;
    animation-delay:calc(var(--i,0) * 70ms + 120ms)}
}
@keyframes tick-set{from{transform:scale(.62)}to{transform:none}}
.ticks.two-col{columns:2;column-gap:38px;max-width:none}
.ticks.two-col li{break-inside:avoid}
@media(max-width:760px){.ticks.two-col{columns:1}}

@media(max-width:899px){
  .hero{min-height:0}
  .hero .lede{font-size:1rem;line-height:1.5}
  .hero .ticks{margin-top:1em}
}


/* 12 — SPLIT SYSTEM =========================================================
   THE PARITY RULE. Paulo's item 1.

   Every two-column block stretches. The media column FILLS the height of the
   text column: figure{height:100%} + img{width:100%;height:100%;object-fit:cover}.
   Sanity guards keep both extremes honest:
     --fill-min  the row can never be shorter than this, so a very short text
                 cannot squeeze the photo into a thin strip
     --fill-max  the media column stops growing here, so a very long text
                 cannot turn the photo into a 1200px poster
   When the text runs past ~720px, the author switches to data-media="sticky"
   and the photo rides along with the reading instead of being cropped.

   NO rule in this file, or in any section file, may set align-items:center on
   a .split. That single line is what produced the empty band Paulo circled.
   ========================================================================== */
.sec-split .split{--split:1fr 1fr;display:grid;grid-template-columns:var(--split);
  gap:var(--gap-split);align-items:stretch}
.sec-split .split > *{min-width:0}

/* proportion modifiers — the lever v1 never had */
.split.text-lead{--split:1.14fr .86fr}
.split.media-lead{--split:.86fr 1.14fr}

.split .col-text{display:flex;flex-direction:column;justify-content:center;min-width:0}
.split .col-text.top{justify-content:flex-start}
.split .col-text > :first-child{margin-top:0}
.split .col-text > :last-child{margin-bottom:0}
.split .col-text p{max-width:62ch}

.split .col-media{display:flex;flex-direction:column;gap:14px;min-width:0;
  min-height:var(--fill-min);max-height:var(--fill-max)}
.split .col-media > *{width:100%;min-width:0}
.split .col-media > :only-child{flex:1 1 auto;min-height:0}
.split .col-media > figure,
.split .col-media > .sec-photo{margin:0;height:100%;min-height:0;overflow:hidden;
  border-radius:var(--r-md);background:var(--bg-alt);position:relative}
.split .col-media img{width:100%;height:100%;object-fit:cover}
.split .col-media figcaption{position:absolute;left:0;right:0;bottom:0;padding:10px 14px;
  background:rgba(25,40,80,.86);color:var(--white);font-weight:700;font-size:.68rem;
  letter-spacing:.14em;text-transform:uppercase}
@media(min-width:901px){.split.media-left .col-media{order:-1}}

/* sticky mode — only when the column genuinely fits the window. Without this
   guard a sticky column has cut a CTA in half on a short laptop screen. */
@media(min-width:980px) and (min-height:760px){
  .sec-split .split[data-media="sticky"] .col-media{
    position:sticky;top:calc(var(--header-h) + 24px);
    align-self:start;height:min(72vh,620px);min-height:0;max-height:none}
}

/* 900-979px — the dead band. The stack below starts at 899px but sticky only
   starts at 980px, so a data-media="sticky" row was left as two columns with
   NOTHING pinned: the FAQ panel is short by design and sat at the top of a
   1200px column, which is the exact pocket of air this redesign removes, and
   the two photo columns became 1:3 slivers. Collapse those rows on the same
   breakpoint sticky uses, the way #cost already did for itself. */
@media(min-width:900px) and (max-width:979px){
  .sec-split .split[data-media="sticky"]{--split:1fr}
  .sec-split .split[data-media="sticky"] .col-media{
    position:static;min-height:0;max-height:none;height:auto}
  .sec-split .split[data-media="sticky"] .col-media > figure,
  .sec-split .split[data-media="sticky"] .col-media > .sec-photo{height:auto}
  .sec-split .split[data-media="sticky"] .col-media img{height:auto;aspect-ratio:16/10}
  .sec-split .split[data-media="sticky"] .col-text{justify-content:flex-start}
}

/* under 900px everything stacks and sticky is off */
@media(max-width:899px){
  .sec-split .split{--split:1fr}
  .sec-split .split .col-media{position:static;min-height:0;max-height:none;height:auto}
  .sec-split .split .col-media > figure,
  .sec-split .split .col-media > .sec-photo{height:auto}
  .sec-split .split .col-media img{height:auto;aspect-ratio:16/10}
  .split .col-text{justify-content:flex-start}
}

/* Full-bleed split (#about). The photo is edge to edge and the text column
   gets its own padding. Still stretch, never center. */
section.bleed{--sec-pad:0px}
section.bleed > .wrap{max-width:none;padding-inline:0}
section.bleed .split{gap:0}
section.bleed .col-media{min-height:clamp(420px,42vw,560px);max-height:none}
section.bleed .col-media > figure,
section.bleed .col-media > .sec-photo{border-radius:0}
section.bleed .col-text{padding:clamp(40px,5vw,72px) clamp(24px,5vw,80px);
  max-width:760px;justify-self:center}
@media(max-width:899px){section.bleed .col-media{min-height:0}}

/* Standalone photo frame used outside a split. */
.sec-photo{--ar:4/3;display:block;overflow:hidden;border-radius:var(--r-md);
  background:var(--bg-alt);position:relative}
.sec-photo > img{aspect-ratio:var(--ar);width:100%;object-fit:cover}
.sec-photo.portrait{--ar:4/5}
.sec-photo.cinema{--ar:16/10}


/* 13 — CARDS, GRIDS, GALLERIES ==============================================*/
.grid{display:grid;gap:22px;align-items:stretch}
.grid > *{height:100%}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g4{grid-template-columns:repeat(4,minmax(0,1fr))}
@media(max-width:900px){.g3,.g4{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:600px){.g2,.g3,.g4{grid-template-columns:minmax(0,1fr)}}

.card{background:var(--bg);border:1px solid var(--line-soft);border-radius:var(--r-md);
  padding:22px 24px;box-shadow:var(--e1)}
.card > :first-child{margin-top:0}
.card > :last-child{margin-bottom:0}
.card p{font-size:.96rem;line-height:1.55;max-width:none}
.card.signal{border-left:4px solid var(--green-surface)}

.mcard{background:var(--bg);border:1px solid var(--line-soft);border-radius:var(--r-md);
  overflow:hidden;display:flex;flex-direction:column;height:100%;box-shadow:var(--e1);
  transition:transform 280ms var(--ease-stone),box-shadow 280ms,border-color 280ms}
.mcard > img{width:100%;aspect-ratio:16/10;object-fit:cover;background:var(--bg-alt);
  transition:transform 500ms var(--ease-stone)}
.mcard .body{padding:20px 22px 22px;flex:1;display:flex;flex-direction:column}
.mcard h3{margin:0 0 .35em}
.mcard p{margin:0;color:var(--ink-soft);font-size:.96rem;line-height:1.55;max-width:none}
@media(hover:hover){
  .mcard:hover{transform:translateY(-4px);box-shadow:var(--e2);border-color:var(--green-surface)}
  .mcard:hover > img{transform:scale(1.05)}
}

/* Material tiles — photo bleeds, name sits on a mitred plaquette. No white
   body, no frame. */
.mat-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;margin-top:26px}
@media(max-width:1000px){.mat-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.mat-grid{grid-template-columns:minmax(0,1fr)}}
.mat{position:relative;display:block;overflow:hidden;border-radius:var(--r-md);
  background:var(--bg-dark);text-decoration:none;aspect-ratio:4/5;
  transition:transform 280ms var(--ease-stone),box-shadow 280ms}
.mat > img{width:100%;height:100%;object-fit:cover}
.mat .mat-name{position:absolute;left:0;bottom:0;z-index:2;padding:9px 16px 9px 14px;
  background:rgba(255,255,255,.94);color:var(--ink);font-weight:800;font-size:.86rem;
  letter-spacing:.06em;
  clip-path:polygon(0 0,calc(100% - var(--miter)) 0,100% var(--miter),100% 100%,0 100%)}
.mat.no-photo{display:flex;align-items:center;justify-content:center;
  background-image:var(--vein);background-repeat:repeat}
.mat.no-photo .ic{--s:72px;--sw:1.25;color:rgba(255,255,255,.62)}
@media(hover:hover){.mat:hover{transform:translateY(-4px);box-shadow:var(--e2)}}

/* Secondary service links. */
.svc-index{list-style:none;padding:0;margin:26px 0 0;display:grid;max-width:none;
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}
.svc-index li{margin:0}
.svc-index a{display:flex;align-items:center;gap:10px;min-height:48px;padding:10px 14px;
  border:1px solid var(--line);border-radius:var(--r-sm);font-weight:600;font-size:.9rem;
  text-decoration:none;color:var(--ink);background:var(--bg);
  transition:border-color 180ms var(--ease-snap),transform 180ms var(--ease-snap)}
.svc-index a:hover{border-color:var(--green-ink);transform:translateY(-1px);color:var(--ink)}
@media(max-width:520px){.svc-index{grid-template-columns:1fr}}

/* Frameless work gallery. Mobile becomes a horizontal scroller. */
.gal-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;
  margin-top:clamp(22px,3vw,34px)}
.gal-item{margin:0;overflow:hidden;border-radius:var(--r-sm);background:var(--bg-alt);
  aspect-ratio:4/3}
.gal-item img{width:100%;height:100%;object-fit:cover;
  transition:transform 600ms var(--ease-stone)}
@media(hover:hover){.gal-item:hover img{transform:scale(1.05)}}
@media(max-width:899px){
  .gal-grid{display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;
    scrollbar-width:none;padding-bottom:6px;
    margin-inline:calc(var(--pad) * -1);padding-inline:var(--pad)}
  .gal-grid::-webkit-scrollbar{display:none}
  .gal-item{flex:0 0 78%;scroll-snap-align:start}
}

/* Sticky side panel (FAQ, cost). */
.panel{background:var(--bg);border:1px solid var(--line-soft);border-radius:var(--r-md);
  padding:24px;box-shadow:var(--e2)}
.panel > :first-child{margin-top:0}
.panel > :last-child{margin-bottom:0}


/* 14 — ICONS AND BADGES =====================================================
   Stone Line contract: stroke weight is set in CSS by size so optical density
   stays constant; butt caps because a cut line is a saw cut; the single green
   path is always "the part that is ours".
   ========================================================================== */
.ic{width:var(--s,24px);height:var(--s,24px);stroke-width:var(--sw,1.75);
  stroke:currentColor;fill:none;stroke-linecap:butt;stroke-linejoin:miter;
  stroke-miterlimit:4;flex:0 0 auto}
.ic .f{fill:var(--green-surface);stroke:none}
.facts .ic{--s:20px;--sw:1.9}
.tl-step .ic{--s:40px;--sw:1.5}
.svc-index .ic{--s:24px;--sw:1.75}
.callbar .ic{--s:19px;--sw:2}
.badge .ic{--s:20px;--sw:1.9}

/* The one badge shape of the whole brand: mitred top-right corner. Valid on a
   seal, a chip, a plaquette, the form card and a diagram plate. NOT on cards. */
.badge{display:inline-flex;align-items:center;gap:9px;min-height:32px;
  padding:9px 14px 9px 12px;font-weight:800;font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;line-height:1;text-decoration:none;
  background:var(--green-wash);color:var(--green-deep);
  box-shadow:inset 0 0 0 1.5px var(--green-surface);
  clip-path:polygon(0 0,calc(100% - var(--miter)) 0,100% var(--miter),100% 100%,0 100%)}
.badge.on-dark{background:transparent;color:var(--white);
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.4)}
.badge.solid{background:var(--green-ink);color:var(--white);box-shadow:none}
.badge.navy{background:rgba(25,40,80,.6);color:var(--white);box-shadow:none}
@media(max-width:600px){.badge{min-height:28px;padding:7px 12px 7px 10px}}


/* 15 — FACT STRIP AND MOBILE JUMP BAR =======================================
   The marquee is dead at every width: WCAG 2.2.2 is level A and hover/focus is
   not a pause mechanism on a touch screen. Four static mitred seals instead.
   ========================================================================== */
.factsband{background:var(--bg-alt);border-block:1px solid var(--line);
  padding:18px 0;position:relative}
.factsband::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--mesh);background-size:72px 72px;opacity:.5}
.facts{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;
  list-style:none;margin:0;padding:0;max-width:none}
.facts li{margin:0;display:flex;align-items:center;gap:10px;font-weight:700;font-size:.78rem;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft);line-height:1.25;
  font-variant-numeric:tabular-nums}
.facts li .ic{color:var(--ink)}
@media(max-width:899px){.facts{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 12px}}
@media(max-width:420px){.facts li{font-size:.72rem}}

.jumpbar{display:none}
@media(max-width:899px){
  .jumpbar{display:flex;gap:9px;overflow-x:auto;scrollbar-width:none;list-style:none;
    margin:16px calc(var(--pad) * -1) 0;padding:0 var(--pad);max-width:none}
  .jumpbar::-webkit-scrollbar{display:none}
  .jumpbar li{margin:0;flex:0 0 auto}
  .jumpbar a{display:inline-flex;align-items:center;min-height:44px;padding:10px 16px;
    border:1px solid var(--line);border-radius:999px;background:var(--bg);
    font-weight:700;font-size:.82rem;color:var(--ink);text-decoration:none;white-space:nowrap}
}


/* 16 — PROCESS RAIL =========================================================*/
.tl{position:relative;margin:34px 0 0;counter-reset:tl}
.tl-track{position:absolute;left:26px;top:14px;bottom:14px;width:3px;background:var(--line);
  border-radius:2px}
.tl-fill{position:absolute;left:0;top:0;width:100%;height:100%;background:var(--green-surface);
  border-radius:2px;transform:scaleY(var(--tlp,0));transform-origin:50% 0}
.tl-step{position:relative;padding-left:74px;margin-bottom:28px;counter-increment:tl}
.tl-step:last-child{margin-bottom:0}
.tl-dot{position:absolute;left:0;top:0;width:54px;height:54px;border-radius:50%;
  background:var(--bg);border:3px solid var(--line);display:flex;align-items:center;
  justify-content:center;font-weight:800;font-size:1.25rem;color:var(--ink-faint);
  transition:background 350ms,border-color 350ms,color 350ms,transform 420ms var(--ease-snap)}
.tl-step.on .tl-dot{background:var(--green-surface);border-color:var(--green-surface);
  color:var(--white);transform:scale(1.06)}
.tl-step h3{margin:.3em 0 .3em}
.tl-step p{margin:0;color:var(--ink-soft);font-size:.96rem;line-height:1.55;max-width:none}
.tl-step .ic{margin-bottom:12px;color:var(--ink)}

@media(min-width:1100px){
  .tl{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:18px;margin-top:64px;
    align-items:stretch}
  /* the horizontal rail is visible again, driven by scaleX, never by width */
  .tl-track{left:10%;right:10%;top:0;bottom:auto;width:auto;height:2px}
  .tl-fill{height:2px;transform:scaleX(var(--tlp,0));transform-origin:0 50%}
  .tl-step{margin:0;text-align:left;height:100%;padding:44px 20px 24px;background:var(--bg);
    border:1px solid var(--line-soft);border-radius:var(--r-md);box-shadow:var(--e1);
    display:flex;flex-direction:column}
  .tl-step h3{font-size:1.06rem;line-height:1.25}
  .tl-step p{flex:1 1 auto}
  .tl-dot{left:50%;top:0;width:26px;height:26px;font-size:.8rem;border-width:2px;
    transform:translate(-50%,-50%)}
  .tl-step.on .tl-dot{transform:translate(-50%,-50%) scale(1.06)}
}
section.dark .tl-step{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.14)}
section.dark .tl-dot{background:var(--navy-deep);border-color:rgba(255,255,255,.26);
  color:rgba(255,255,255,.72)}


/* 17 — BEFORE / AFTER COMPARATOR ============================================
   @property is required: without it --pos does not interpolate and the invite
   animation cannot run.
   ========================================================================== */
@property --pos{syntax:"<percentage>";inherits:true;initial-value:50%}

.ba-card{background:var(--bg);border:1px solid var(--line-soft);border-radius:var(--r-md);
  overflow:hidden;box-shadow:var(--e1);text-align:left}
.ba-compare{position:relative;aspect-ratio:16/9;background:var(--bg-alt);touch-action:pan-y;
  isolation:isolate;--pos:50%}
.ba-compare img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;margin:0}
.ba-before{clip-path:inset(0 calc(100% - var(--pos)) 0 0);z-index:2}
.ba-after{z-index:1}
.ba-tag{position:absolute;bottom:12px;z-index:4;pointer-events:none}
.ba-tag-b{left:12px}
.ba-tag-a{right:12px}
.ba-line{position:absolute;top:0;bottom:0;z-index:5;left:var(--pos);width:3px;
  margin-left:-1.5px;background:var(--white);box-shadow:0 0 0 1px rgba(25,40,80,.25);
  pointer-events:none}
.ba-knob{position:absolute;z-index:6;top:50%;left:var(--pos);width:44px;height:44px;
  margin:-22px 0 0 -22px;border-radius:50%;background:var(--white);
  box-shadow:0 6px 18px -6px rgba(25,40,80,.55);display:flex;align-items:center;
  justify-content:center;color:var(--ink);pointer-events:none}
.ba-range{position:absolute;inset:0;z-index:7;width:100%;height:100%;margin:0;opacity:0;
  cursor:ew-resize;-webkit-appearance:none;appearance:none;background:transparent}
.ba-range::-webkit-slider-thumb{-webkit-appearance:none;width:44px;height:100%}
.ba-range::-moz-range-thumb{width:44px;height:100%;border:0;background:transparent}
.ba-range:focus-visible + .ba-line{box-shadow:0 0 0 3px var(--green-surface)}
.ba-body{padding:16px 18px 18px}
.ba-body h3{margin:0 0 5px}
.ba-body p{margin:0;font-size:.9rem;line-height:1.55;color:var(--ink-soft);max-width:none}
@media(prefers-reduced-motion:no-preference){
  .js .ba-compare.invite{animation:ba-invite 1800ms var(--ease-swap) 380ms 1}
}
@keyframes ba-invite{0%{--pos:50%}30%{--pos:66%}62%{--pos:38%}100%{--pos:50%}}
section.dark .ba-card{background:#1F3157;border-color:#2A3C68}


/* 18 — TABLES ===============================================================*/
.tablewrap{overflow-x:auto;border:1px solid var(--line-soft);border-radius:var(--r-md);
  background:var(--bg);box-shadow:var(--e1);margin:1.4em 0 0}
table{width:100%;border-collapse:collapse;font-size:.94rem}
th{font-weight:800;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;
  text-align:left;background:var(--bg-alt);color:var(--ink-faint);padding:13px 16px;
  border-bottom:1px solid var(--line);white-space:nowrap}
td{padding:13px 16px;border-bottom:1px solid var(--line-soft);vertical-align:top;
  text-align:left;color:var(--ink);font-variant-numeric:tabular-nums}
tr:last-child td{border-bottom:0}
caption{text-align:left;padding:13px 16px;font-weight:600;color:var(--ink-soft)}


/* 19 — FAQ =================================================================*/
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{display:flex;align-items:flex-start;gap:16px;font-weight:700;font-size:1.02rem;
  line-height:1.35;padding:20px 0;cursor:pointer;list-style:none}
.faq summary::-webkit-details-marker{display:none}
.faq .faq-mark{margin-left:auto;flex:0 0 auto;color:var(--green-ink);
  transition:transform 280ms var(--ease-swap)}
.faq details[open] .faq-mark{transform:rotate(90deg)}
.faq .faq-mark .bar-v{transform-origin:12px 12px;transition:transform 220ms var(--ease-swap)}
.faq details[open] .faq-mark .bar-v{transform:scaleY(0)}
.faq details > :not(summary){padding:0 44px 20px 0;margin:0;max-width:72ch}
.faq details > p + p{padding-top:.8em}
@supports(interpolate-size:allow-keywords){
  html{interpolate-size:allow-keywords}
  .faq details::details-content{block-size:0;overflow:hidden;
    transition:block-size 280ms var(--ease-swap),content-visibility 280ms allow-discrete}
  .faq details[open]::details-content{block-size:auto}
}


/* 20 — LEAD FORM ============================================================
   Skin only. The three-step mechanics, the three consent checkboxes and their
   full texts, the age gate and the Privacy / Terms links do not change.
   ========================================================================== */
.formcard{background:var(--bg);border:1px solid var(--line-soft);border-top:4px solid var(--green-surface);
  border-radius:var(--r-md);padding:24px;box-shadow:var(--e3);scroll-margin-top:110px;
  contain:layout}
.formcard > :first-child{margin-top:0}
.formcard h2{margin:0 0 .3em;font-size:1.34rem;line-height:1.2}
.formcard h3{margin:0 0 .4em}
.formcard .sub{margin:0 0 16px;color:var(--ink-soft);font-size:.94rem}

/* one continuous rail instead of three detached bars — the <ul><li> markup and
   the .on class the JS toggles stay exactly as they are */
.fsteps{display:flex;gap:0;list-style:none;margin:0 0 16px;padding:0;height:4px;
  border-radius:2px;overflow:hidden;background:var(--line);max-width:none}
.fsteps li{flex:1;margin:0;background:transparent;
  transition:background 420ms var(--ease-stone)}
.fsteps li.on{background:var(--green-surface)}
.fstepcount{font-weight:800;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-faint);margin:0 0 8px}

.field{margin-bottom:14px}
.field label.flabel{display:block;font-weight:600;font-size:.9rem;margin-bottom:5px;
  color:var(--ink)}
.field label.flabel .req{color:#A3261B;font-weight:700}
.field :is(input,select,textarea){width:100%;padding:13px 14px;font-size:1rem;
  font-family:inherit;color:var(--ink);background:var(--bg);border:2px solid var(--line-strong);
  border-radius:var(--r-sm);transition:border-color 180ms var(--ease-snap)}
.field :is(input,select,textarea):focus{border-color:var(--green-ink)}
.field.bad :is(input,select,textarea){border-color:#A3261B}
.two{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:600px){.two{grid-template-columns:1fr}}

.consent-block{margin:18px 0 8px;padding:16px 0 0;border:0;border-top:1px solid var(--line)}
.consent-block > legend.ct{float:left;width:100%;padding:0;margin:0 0 10px;font-size:.68rem;
  font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint)}
.consent-block > legend.ct + *{clear:both}
label.consent{display:flex;gap:11px;align-items:flex-start;margin-bottom:10px;cursor:pointer;
  min-height:44px;padding:6px 0}
label.consent input[type=checkbox]{flex:0 0 17px;width:17px;height:17px;margin:.3em 0 0;
  accent-color:var(--green-ink);cursor:pointer}
label.consent span{font-size:.84rem;line-height:1.48;color:var(--ink-soft)}
.agegate{font-size:.84rem;line-height:1.48;color:var(--ink-soft);margin:2px 0 0;font-weight:600}

.fnav{display:flex;gap:10px;margin-top:16px}
.fnav .btn{flex:1}
.fnav .btn.back{flex:0 0 auto;min-width:104px}
.formmsg{display:none;margin-top:14px;padding:12px 14px;border-radius:var(--r-sm);font-size:.92rem}
.formmsg.err{display:block;background:#FCECEA;border:1px solid #E3B3AB;color:#8A2A1C}
.formmsg.ok{display:block;background:#EEF7E7;border:1px solid var(--green);color:var(--green-deep)}

.js form.leadform[data-steps] .fstep{display:none}
.js form.leadform[data-steps] .fstep.on{display:block}
@media(prefers-reduced-motion:no-preference){
  .js form.leadform[data-steps] .fstep.on{animation:fstep-in 260ms var(--ease-swap) 120ms both}
  .js .field.shake :is(input,select,textarea){animation:field-shake 260ms var(--ease-snap) 1}
}
@keyframes fstep-in{from{opacity:0;transform:translateX(calc(var(--dir,1) * 26px))}
  to{opacity:1;transform:none}}
@keyframes field-shake{0%,100%{transform:none}25%{transform:translateX(-5px)}
  60%{transform:translateX(4px)}}

@media(max-width:899px){
  .formcard .fnav{position:sticky;bottom:calc(84px + env(safe-area-inset-bottom));z-index:2;
    background:var(--bg);padding-top:10px;margin-inline:-24px;padding-inline:24px;
    box-shadow:0 -10px 18px -14px rgba(25,40,80,.5)}
}


/* 21 — SERVICE AREA MAP =====================================================
   Vector only, no third-party iframe. The section author supplies the <svg>
   geometry and the pin coordinates; this file owns every state.
   Pins are <button> or <a> so keyboard users get the same popup as hover.
   ========================================================================== */
.map-wrap{position:relative;margin-top:clamp(22px,3vw,34px)}
.map-svg{width:100%;height:auto;overflow:visible}
.map-plate{fill:var(--bg-alt);stroke:var(--line);stroke-width:1}
section.dark .map-plate{fill:rgba(255,255,255,.04);stroke:rgba(255,255,255,.16)}
.map-grid{stroke:currentColor;stroke-width:1;stroke-opacity:.09;fill:none}

/* the served territory */
.map-area{fill:rgba(0,166,82,.10);stroke:var(--green-surface);stroke-width:2;
  stroke-linejoin:miter;stroke-dasharray:1200;stroke-dashoffset:0}
@media(prefers-reduced-motion:no-preference){
  .js .map-wrap[data-anim] .map-area{stroke-dashoffset:1200}
  .js .map-wrap[data-anim].is-in .map-area{stroke-dashoffset:0;
    transition:stroke-dashoffset 1100ms var(--ease-swap)}
}
.map-road{stroke:currentColor;stroke-opacity:.22;stroke-width:1.5;fill:none}
.map-label{font-family:inherit;font-weight:800;font-size:9px;letter-spacing:.14em;
  text-transform:uppercase;fill:var(--ink-faint)}
section.dark .map-label{fill:rgba(255,255,255,.66)}

/* pins */
.map-pin{cursor:pointer;transform-box:fill-box;transform-origin:50% 100%;
  transition:transform 220ms var(--ease-snap)}
.map-pin .pin-body{fill:var(--ink);stroke:none}
.map-pin .pin-dot{fill:var(--white)}
.map-pin.is-home .pin-body{fill:var(--green-surface)}
.map-pin:is(:hover,:focus-visible,.is-active){transform:scale(1.18)}
.map-pin:is(:hover,:focus-visible,.is-active) .pin-body{fill:var(--green-ink)}
.map-ring{fill:none;stroke:var(--green-surface);stroke-width:2;transform-box:fill-box;
  transform-origin:50% 50%;opacity:0}
@media(prefers-reduced-motion:no-preference){
  .js .map-wrap.is-in .map-ring{animation:map-pulse 2600ms var(--ease-stone) infinite;
    animation-delay:calc(var(--i,0) * 420ms)}
  .js .map-wrap.is-in .map-pin{animation:pin-drop 520ms var(--ease-snap) both;
    animation-delay:calc(var(--i,0) * 90ms)}
}
@keyframes map-pulse{0%{opacity:.55;transform:scale(.4)}70%{opacity:0;transform:scale(1.9)}
  100%{opacity:0;transform:scale(1.9)}}
@keyframes pin-drop{from{transform:translateY(-14px) scale(.7)}to{transform:none}}

/* popup — plain HTML on top of the SVG, positioned by --x / --y in percent */
.map-pop{position:absolute;left:var(--x,50%);top:var(--y,50%);z-index:3;
  transform:translate(-50%,calc(-100% - 16px)) scale(.96);transform-origin:50% 100%;
  min-width:212px;max-width:260px;padding:13px 15px;background:var(--bg);
  border:1px solid var(--line-soft);border-radius:var(--r-md);box-shadow:var(--e2);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity 180ms var(--ease-snap),transform 180ms var(--ease-snap),
             visibility 0s linear 180ms}
.map-pop[data-open]{opacity:1;visibility:visible;transform:translate(-50%,calc(-100% - 16px)) scale(1);
  transition-delay:0s}
.map-pop::after{content:"";position:absolute;left:50%;bottom:-7px;width:12px;height:12px;
  margin-left:-6px;background:var(--bg);border-right:1px solid var(--line-soft);
  border-bottom:1px solid var(--line-soft);transform:rotate(45deg)}
.map-pop-name{display:block;font-weight:800;font-size:1rem;line-height:1.2;color:var(--ink)}
.map-pop-zip{display:block;margin-top:4px;font-weight:800;font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--green-deep);font-variant-numeric:tabular-nums}
.map-pop-meta{display:block;margin-top:7px;font-size:.84rem;line-height:1.45;color:var(--ink-soft)}
@media(max-width:899px){.map-pop{min-width:180px;max-width:220px;padding:11px 13px}}

.map-legend{display:flex;flex-wrap:wrap;gap:10px 22px;margin:16px 0 0;padding:0;list-style:none}
.map-legend li{margin:0;display:flex;align-items:center;gap:9px;font-weight:700;font-size:.72rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink-faint)}
.map-legend .key{width:14px;height:14px;flex:0 0 14px;border-radius:3px;
  background:var(--green-surface)}
.map-legend .key.outline{background:rgba(0,166,82,.10);
  box-shadow:inset 0 0 0 2px var(--green-surface)}
section.dark .map-legend li{color:rgba(255,255,255,.66)}


/* 22 — CITY CHIPS ===========================================================*/
.citychips{display:flex;flex-wrap:wrap;gap:10px;list-style:none;padding:0;
  margin:clamp(20px,2.6vw,28px) 0 0;max-width:none}
.citychips li{margin:0}
.citychips a,.citychips span{display:inline-flex;align-items:center;min-height:44px;
  padding:10px 16px;font-weight:700;font-size:.82rem;letter-spacing:.04em;white-space:nowrap;
  border:1px solid var(--line);border-radius:999px;color:var(--ink);text-decoration:none;
  background:var(--bg);transition:border-color 180ms var(--ease-snap),color 180ms var(--ease-snap)}
.citychips a:hover{border-color:var(--green-ink);color:var(--green-deep)}
section.dark .citychips :is(a,span){background:rgba(255,255,255,.06);
  border-color:rgba(255,255,255,.28);color:#E8EEF9}
section.dark .citychips a:hover{border-color:var(--white);color:var(--white)}


/* 23 — FOOTER ===============================================================*/
footer{background:var(--bg-dark);color:#C6CFE0;padding:48px 0 32px;font-size:.92rem;
  position:relative}
footer h4{color:var(--white);margin:0 0 12px}
footer a{color:var(--green-on-navy);text-decoration:none;display:inline-block;padding:9px 0;
  min-height:44px;line-height:26px}
footer a:hover{color:var(--white);text-decoration:underline}
footer p{color:#C6CFE0;max-width:68ch}
footer .fine{color:#B9C2D6;max-width:none}
.f-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1.2fr;gap:32px;margin-bottom:30px}
@media(max-width:900px){.f-grid{grid-template-columns:1fr 1fr}}
@media(max-width:600px){.f-grid{grid-template-columns:1fr}}
.f-grid ul{list-style:none;padding:0;margin:0;max-width:none}
.f-grid li{margin-bottom:.2em}
.f-name{font-weight:800;font-size:1.1rem;color:var(--white);margin-bottom:10px}
.f-bottom{border-top:1px solid rgba(255,255,255,.12);padding-top:16px;display:flex;
  justify-content:space-between;gap:10px 16px;flex-wrap:wrap;color:#94A2BF;font-size:.85rem}
.f-bottom nav a{margin-left:16px}
@media(max-width:600px){.f-bottom nav a{margin:0 14px 0 0}}


/* 24 — PRINT ================================================================*/
@media print{
  .callbar,.jumpbar,header:not(.sec-head),.map-wrap{display:none}
  section{padding:18px 0}
  section.dark{background:none;color:#000}
  a{color:#000}
}


/* 10 — HERO AND FACT STRIP ==================================================
   source: parts/10-hero.css
   ======================================================================== */

/* ===========================================================================
   10 — HERO AND FACT STRIP
   ---------------------------------------------------------------------------
   This file owns the .hs-* prefix and nothing else.

   The veil, the text plate, the registration marks, the tick glyph,
   .badge, .formcard, .facts and .jumpbar all live in
   assets/site.css and are used here unchanged — no property of theirs is
   redeclared, no id selector, no !important, and there is no compaction block
   at the end of this file.
   ========================================================================== */


/* --- 1. THE PHOTOGRAPH ------------------------------------------------------
   Art direction needs <picture>, which means the LCP <img> is no longer a
   direct child of .hero, so site.css's `.hero > img` can no longer match it.
   The wrapper takes that job over one level out. This is the same decision as
   the foundation's (cover, 60% / 58%), not a second opinion about it: the
   clean window of this photograph sits between 48% and 72% across, which is
   exactly the strip the text plate and the form card leave uncovered.
   The image itself never animates and never fades. */
.hs-shot{position:absolute;left:0;right:0;top:0;height:100%;z-index:0;overflow:hidden}
.hs-shot img{width:100%;height:100%;object-fit:cover;object-position:60% 58%}

@media(max-width:899px){
  /* The phone box used to be 390x1647: a 4:3 photograph stretched into a
     vertical sliver of about 17% of the frame. Three moves, one idea — give
     the photograph a bounded band with its own 3:4 crop (served by <picture>),
     pin that crop to its bottom edge so the band is the island and the run of
     stone rather than the ceiling, and let it dissolve into the navy the hero
     is already painted with. The text plate then starts BELOW the band, so on
     a phone the first thing on screen is stone and not a navy box. */
  .hs-shot{height:clamp(230px,30vh,290px)}
  .hs-shot img{object-position:50% 100%}
  .hs-shot::after{content:"";position:absolute;inset:0;
    background:linear-gradient(180deg,rgba(25,40,80,0) 62%,var(--bg-dark) 100%)}
  .hs-copy{margin-top:clamp(105px,19vh,185px)}
}


/* --- 2. THE TWO BUTTON ROUTES -----------------------------------------------
   Desktop: the form card is already the primary action, so the copy keeps two
   quiet routes. Phone: one green action above the list, and no Call button —
   the fixed call bar carries that number literally. */
.hs-cta-lead{display:none}
.hs-arrow{font-weight:700}

@media(max-width:899px){
  .hs-cta-lead{display:inline-flex;max-width:360px;margin:.4em 0 0}
  .hs-call{display:none}
  /* first screen target: H1 + lede + two ticks + the action.
     The two facts that come off here are carried by #cost and #financing. */
  .hs-tick-more{display:none}
}


/* --- 3. THE HERO LEAD FORM --------------------------------------------------
   Skin only. The card keeps data-steps="3", the three consent checkboxes and
   their full texts, the age gate and the Privacy / Terms links. */
@media(max-width:1080px){
  /* 901–1080px was the broken band: the single-column grid let the card render
     wider than the H1, so the page read as a form with a headline attached. */
  .hs-form{max-width:520px}
}

/* The chip that replaces the Bebas Neue one. A span, not a <p>, so the light
   card ink rule inside a photo hero cannot repaint white text as --ink-soft on
   the green plate. */
.badge.hs-chip{display:flex;width:max-content;max-width:100%;margin:0 0 14px}

/* On a solid --green-ink plate everything on it is already ours, so the green
   accent path inverts to white; left green it would read 1.4:1 against its own
   background. This is scoped to the chip and changes no other icon. */
.hs-chip .ic .f{fill:var(--white)}

/* site.css declares [hidden]{display:none} at the top of the reset and
   .btn{display:inline-flex} three hundred lines later, so a step button
   carrying `hidden` still renders and all three navigation buttons show at
   once. Put back here, scoped to this nav — .btn itself is not touched. */
/* the [hidden] repair this file carried is now in section 10 of the foundation */


/* 20 — MATERIALS, SERVICE INDEX, REMNANTS ===================================
   source: parts/20-materials.css
   ======================================================================== */

/* ============================================================================
   #materials — material tiles, service index and the remnants panel.

   Namespace: .mt-*
   Borrowed from the foundation, unchanged: .mat-grid .mat .mat-name .no-photo
   .svc-index .split .col-text .col-media .sec-head .sec-eyebrow .note .datum
   .datum-seam .btn .btn-row .ic

   Nothing here redeclares a foundation property. The one opt-in modifier is
   .mat-grid.mt-rail, which the markup asks for by name: under 560px the four
   tiles become a native snap rail instead of 2,400px of stacked cards. The old
   carousel died with its clones, its dots and its typographic arrows.
   ========================================================================== */


/* --- the tile card ---------------------------------------------------------
   No white body and no frame: the photo bleeds, the name sits on the mitred
   plaquette, and the copy lives directly on the paper. The card is a column so
   the four "Explore ..." lines land on the same baseline whatever the copy and
   the chips do above them.
   -------------------------------------------------------------------------- */
.mt-card{position:relative;display:flex;flex-direction:column;height:100%;
  transition:transform 280ms var(--ease-stone)}
@media(hover:hover){.mt-card:hover{transform:translate3d(0,-4px,0)}}

/* the material vector rides inside the mitred name plaquette */
.mt-card .mat-name{display:inline-flex;align-items:center;gap:8px}
.mt-card .mat-name .ic{--s:18px;--sw:1.9}

/* landscape and square originals crop low so the countertop, not the ceiling,
   owns the frame */
.mt-shot-low{object-position:50% 62%}

/* ONE link per card. The H3 carries the anchor text — that is the internal
   link the page is here for — and its ::after makes the whole tile clickable.
   No nested interactive elements and no duplicate target for a screen reader. */
.mt-title{margin:16px 0 0}
.mt-title a{color:inherit;text-decoration:none}
.mt-title a::after{content:"";position:absolute;inset:0;z-index:3}
.mt-card:hover .mt-title a{color:var(--green-ink);text-decoration:underline;
  text-decoration-thickness:2px;text-underline-offset:4px}

.mt-copy{margin:.5em 0 0;font-size:.96rem;line-height:1.55;color:var(--ink-soft);
  max-width:none}

/* The only place on the page where the four materials differ by DATA instead
   of by adjective. Every chip is a phrase that already exists in the approved
   copy — no hardness scale, no invented source.
   margin-top:auto sits here and not on the CTA, so the chip block AND the
   "Explore ..." line both anchor to the bottom of the card and all four line
   up however many lines the copy above them runs to. */
.mt-specs{list-style:none;display:flex;flex-wrap:wrap;gap:6px;margin:auto 0 0;
  padding:14px 0 0;max-width:none}
.mt-spec{margin:0;display:inline-flex;align-items:center;background:var(--bg-alt);
  border:1px solid var(--line-soft);border-radius:var(--r-sm);padding:5px 10px;
  font-weight:700;font-size:.72rem;letter-spacing:.06em;text-transform:uppercase;
  line-height:1.15;color:var(--ink-soft)}

.mt-cta{padding-top:18px;display:inline-flex;align-items:center;
  gap:10px;font-weight:800;font-size:.74rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--green-ink)}
.mt-card:focus-within .mt-cta{color:var(--green-deep)}

/* --- the link rule ---------------------------------------------------------
   device-link-rule.svg replaces the typographic chevron of v1, which changed
   shape with the font and could not animate. Same drawing language as the
   datum: green origin tick, rule, mitred head.
   -------------------------------------------------------------------------- */
.mt-rule{width:44px;height:16px;flex:0 0 auto;color:currentColor}
.mt-rule .lr-origin{stroke:var(--green-surface)}
/* inside a ghost button the fill inverts on hover, so the tick follows the ink */
.btn .mt-rule{width:38px}
.btn .mt-rule .lr-origin{stroke:currentColor}

@media(prefers-reduced-motion:no-preference){
  .mt-rule .lr-rule,.mt-rule .lr-head{transition:transform 220ms var(--ease-snap)}
  .mt-card:hover .mt-rule .lr-rule,.mt-card:hover .mt-rule .lr-head,
  .mt-card:focus-within .mt-rule .lr-rule,.mt-card:focus-within .mt-rule .lr-head,
  .btn:hover .mt-rule .lr-rule,.btn:hover .mt-rule .lr-head{transform:translateX(4px)}
}


/* --- service index --------------------------------------------------------- */
.mt-svc{margin:clamp(30px,3.6vw,46px) 0 0}
.mt-svc-label{margin:0}


/* --- remnants panel --------------------------------------------------------
   Absorbed from the section that used to sit between #process and #financing.
   data-media="fill": measured at 1440px the text column is 464px and the photo
   column is 455x464 — same top edge, same bottom edge. No sticky, and no photo
   floating with empty band above and below it.
   -------------------------------------------------------------------------- */
.mt-rem{margin:clamp(28px,3.6vw,44px) 0 0}


/* --- phone rail ------------------------------------------------------------
   561px and up the foundation grid stands: four columns, then two. Below that
   the four tiles are one swipe apart with native scroll-snap — no arrows, no
   dots, no clones, and no horizontal overflow on the page itself (the negative
   inline margin only cancels the .wrap gutter, exactly like .gal-grid).
   -------------------------------------------------------------------------- */
@media(max-width:560px){
  .mat-grid.mt-rail{display:flex;flex-wrap:nowrap;overflow-x:auto;
    overscroll-behavior-x:contain;scroll-snap-type:x mandatory;
    scroll-padding-inline:var(--pad);scrollbar-width:none;
    margin-inline:calc(var(--pad) * -1);padding-inline:var(--pad);
    padding-bottom:8px}
  .mat-grid.mt-rail::-webkit-scrollbar{display:none}
  .mt-rail .mt-card{flex:0 0 78%;scroll-snap-align:start}
  .mt-cta{padding-top:14px}
}


/* 30 — ABOUT, KITCHEN & BATHROOM, FABRICATION ===============================
   source: parts/30-about-fabrication.css
   ======================================================================== */

/* ===========================================================================
   BLOCK 30 — #about · Kitchen & Bathroom · #fabrication
   Styles for parts/30-about-fabrication.html. Loads AFTER assets/site.css.

   WHAT IS NOT IN THIS FILE, ON PURPOSE
   - no align-items on any .split. Section 12 of the foundation stretches every
     two-column row, and that stretch is the fix Paulo asked for.
   - no height:auto + aspect-ratio + max-height on a split photo. The one
     aspect-ratio below is on a FIGURE that is one of two frames in the same
     media column, and the second frame absorbs the remaining height, so the
     column still ends exactly level with the text. See block 1.
   - no ID selector, no !important, no compaction tail. Every rule below is
     scoped to a class this block owns: .ab-* (#about), .kb-* (kitchen and
     bath), .fab-* (#fabrication), plus the cs-* / ed-* names that ship inside
     the two drawings.

   THE ONE TRAP THIS FILE HAS TO DISARM
   #fabrication is a navy section holding two light drafting sheets. The
   foundation only re-inks .card, .mcard, .formcard, .tablewrap and .panel
   inside section.dark, so anything else light in there has to set its own ink
   or it renders pale-on-pale — which is exactly how v1 shipped a white
   heading on a white card. Each sheet below therefore sets color itself, and
   the two text bits inside them are re-inked with enough specificity to beat
   `section.dark .note` (0,2,1) and `section.dark :is(p,li)` (0,1,2).
   ========================================================================== */


/* 1 — #about ================================================================
   Full-bleed split, media left, data-media="fill".

   The media column carries two frames. The storefront is a fixed-ratio band,
   because the shop only occupies the top 45% of that file and a split column
   is always narrower than 16:9 — object-position alone can never push the
   parking lot out of the frame. The job-site photo below is flex:1, so it
   swallows whatever height the text column has left and the two columns
   finish on the same line at every width.

   Specificity note: the two rules that size the band are 4 classes (0,4,0) so
   they also win under 900px, where the foundation hands every split photo a
   16/10 box. On the phone that default would put the asphalt straight back.
   ========================================================================== */
/* 2.1:1, not 3.42:1 — Paulo, 18/09/2026. The 3.42 band was cut for the old
   warehouse file, whose bottom half was empty asphalt. The facade photograph
   that replaced it carries the storefront at 79% of its height, and a 3.42
   band shows only 52% of the frame: the roofline and the sky, never the glass.
   .ab-crew is flex:1 1 auto, so the height this band takes comes out of the
   frame below it and the column still finishes level with the text. */
/* 18/09/2026, Paulo: a 2a foto (as chapas) saiu — a secao estava gigante. Com uma
   figura so, a tira de proporcao fixa deixaria de alcancar a altura do texto e o
   bolsao de ar voltaria. Entao a fachada passa a PREENCHER a coluna: cresce ate a
   altura do texto e corta pela lateral, o que ainda mostra a loja grande e legivel. */
.split .col-media.ab-stack > .ab-front{flex:1 1 auto;height:auto;aspect-ratio:auto;min-height:0}
.split .col-media.ab-stack > .ab-crew{flex:1 1 auto;min-height:0}

/* Anchored to the BOTTOM: what the crop drops is sky, and what it keeps is the
   storefront, the signage and the entrance. At 3% it kept the sky instead. */
/* 62% na horizontal: o corte mantem a entrada 2114 inteira e parte da 2116.
   100% na vertical continua descartando ceu, nunca a vitrine. */
.col-media.ab-stack .ab-front img{height:100%;width:100%;aspect-ratio:auto;object-fit:cover;object-position:62% 100%}
.col-media.ab-stack .ab-crew img{object-position:50% 68%}

/* The crew frame needs a DEFINITE flex base, or the column's max-content height
   becomes the photograph's own height and the row stops being sized by the text.
   The kitchen file is portrait (936x1248): at 1440 that base was 960px and the
   media column ran 444px past the text column, which is the pocket of air this
   redesign removes, inverted onto the text side. The base is pinned the way the
   band above it is, and flex-grow still swallows whatever height the text column
   has left over, so the two columns finish level exactly as they did before.
   Scoped to >=900px: below that the stack is off and the foundation already
   hands the frame and the image the same 16/10 box. */
@media(min-width:900px){
  .split .col-media.ab-stack > .ab-crew{height:auto;aspect-ratio:16/10}
}

/* Under ~700px the plate caption no longer fits on one line, so the band
   gains height to carry two. The height it gains is curb and asphalt, and the
   plate lands on exactly that part of the frame — measured, not guessed. */
@media(max-width:699px){
  .split .col-media.ab-stack > .ab-front{aspect-ratio:1600/900}
}

/* The mitred tick of the plate caption already reads as a drafting title
   block; on the band it only needs room to breathe on a narrow phone. */
@media(max-width:420px){
  .ab-front .plate{gap:2px 12px}
}


/* 1b — THE SHORT-WINDOW FALLBACK ===========================================
   Sticky needs 980px of width AND 760px of height. A 1366x768 laptop, which
   is exactly the machine this audience owns, ends up a few pixels under that
   and the foundation falls back to fill — where the media column stops at
   --fill-max (720px) while these two text columns run to 840-920px. That
   difference is a pocket of air under the media: the defect this redesign
   exists to remove, arriving through the back door.

   Raising the token on these two columns lets the media stretch the whole
   way instead. It is a token override on my own elements, not a redeclared
   foundation property, and it is inert wherever sticky is actually on
   (sticky sets max-height:none itself) and under 900px (so does the stack).
   ========================================================================== */
.kb-media,.fab-media{--fill-max:none}


/* 2 — Kitchen & Bathroom ====================================================
   No rules. The section from the rejected screenshot is now pure foundation:
   .split.text-lead + data-media="sticky". At 1440px the media column lands at
   roughly 455x620, which is within a hair of the file's native 3:4, so there
   is nothing left to correct. A .kb-shot hook is on the figure in the markup
   in case a future crop needs one.
   ========================================================================== */


/* 3 — #fabrication: the cut plan sheet ======================================
   A drafting sheet, not a photo frame. The foundation already gives the
   figure inside .col-media its margin, height, radius, overflow, position and
   the --bg-alt board tint, so nothing here repeats any of that: this block
   only turns the sheet into a flex column, gives it drawing margins, sets its
   ink, and lays the drafting mesh underneath with a ::before.

   padding-bottom clears the figcaption, which the foundation pins to the
   bottom of the frame as a navy title block.
   ========================================================================== */
.fab-plan{--fab-pad:clamp(14px,1.7vw,22px);
  display:flex;flex-direction:column;
  padding:var(--fab-pad) var(--fab-pad) calc(var(--fab-pad) + 44px);
  color:var(--ink)}
.fab-plan::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--mesh);background-size:72px 72px;opacity:.5}

/* the stamp in the corner of the sheet */
.fab-seal{position:relative;z-index:1;align-self:flex-end;
  margin:0 0 clamp(10px,1.3vw,16px)}

/* The drawing keeps its own 560:300 and takes the vertical slack as equal
   margins, so it sits centred on the sheet at every column height instead of
   stretching or hugging one edge. */
.fab-cutplan{position:relative;z-index:1;flex:0 0 auto;
  width:100%;height:auto;margin-block:auto}

/* The sheet stops stretching past its own content. .fab-cutplan is locked to
   560:300 and cannot get taller without getting wider, so the foundation's
   620px sticky column left ~90px of blank sheet above the drawing and ~90px
   below it — a pocket of air inside the media column, which is the one thing
   this redesign is not allowed to ship. Pinning the sheet at its natural
   height keeps the sticky ride and removes the air. */
@media(min-width:980px) and (min-height:760px){
  .sec-split .split[data-media="sticky"] .col-media.fab-media{height:auto}
}

@media(max-width:599px){
  .fab-plan{--fab-pad:12px}
}


/* 4 — #fabrication: the edge profile rail ===================================
   Full width under the split, on the same drafting board as the sheet above.
   The five cells of the drawing are 128 units wide inside a 640 viewBox, so a
   five-column grid of names lines up with them exactly.

   Under about 640px the rail scrolls sideways inside its own box rather than
   shrinking the names below the annotation size — this audience is 45+ and
   .68rem is already the floor.
   ========================================================================== */
.fab-edges{margin:clamp(30px,4vw,54px) 0 0;padding:clamp(16px,2vw,26px);
  position:relative;overflow:hidden;
  background:var(--bg-alt);color:var(--ink);border-radius:var(--r-md)}
.fab-edges::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--mesh);background-size:72px 72px;opacity:.5}

/* line-height:0 so the chip's inline box does not add a descender gap under
   the plaquette. No flex here on purpose: the only align-items on a two
   column row in this project is the foundation's stretch. */
.fab-edges-head{position:relative;z-index:1;line-height:0;
  margin:0 0 clamp(14px,1.8vw,20px)}

.fab-edges-scroll{position:relative;z-index:1;
  overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:thin}
.fab-edges-track{min-width:520px}

.fab-edge-svg{width:100%;height:auto}

.fab-edge-names{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  list-style:none;margin:10px 0 0;padding:0;max-width:none}
.fab-edges .fab-edge-names li{margin:0;text-align:center}
/* beats section.dark .note (0,2,1): these names sit on a light sheet */
.fab-edges .fab-edge-names .note{color:var(--ink-faint)}


/* 5 — MOTION ================================================================
   One idea, two drawings: every animated stroke ships with pathLength="100",
   so stroke-dashoffset runs 100 -> 0 and the line draws itself once. The
   section script puts .is-in on each [data-draw] sheet when it enters the
   viewport and then stops watching it, so nothing re-runs on scroll.

   Two safety rails, both deliberate:
   - the hidden state lives inside prefers-reduced-motion:no-preference AND
     behind .js, so with the script dead or motion turned down the drawings
     are simply already finished. Never "does not appear".
   - the reduce kill switch in the foundation is animation-duration:.01ms,
     which lands every animation below on its end state because they all use
     fill mode both.
   Peak concurrency is four strokes, well inside the twelve-element ceiling.
   ========================================================================== */
@keyframes cutplan-draw{from{stroke-dashoffset:100}to{stroke-dashoffset:0}}
@keyframes fab-ink{from{opacity:0}to{opacity:1}}
@keyframes fab-fill{from{fill-opacity:0}to{fill-opacity:.08}}

@media(prefers-reduced-motion:no-preference){

  /* --- the cut plan: wall, top, veins, dimensions, seam, then the section --- */
  .js .fab-plan :is(.cs-top,.cs-seam,.cs-datum,.cs-datum-green,
                    .cs-slab,.cs-vein-sec,.cs-seam-sec),
  .js .fab-plan .cs-veins path{stroke-dasharray:100;stroke-dashoffset:100}
  .js .fab-plan :is(.cs-wall,.cs-wall-hatch,.cs-dim,.cs-box,.cs-break,
                    .cs-rod,.cs-origin){opacity:0}

  .js .fab-plan.is-in :is(.cs-wall,.cs-wall-hatch){
    animation:fab-ink 240ms linear 60ms both}
  .js .fab-plan.is-in .cs-top{
    animation:cutplan-draw 560ms var(--ease-stone) 140ms both}
  .js .fab-plan.is-in .cs-veins path{
    animation:cutplan-draw 520ms var(--ease-stone) calc(420ms + var(--i,0) * 80ms) both}
  .js .fab-plan.is-in .cs-plan .cs-dim{
    animation:fab-ink 260ms linear 760ms both}

  /* the moment the drawing exists for */
  .js .fab-plan.is-in .cs-seam{
    animation:cutplan-draw 420ms var(--ease-swap) 820ms both}

  .js .fab-plan.is-in :is(.cs-datum,.cs-datum-green){
    animation:cutplan-draw 420ms linear 900ms both}
  .js .fab-plan.is-in .cs-origin{
    animation:fab-ink 200ms linear 900ms both}

  .js .fab-plan.is-in .cs-slab{
    animation:cutplan-draw 460ms var(--ease-stone) 1000ms both}
  .js .fab-plan.is-in .cs-box{
    animation:fab-ink 260ms linear 1180ms both}
  .js .fab-plan.is-in .cs-break{
    animation:fab-ink 260ms linear 1200ms both}
  .js .fab-plan.is-in .cs-vein-sec{
    animation:cutplan-draw 420ms var(--ease-stone) 1220ms both}
  .js .fab-plan.is-in .cs-section .cs-dim{
    animation:fab-ink 260ms linear 1260ms both}
  .js .fab-plan.is-in .cs-seam-sec{
    animation:cutplan-draw 380ms var(--ease-swap) 1320ms both}
  .js .fab-plan.is-in .cs-rod{
    animation:fab-ink 300ms linear 1500ms both}

  /* --- the callouts: dot, leader, label — only after the drawing is done ---
     The drawing finishes at 1800ms (.cs-rod, 300ms from 1500ms), so nothing
     here starts before 1780ms. Per callout the order is the order a drafter
     reads it: the dot blinks in first because it is the anchor, the leader
     then DRAWS itself from that dot to the end of its landing with the same
     cutplan-draw gesture as the rest of the sheet, and only once the line has
     arrived does the label ink in. Never a fade of the whole group — that
     would kill the reading of a shop drawing.

     The hidden state sits behind .js AND inside no-preference exactly like
     the drawing above it, because these labels now carry the INFORMATION: a
     callout that fails to paint puts the sheet back to the drawing nobody
     could read. With the script dead or motion turned down they are simply
     already there, and the foundation's reduce kill switch lands all three
     rules on their end state because every one of them uses fill mode both.
     Peak concurrency: 100ms of stagger against a 280ms longest animation is
     ~3 callouts x 2 elements = 6, inside the twelve-element ceiling. */
  .js .fab-plan .cs-callouts .co-line{stroke-dasharray:100;stroke-dashoffset:100}
  .js .fab-plan .cs-callouts :is(.co-dot,.co-txt){opacity:0}

  .js .fab-plan.is-in .cs-callouts .co-dot{
    animation:fab-ink 160ms linear calc(1780ms + var(--i,0) * 100ms) both}
  .js .fab-plan.is-in .cs-callouts .co-line{
    animation:cutplan-draw 280ms var(--ease-stone) calc(1840ms + var(--i,0) * 100ms) both}
  .js .fab-plan.is-in .cs-callouts .co-txt{
    animation:fab-ink 200ms linear calc(2100ms + var(--i,0) * 100ms) both}

  /* --- the edge rail: datum first, then the five profiles, joint last ----- */
  .js .fab-edges :is(.ed-datum,.ed-datum-green,.ed-joint,.ed-ln){
    stroke-dasharray:100;stroke-dashoffset:100}
  .js .fab-edges .ed-ln{fill-opacity:0}
  .js .fab-edges :is(.ed-cell,.ed-break,.ed-origin){opacity:0}

  .js .fab-edges.is-in :is(.ed-datum,.ed-datum-green){
    animation:cutplan-draw 460ms linear 80ms both}
  .js .fab-edges.is-in .ed-origin{
    animation:fab-ink 200ms linear 120ms both}
  .js .fab-edges.is-in .ed-cell{
    animation:fab-ink 300ms linear 220ms both}
  .js .fab-edges.is-in .ed-ln{
    animation:cutplan-draw 560ms var(--ease-stone) calc(240ms + var(--i,0) * 90ms) both,
              fab-fill 360ms linear calc(540ms + var(--i,0) * 90ms) both}
  .js .fab-edges.is-in .ed-break{
    animation:fab-ink 260ms linear 760ms both}
  .js .fab-edges.is-in .ed-joint{
    animation:cutplan-draw 340ms var(--ease-swap) 860ms both}
}


/* 40 — PROCESS, COST, FINANCING =============================================
   source: parts/40-process-cost.css
   ======================================================================== */

/* ============================================================================
   40 — #process  ·  #cost (+ #financing)
   ----------------------------------------------------------------------------
   Section CSS. It consumes assets/site.css and adds nothing to it.

   House rules honoured here:
     · no ID selector, no !important, no compaction block at the end
     · no align-items:center on a .split, and no height:auto + aspect-ratio +
       max-height on anything living in a split media column
     · no new hex: every colour is a token
     · everything authored here is prefixed .pr-* (process) or .cst-* (cost)
   ========================================================================== */


/* 1 — #process: the five plates =============================================
   The foundation owns the rail itself (.tl, .tl-track, .tl-fill, .tl-dot) and
   the 40px trade icon. What this block adds is the plate, translated from
   assets/svg/device-step-rail.svg: a square cornered outline with the top right
   corner mitred off and the green facet laid across the cut.

   The clip path carries a notch in the middle of its top edge because .tl-dot
   sits half above the card on the desktop. Without the notch the clip would cut
   the numbered dot in half.
   ========================================================================== */
@media(min-width:1100px){
  .pr-step{
    --pr-miter:16px;                    /* the cut, at plate scale */
    --pr-notch:22px;                    /* half the gap the numbered dot rides in */
    border-color:transparent;           /* a clip path cuts a real border open */
    border-radius:0;
    box-shadow:inset 0 0 0 1.5px var(--line);
    transition:box-shadow 200ms var(--ease-snap);
    clip-path:polygon(
      0 0,
      calc(50% - var(--pr-notch)) 0,
      calc(50% - var(--pr-notch)) calc(var(--pr-notch) * -1),
      calc(50% + var(--pr-notch)) calc(var(--pr-notch) * -1),
      calc(50% + var(--pr-notch)) 0,
      calc(100% - var(--pr-miter)) 0,
      100% var(--pr-miter),
      100% 100%,
      0 100%)}

  /* the green facet — a bar laid along the cut, drawn inward so the clip path
     keeps all of it */
  .pr-step::after{content:"";position:absolute;top:0;
    left:calc(100% - var(--pr-miter));
    width:calc(var(--pr-miter) * 1.4143);height:2.5px;
    background:var(--green-surface);
    transform-origin:0 0;transform:rotate(45deg)}

  /* The rail is painted before the plates in source order, so a plate
     background buries it. It rides above the plates and below the numbered
     dots, which keeps the line unbroken between the steps and clean inside
     each dot. */
  .pr-rail .tl-track{z-index:1}
  .pr-step .tl-dot{z-index:2}

  @media(hover:hover){
    .pr-step:hover{box-shadow:inset 0 0 0 1.5px var(--green-surface)}
  }
}


/* 2 — #cost: the drawing plate ==============================================
   In v1 this column held a 534x401 photo centred inside a 1105px text column —
   704px of empty background, the hole Paulo circled. It is now a plate that
   fills the media column: plan view on top, elevation below.

   .cst-dwg never grows past its own size, so when the column is taller than the
   drawings the slack is spent on the gaps between them (justify-content), never
   as empty space inside a drawing. It does shrink, which is what keeps the
   plate honest inside the 620px sticky column.
   ========================================================================== */
.cst-plate{display:flex;flex-direction:column;justify-content:space-between;
  gap:clamp(14px,1.8vw,20px);container-type:inline-size}
.cst-plate > .badge{align-self:flex-start}
.cst-dwg{flex:0 1 auto;min-height:0;width:100%;height:auto}

/* Drawing language, shared with assets/svg/device-*.svg: butt caps, mitre
   joins, currentColor for the stone, green for the dimension system only. */
.cst-stone{stroke:currentColor;stroke-width:2.4;stroke-linecap:butt;
  stroke-linejoin:miter;stroke-miterlimit:4;fill:none}
.cst-cut{stroke:currentColor;stroke-width:2;stroke-linecap:butt;fill:none}
.cst-thin{stroke:currentColor;stroke-opacity:.5;stroke-width:2;
  stroke-linecap:butt;fill:none}
.cst-hatch{stroke:currentColor;stroke-opacity:.28;stroke-width:1;
  stroke-linecap:butt;fill:none}
.cst-ghost{stroke:currentColor;stroke-opacity:.17;stroke-width:1.5;
  stroke-dasharray:6 5;fill:none}
.cst-ext{stroke:currentColor;stroke-opacity:.3;stroke-width:1;
  stroke-linecap:butt;fill:none}
.cst-lead{stroke:currentColor;stroke-opacity:.45;stroke-width:1;
  stroke-linecap:butt;fill:none}
.cst-dot{fill:currentColor;fill-opacity:.5;stroke:none}
.cst-dim{stroke:var(--green-surface);stroke-width:1.8;stroke-linecap:butt;fill:none}
.cst-tick{stroke:var(--green-surface);stroke-width:1.5;stroke-linecap:butt;fill:none}
.cst-lbl{fill:currentColor;fill-opacity:.8;font-size:15px;font-weight:700;
  letter-spacing:1px}

/* The drawing scales with its plate, so the callouts are sized against the
   plate and not against the viewport: the target is the 12px annotation voice
   of .note at every width. The media query is the floor for engines without
   container queries; the container rules come after it and refine it. */
@media(max-width:899px){.cst-lbl{font-size:19px}}
@container (min-width:600px){.cst-lbl{font-size:10px}}
@container (max-width:460px){.cst-lbl{font-size:19px}}
@container (max-width:360px){.cst-lbl{font-size:24px}}

/* the price table: the factor column gets weight and a fixed measure so the six
   rows read as a list of factors, not as a wall */
.cst-table th:first-child{width:36%}
.cst-table td:first-child{font-weight:600}
@media(max-width:600px){.cst-table th:first-child{width:auto}}


/* 3 — #financing, the closing block of #cost ================================
   Same surface, same argument, stitched on with the full width datum. Inverted
   hierarchy: the estimate is the green button, the credit application is the
   ghost. Synchrony stays as text and link — never as a drawn third party mark.
   ========================================================================== */
.cst-fin-grid{display:grid;grid-template-columns:1.14fr .86fr;
  gap:var(--gap-split);align-items:stretch;
  margin-top:clamp(20px,2.4vw,30px)}
.cst-fin-copy > :first-child{margin-top:0}
.cst-fin-copy > :last-child{margin-bottom:0}
.cst-fin-copy h3{margin:0 0 .5em}
/* the action column is a rail, not a floating pair of buttons: the green rule
   runs the full height of the block and the buttons sit at its head */
.cst-fin-act{display:flex;flex-direction:column;justify-content:flex-start;
  gap:12px;max-width:340px}
@media(min-width:900px){
  .cst-fin-act{border-left:3px solid var(--green-surface);
    padding-left:clamp(18px,2vw,26px);max-width:none}
}


/* 4 — under 900px ===========================================================*/
@media(max-width:979px){
  /* Sticky needs 980px of width. Under it the row stacks instead of leaving the
     plate capped at --fill-max with the text running on past it: the question,
     then what gets measured, then the six factors in words. */
  .sec-split .cst-split{--split:1fr}
  .cst-split .col-media{order:-1;margin-bottom:8px;--fill-max:none}
}
@media(max-width:899px){
  .cst-fin-grid{grid-template-columns:1fr;gap:26px}
}


/* 5 — motion ================================================================
   One beat only: the three dimension lines of the plan view draw themselves
   when the plate arrives, in the order a take-off is measured. The hidden state
   lives inside no-preference, so the worst case is "appears without drawing".
   The reduced motion kill switch in the foundation collapses the duration and
   `forwards` leaves the lines on screen.
   ========================================================================== */
@media(prefers-reduced-motion:no-preference){
  .js .cst-plate[data-anim] .cst-dim{stroke-dasharray:100;stroke-dashoffset:100}
  .js .cst-plate[data-anim].is-in .cst-dim{
    animation:cst-dim-draw 620ms var(--ease-stone) forwards;
    animation-delay:calc(var(--i,0) * 110ms + 220ms)}
}
@keyframes cst-dim-draw{to{stroke-dashoffset:0}}


/* 50 — PROJECTS AND REVIEWS =================================================
   source: parts/50-projects-reviews.css
   ======================================================================== */

/* ===========================================================================
   THE PROOF BLOCK — #projects + #reviews
   Owns the prefixes .pj-* (projects) and .rv-* (reviews). Nothing here is an
   ID selector, nothing is !important, nothing redeclares a property on a
   selector the foundation owns, and there is no compaction block at the end.

   Where a foundation object has to behave differently in this block, it is
   reached through one of MY classes (.pj-grid .gal-item, .pj-hero .ba-compare,
   .rv-head .sec-head) — the modifier route house rule 1 asks for.

   Neither section is a two-column .split, so the parity rule has nothing to
   bite on here: there is no media column that could float in a taller text
   column. #projects is a full-width comparator over a frameless square grid;
   #reviews is a three-card grid where .grid already gives every card the same
   height. No align-items:center is written anywhere in this file.

   Colour budget: --green-surface as a vector only (the saw line, the quote
   mark, the datum's own green), --green-ink behind the white AFTER tag through
   .badge.solid, navy behind the white BEFORE tag through .badge.navy. No new
   hex, no gold, no olive.
   ========================================================================== */


/* 1 — THE SEAM ==============================================================
   #projects and #reviews share section.alt. The .datum-seam closes the
   gallery, so the gallery keeps a pillar's air above it and a short foot below
   it — otherwise the rule would sit 64px under the photos and 184px above the
   reviews, and would read as belonging to neither.
   ========================================================================== */
.pj-sec{padding-bottom:clamp(26px,3.4vw,48px)}


/* 2 — THE WIDE COMPARATOR ===================================================
   At 1180 the wrap is 1116 wide and the foundation's 16/9 gives 628px tall —
   the 1180x620 block the spec asks for, with no ratio override needed.
   Both frames take the same object-position so the two halves of the split
   line up while the handle travels.
   ========================================================================== */
.pj-hero .ba-compare img{object-position:50% 56%}

/* On a phone 16/9 would crush the kitchen into a 219px letterbox. 4/3 is the
   native ratio of both source files, so the phone sees the whole photograph. */
@media(max-width:700px){
  .pj-hero .ba-compare{aspect-ratio:4/3}
}


/* 3 — THE FRAMELESS WORK GRID ===============================================
   The weight is in the photograph. A square frame is what lets three phone
   photos of three different shapes read as one set, and it is what gives the
   crop enough travel to push a popcorn ceiling or a cluttered counter out of
   frame. Each frame lands at about 362px on a 1180 page — under the 480px
   ceiling the curation rule puts on archive photography.
   ========================================================================== */
.pj-grid .gal-item{aspect-ratio:1/1}

.pj-work{display:block}
.pj-cap{margin:0}
.pj-title{margin:16px 0 0;font-size:1.06rem;line-height:1.28}
.pj-line{margin:6px 0 0;font-size:.9rem;line-height:1.55;color:var(--ink-soft);max-width:40ch}

/* Per-photo crops. These are curation decisions, not styling:
   island     pushes down past the popcorn ceiling onto the island and backsplash
   backsplash centres the counter-to-backsplash band, which is the whole point
   backlit    pulls left, off the counter clutter on the right-hand edge */
.pj-shot-island{object-position:50% 72%}
.pj-shot-backsplash{object-position:50% 60%}
.pj-shot-backlit{object-position:22% 50%}

/* Below 900px the foundation turns .gal-grid into a snap scroller and hands
   flex:0 0 78% to .gal-item. Here .gal-item is not the flex item — .pj-work is,
   and .pj-work is a plain block, so that rule lands on nothing and no reset is
   needed. The flex basis simply moves up one level. */
@media(max-width:899px){
  /* the scroller pulls itself out to the screen edge and puts the gutter back
     as padding; without a matching scroll-padding the snapport starts at the
     border box, so mandatory snap parks the first photo 20px off — flush with
     the screen edge and out of line with every other block on the page. */
  .pj-grid{scroll-padding-inline:var(--pad)}
  .pj-grid .pj-work{flex:0 0 78%;scroll-snap-align:start}
}
/* A tablet fits two whole photos and the edge of a third, and 78% there would
   blow a 700px frame past the 480px ceiling the curation rule puts on archive
   photography — these are phone pictures, and size is what exposes them. */
@media(min-width:700px) and (max-width:899px){
  .pj-grid .pj-work{flex:0 0 46%}
}
@media(max-width:560px){
  .pj-grid .pj-work{flex:0 0 86%}
}


/* 4 — SLAB-CUT, THE SIGNATURE ===============================================
   Three layers on one photograph, once on the whole page:
     1. the picture opens by clip-path along a 2.6 degree diagonal   1100ms
     2. a 3px green cut line runs just ahead of the opening edge     1100ms
     3. a polishing pass sweeps the finished surface   +420ms delay, 1100ms
   Total 1.52s. clip-path is the one non-composited property in the set, which
   is exactly why there is only one of these and why the spec caps the page at
   four and forbids two on the same screen.

   Safety: the closed clip-path only exists inside no-preference AND behind
   .js. Reduced motion, no script, or a failed observer all end at "the photo
   is simply there" — never at "the photo is missing". The two moving layers
   rest at opacity:0 outside the media query, so reduced motion never shows a
   stray green bar sitting on the picture.
   ========================================================================== */
.pj-cut{position:relative}

.pj-saw,.pj-polish{position:absolute;top:-10%;left:0;width:100%;height:120%;
  z-index:2;pointer-events:none;opacity:0}

.pj-saw{background:linear-gradient(90deg,var(--green-surface) 0 3px,rgba(0,166,82,0) 3px);
  transform:translateX(2%) rotate(-2.6deg)}

.pj-polish{background:linear-gradient(100deg,
    rgba(255,255,255,0) 0 40%,rgba(255,255,255,.40) 50%,rgba(255,255,255,0) 60%);
  transform:translateX(-70%)}

@media(prefers-reduced-motion:no-preference){
  .js .pj-cut img{clip-path:polygon(0% -6%,0% -6%,-6% 106%,-6% 106%)}
  .js .pj-cut.is-in img{animation:pj-slab-cut 1100ms var(--ease-swap) both}
  .js .pj-cut.is-in .pj-saw{animation:pj-saw-pass 1100ms var(--ease-swap) both}
  .js .pj-cut.is-in .pj-polish{animation:pj-polish-pass 1100ms var(--ease-snap) 420ms both}
}

/* Four points in, four points out, so the polygon interpolates. The trailing
   edge leans 6% of the width across the height: 2.6 degrees on a square frame. */
@keyframes pj-slab-cut{
  from{clip-path:polygon(0% -6%,0% -6%,-6% 106%,-6% 106%)}
  to{clip-path:polygon(0% -6%,112% -6%,106% 106%,0% 106%)}
}
/* the blade leads the opening edge by about 4% of the frame — the stone
   parts behind the cut, never in front of it. */
@keyframes pj-saw-pass{
  0%{opacity:0;transform:translateX(2%) rotate(-2.6deg)}
  10%{opacity:1}
  88%{opacity:1}
  100%{opacity:0;transform:translateX(118%) rotate(-2.6deg)}
}
@keyframes pj-polish-pass{
  0%{opacity:0;transform:translateX(-70%)}
  18%{opacity:1}
  100%{opacity:0;transform:translateX(130%)}
}


/* 5 — REVIEWS ===============================================================
   The head and the source chip share one row: the chip reads as a stamp on the
   heading, not as a fourth sentence saying the same thing.
   ========================================================================== */
.rv-head{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;
  gap:18px 40px;margin:0 0 clamp(30px,3.6vw,52px)}
.rv-head .sec-head{margin-bottom:0;flex:1 1 440px}
.rv-source{flex:0 0 auto;margin-bottom:5px}

/* The card: the mitred quote mark hangs off the top-left corner by 11px, the
   way a drafting stamp sits on the corner of a sheet. .card carries no
   position of its own, so this is an addition, not an override. */
.rv-quote{position:relative;display:flex;flex-direction:column;padding-top:28px}
.rv-mark{--s:30px;position:absolute;top:-11px;left:-11px}
.rv-text{margin:0 0 1.1em;font-size:1.02rem;line-height:1.62;max-width:none}
/* the attribution is pinned to the foot so three quotes of different lengths
   still line their names up — .grid already stretches the cards to match. */
.rv-by{margin-top:auto;padding-top:14px}

@media(max-width:600px){
  .rv-head{align-items:flex-start}
  .rv-source{margin-bottom:0}
}


/* 60 — SHOWROOM, FAQ, GET ESTIMATE, FOOTER ==================================
   source: parts/60-showroom-faq-estimate.css
   ======================================================================== */

/* ============================================================================
   American Countertops & Design — home v2
   BLOCK 6 · #showroom · #faq · #get-estimate · page footer
   ----------------------------------------------------------------------------
   Consumes assets/site.css. It adds nothing to that file and redeclares nothing
   in it. Everything here is either a new class with this block's prefix
   (.sr-* showroom, .fq-* FAQ, .ge-* estimate, .f-* footer, the prefix the
   foundation already uses there) or a contextual variant the foundation does
   not cover.

   House rules honoured:
   - no ID selector
   - no !important
   - no align-items:center on a .split, at any width
   - no height:auto + aspect-ratio + max-height on a split photo
   - no new hex: every colour is a token or rgba(0,166,82,a) / rgba(25,40,80,a)
   - no compaction block at the end of the file
   ========================================================================== */


/* 1 — SHOWROOM ==============================================================
   The split itself needs no CSS: .split + data-media="fill" already stretches
   the photograph to the height of the text column. What is authored here is
   the address block, which in v1 was a single .fine paragraph with four <br>
   and read as a footnote instead of as the reason to drive over.
   ========================================================================== */

/* badge-showroom.svg, inlined. The asset draws its own mitred plate, so it must
   NOT carry .badge — that class would wrap a second plate around it. */
.sr-seal{width:278px;color:var(--ink);margin:6px 0 0}

.sr-name{font-weight:800;font-size:1.02rem;line-height:1.3;color:var(--ink);
  margin:18px 0 0}

.sr-facts{margin:12px 0 0;padding:0}
.sr-facts .sr-row{display:grid;grid-template-columns:92px minmax(0,1fr);
  gap:2px 16px;align-items:start;padding:11px 0;border-top:1px solid var(--line-soft)}
.sr-facts .sr-row:first-child{border-top:0}
.sr-facts dt{padding-top:7px}          /* optical baseline against the 18px value */
.sr-facts dd{margin:0;font-weight:600;line-height:1.66;color:var(--ink)}
/* the touch target is 44px; the negative block margin keeps the row compact */
.sr-facts dd a{display:inline-block;min-height:44px;padding-block:9px;
  margin-block:-9px;font-weight:700;text-decoration:none}
.sr-facts dd a:hover{text-decoration:underline}

/* two ragged buttons side by side is what a phone shows when a desktop row is
   left alone; below 600px each one takes the full column */
@media(max-width:600px){
  .sr-cta .btn{flex:1 1 100%}
}

@media(max-width:420px){
  .sr-facts .sr-row{grid-template-columns:minmax(0,1fr);gap:1px}
  .sr-facts dt{padding-top:0}
}


/* 2 — FAQ ==================================================================
   The answer column runs to roughly 875px at 1440px, well past the ~720px
   threshold, so the second column travels with the reading. It holds a panel
   rather than a photograph, so it is .fq-side and not .col-media, and the
   sticky is declared here under exactly the guard the foundation uses for
   media: wide enough AND tall enough, otherwise it stays put.
   ========================================================================== */
.fq-side{display:flex;flex-direction:column;gap:14px;min-width:0}

.fq-panel{display:flex;flex-direction:column;align-items:flex-start}
.fq-panel .badge{margin:0 0 14px}
.fq-panel p{margin:0 0 16px;font-size:.96rem;line-height:1.55;max-width:none}
.fq-panel .fine{margin:14px 0 0}

@media(min-width:980px) and (min-height:760px){
  .fq-side .fq-panel{position:sticky;top:calc(var(--header-h) + 24px)}
}


/* 3 — GET ESTIMATE =========================================================
   The right column is a form, not media: it keeps its natural height, which
   changes by ~270px between step 1 and step 3. It carries no surface of its
   own — the card does — so the difference between the two columns cannot read
   as the empty band the v1 photographs left behind.
   ========================================================================== */
.ge-side{display:flex;flex-direction:column;gap:16px;min-width:0}

/* The copy column tops out level with the card instead of floating in the
   middle of it. .col-text centres by default, and this file already notes that
   the form grows ~270px between step 1 and step 3 — centred, that pushed the
   headline up by half of it WHILE the visitor was typing. Top aligned, the two
   columns start on one line and nothing moves under the cursor. */
.ge-split .col-text{justify-content:flex-start}

/* The step machine hides Back on step 1 and Submit until step 3 with the
   hidden attribute. In v1 that worked because [hidden] carried !important;
   the foundation dropped the !important (correctly) and [hidden]{display:none}
   now loses to .btn{display:inline-flex} on equal specificity, so all three
   buttons render at once. This is the scoped repair — it applies to any form
   card that uses the .fnav rail, the hero one included. */
/* the [hidden] repair this file carried is now in section 10 of the foundation */
.ge-side .formcard .badge{margin:0 0 14px}
.ge-side .steplead{margin:0 0 14px}
.ge-side > .fine{margin:0}

@media(max-width:899px){
  .ge-side{gap:14px}
}


/* 4 — FOOTER ===============================================================
   The foundation already owns the surface, the grid and the link treatment.
   Two things are missing there: the datum rule has no footer context (it is
   navy ink on a navy floor, so it would be invisible), and the city list is
   plain text that needs its own leading.
   ========================================================================== */
footer .datum{color:var(--white)}

/* the other three columns are 44px link rows; plain text needs the extra
   leading to sit on the same rhythm */
.f-cities li{margin-bottom:.55em}

.f-sms{margin:0 0 22px}

/* --- ASSEMBLER ADDENDUM (block 60) -----------------------------------------
   Between 900px and 1023px the showroom photograph stopped at the foundation
   ceiling --fill-max (720px) while its text column ran to 767px, and the 47px
   left over sat under the photo. It is the same back door block 30 closed on
   its two sticky columns, so it is closed the same way: a token override on one
   element of this block, not a redeclared foundation property. Measured at
   900x900 the column goes from 720 to 767 and the band disappears.
   -------------------------------------------------------------------------- */
.sr-media{--fill-max:none}


/* 70 — SERVICE AREA MAP =====================================================
   source: parts/map.css
   ======================================================================== */

/* ===========================================================================
   SERVICE AREA MAP — styles for parts/map.html
   Loads AFTER assets/site.css.

   Section 21 of the foundation owns .map-wrap, .map-svg, .map-plate,
   .map-grid, .map-area, .map-road, .map-label, .map-pin (+ .pin-body,
   .pin-dot, .is-home, .is-active), .map-ring, .map-pop (+ -name, -zip,
   -meta) and .map-legend. Nothing below redeclares a property the
   foundation already set on one of those selectors, with two exceptions
   flagged EDGE GUARD: the popup arrow offset and the flipped-below state.
   The foundation popup can only sit centred above the pin, which is exactly
   how a tooltip walks off the map on a border pin, so those two are the
   whole point of the component.

   Two contracts this file depends on:
   1. .mp-board keeps the 480:960 ratio of the viewBox. Pin --x / --y are
      percentages of that box, so any letterboxing would move every pin.
   2. The area outline carries pathLength="1200", which is exactly the
      stroke-dasharray the foundation sets, so offset 1200 -> 0 draws the
      whole border once instead of a twelfth of it.
   ========================================================================== */


/* 1 — BLOCK LAYOUT =========================================================
   Map left, city table right, both columns stretched to the same height.

   The rule that keeps them level: the map column is sized so the plate is
   always a little TALLER than the table's natural height, and the table rows
   flex, so they stretch to take up the difference and both columns finish on
   the same line. Sizing it the other way round is what left the pocket of
   air that got v1 rejected — a plate with a fixed ratio cannot stretch, so
   it must be the one that sets the height.

   Below 1080px the table no longer has the width to stay short, so the two
   stack instead of leaving that gap. --mp-beside is the single place that
   break lives: map.js reads it rather than repeating the number. */
.mp-layout{--mp-beside:0;position:relative;
  display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(24px,4.5vw,34px);
  align-items:stretch}
@media(min-width:1080px){
  .mp-layout{--mp-beside:1;gap:var(--gap-split);
    /* 440px, not 400px. Lantana, Lake Worth Beach and Boynton Beach sit within
       a few real miles of each other, so at 400px their pins touched. Widening
       past ~440 is not free: the plate keeps the 480:960 viewBox ratio, so the
       map would grow taller than the city table beside it and reopen exactly
       the column-height gap this redesign exists to close. */
    grid-template-columns:clamp(320px,41%,440px) minmax(0,1fr)}
  /* .map-wrap brings its own top margin from the foundation; the table
     matches it so the two columns start on the same line */
  .mp-side{margin-top:clamp(22px,3vw,34px)}
}

.mp-stage{display:flex;flex-direction:column;justify-content:center;min-width:0;
  gap:clamp(14px,1.6vw,20px)}
.mp-side{display:flex;flex-direction:column;min-width:0}
.mp-side > p{max-width:64ch}


/* 2 — THE PLATE ============================================================
   width drives height, never the other way round: one definite dimension
   plus aspect-ratio can never letterbox the svg. */
.mp-board{position:relative;width:min(100%,420px);margin-inline:auto;
  aspect-ratio:480/960;--mp-fs:10.5px}
@media(min-width:1080px){.mp-board{width:100%}}
.mp-board .mp-map{display:block;width:100%;height:100%}


/* 3 — MAP LAYERS AND TYPE ==================================================*/
.map-wrap .mp-lbl{font-size:var(--mp-fs)}
.mp-mid{text-anchor:middle}
/* a label that lands on a road or on the Everglades hatch reads through it */
.mp-halo{paint-order:stroke;stroke:var(--bg-alt);stroke-width:3px;stroke-linejoin:round}

.mp-north{fill:var(--ink-faint);fill-opacity:.85}
.mp-scale{fill:none;stroke:var(--ink-faint);stroke-opacity:.7;stroke-width:1.2}

/* the light that runs down the territory once, clipped to its own shape */
.mp-stop-a{stop-color:var(--green-surface);stop-opacity:0}
.mp-stop-b{stop-color:var(--green-surface);stop-opacity:.3}


/* 4 — REVEAL TIMELINE ======================================================
   One IntersectionObserver hit, one build, in cartographic order: the
   territory and the coast are drawn with a pen, then the water, the
   Everglades, the roads, the county lines and the type settle in behind
   them, then the pins rain down north to south.

   Every hidden start state lives inside no-preference, so the worst case is
   "appears without animating", never "does not appear". */
.mp-coast-line{--mp-dur:1450ms;--mp-del:120ms}
.mp-coast-bank{--mp-dur:1250ms;--mp-del:300ms}
.mp-coast-icw {--mp-dur:1250ms;--mp-del:420ms}
.mp-coast-water{--mp-del:260ms}
.mp-edge  {--mp-del:560ms}
.mp-roads {--mp-del:760ms}
.mp-county{--mp-del:900ms}
.mp-names {--mp-del:1100ms}
.mp-tools {--mp-del:1250ms}

@media(prefers-reduced-motion:no-preference){
  .js .map-wrap .mp-draw{stroke-dasharray:100;stroke-dashoffset:100}
  .js .map-wrap.is-in .mp-draw{stroke-dashoffset:0;
    transition:stroke-dashoffset var(--mp-dur,1400ms) var(--ease-swap) var(--mp-del,0ms)}

  .js .map-wrap .mp-fade{opacity:0}
  .js .map-wrap.is-in .mp-fade{opacity:1;
    transition:opacity 620ms linear var(--mp-del,0ms)}

  /* the tint arrives after the border closes: outlined first, then filled.
     fill-opacity is free — the foundation only sets fill on .map-area. */
  .js .map-wrap .mp-area{fill-opacity:0}
  .js .map-wrap.is-in .mp-area{fill-opacity:1;
    transition:fill-opacity 900ms linear 620ms}

  .js .map-wrap .mp-sweep-bar{opacity:0}
  .js .map-wrap.is-in .mp-sweep-bar{animation:mp-sweep 1100ms var(--ease-swap) 1150ms 1 both}
}
@keyframes mp-sweep{
  from{opacity:0;transform:translateY(0)}
  22%{opacity:1}
  80%{opacity:1}
  to{opacity:0;transform:translateY(1280px)}}


/* 5 — PINS =================================================================
   A 44px target around a 22x30 teardrop. The button is placed by its tip:
   margin pulls the box up so the point of the pin lands on --x / --y, and
   margin is used rather than a translate because the foundation already
   owns transform on .map-pin (hover scale and the pin-drop keyframes).

   Scale on hover/focus is carried by --mp-s on the art inside the button.
   The foundation's own scale(1.18) is written for a pin that is not also
   running pin-drop with fill both, which wins over it after the drop.

   --i is the shared timeline dial. The foundation reads it as
   animation-delay: var(--i) * 90ms for the drop, so the pins carry 8 to
   12.95: base 720ms (the border has almost finished drawing) and a spread
   of 4.45 steps, inside the five-step stagger ceiling. The opacity below
   uses the same formula so the two never drift apart. */
.mp-pin{position:absolute;left:var(--x,50%);top:var(--y,50%);
  width:44px;height:44px;margin:-39px 0 0 -22px;
  padding:0;border:0;background:none;overflow:visible;z-index:2;
  border-radius:var(--r-sm);-webkit-tap-highlight-color:transparent;
  /* how far above the tip the head of the pin reaches once it has grown
     under the cursor. map.js reads it so the card clears the pin instead of
     sitting on top of the thing it is describing. */
  --mp-lift:36px}
.mp-art{position:absolute;left:50%;bottom:5px;width:22px;height:30px;overflow:visible;
  transform:translateX(-50%) scale(var(--mp-s,1));transform-origin:50% 100%;
  transition:transform 220ms var(--ease-snap)}
.mp-pin-core{--mp-lift:48px}
.mp-pin-core .mp-art{width:30px;height:40px}
/* the far edge of the range reads lighter and smaller. Weight only: the
   approved copy makes no distinction between these cities and the rest. */
.mp-pin-edge{--mp-s:.88}
.mp-pin-edge .pin-body{fill-opacity:.72}
.mp-pin:is(:hover,:focus-visible,.is-active){--mp-s:1.16;z-index:3}
.mp-pin:is(:hover,:focus-visible,.is-active) .pin-body{fill-opacity:1}

@media(prefers-reduced-motion:no-preference){
  .js .map-wrap .mp-pin{opacity:0}
  .js .map-wrap.is-in .mp-pin{opacity:1;
    transition:opacity 200ms linear calc(var(--i,0) * 90ms)}
}


/* 6 — POPUP ================================================================
   EDGE GUARD. map.js measures the card, clamps it inside .mp-layout and
   hands back two values: --mp-arrow moves the tail back under the pin, and
   .mp-under flips the whole card below a pin that has no room above it.
   Selectors are scoped under .mp-layout so they win on specificity rather
   than on load order. */
.mp-pop-line{display:block;margin-top:6px;font-weight:700;font-size:.78rem;
  line-height:1.35;color:var(--ink-soft)}

.mp-layout .mp-pop::after{left:calc(50% + var(--mp-arrow,0px))}

.mp-layout .mp-pop.mp-under{transform:translate(-50%,16px) scale(.96)}
.mp-layout .mp-pop.mp-under[data-open]{transform:translate(-50%,16px) scale(1)}
.mp-layout .mp-pop.mp-under::after{top:-7px;bottom:auto;
  border-right:0;border-bottom:0;
  border-left:1px solid var(--line-soft);border-top:1px solid var(--line-soft)}


/* 7 — CITY TABLE ===========================================================
   The twelve cities stay plain indexable HTML: name, ZIP codes and drive
   time are all real text, from parts/map-data.json. The rows flex so the
   column finishes level with the plate. */
.mp-cols,.mp-city{display:grid;gap:6px 16px;
  grid-template-columns:minmax(118px,1.05fr) minmax(0,1.7fr) max-content}
.mp-cols{max-width:none;margin:clamp(10px,1.4vw,18px) 0 6px;padding:0 10px;align-items:end}
.mp-cols span{white-space:nowrap}
/* the long label wraps to two lines instead of widening the track and
   squeezing the ZIP column */
.mp-cols span:last-child{justify-self:end;text-align:right;max-width:104px;white-space:normal}

.mp-cities{flex:1 1 auto;display:flex;flex-direction:column;
  list-style:none;margin:0;padding:0;max-width:none;
  border-top:1px solid var(--line-soft)}
.mp-city{flex:1 1 auto;align-items:center;margin:0;padding:7px 10px;min-height:46px;
  border-bottom:1px solid var(--line-soft);
  transition:background 160ms var(--ease-snap),box-shadow 160ms var(--ease-snap)}
.mp-city.is-active{background:rgba(0,166,82,.055);
  box-shadow:inset 2px 0 0 var(--green-surface)}
@media(hover:hover){
  .mp-city:hover{background:rgba(0,166,82,.055);
    box-shadow:inset 2px 0 0 var(--green-surface)}
}

.mp-city-name{font-weight:700;font-size:.95rem;line-height:1.25;color:var(--ink)}
a.mp-city-name{text-decoration:none;
  transition:color 160ms var(--ease-snap)}
a.mp-city-name:hover,a.mp-city-name:focus-visible{color:var(--green-deep);
  text-decoration:underline}
.mp-city-zip{font-size:.78rem;line-height:1.35;color:var(--ink-faint)}
.mp-city-drive{justify-self:end;white-space:nowrap;
  font-weight:700;font-size:.8rem;color:var(--ink-soft)}


/* 8 — LEGEND ===============================================================*/
.map-legend .mp-key-city{background:var(--ink)}


/* 9 — PHONE ================================================================
   The map is already stacked above the table by default; this is only the
   last step down, where the three-column row stops fitting. Nothing here is
   hover-only: a tap on a pin opens the same card, and the table holds the
   same ZIP codes and drive times as plain text either way. */
@media(max-width:1079px){.map-legend{justify-content:center}}

@media(max-width:659px){
  .mp-board{width:min(100%,330px);--mp-fs:12.5px}

  .mp-cols,.mp-city{grid-template-columns:minmax(0,1fr) max-content;gap:2px 14px}
  .mp-cols span:nth-child(2){display:none}
  .mp-city{padding:10px;align-items:start}
  /* placed, not auto-flowed: the drive time belongs on the name's line and
     the ZIP codes run underneath across the full width */
  .mp-city-name {grid-column:1;grid-row:1}
  .mp-city-drive{grid-column:2;grid-row:1}
  .mp-city-zip  {grid-column:1 / -1;grid-row:2}
}


/* ===========================================================================
   90 — VISUAL REVIEW PASS
   Finish corrections found by looking at the rendered page at 1440 and at a
   narrow viewport. Overrides only, in the idiom the sections above already
   use: a token or a single declaration on an element this file owns, never a
   redeclared foundation rule and never align-items on a .split.
   ========================================================================== */


/* 1 — KITCHEN & BATHROOM: PARITY INSTEAD OF RIDE ============================
   This is the section that was rejected twice, so it is measured rather than
   argued. At 1440px the text column runs 840px and sticky pins the media at
   height:min(72vh,620px). 620 against 840 is a 220px pocket of air under the
   photo — the same defect as v1, one third the size.

   Sticky is the right answer when the text is far taller (#fabrication runs
   1003, #cost and the FAQ run further). It is the wrong answer at 1.36x: the
   column barely travels and all the reader sees is the hole. So this one
   column opts out of sticky inside the sticky band and stretches to the row
   like a fill column. Top edge and bottom edge now land on the same line.

   Scoped with .kb-media so it cannot reach #fabrication, #cost or the FAQ,
   and written the same way line 1412 already overrides .fab-media. Every
   other breakpoint — the 900-979 dead band and the stack under 900 — still
   reads data-media="sticky" and behaves exactly as before.
   ========================================================================== */
@media(min-width:980px) and (min-height:760px){
  .sec-split .split[data-media="sticky"] .col-media.kb-media{
    position:static;align-self:stretch;height:auto;max-height:none}
}


/* 2 — SERVICE MAP: THE EDGE PINS READ AS PINS, NOT AS A FAILED LOAD =========
   The far-range cities are drawn lighter so the core of the territory stays
   loudest. At fill-opacity .72 on a 400px plate that no longer reads as
   depth: five of the twelve pins look half-loaded, and there is no legend
   entry that would explain a second pin state. Same intent, closer values —
   the hierarchy survives, the glitch does not.
   ========================================================================== */
.mp-pin-edge{--mp-s:.93}
.mp-pin-edge .pin-body{fill-opacity:.88}


/* 3 — HERO REGISTRATION MARKS: TWO CORNERS OR NONE ==========================
   .hero-marks is inset 24px from the hero box and reads as a drafting frame
   only while both corners sit on the photograph. Under 1081px the copy plate
   and the form card take the whole width, the bottom-right mark lands alone
   on bare navy under the card, and an L of half-transparent white with
   nothing attached to it looks like a rendering fault. The motif is a
   desktop motif; below that width it is switched off rather than orphaned.
   ========================================================================== */
@media(max-width:1080px){.hero-marks{display:none}}


/* 4 — THE PHOTOLESS TILE KEEPS THE BADGE RHYTHM =============================
   .mat.no-photo is the fallback a material tile falls back to when there is
   no honest photograph for it: the vector on the veined navy field. All four
   tiles carry a photo today, so nothing in index.html uses it — the state is
   kept because the next material without a usable shot needs it, and because
   a tile that shows an icon instead of a photo must not read as a tile whose
   icon failed. Every chip carries icon + word, photo or not.

   .mat.no-photo .ic is the rule that blows the centre mark up to 72px and
   tints it 62% white for the navy field; the chip sits on a light pill, so
   the ink is handed back here. Size and stroke are already corrected by
   .mt-card .mat-name .ic further up.
   ========================================================================== */
.mt-card .mat.no-photo .mat-name .ic{color:currentColor}
