/* ==== src/css/00-foundation-pages.css ==== */
/* ============================================================================
   American Countertops & Design - inner pages foundation (pages.css, part 00)
   ----------------------------------------------------------------------------
   Loaded after assets/site.css on every page, the home included. site.css is
   the approved foundation and is never edited: this file only adds components
   the inner pages need, with the same house rules -
     no ID selector, no !important, no property redeclared on a site.css
     selector (a modifier class of our own instead), tokens only, no new hex,
     no gold, no olive, every component under its own prefix.
   Page files (src/css/p-<slug>.css) are appended after this one.

   P00 Header row repair (the Menu button, D3)
   P01 Main nav: dropdown panels and the phone accordion
   P02 Page head: the navy plate (.page-head) and the photograph it now sits
       on by default (.page-head.ph-photo) - docs/TEMPLATE-GUIDE.md section 2
   P03 Breadcrumbs (.crumbs)
   P04 Related links (.rel-*)
   P05 Mid-page CTA band (.plan-band.cta-band)
   P06 Lightbox (.lbx-*)
   P07 Prose (.prose, .prose.legal)
   P08 Article layout and table of contents (.art-layout, .toc)
   P09 Blog index cards (.post-*)
   P10 Pagination (.pager)
   P11 Process rail with 3 or 4 steps (.tl-3, .tl-4)
   P12 Not found sheet (.nf-*)
   P13 Contact location plate (.loc-*)
   P14 Print
   P15 Footer: the nav's four groups (.f-five)
   P16 Phone call bar, one action (.callbar.cb-solo) + the home hero button row
   P17 Optional field label (.fopt)
   ========================================================================== */


/* P00 - HEADER ROW REPAIR ====================================================
   Below 1040px .hd-actions has no visible child (the phone box shows from
   1280px, the CTA from 1040px) but it was still the last item of the
   space-between row, so the Menu button sat 52px short of the right margin on
   a phone. Taking the empty box out of the flow puts the button on the edge.
   position is not declared on .hd-actions anywhere in site.css, so this is an
   addition, not a redeclaration (DECISOES D3: done here, site.css untouched). */
@media(max-width:1039px){.hd-actions{position:absolute}}


/* P01 - MAIN NAV: DROPDOWN PANELS ============================================
   Variant A, measured in DESIGN-SYSTEM 5.3: Countertops, Services, Our Stock,
   Projects, Company. Triggers are <button aria-expanded>; the links stay in
   the served HTML. pages.js opens a group with .open (click, keyboard, and
   hover-intent on a desktop pointer). Without pages.js, hover and
   focus-within open the panels. */
.nav-group{position:relative;display:flex}
.nav-top{display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-weight:600;
  font-size:.84rem;line-height:inherit;color:var(--ink);background:none;border:0;padding:10px;
  border-radius:var(--r-sm);cursor:pointer;white-space:nowrap;
  transition:background 180ms var(--ease-snap),color 180ms var(--ease-snap)}
.nav-top:hover,.nav-group.open > .nav-top{background:var(--bg-alt);color:var(--green-deep)}
.nav-group.is-current > .nav-top{background:var(--green);color:var(--white)}
nav.main a[aria-current="true"]{background:var(--green);color:var(--white)}
.nav-caret{width:10px;height:6px;flex:0 0 10px;display:inline-block;fill:none;stroke:currentColor;
  stroke-width:1.8;stroke-linecap:butt;stroke-linejoin:miter;
  transition:transform 220ms var(--ease-swap)}
.nav-group.open .nav-caret{transform:rotate(180deg)}
.nav-panel{display:none}
.nav-panel .note{margin:0 0 6px;padding:0 10px}
nav.main .nav-panel a{display:flex;align-items:center;gap:10px;min-height:44px;padding:9px 10px}
nav.main .nav-panel a .ic{--s:20px;--sw:1.9;color:var(--ink)}
nav.main .nav-panel a[aria-current] .ic{color:currentColor}
nav.main .nav-panel a[aria-current] .ic .f{fill:var(--white)}

@media(min-width:900px) and (max-width:1079px){.nav-top{padding-inline:6px;font-size:.8rem}}
@media(min-width:900px){
  /* the sheet: paper, a green top rule that lands on the header's bottom line,
     navy shadow. No miter: it is a panel, not a seal. */
  .nav-panel{display:grid;gap:2px 18px;position:absolute;left:-14px;z-index:60;
    top:calc((var(--header-h) + 100%) / 2 - 1px);
    min-width:250px;padding:14px 14px 14px;background:var(--bg);
    border:1px solid var(--line-soft);border-top:3px solid var(--green-surface);
    border-radius:0 0 var(--r-md) var(--r-md);box-shadow:var(--e2);
    opacity:0;visibility:hidden;transform:translateY(-6px);
    transition:opacity 160ms var(--ease-snap),transform 160ms var(--ease-snap),visibility 0s linear 160ms}
  .nav-panel::before{content:"";position:absolute;left:0;right:0;top:-24px;height:24px}  /* hover bridge */
  .nav-panel.two{grid-template-columns:repeat(2,minmax(0,1fr));min-width:520px}
  .nav-panel.end{left:auto;right:-14px}
  .nav-col{display:flex;flex-direction:column}
  .nav-group.open > .nav-panel{opacity:1;visibility:visible;transform:none;transition-delay:0s}
  html:not(.nav-js) .nav-group:is(:hover,:focus-within) > .nav-panel{opacity:1;visibility:visible;
    transform:none;transition-delay:0s}
}
/* 900-1039px: the header has no CTA, the nav sits flush right, and a
   left-anchored 520px sheet would run 20px past the viewport (measured at
   1000px). Hang it from the trigger's right edge instead. */
@media(min-width:900px) and (max-width:1039px){.nav-panel.two{left:auto;right:-14px}}
@media(max-width:899px){
  /* one sheet that scrolls inside itself: an accordion taller than the screen
     inside a sticky header would leave its last links out of reach */
  nav.main.open{max-height:calc(100dvh - var(--header-h) - 76px - env(safe-area-inset-bottom));
    overflow-y:auto;overscroll-behavior:contain}
  .nav-group{flex-direction:column}
  .nav-top{width:100%;justify-content:space-between;padding:13px 6px;border-radius:0;
    border-bottom:1px solid var(--line-soft)}
  .nav-top:hover{background:none}
  .nav-group.is-current > .nav-top{background:none;color:var(--green-deep)}
  .nav-group.open > .nav-panel{display:block;padding:6px 0 10px 14px;
    box-shadow:inset 2px 0 0 var(--green-surface)}
  .nav-panel .note{padding:10px 6px 2px}
  nav.main .nav-panel a{border-bottom:0;padding-inline:6px}
}


/* P02 - PAGE HEAD =============================================================
   The top of every inner page. Decided by the D9 judge panel (22/09/2026):
     .page-head               THE OFFICIAL HEAD. Navy stone, registration marks
                              (DESIGN-SYSTEM 7.1). The breadcrumb rides inside
                              .ph-copy under the datum, as the kicker of the
                              title block, instead of an eyebrow repeating the H1.
       .ph-form   formcard #estimate in the right column
       .ph-media  a 4:3 photo in the right column
       .ph-plain  copy only, 62ch
       .ph-article  post title on the .lead step + a meta line
     .page-head.ph-photo      THE DEFAULT TREATMENT since 23/09/2026, asked for
                              by the owner: every inner head carries a
                              photograph, on the phone and on the desktop.
                              It layers onto any of the four variants above.
   Everything in the head is first fold: .fold-in only, never data-anim (the
   datum carries .no-anim). A photo in the head is the LCP: fetchpriority high,
   decoding sync, preloaded through page.json lcp_preload, never animated. */
section.page-head{--sec-pad:0px;padding:clamp(22px,3vw,34px) 0 clamp(52px,6vw,88px)}
.page-head .hero-grid{align-items:start}
.page-head .ph-copy{padding-top:clamp(10px,2vw,26px)}
/* 22ch, not 18ch: at 900px 18ch broke "Quartz / Countertops in / Boynton
   Beach" with an orphan, where the home H1 runs two lines */
.page-head h1{max-width:22ch;margin:0 0 .45em}
.page-head .lede{color:#E3E8F2}
.page-head .ticks li{color:var(--white)}
.page-head .sec-eyebrow{margin-bottom:14px}
.page-head .ph-copy > .crumbs{margin:0 0 clamp(14px,1.6vw,20px)}
/* The top-left registration mark sits 24px inside the head. Between 1081 and
   1239px the wrap gutter is narrower than the mark and the corner lands on the
   datum and the crumbs: two corners or none (site.css 90.3), so none. */
@media(max-width:1239px){.page-head .hero-marks{display:none}}
@media(max-width:1080px){.page-head .formcard{max-width:520px}}
/* 960-1080px (iPad landscape, small laptops): copy and form stay side by side
   instead of a 1,277px stacked head, with the form at the home's minimum card
   width. The third tick and the Call button come off (the topbar carries the
   number at these widths). 900-959px stacks, so the head gets the phone's
   green action before the ticks. */
@media(min-width:960px) and (max-width:1080px){
  .page-head.ph-form:not(.ph-photo) .hero-grid{grid-template-columns:minmax(0,1fr) 372px}
}
@media(min-width:900px) and (max-width:1080px){
  .page-head .hs-tick-more,.page-head .hs-call{display:none}
}
@media(min-width:900px) and (max-width:959px){
  .page-head .hs-cta-lead{display:inline-flex;max-width:360px;margin:.4em 0 0}
}
/* the lower card keeps its seal and step-lead spacing outside #get-estimate too
   (site.css gives them only inside .ge-side); same values, so nothing moves there */
.formcard > .badge{margin:0 0 14px}
.formcard .steplead{margin:0 0 14px}
/* .sec-head .sec-sub (site.css) outweighs section.dark :is(p,li), so a sub
   title on the stone came out in paper ink, nearly invisible. The home never
   puts a .sec-sub on navy, so this changes nothing there. */
section.dark .sec-head .sec-sub{color:#DCE3F0}
@media(max-width:899px){.page-head .lede{font-size:1rem;line-height:1.5}}
.page-head.ph-media .hero-grid,
.page-head.ph-plain:not(.ph-side) .hero-grid,
.page-head.ph-article:not(.ph-side) .hero-grid{grid-template-columns:minmax(0,1fr)}
@media(min-width:1081px){.page-head.ph-media .hero-grid{grid-template-columns:1.14fr .86fr}}
.page-head .ph-shot{margin:0;overflow:hidden;border-radius:var(--r-md);box-shadow:var(--e3);
  aspect-ratio:4/3;position:relative;background:var(--navy-deep)}
.page-head .ph-shot img{width:100%;height:100%;object-fit:cover}
.page-head.ph-plain:not(.ph-side) .ph-copy,
.page-head.ph-article:not(.ph-side) .ph-copy{max-width:62ch}
/* --- .ph-side: the right column of a text-only head ---------------------------
   Measured 23/09 across the 35 routes: the .ph-form heads fill the 1,116px
   hero track 668 + 404 and the .ph-media heads 611 + 461, but .ph-plain and
   .ph-article ran the copy alone in a single track capped at 62ch (558px), so
   half of the navy band was empty on 14 routes and, side by side in a contact
   sheet, those heads read as a page whose right column failed to load. The
   copy of a text-only head also had to wrap an H1 inside 558px, which forced
   four lines on /service-areas/.
   .ph-side keeps the site.css hero track (1fr + 360-404px) and puts a light
   .panel in the second cell - the same white object the form heads and the
   photo heads already put there. What goes in the panel belongs to the page:
   its own contents list (.toc) or a dl.sr-facts datasheet. Below 1081px the
   panel stacks under the copy, where a phone reads it as the disclosure it is,
   and the copy goes back to its 62ch measure. */
.page-head.ph-side .ph-copy{max-width:none}
/* the 22ch cap was set for a copy column that was the whole track; inside the
   1fr cell of .ph-side the cell is the cap (about 24ch at 1440), and 22ch was
   pushing the /service-areas/ H1 onto a fourth line for nothing */
.page-head.ph-side h1{max-width:none}
@media(max-width:1080px){
  .page-head.ph-side .hero-grid{grid-template-columns:minmax(0,1fr)}
  .page-head.ph-side .ph-copy{max-width:62ch}
  .page-head.ph-side .ph-aside{max-width:520px}
}
/* Tried and rejected on 23/09: keeping the two columns from 960 to 1080, the
   band where a .ph-form head keeps its card beside the copy. Below 1080px
   00-foundation-pages.js closes the contents disclosure (that is the
   .art-layout side-column breakpoint), so the second cell became a collapsed
   bar floating in the navy - worse than the stack. It stacks below 1081px. */
.page-head .ph-aside{align-self:start;margin:0}
/* site.css 299-304 already returns a .panel on navy to paper ink; .note is the
   one label class it does not cover, and a .toc summary is a .note. */
.page-head .ph-aside .note{color:var(--ink-faint)}
.page-head .ph-aside .toc a{color:var(--ink)}
.page-head .ph-aside .toc a:hover,
.page-head .ph-aside .toc a[aria-current="true"]{color:var(--green-deep)}
.page-head .ph-aside.toc{padding:16px 12px}
.page-head .ph-aside .sr-facts{margin:10px 0 0}
.page-head .ph-aside .sr-facts .sr-row{padding:9px 0}
/* QA final 23/09 (PENDENCIAS-PAULO P-H2, now closed). D23 took the "Free
   Estimate" chip off the phone call bar, and the 6 .ph-article posts are the
   only pages whose head carries no CTA: on a phone the route to the form was
   the foot of a 20.000px article. The byline panel now ends on the action, so
   the post reads "here is the guide, here is what to do next". It is the
   estimate voice the other 29 heads already use - no new colour, no new size. */
.page-head .ph-aside .ph-aside-cta{display:block;margin-top:14px}
@media(min-width:1081px){.page-head .ph-aside .ph-aside-cta{margin-top:18px}}
.page-head.ph-article h1{font-size:clamp(2.35rem,3.55vw,2.85rem);line-height:1.05;
  letter-spacing:-.028em;max-width:24ch}          /* the .lead step, not a new size */
.ph-meta{margin:14px 0 0;max-width:none}

/* --- .ph-photo: the head is photo-first (owner, 23/09/2026) -------------------
   "On the phone put the photo under the header and above all the text, then let
   it fade into the navy and hand over to the text blocks. On the desktop use it
   as the background of the head with a dark overlay - and keep those textures,
   those little marks, I liked them a lot."

   So there are two geometries of one idea, and the home hero is the reference
   for both (home-v2 site.css 11, .hero.photo-hero):

     >= 900px  the photograph is the ground of the whole head. Home hero box
               (min-height and padding), the black exposure veil, the navy plate
               under the copy with its green fillet, the registration marks.
     <  900px  a BAND: the datum rules first, then a bounded crop of the
               photograph, then a soft gradient into the navy, then the copy on
               plain navy. The band's top is --ph-band-top (the datum's own
               height plus the head's padding) and its height --ph-band-h; the
               datum's bottom margin is what pushes the copy past the band, so
               the rule stays above the picture exactly as he described.

   What rides ON the photograph, and why: the vein tile (section.dark::before,
   raised to z-index 1 here, where the navy heads have it at 0 behind nothing)
   and the two registration corners. Both were his ask; together they are what
   keeps a photo head reading as this site and not as a stock hero.

   Layers: photograph 0 (its own stacking context, veil in ::after) - vein 1 -
   corner marks 1 - .wrap 2.

   The overlay knob is a class on the <picture>, written by build.py from
   page.json head_photo.veil, so a bright kitchen and a dark shop both keep the
   lede legible without a line of page CSS:
       ph-veil-soft    x0.78  a photo that is already dark
       (none)          x1     the measured default
       ph-veil-strong  x1.28  a bright kitchen, a white slab wall
       ph-veil-max     x1.55  a blown-out window behind the copy
   The same class lifts the navy plate through the sibling .wrap, because the
   plate is what the words actually sit on. */
section.page-head.ph-photo{background:var(--bg-dark);overflow:hidden;display:flex;align-items:center;
  min-height:clamp(600px,78vh,760px);padding:clamp(34px,5vw,60px) 0;--ph-band-top:0px}
/* the drafting texture belongs on top of the photograph, not under it */
section.page-head.ph-photo::before{z-index:1}
.page-head.ph-photo > .wrap{z-index:2;width:100%}
.ph-bg{position:absolute;left:0;right:0;top:var(--ph-band-top);z-index:0;overflow:hidden;margin:0;
  display:block;height:calc(100% - var(--ph-band-top));--ph-veil:1}
.ph-bg img{width:100%;height:100%;object-fit:cover;object-position:var(--ph-pos,50% 55%)}
/* The veil over the photograph is BLACK, not navy (the home's rule): it only
   darkens, it must not tint. It lives on the picture so that the band on a
   phone carries its own exposure and its own dissolve into the stone. */
.ph-bg::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(0,0,0,calc(.42 * var(--ph-veil))) 0%,
    rgba(0,0,0,calc(.08 * var(--ph-veil))) 26%,
    rgba(0,0,0,calc(.58 * var(--ph-veil))) 100%)}
.ph-bg.ph-veil-soft{--ph-veil:.78}
.ph-bg.ph-veil-strong{--ph-veil:1.28}
.ph-bg.ph-veil-max{--ph-veil:1.55}
/* the plate is a sibling of the picture, so the knob reaches it through .wrap */
.ph-bg.ph-veil-soft ~ .wrap .ph-copy{--ph-plate:.94}
.ph-bg.ph-veil-strong ~ .wrap .ph-copy{--ph-plate:1.06}
.ph-bg.ph-veil-max ~ .wrap .ph-copy{--ph-plate:1.12}
/* The home plate fades to nothing at 100% so it never reads as a box. Here the
   fade is pushed into the last 16%, past the measure of the lede and the ticks:
   at 1440 the copy cell is 668px and the longest tick ends at 79% of it, so no
   word ever sits on bare photograph, and the edge is still a fade, not a box. */
.page-head.ph-photo .ph-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,calc(.90 * var(--ph-plate,1))) 0%,
    rgba(25,40,80,calc(.86 * var(--ph-plate,1))) 60%,
    rgba(25,40,80,calc(.72 * var(--ph-plate,1))) 88%,
    rgba(25,40,80,0) 100%);
  backdrop-filter:blur(2px) saturate(1.06);border-left:3px solid var(--green-surface)}
.page-head.ph-photo .ph-copy .datum{color:var(--white)}
.page-head.ph-photo h1{margin-bottom:.34em}
/* the light objects of the other three variants need to sit ON the photograph,
   not float in it: the form card already carries the home's elevation, the 4:3
   figure has e3, and the .ph-side panel gets the same lift here */
.page-head.ph-photo .ph-aside{box-shadow:var(--e3)}
/* registration marks: hidden from 1240px down on a navy head because the corner
   landed on the datum and the crumbs, but on a photo head the plate is a
   surface of its own and the corner reads as the frame of the picture, which is
   what he asked to keep. Same floor as the home hero: 1081px. */
@media(min-width:1081px) and (max-width:1239px){
  .page-head.ph-photo .hero-marks{display:block}
}
/* a post head is a title and a three-row datasheet: at the full hero floor the
   plate ended two thirds of the way up and the rest was bare photograph */
section.page-head.ph-photo.ph-article{min-height:clamp(430px,54vh,580px)}
@media(min-width:1081px){.page-head.ph-photo .hero-grid{align-items:center}}
@media(max-width:1080px){
  .page-head.ph-photo .ph-copy{margin-left:0;padding-inline:clamp(18px,4vw,28px);border-radius:var(--r-md)}
}
@media(max-width:899px){
  /* THE BAND. --ph-band-top is the datum's line: the head's own padding, the
     copy's padding, the 14px datum and the air after it. The copy is pushed
     past the band by the datum's bottom margin, so the order on the phone is
     rule, photograph, gradient, text - never text over a picture. */
  section.page-head.ph-photo{min-height:0;display:block;
    --ph-band-h:clamp(206px,27vh,268px);
    --ph-band-top:calc(clamp(34px,5vw,60px) + clamp(10px,2vw,26px) + 14px + clamp(9px,2.4vw,16px))}
  .ph-bg{height:var(--ph-band-h)}
  .ph-bg img{object-position:var(--ph-pos-phone,var(--ph-pos,50% 60%))}
  .ph-bg::after{background:
    linear-gradient(180deg,rgba(25,40,80,0) 54%,var(--bg-dark) 100%),
    linear-gradient(180deg,
      rgba(0,0,0,calc(.26 * var(--ph-veil))) 0%,
      rgba(0,0,0,calc(.05 * var(--ph-veil))) 44%,
      rgba(0,0,0,calc(.18 * var(--ph-veil))) 100%)}
  /* under the band the stone is the surface, so the plate steps aside: no
     gradient to read through, no blur, and the copy keeps the page's gutter */
  .page-head.ph-photo .ph-copy{margin-left:0;padding:clamp(10px,2vw,26px) 0 0;
    background:none;backdrop-filter:none;border-left:0;border-radius:0}
  .page-head.ph-photo .ph-copy > .datum{margin-bottom:calc(var(--ph-band-h) + clamp(16px,3.4vw,26px))}
}


/* P03 - BREADCRUMBS ============================================================
   Annotation voice. The separator is the 45 degree dimension tick of the cut
   plan, not a typographic slash. Two levels on material, service and company
   pages, three on cities and posts (pages.json). The last item is text. */
.crumbs{margin:0 0 clamp(20px,2.6vw,34px)}
.crumbs ol{display:flex;flex-wrap:wrap;align-items:center;gap:4px 0;list-style:none;margin:0;padding:0;
  max-width:none}
.crumbs li{display:inline-flex;align-items:center;margin:0;font-weight:700;font-size:.68rem;
  letter-spacing:.16em;text-transform:uppercase;line-height:1.3;color:var(--ink-faint)}
.crumbs li + li::before{content:"";width:9px;height:1.5px;margin:0 12px;background:currentColor;
  opacity:.55;transform:rotate(-45deg)}
.crumbs a{color:inherit;text-decoration:none;display:inline-block;padding:12px 0;margin:-12px 0}
.crumbs a:hover{text-decoration:underline;text-underline-offset:4px}
.crumbs [aria-current]{color:var(--ink)}
section.dark .crumbs li,section.dark .crumbs a:not(.btn){color:rgba(255,255,255,.66)}
section.dark .crumbs a:not(.btn):hover,section.dark .crumbs [aria-current]{color:var(--white)}
@media(max-width:599px){.crumbs li[aria-current]:not(:nth-child(2)){display:none}}


/* P04 - RELATED LINKS ==========================================================
   Text cards for "Related services", "Other materials", "Keep reading". One
   link per card: the title carries the anchor and its ::after covers the card.
   Use .mt-card tiles instead when every card has an honest photograph. */
.rel-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;list-style:none;
  margin:0;padding:0;max-width:none}
.rel-grid.two{grid-template-columns:repeat(2,minmax(0,1fr))}
.rel-grid.four{grid-template-columns:repeat(4,minmax(0,1fr))}
@media(max-width:1000px){.rel-grid.four{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* three cards stay three down to 900px (2 + 1 left an orphan card), then one
   column; .two and .four keep their own steps */
@media(max-width:899px){.rel-grid{grid-template-columns:minmax(0,1fr)}}
@media(max-width:600px){.rel-grid.two,.rel-grid.four{grid-template-columns:minmax(0,1fr)}}
.rel-card{position:relative;margin:0;display:flex;flex-direction:column;gap:8px;height:100%;
  padding:20px 22px 18px;background:var(--bg);border:1px solid var(--line-soft);
  border-radius:var(--r-md);box-shadow:var(--e1);
  transition:transform 280ms var(--ease-stone),box-shadow 280ms,border-color 280ms}
.rel-card > .ic{--s:28px;--sw:1.6;color:var(--ink);margin:0 0 4px}
.rel-card .note{margin:0}
.rel-card h3{font-size:1.06rem;line-height:1.25;margin:0}
.rel-link{color:inherit;text-decoration:none}
.rel-link::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit}
.rel-card p:not(.note){margin:0;font-size:.92rem;line-height:1.55;color:var(--ink-soft);max-width:none}
.rel-card .mt-cta{margin-top:auto;padding-top:10px}
@media(hover:hover){
  .rel-card:hover{transform:translateY(-3px);box-shadow:var(--e2);border-color:var(--green-surface)}
}
.rel-card:is(:hover,:focus-within) .rel-link{color:var(--green-ink);text-decoration:underline;
  text-decoration-thickness:2px;text-underline-offset:4px}
@media(prefers-reduced-motion:no-preference){
  .rel-card .mt-rule .lr-rule,.rel-card .mt-rule .lr-head,
  .post-card .mt-rule .lr-rule,.post-card .mt-rule .lr-head{transition:transform 220ms var(--ease-snap)}
  .rel-card:is(:hover,:focus-within) .mt-rule :is(.lr-rule,.lr-head),
  .post-card:is(:hover,:focus-within) .mt-rule :is(.lr-rule,.lr-head){transform:translateX(4px)}
}
section.dark .rel-card{color:var(--ink)}
section.dark .rel-card :is(h3,.rel-link){color:var(--ink)}
section.dark .rel-card p:not(.note){color:var(--ink-soft)}
section.dark .rel-card .note{color:var(--ink-faint)}


/* P05 - MID-PAGE CTA BAND ======================================================
   The foundation .plan-band (160px navy, dashed centre line) with one approved
   sentence between the seal and the button. Counts as navy in the rhythm
   budget and never touches another navy surface (DESIGN-SYSTEM 6.2, 7.13). */
.plan-band.cta-band .wrap{justify-content:space-between}
.cta-line{margin:0;flex:1 1 320px;font-weight:700;font-size:1.06rem;line-height:1.4;color:var(--white);
  max-width:48ch}
.plan-band.cta-band .badge{flex:0 0 auto;background:var(--bg-dark)}
@media(max-width:600px){.plan-band.cta-band .btn{flex:1 1 100%}}


/* P06 - LIGHTBOX ==============================================================
   Native <dialog> (Esc, focus trap and ::backdrop for free), created by
   pages.js on first use. Stock photos get the drafting plate under the photo
   (true payload: slab id + data); client work gets a plain caption. */
.lbx{width:min(1180px,94vw);max-width:none;max-height:94vh;padding:0;border:0;background:transparent;
  overflow:visible;color:var(--ink)}
.lbx::backdrop{background:rgba(25,40,80,.94)}
.lbx-sheet{margin:0;border-radius:var(--r-md);overflow:hidden;box-shadow:var(--e3);position:relative}
.lbx-stage{position:relative;background:var(--bg-dark);display:flex;align-items:center;justify-content:center;
  min-height:200px}
.lbx-stage img{width:auto;max-width:100%;height:auto;max-height:78vh;object-fit:contain;margin:0 auto}
.lbx-stage .hero-marks{inset:14px;display:block}
.lbx-plate.plate{position:relative}                   /* under the photo, not on it */
.lbx-plate.plate[hidden]{display:none}               /* .plate sets display:flex after the reset */
.lbx-cap{margin:0;max-width:none;padding:12px 16px;background:var(--bg);color:var(--ink);font-weight:600;font-size:.94rem}
.lbx-cap:empty{display:none}
.lbx-count{position:absolute;left:0;top:-34px;margin:0;color:rgba(255,255,255,.72)}
.lbx-btn{position:absolute;z-index:3;width:48px;height:48px;display:flex;align-items:center;justify-content:center;
  padding:0;border:0;border-radius:var(--r-sm);background:var(--bg-dark);color:var(--white);cursor:pointer;
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.28);transition:background 180ms var(--ease-snap)}
.lbx-btn:hover{background:var(--green-ink)}
.lbx-btn svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:butt;
  stroke-linejoin:miter}
.lbx-btn[hidden]{display:none}
.lbx-prev{left:-64px;top:50%;margin-top:-24px}
.lbx-next{right:-64px;top:50%;margin-top:-24px}
.lbx-close{right:0;top:-58px}
@media(max-width:1300px){.lbx-prev{left:8px}.lbx-next{right:8px}}
.lbx :focus-visible{outline:3px solid var(--white);outline-offset:2px}
@media(max-width:600px){.lbx{width:100vw;max-height:100vh}.lbx-sheet{border-radius:0}.lbx-close{right:8px}}
[data-lightbox]{cursor:zoom-in}


/* P07 - PROSE =================================================================
   Blog posts and the two legal pages. The .quiet step is the only h2 size prose
   may use; the h2 opens with the green segment of the datum. No italics
   (Montserrat italic is not loaded) and no data-anim on the prose. */
.prose{max-width:68ch}
.prose > :first-child{margin-top:0}
.prose :is(p,li):not(.note){font-size:1rem;line-height:1.66}
.prose :is(p,ul,ol){max-width:none}          /* the measure is the .prose column (68ch) */
.prose h2{font-size:clamp(1.45rem,1.9vw,1.72rem);line-height:1.15;letter-spacing:-.01em;font-weight:700;
  margin:2.1em 0 .6em;max-width:30ch}
.prose h2::before{content:"";display:block;width:64px;height:2px;margin:0 0 14px;
  background:var(--green-surface)}
.prose h3{margin:1.7em 0 .5em}
.prose ul{list-style:none;padding-left:1.3em}
.prose ul li{position:relative}
.prose ul li::before{content:"";position:absolute;left:-1.1em;top:.7em;width:7px;height:7px;
  background:var(--green-surface);clip-path:polygon(0 0,5px 0,7px 2px,7px 7px,0 7px)}
.prose ol{padding-left:1.6em}
.prose ol li::marker{font-weight:800;color:var(--ink-faint);font-variant-numeric:tabular-nums}
.prose a{text-decoration-thickness:1px}
.prose blockquote{margin:1.6em 0;padding:2px 0 2px 20px;border-left:3px solid var(--green-surface)}
.prose blockquote p{font-weight:600;color:var(--ink)}
.prose figure{margin:1.9em 0}
.prose figure.sec-photo{background:none;overflow:visible}
.prose figure.sec-photo > img{border-radius:var(--r-md)}
.prose figcaption.note{margin:10px 0 0}
.prose .tablewrap{margin:1.6em 0}
.prose .datum{transform:none}                 /* no data-anim in prose: the rule is simply drawn */
.prose .datum-seam{margin:2.2em 0}
.prose.legal h2{margin-top:1.8em}
.prose.legal h2::before{display:none}           /* legal text reads as a document, not a story */
.prose .prose-updated{margin:0 0 1.6em}


/* P08 - ARTICLE LAYOUT AND TABLE OF CONTENTS ====================================
   A sticky side column on a desktop that is wide AND tall enough (the same
   guard as the foundation sticky media); a <details> above the article on a
   phone. Active state = the 2px green rule of .mp-city.is-active. */
.art-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--gap-split)}
@media(min-width:1080px){
  .art-layout{grid-template-columns:minmax(0,1fr) 280px}
  .art-layout .toc{grid-column:2;grid-row:1;align-self:start}
  .art-layout > .prose{grid-column:1;grid-row:1}
}
@media(min-width:1080px) and (min-height:760px){
  .art-layout .toc{position:sticky;top:calc(var(--header-h) + 24px)}
}
.toc{padding:16px 12px}
.toc summary{display:flex;align-items:center;justify-content:space-between;min-height:44px;
  padding:0 12px;margin:0;list-style:none;cursor:pointer}
.toc summary::-webkit-details-marker{display:none}
.toc summary::after{content:"";width:8px;height:8px;margin:-4px 2px 0 0;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform 220ms var(--ease-swap)}
.toc[open] summary::after{transform:rotate(-135deg);margin-top:4px}
@media(min-width:1080px){.toc summary{cursor:default}.toc summary::after{display:none}}
.toc ol{list-style:none;margin:6px 0 0;padding:0;max-width:none}
.toc li{margin:0}
.toc a{display:flex;align-items:center;min-height:44px;padding:8px 12px;font-weight:600;font-size:.9rem;
  line-height:1.3;color:var(--ink);text-decoration:none;border-radius:0 var(--r-sm) var(--r-sm) 0}
.toc a:hover,.toc a[aria-current="true"]{color:var(--green-deep);box-shadow:inset 2px 0 0 var(--green-surface);
  background:rgba(0,166,82,.055)}


/* P09 - BLOG INDEX CARDS =======================================================
   .mcard (photo 16/10, lift on hover) + one link per card. */
.post-card{position:relative}
.post-card p.post-meta{margin:0 0 8px;font-size:.68rem;line-height:1.3;color:var(--ink-faint)}
.post-card h3{margin:0 0 .35em}
.post-link{color:inherit;text-decoration:none}
.post-link::after{content:"";position:absolute;inset:0;z-index:2}
.post-card:is(:hover,:focus-within) .post-link{color:var(--green-ink);text-decoration:underline;
  text-decoration-thickness:2px;text-underline-offset:4px}
.post-card .mt-cta{margin-top:auto;padding-top:14px}


/* P10 - PAGINATION ============================================================= */
.pager{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;
  margin:clamp(30px,4vw,48px) 0 0}
.pager ol{display:flex;gap:6px;list-style:none;margin:0;padding:0;max-width:none}
.pager li{margin:0}
.pager ol a{display:flex;align-items:center;justify-content:center;width:44px;height:44px;
  border:1px solid var(--line);border-radius:var(--r-sm);font-weight:800;color:var(--ink);text-decoration:none;
  font-variant-numeric:tabular-nums;transition:border-color 180ms var(--ease-snap)}
.pager ol a:hover{border-color:var(--green-ink)}
.pager ol a[aria-current="page"]{background:var(--green-ink);border-color:var(--green-ink);color:var(--white)}
.pager-step{display:inline-flex;align-items:center;gap:10px;min-height:44px;font-weight:800;font-size:.74rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--green-ink);text-decoration:none}
.pager-step:hover{color:var(--green-deep);text-decoration:underline;text-underline-offset:4px}
.pager-step[aria-disabled="true"]{visibility:hidden}


/* P11 - PROCESS RAIL WITH 3 OR 4 STEPS ===========================================
   The foundation rail is set in five columns with the track from 10% to 90%.
   Use class="tl pr-rail tl-3" (site.js paints only the FIRST .pr-rail). */
@media(min-width:1100px){
  .tl.tl-3{grid-template-columns:repeat(3,minmax(0,1fr))}
  .tl.tl-3 .tl-track{left:16.667%;right:16.667%}
  .tl.tl-4{grid-template-columns:repeat(4,minmax(0,1fr))}
  .tl.tl-4 .tl-track{left:12.5%;right:12.5%}
}


/* P12 - NOT FOUND SHEET ==========================================================
   A drawing sheet: the slab in plan, the missing piece in ghost dashes, a
   callout that says PIECE NOT FOUND. Draws itself once through [data-draw]. */
.nf-sheet{position:relative;margin:0 0 clamp(24px,3vw,36px);padding:clamp(16px,2vw,26px);
  background:var(--bg-alt);border-radius:var(--r-md);overflow:hidden;max-width:720px;color:var(--ink)}
.nf-sheet::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:var(--mesh);background-size:72px 72px;opacity:.5}
.nf-sheet svg{position:relative;width:100%;height:auto}
.nf-ln{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter}
.nf-ghost{fill:none;stroke:currentColor;stroke-opacity:.55;stroke-width:1.5;stroke-dasharray:6 5}
.nf-dim{fill:none;stroke:var(--green-surface);stroke-width:1.5;stroke-linecap:butt}
.nf-label{font-family:inherit;font-weight:800;font-size:11px;letter-spacing:.16em;fill:currentColor}
.nf-dot{fill:var(--green-surface)}
@media(prefers-reduced-motion:no-preference){
  .js .nf-sheet[data-draw] .nf-ln,.js .nf-sheet[data-draw] .nf-dim{stroke-dasharray:100;stroke-dashoffset:100}
  .js .nf-sheet[data-draw].is-in .nf-ln,.js .nf-sheet[data-draw].is-in .nf-dim{stroke-dashoffset:0;
    transition:stroke-dashoffset 900ms var(--ease-swap)}
  .js .nf-sheet[data-draw] .nf-label,.js .nf-sheet[data-draw] .nf-ghost{opacity:0}
  .js .nf-sheet[data-draw].is-in .nf-label,.js .nf-sheet[data-draw].is-in .nf-ghost{opacity:1;
    transition:opacity 420ms linear 700ms}
}


/* P13 - CONTACT LOCATION PLATE ====================================================
   The vector plate of DESIGN-SYSTEM 7.10: no iframe, no request, no cookie.
   Reuses the foundation map vocabulary (.map-plate, .map-grid, .map-road,
   .map-label, .map-pin.is-home) and adds the local streets, the rail line and
   the callout. The figcaption is true payload (the address), so the navy strip
   of .col-media figcaption stays. */
.loc-plate{position:relative;margin:0;background:var(--bg-alt);border-radius:var(--r-md);overflow:hidden;
  color:var(--ink)}
.loc-plate .map-svg{width:100%;height:100%;display:block}
@media(min-width:900px){.split .col-media > .loc-plate .map-svg{position:absolute;inset:0}}
@media(max-width:899px){.loc-plate{padding-bottom:40px}}   /* room for the address strip under the plate */
.loc-road{fill:none;stroke:currentColor;stroke-opacity:.16;stroke-width:9;stroke-linecap:butt}
.loc-road-edge{fill:none;stroke:currentColor;stroke-opacity:.34;stroke-width:1;stroke-linecap:butt}
.loc-street{fill:none;stroke:currentColor;stroke-opacity:.42;stroke-width:1;stroke-linecap:butt}
.loc-rail{fill:none;stroke:currentColor;stroke-opacity:.35;stroke-width:1.2;stroke-dasharray:1 5;
  stroke-linecap:butt}
.loc-water{fill:none;stroke:var(--green-surface);stroke-opacity:.35;stroke-width:2}
.loc-leader{fill:none;stroke:currentColor;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter}
.loc-callout{font-family:inherit;font-weight:800;font-size:11px;letter-spacing:.14em;fill:currentColor}
.loc-scale{fill:none;stroke:currentColor;stroke-width:1.2}
.loc-actions{margin:14px 0 0}


/* P14 - PRINT ==================================================================== */
@media print{
  .js [data-anim]{opacity:1;transform:none}
  .nav-panel,.lbx,.toc{display:none}
  section.page-head,section.page-head.ph-photo{background:none;color:var(--ink);min-height:0}
}


/* P15 - FOOTER: THE NAV'S FOUR GROUPS ========================================
   The approved footer is four columns (D7) and carried five groups of links:
   the address, Countertops, "Company" (which held Installation, Fabrication,
   Cabinetry and Sinks, all of them under Services in the header) and Service
   Area. Splitting Services out of Company needs a fifth track, and the header
   is the one that decides what a page is called. Additive: site.css keeps its
   1.4fr 1fr 1fr 1.2fr and its two phone steps, and a class of ours states the
   desktop track. */
@media(min-width:1000px){
  .f-grid.f-five{grid-template-columns:1.5fr 1.08fr 1.08fr .84fr 1.1fr;gap:28px}
}
@media(min-width:901px) and (max-width:999px){
  .f-grid.f-five{grid-template-columns:repeat(3,minmax(0,1fr))}
}


/* P16 - PHONE CALL BAR, ONE ACTION ===========================================
   Client's 14-day test (DECISOES D23): the "Free Estimate" chip is off the bar
   and Call Us takes the whole width, so the bar reads as one button instead of
   two half-width chips competing for the same thumb.

   Additive, as the house rule asks: site.css keeps .callbar{grid-template-
   columns:1fr 1fr} and .callbar a{min-height:50px}; the new geometry hangs off
   .cb-solo / .cb-primary, which site.css never mentions. Heights: 8 + 54 + 2
   (the pressed-edge shadow) + 8 = 72px, still under the 76px of bottom padding
   that site.css reserves on <900px, so nothing is covered. The safe-area
   padding stays exactly as declared in site.css.
   Contrast: --green behind --white is 4.57:1, and the pressed state goes to
   --green-ink (5.82:1) rather than only dimming with a filter. */
.callbar.cb-solo{grid-template-columns:1fr}
.callbar.cb-solo .cb-primary{min-height:54px;font-size:1.06rem;letter-spacing:.01em;
  box-shadow:0 2px 0 var(--green-ink)}
.callbar.cb-solo .cb-primary .ic{--s:21px}
.callbar.cb-solo .cb-primary:active{background:var(--green-ink);
  box-shadow:none;transform:translateY(2px)}

/* The home hero button row lost its "Browse 500+ slabs in stock" ghost button
   (D23) and its only remaining child, .hs-call, is display:none under 900px.
   An empty flex row still carries its 1.5em top margin, which showed up as a
   gap between the tick list and the form card on a phone. */
@media(max-width:899px){
  .btn-row.hs-row-solo{display:none}
}


/* P17 - OPTIONAL FIELD LABEL ==================================================
   Email stopped being required on both form cards (D24). The red asterisk went
   with the requirement; the word is what tells the visitor they may skip it,
   and it is deliberately quieter than the label it follows. */
.field label.flabel .fopt{font-weight:400;color:var(--ink-faint)}

/* ==== src/css/10-template.css ==== */
/* ============================================================================
   American Countertops & Design - inner page TEMPLATE (pages.css, part 10)
   ----------------------------------------------------------------------------
   The shared blocks of the official inner-page template, harvested from the
   two quartz prototypes (22/09/2026) and promoted out of page scope so every
   inner page builds from the same parts. docs/TEMPLATE-GUIDE.md is the manual:
   which block goes on which page type, and the markup of each one.

   Same house rules as 00: no ID selector, no !important, no property
   redeclared on a site.css selector (a modifier class of our own instead),
   tokens only (or the legitimate off-root hexes of DESIGN-SYSTEM 2), no gold,
   no olive, every block under its own prefix. Page files (p-<slug>.css) come
   after this one and may only add curation (crops) and true one-offs.

   T01 Seamed bands: sections that share one surface (.band-first/-mid/-last)
   T02 Photo focal point without page CSS (img.focal, --focal, --focal-phone)
   T03 Photo first on a phone (.col-media.media-first)
   T04 Filter bar (.fbar-*), driven by pages.js T04
   T05 Swatch catalog (.swatch-*): the 18 quartz colors, reused by /our-stock/
   T06 Two-column note block (.duo-*)
   T07 Pros and cons ledger (.ledger-*)
   T08 Comparison table that becomes a list on a phone (.cmp, .cmp-note)
   T09 Schedule of uses (.sched-*)
   T10 Callout with a mitred badge (.callout)
   T11 Heading beside a list (.hgrid) and the numbered ruled list (.numlist)
   T12 Navy pillar: head, rail on stone, note, actions (.pillar-*, .rail-navy)
   T13 Title block under a drawing sheet (.tblock-*)
   T14 Cost: table beside a drawing, the slab layout plate, price band
       (.cost-grid, .slab-plan, .sp-*, .price-*)
   T15 Gallery: one lead frame and two (.gal-lead) + lightbox buttons (.gal-open)
   T16 Reviews under a gallery (.rv-block, .rv-more)
   T17 Related cards with chips (.rel-card additions)
   T18 Get estimate: showroom facts beside the form, service-area strip
       (.ge-split.has-facts, .ge-facts, .ge-area)
   T21 Reviews widget: the owner's live reputation embed (.rw-sec, .rw-frame)
   ========================================================================== */


/* T01 - SEAMED BANDS =========================================================
   Two or three sections that argue one point share one surface (DESIGN-SYSTEM
   6.2 rule 3). The first keeps its top air, the last its bottom air, and each
   later one opens with the full-width datum instead of a new background:
     <section class="alt band-first" id="a"> ... </section>
     <section class="alt band-mid" id="b"><div class="wrap">
       <span class="datum datum-seam" aria-hidden="true" data-anim="rise"></span> ...
     <section class="alt band-last" id="c"> ... */
section.band-first{padding-bottom:clamp(26px,3.4vw,48px)}
section.band-mid{padding-top:clamp(30px,3.6vw,52px);padding-bottom:clamp(26px,3.4vw,48px)}
section.band-last{padding-top:clamp(30px,3.6vw,52px)}
/* a seam inside a section, followed by a sub-block: the air the datum's own margin gives */
.datum-seam + :is(.duo,.rv-block,.ge-area,.ledger,.hgrid){margin-top:clamp(24px,2.8vw,36px)}
section.band-mid > .wrap > .datum-seam:first-child,
section.band-last > .wrap > .datum-seam:first-child{margin:0 0 clamp(28px,3.2vw,44px)}


/* T02 - FOCAL POINT ==========================================================
   Crops are curation, not styling: the page sets them inline, so a page needs
   no CSS file just to aim a photograph.
     <img class="focal" style="--focal:58% 62%;--focal-phone:60% 40%" ...> */
img.focal{object-position:var(--focal,50% 50%)}
@media(max-width:899px){img.focal{object-position:var(--focal-phone,var(--focal,50% 50%))}}


/* T03 - PHOTO FIRST ON A PHONE ===============================================
   The first split under the page head opens on the stone on a phone: the
   photograph comes before the argument, right after the facts band. */
@media(max-width:899px){.split .col-media.media-first{order:-1}}


/* T04 - FILTER BAR ===========================================================
   Mitred chips (a chip is a seal, so the miter is allowed) over a list that
   pages.js T04 filters. Without the script the bar is not drawn and every
   item shows: no dead chips. Same skin as the stock chips of DESIGN-SYSTEM 7.3. */
.fbar{display:none;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px 24px;
  margin:0 0 clamp(22px,2.6vw,32px)}
.js .fbar{display:flex}
.fbar-chips{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0;padding:0;max-width:none}
.fbar-chips li{margin:0}
.fbar-chip{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:10px 16px 10px 14px;
  font-family:inherit;font-weight:800;font-size:.7rem;letter-spacing:.13em;text-transform:uppercase;
  line-height:1;color:var(--ink);background:var(--bg);border:0;cursor:pointer;
  box-shadow:inset 0 0 0 1.5px var(--line-strong);
  clip-path:polygon(0 0,calc(100% - var(--miter)) 0,100% var(--miter),100% 100%,0 100%);
  transition:background 180ms var(--ease-snap),color 180ms var(--ease-snap),box-shadow 180ms var(--ease-snap)}
.fbar-chip:hover{box-shadow:inset 0 0 0 1.5px var(--green-ink);color:var(--green-deep)}
.fbar-chip[aria-pressed="true"]{background:var(--green-ink);color:var(--white);box-shadow:none}
.fbar-n{font-variant-numeric:tabular-nums;font-weight:700;opacity:.72}
.fbar-count{margin:0;max-width:none}
section.alt .fbar-chip:not([aria-pressed="true"]){background:var(--bg)}
@media(max-width:599px){
  /* one swipeable row instead of three wrapped lines of chips */
  .fbar-chips{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;overscroll-behavior-x:contain;
    margin-inline:calc(var(--pad) * -1);padding:2px var(--pad);width:calc(100% + var(--pad) * 2)}
  .fbar-chips::-webkit-scrollbar{display:none}
  .fbar-chips li{flex:0 0 auto}
}


/* T05 - SWATCH CATALOG =======================================================
   Whole slabs at 2:1, one continuous grid, so every row closes: 18 colors are
   3 x 6 on a desktop and 2 x 9 on a tablet (DECISOES D15 set, shared with
   /our-stock/). The family of each color is a filter, not a heading row that
   would leave ragged half rows. Hairline on the frame so a white slab keeps
   an edge on white paper. "Manufacturer photo" sits under the frame, never on
   the stone. A color with no honest photo gets a ghost frame, never a
   stand-in picture. On a phone the grid becomes a snap rail, like the home's
   material tiles. */
.swatch-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(28px,2.8vw,38px) clamp(16px,1.8vw,24px);list-style:none;margin:0;padding:0;max-width:none}
@media(max-width:899px){.swatch-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
.swatch{position:relative;margin:0;min-width:0;display:flex;flex-direction:column}
.swatch[hidden]{display:none}
.swatch-shot{display:block;position:relative;width:100%;margin:0;padding:0;border:0;aspect-ratio:2/1;
  overflow:hidden;border-radius:var(--r-sm);background:var(--bg-alt);box-shadow:var(--e1);
  font:inherit;color:inherit}
.swatch-shot::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(25,40,80,.13)}
.swatch-shot img{width:100%;height:100%;object-fit:cover;transition:transform 700ms var(--ease-stone)}
@media(hover:hover){
  button.swatch-shot{transition:box-shadow 280ms var(--ease-stone),transform 280ms var(--ease-stone)}
  button.swatch-shot:hover{box-shadow:var(--e2);transform:translateY(-2px)}
  button.swatch-shot:hover img{transform:scale(1.04)}
}
.swatch-none{display:flex;align-items:center;justify-content:center;box-shadow:none;color:var(--ink-faint)}
.swatch-none::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:var(--mesh);background-size:36px 36px;opacity:.7}
.swatch-none::after{inset:10px;border-radius:2px;box-shadow:none;border:1.5px dashed rgba(25,40,80,.3)}
.swatch-none .ic{position:relative;--s:34px;--sw:1.4}
/* name and credit share one line only where every name fits beside its credit
   (from 1200px); below that the credit always drops under the name, so the
   cards of one row never mix one-line and two-line heads */
.swatch-head{display:flex;flex-direction:column;align-items:flex-start;gap:3px;margin:14px 0 0}
@media(min-width:1200px){
  .swatch-head{flex-direction:row;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:2px 12px}
}
.swatch-name{margin:0;font-weight:800;font-size:1.02rem;line-height:1.25;letter-spacing:-.005em;
  color:var(--ink);max-width:none}
.swatch-credit{margin:0;max-width:none}
.swatch-look{margin:5px 0 0;font-size:.9rem;line-height:1.5;color:var(--ink-soft);max-width:none}
/* the ask line speaks in the home's card-CTA voice (.mt-cta: "Explore quartz"):
   green-ink small caps and the link rule, text only, never a green fill */
.swatch-ask{align-self:flex-start;display:inline-flex;align-items:center;gap:10px;min-height:44px;
  margin-top:auto;padding-top:6px;font-weight:800;font-size:.72rem;letter-spacing:.12em;
  text-transform:uppercase;line-height:1.2;color:var(--green-ink);text-decoration:none}
.swatch-ask .mt-rule{width:34px}
.swatch-ask:hover{color:var(--green-deep);text-decoration:underline;text-underline-offset:4px}
@media(prefers-reduced-motion:no-preference){
  .swatch-ask:hover .mt-rule .lr-rule,.swatch-ask:hover .mt-rule .lr-head,
  .swatch-ask:focus-visible .mt-rule .lr-rule,.swatch-ask:focus-visible .mt-rule .lr-head{transform:translateX(4px)}
}
@media(max-width:559px){
  .swatch-grid{display:flex;gap:16px;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:4px var(--pad) 10px}
  .swatch-grid::-webkit-scrollbar{display:none}
  .swatch{flex:0 0 76%;scroll-snap-align:start}
}


/* T06 - TWO-COLUMN NOTE BLOCK ================================================
   A short sub-block under a catalog or a grid: the main paragraph under an h3
   on the left, the side notes and a ghost action on the right, a hairline
   between them. */
.duo{display:grid;grid-template-columns:minmax(0,1.14fr) minmax(0,.86fr);gap:22px var(--gap-split)}
.duo h3{margin:0 0 .55em}
.duo p{max-width:60ch}
.duo-main > :last-child,.duo-side > :last-child{margin-bottom:0}
.duo-side .btn-row{margin-top:1.1em}
@media(min-width:900px){
  .duo-side{padding:4px 0 0 clamp(20px,2.4vw,32px);border-left:1px solid var(--line)}
}
@media(max-width:899px){.duo{grid-template-columns:minmax(0,1fr)}}


/* T07 - PROS AND CONS LEDGER ==================================================
   One sheet, two columns, a hairline between them. What the material does well
   carries the green tick; what to plan around carries the tick's own mitred
   square in navy with a bar, so green stays the mark of what is in favour. */
.ledger{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));background:var(--bg);
  border:1px solid var(--line-soft);border-radius:var(--r-md);box-shadow:var(--e1)}
.ledger-col{padding:clamp(22px,2.6vw,34px) clamp(22px,2.8vw,38px);min-width:0}
.ledger-col + .ledger-col{border-left:1px solid var(--line-soft)}
.ledger h3{font-size:1.14rem;margin:0 0 1em}
.ledger .ticks{margin:0;max-width:none}
.ledger .ticks li:last-child{margin-bottom:0}
.ledger-cons{list-style:none;margin:0;padding:0;max-width:none}
.ledger-cons li{position:relative;padding-left:32px;margin-bottom:.5em;font-weight:600;font-size:.96rem;line-height:1.5}
.ledger-cons li:last-child{margin-bottom:0}
.ledger-cons li::before{content:"";position:absolute;left:0;top:.28em;width:18px;height:18px;
  background:var(--ink);clip-path:polygon(0 0,14px 0,18px 4px,18px 18px,0 18px)}
.ledger-cons li::after{content:"";position:absolute;left:5px;top:calc(.28em + 8px);width:8px;height:2px;
  background:var(--white)}
@media(max-width:759px){
  .ledger{grid-template-columns:minmax(0,1fr)}
  .ledger-col + .ledger-col{border-left:0;border-top:1px solid var(--line-soft)}
}


/* T08 - COMPARISON TABLE ======================================================
   The material in question is marked by a green rule under its column head and
   a heavier weight, never a green fill. Under 601px the sheet becomes a list:
   one block per property, one labelled line per material (labels come from
   data-col), nothing to scroll sideways.
     <div class="tablewrap cmp"><table><caption class="note">...</caption>
       <thead><tr><td></td><th scope="col" class="cmp-hl">Quartz</th>...
       <tbody><tr><th scope="row">Sealing</th><td class="cmp-hl" data-col="Quartz">Never</td>... */
.cmp{margin-top:clamp(22px,2.6vw,32px)}
.cmp table{min-width:640px}
.cmp caption{padding:14px 18px 12px;border-bottom:1px solid var(--line-soft);color:var(--ink-faint)}
.cmp thead td{background:var(--bg-alt);border-bottom:1px solid var(--line);width:20%}
.cmp thead th{width:26.66%}
.cmp thead th.cmp-hl{color:var(--ink);box-shadow:inset 0 -3px 0 var(--green-surface)}
.cmp tbody th{background:var(--bg);border-bottom:1px solid var(--line-soft);vertical-align:top}
.cmp td.cmp-hl{font-weight:700}
@media(max-width:760px) and (min-width:601px){
  .cmp tbody th,.cmp thead td{position:sticky;left:0;z-index:1;box-shadow:1px 0 0 var(--line)}
}
@media(max-width:600px){
  .cmp table{min-width:0}
  .cmp caption{display:block}
  .cmp thead{display:none}
  .cmp tbody,.cmp tr,.cmp th,.cmp td{display:block}
  .cmp tbody tr{border-top:1px solid var(--line)}
  .cmp tbody th{box-shadow:none;border:0;padding:12px 16px 8px;white-space:normal;width:auto}
  .cmp td{display:grid;grid-template-columns:96px minmax(0,1fr);gap:10px;border:0;padding:6px 16px;width:auto}
  .cmp td:last-child{padding-bottom:14px}
  .cmp td::before{content:attr(data-col);font-weight:800;font-size:.66rem;letter-spacing:.13em;
    text-transform:uppercase;color:var(--ink-faint);padding-top:3px}
  .cmp td.cmp-hl::before{color:var(--ink)}
}
.cmp-note{margin:22px 0 0;max-width:72ch;color:var(--ink-soft)}


/* T09 - SCHEDULE OF USES ======================================================
   Three cells on one rule, each opening with the datum's green origin and a
   Stone Line icon. A schedule, not a third row of cards. */
.sched{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid var(--line)}
.sched-cell{position:relative;padding:28px clamp(18px,2.4vw,34px) 6px;min-width:0}
.sched-cell:first-child{padding-left:0}
.sched-cell + .sched-cell{border-left:1px solid var(--line)}
.sched-cell::before{content:"";position:absolute;top:-1px;height:2px;width:48px;
  left:clamp(18px,2.4vw,34px);background:var(--green-surface)}
.sched-cell:first-child::before{left:0}
.sched-cell > .ic{--s:40px;--sw:1.5;color:var(--ink);margin:0 0 16px}
.sched-cell h3{margin:0 0 .45em}
.sched-cell p{margin:0;font-size:.96rem;line-height:1.6;color:var(--ink-soft);max-width:none}
@media(max-width:899px){
  .sched{grid-template-columns:minmax(0,1fr);border-top:0}
  .sched-cell,.sched-cell:first-child{padding:24px 0 22px;border-top:1px solid var(--line)}
  .sched-cell + .sched-cell{border-left:0}
  .sched-cell::before,.sched-cell:first-child::before{left:0}
}


/* T10 - CALLOUT ===============================================================
   One firm sentence behind the navy mitred seal the home already uses for
   BEFORE. Not an alert box: no left border, no tinted panel. */
.callout{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;
  margin:clamp(28px,3.2vw,40px) 0 0;padding:18px 0 0;border-top:1px solid var(--line);
  font-weight:600;color:var(--ink);max-width:none}
.callout .badge{flex:0 0 auto;background:var(--ink)}
.callout b{font-weight:800}


/* T11 - HEADING BESIDE A LIST, AND THE NUMBERED RULED LIST ===================
   .hgrid: the section head on the left, the content on the right (stacks under
   900px). .numlist: habits or steps set as a schedule, 01-06 in the
   annotation voice, one hairline per row. */
.hgrid{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:18px var(--gap-split)}
.hgrid > .sec-head{margin:0}
@media(max-width:899px){.hgrid{grid-template-columns:minmax(0,1fr)}}
.numlist{list-style:none;margin:0;padding:0;max-width:none;counter-reset:numlist;border-top:1px solid var(--line)}
.numlist li{display:grid;grid-template-columns:48px minmax(0,1fr);margin:0;padding:15px 0;
  border-bottom:1px solid var(--line);counter-increment:numlist;font-weight:600;line-height:1.5}
.numlist li::before{content:counter(numlist,decimal-leading-zero);padding-top:.28em;font-weight:800;
  font-size:.72rem;letter-spacing:.16em;color:var(--ink-faint);font-variant-numeric:tabular-nums}


/* T12 - NAVY PILLAR ===========================================================
   Head: the lead title on the left, its sub and intro on the right, standing
   on the title's baseline (stacked under 1100px). Rail: the home's five
   plates re-inked for the stone through .rail-navy. */
.pillar-head{display:grid;grid-template-columns:minmax(0,1fr);gap:0 var(--gap-split)}
.pillar-sub{font-size:1.08rem;line-height:1.55;font-weight:600}
section.dark .pillar-sub{color:#E3E8F2}
.pillar-intro > :last-child{margin-bottom:0}
@media(min-width:1100px){
  .pillar-head{grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);align-items:end}
  .pillar-head .sec-head{margin:0}
  .pillar-intro{padding-bottom:6px}
}
@media(max-width:1099px){.pillar-intro{margin-top:-10px}}
.rail-navy .tl-track{background:rgba(255,255,255,.16)}
.rail-navy .tl-step .ic{color:var(--white)}
section.dark .rail-navy .tl-step p{color:#C6CFE0}
@media(min-width:1100px){
  .tl.rail-navy{margin-top:clamp(60px,5.4vw,78px)}
  .rail-navy .pr-step{background:rgba(255,255,255,.035);box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.18)}
}
@media(min-width:1100px) and (hover:hover){
  .rail-navy .pr-step:hover{box-shadow:inset 0 0 0 1.5px var(--green-surface)}
}
@media(max-width:1099px){section.dark .rail-navy .tl-step{background:none;border-color:transparent}}
.rail-note{margin:24px 0 0}
.pillar-cta{margin-top:clamp(28px,3vw,40px)}


/* T13 - TITLE BLOCK UNDER A DRAWING SHEET =====================================
   The spec row that closes a light sheet (the edge profiles, a plan): four
   cells on one rule inside the sheet, so the drawing and its data read as one
   shop drawing. */
.tblock{position:relative;z-index:1;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  margin:clamp(18px,2.2vw,26px) 0 0;border-top:1px solid var(--line)}
.tblock-cell{min-width:0;padding:14px clamp(14px,1.6vw,20px) 2px;border-left:1px solid var(--line)}
.tblock-cell:first-child{border-left:0;padding-left:0}
/* (0,3,0): section.dark .note would otherwise paint the labels white on the light sheet */
.tblock .tblock-cell .note{margin:0 0 6px;color:var(--ink-faint)}
.tblock dd{margin:0;font-weight:600;font-size:.9rem;line-height:1.5;color:var(--ink)}
@media(max-width:899px){
  .tblock{grid-template-columns:repeat(2,minmax(0,1fr))}
  .tblock-cell:nth-child(3){border-left:0;padding-left:0}
  .tblock-cell:nth-child(n+3){border-top:1px solid var(--line);margin-top:12px}
}
@media(max-width:520px){
  .tblock{grid-template-columns:minmax(0,1fr)}
  .tblock-cell{border-left:0;padding-left:0}
  .tblock-cell + .tblock-cell{border-top:1px solid var(--line);margin-top:12px}
}


/* T14 - COST ==================================================================
   The factor table carries the reading; a drawing that explains one factor
   rides beside it on a screen wide AND tall enough (the foundation's sticky
   guard). Under it the home's financing band (.cst-fin) carries the pricing
   sentence and the green action.
   The slab layout plate (.slab-plan): a run longer than one slab, the planned
   seam in green, keynotes instead of labels on the stone. Reused wherever a
   seam or a slab limit is explained (fabrication, kitchen, remnants). */
.cost-grid{display:grid;grid-template-columns:minmax(0,1.18fr) minmax(0,.82fr);gap:22px var(--gap-split)}
.cost-grid > .tablewrap{margin:0;align-self:start}
.cost-grid > *{min-width:0}
.cost-grid > .slab-plan{align-self:start}   /* natural height: sticky, never a stretched empty sheet */
@media(min-width:980px) and (min-height:760px){
  .cost-grid .slab-plan{position:sticky;top:calc(var(--header-h) + 24px)}
}
@media(max-width:979px){
  .cost-grid{grid-template-columns:minmax(0,1fr)}
  .cost-grid .slab-plan{max-width:600px}
}
.slab-plan{margin:0;display:flex;flex-direction:column;align-items:flex-start;gap:16px;
  color:var(--ink);position:relative;overflow:hidden}
.slab-plan::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:var(--mesh);background-size:36px 36px;opacity:.35}
.slab-plan > *{position:relative}
.slab-plan .note{margin:0}
.slab-plan-svg{width:100%;height:auto}
.sp-seam{stroke:var(--green-surface);stroke-width:3;stroke-linecap:butt}
.sp-lbl{fill:currentColor;fill-opacity:.82;font-family:inherit;font-weight:800;font-size:11.5px;letter-spacing:1.4px}
.sp-keytag circle{fill:var(--bg);stroke:currentColor;stroke-width:1.3}
.sp-keytag text{fill:currentColor;font-family:inherit;font-weight:800;font-size:11px}
.sp-datum{stroke:currentColor;stroke-opacity:.18;stroke-width:2;stroke-linecap:butt}
.sp-datum-g{stroke:var(--green-surface);stroke-width:2;stroke-linecap:butt}
.sp-origin{stroke:var(--green-surface);stroke-width:1.5;stroke-linecap:butt}
@media(max-width:560px){
  /* the plate is ~310px wide here: the drawing scales down, its type must not */
  .sp-lbl{font-size:15px;letter-spacing:1px}
  .sp-keytag text{font-size:13px}
}
.sp-keys{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:8px 18px;width:100%;max-width:none}
.sp-keys li{display:flex;align-items:center;gap:10px;margin:0;font-weight:600;font-size:.86rem;
  line-height:1.3;color:var(--ink)}
.sp-key{flex:0 0 22px;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;
  border-radius:50%;box-shadow:inset 0 0 0 1.3px var(--ink);font-weight:800;font-size:.66rem;line-height:1}
@media(prefers-reduced-motion:no-preference){
  .js .slab-plan[data-draw] .sp-seam,.js .slab-plan[data-draw] .sp-dim{stroke-dasharray:100;stroke-dashoffset:100}
  .js .slab-plan[data-draw].is-in .sp-dim{transition:stroke-dashoffset 620ms var(--ease-stone) 220ms;stroke-dashoffset:0}
  .js .slab-plan[data-draw].is-in .sp-seam{transition:stroke-dashoffset 420ms var(--ease-swap) 700ms;stroke-dashoffset:0}
}
.price-band .cst-fin-grid{margin-top:clamp(22px,2.6vw,32px)}
.price-lead{margin:0;font-size:1.04rem;line-height:1.6;color:var(--ink)}
.price-act .price-fin{margin:6px 0 0;max-width:none;font-weight:600;font-size:.94rem;line-height:1.5;color:var(--ink)}
.price-act .fine{margin:0;max-width:none}


/* T15 - GALLERY: A LEAD FRAME AND TWO =========================================
   The home's frameless gallery and slab-cut signature (DESIGN-SYSTEM 3.11),
   composed as one lead frame and two stacked beside it. The lead is the
   cleanest photograph and loads eagerly with fetchpriority low (a lazy image
   under the slab-cut clip can stay blank). Under 900px the foundation rail
   takes over with the home's square frames. Each photo is a lightbox button
   (.gal-open) with data-lbx-caption: client work gets a plain caption. */
@media(min-width:900px){
  .pj-grid.gal-lead{grid-template-columns:minmax(0,1.62fr) minmax(0,1fr);grid-template-rows:auto auto;gap:24px 22px}
  .gal-lead .pj-lead{grid-row:1 / span 2;display:flex;flex-direction:column}
  .gal-lead .pj-lead .gal-item{flex:1 1 auto;aspect-ratio:auto;min-height:0}
  .gal-lead .pj-work:not(.pj-lead) .gal-item{aspect-ratio:16/9}
}
.gal-open{display:block;width:100%;height:100%;margin:0;padding:0;border:0;background:none;
  color:inherit;font:inherit}


/* T16 - REVIEWS UNDER A GALLERY ===============================================
   Real excerpts, verbatim, the home's attribution chip: no star, no score,
   no count (D13). */
.rv-block .rv-source{margin:0 0 clamp(26px,3vw,36px)}
.rv-block .rv-quote{padding:30px 28px 22px}
.rv-block .rv-text{font-size:1.06rem;line-height:1.6}
.rv-more{margin:24px 0 0;max-width:none;color:var(--ink-soft)}


/* T17 - RELATED CARDS WITH CHIPS ==============================================
   P04 .rel-card on paper for "Compare other materials" and "Related
   services": the card's own icon, the chips of the home's material tiles
   (.mt-specs) and the card CTA voice. Text only, no photo, never navy tiles
   in front of the navy close. */
.rel-card .mt-specs{margin:auto 0 0;padding-top:12px}
.rel-card .mt-specs + .mt-cta{margin-top:0}
.rel-card .mt-cta .mt-rule{width:34px}      /* "EXPLORE QUARTZITE" + rule on one line in a 900px three-up */


/* T18 - GET ESTIMATE: SHOWROOM FACTS AND SERVICE AREA ==========================
   Rendered by {{GET_ESTIMATE}} when page.json get_estimate.showroom is true
   (partial src/partials/ge-facts.html) and get_estimate.area is set. From
   900px the facts sit under the copy, in the left column, so it ends level
   with the form card; on a phone they come AFTER the form (DOM order), so
   the form is never pushed down by the address list. */
@media(min-width:900px){
  .sec-split .ge-split.has-facts{grid-template-rows:auto 1fr;row-gap:clamp(22px,2.6vw,30px)}
  .ge-split.has-facts > .col-text{grid-column:1;grid-row:1}
  .ge-split.has-facts > .ge-side{grid-column:2;grid-row:1 / span 2}
  .ge-split.has-facts > .ge-facts{grid-column:1;grid-row:2;align-self:start}
}
.ge-facts{margin:0}
.ge-facts .sr-row{border-top:1px solid rgba(255,255,255,.14)}
.ge-facts dd{color:var(--white)}
.ge-facts dd a{font-weight:700}
@media(min-width:1200px){
  /* address and hours side by side: phone and email are already in the line
     under the form, so the facts add only what is not there */
  .ge-facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:28px}
  .ge-facts .sr-row{grid-template-columns:minmax(0,1fr);gap:2px;align-content:start;padding:14px 0 12px}
  .sr-facts.ge-facts .sr-row:first-child{border-top:1px solid rgba(255,255,255,.14)}
  .ge-facts dt{padding-top:0}
}
.ge-area{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px 28px}
.ge-area p{margin:0;max-width:78ch}


/* T19 - SHARED BLOCKS INSIDE AN ARTICLE COLUMN ===============================
   Promoted from six page files in the 23/09 synchronisation round: every post
   built the same three modifiers by hand, so they live here under neutral
   names and the page files that copied them were deleted.

   1. .cmp inside .prose. T08 keeps a 640px floor so three material columns
      never crush. Inside the article column (.prose, 68ch, about 612px) that
      floor forced a sideways scroll on a desktop for a sheet that carries
      only two materials. The phone list mode (under 601px) is untouched.
   2. .cmp last row. site.css clears only "tr:last-child td", so the row header
      kept its bottom rule and drew a half-width line across the rounded bottom
      corner of the sheet (seen at 1440, 1100, 1024 and 900) on every page that
      uses .cmp, not only inside a post.
   3. .toc under 1080px. Below the side-column breakpoint the table of contents
      sits above the article; at full wrap width it ran past the 68ch text
      column it indexes. It now ends where the text ends. From 1080px it is the
      280px side column, untouched.
   4. .sp-keys on a narrow phone. Two key columns under the slab plate leave
      about 150px per label; one column keeps each key on its line. Pages that
      need the single column earlier keep their own wider breakpoint. */
.prose .cmp table{min-width:0}
.cmp tbody tr:last-child th{border-bottom:0}
.art-layout > .toc{max-width:68ch}
@media(min-width:1080px){
  .art-layout > .toc{max-width:none}
}
@media(max-width:560px){
  .sp-keys{grid-template-columns:minmax(0,1fr)}
}

/* 5. A drawing's own lists inside .prose. P07 gives every list in the article
      column the reading skin (indent, mitred marker, body size); the profile
      names under the edge ruler and the keys under the slab plate are
      annotation, not a reading list, so they keep the drawing's metrics.
      Promoted from two post files in the same round. */
.prose :is(.fab-edges,.slab-plan) :is(ul,ol){padding-left:0}
.prose .fab-edge-names li::before{content:none}
.prose .sp-keys li{font-size:.86rem;line-height:1.3}


/* T20 - CITY CHIPS: THE LINK LOOKS LIKE A LINK ================================
   site.css 22 styles .citychips a and .citychips span with one rule and only
   :hover tells them apart, so on /contact/ five of twelve chips open a page
   and seven are plain text with no resting difference - and on a touch screen
   there is no hover at all. Additive only (site.css is D3-frozen): the link
   keeps the green ink of every other link on the site plus the 2px green rule
   the nav panel uses for "you are here", and the plain city drops to the note
   ink so it reads as a label. */
.citychips a{color:var(--green-deep);border-color:var(--green-surface);
  box-shadow:inset 2px 0 0 var(--green-surface)}
.citychips span{color:var(--ink-soft)}
.citychips a:hover{background:rgba(0,166,82,.055)}
section.dark .citychips a{color:var(--green-on-navy);border-color:rgba(255,255,255,.34);
  box-shadow:inset 2px 0 0 var(--green-surface)}
section.dark .citychips span{color:rgba(255,255,255,.62)}


/* T21 - REVIEWS WIDGET =========================================================
   The owner's own reputation embed ({{REVIEWS}}, src/partials/reviews-widget.html).
   It replaces the hand-picked .rv-quote cards: his widget pulls the Google and
   Facebook reviews and keeps itself current, so the site stops typing quotes,
   stops choosing which customer gets published, and never writes a score or a
   count of its own. Everything a visitor reads inside the frame is served by
   the widget; everything around it is ours.

   The frame is the house's light surface, the same one .panel gives the form
   card and the aside: paper, soft rule, medium radius, e1. It reserves its
   height until the widget reports its own (pages.js T21 releases .rw-frame with
   .is-live), because the block sits above the close on most pages and a late
   iframe would push the CTA down under the reader's thumb.

   .rw-offline is the one line we own: it is revealed only if the widget never
   arrives (blocked script, no network, tracking blocker), and it links to the
   Google Business Profile instead of pretending to quote it. */
.rw-sec .sec-head{margin-bottom:clamp(22px,2.6vw,32px)}
.rw-frame{position:relative;background:var(--bg);border:1px solid var(--line-soft);
  border-radius:var(--r-md);box-shadow:var(--e1);padding:clamp(14px,1.8vw,22px);
  min-height:var(--rw-min,500px);overflow:hidden;transition:min-height 180ms linear}
/* measured 23/09 on the live embed: 458px of widget at 1440, 914px at 390. The
   reserve matches it, so the close below never moves when the widget lands; and
   the moment the widget reports its own height the reserve is dropped, so a
   longer or a shorter list costs nothing either way. */
.rw-frame.is-live{min-height:0}
/* nothing came: the box collapses onto the one line we own instead of leaving
   half a screen of empty paper above the call to action */
.rw-frame.is-offline{min-height:0}
.rw-frame.is-offline iframe{display:none}
.rw-frame iframe{display:block;width:100%;border:0}
/* the reserved box is not empty while it waits: the same hairline grid the
   drafting surfaces use, so a slow widget reads as a sheet being filled in */
.rw-frame::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:var(--mesh);opacity:.6;transition:opacity 180ms linear}
.rw-frame.is-live::before{opacity:0}
.rw-frame > *{position:relative;z-index:1}
.rw-offline{margin:0;padding:clamp(18px,2.4vw,30px) 0;text-align:center;max-width:none;
  font-size:.92rem;line-height:1.55;color:var(--ink-soft)}
.rw-offline[hidden]{display:none}
section.dark .rw-frame{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.14)}
@media(max-width:899px){
  .rw-frame{--rw-min:930px;padding:10px}
}

/* ==== src/css/20-launcher.css ==== */
/* ============================================================================
   American Countertops & Design - floating launcher (pages.css, part 20)
   ----------------------------------------------------------------------------
   Loaded after 00-foundation-pages.css and 10-template.css, before the page
   files. Same house rules as every other file here: no ID selector, no
   !important, tokens only, no new hex, nothing redeclared on a site.css
   selector (a class of ours instead).

   P18 Floating launcher (.lnch-*) - the round navy bubble, the chooser card,
       and the one rule that hides GoHighLevel's own bubble

   Markup: src/partials/launcher.html   Behaviour: src/js/20-launcher.js

   THE STACK AT THE BOTTOM OF THE SCREEN (why z-index 55 and not 60)
     60  .callbar         the fixed full-width "Call Us" bar, phones only
     55  .lnch            us
     50  header           sticky, and the phone drawer lives inside it
      2  .formcard .fnav  the sticky Back / Continue row of the lead form
   The launcher is above the header on purpose (it has to clear a sticky bar at
   the top) and below the call bar on purpose (a phone's thumb owns that strip).
   Because the drawer and the form navigation sit BELOW us in the stack, the
   script stands the launcher down whenever either of them is on screen; the
   geometry here only guarantees it never lands on the call bar.
   ========================================================================== */


/* P18 - THE FIXED CORNER =====================================================
   column-reverse puts the card above the button while keeping the button first
   in the DOM, which is the order aria-expanded / aria-controls wants.
   The whole block starts with [hidden] in the partial: without pages.js there
   is no chooser to open, so there must be no button either. */
.lnch{position:fixed;right:clamp(20px,4vw,32px);bottom:24px;z-index:55;
  display:flex;flex-direction:column-reverse;align-items:flex-end;gap:12px;
  /* --lnch-lift is written by the script when the hero form's sticky Continue
     row reaches this corner (laptop widths, 1100-1260): the launcher steps up
     over it instead of disappearing for the whole first screen. */
  transform:translateY(calc(-1 * var(--lnch-lift, 0px)));
  transition:opacity 220ms var(--ease-stone),transform 220ms var(--ease-stone)}
.lnch[hidden]{display:none}

/* Stood down: the form's Back/Continue row, the footer's legal line, the open
   phone drawer or the GHL panel itself is holding this corner.
   It fades but does NOT move: the script decides by comparing rectangles, and
   a launcher that shifts while it hides would change the answer it was just
   given and flicker between the two states. */
.lnch.is-down{opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity 180ms var(--ease-stone),visibility 0s linear 180ms}


/* P18 - THE BUTTON ===========================================================
   Navy surface, white line work, one green ring and one green shape inside the
   icon. It is deliberately NOT green: the call bar and the header CTA are the
   two green buttons of this site, and a third one would read as a third offer.
   #00A652 on #192850 is 4.5:1, which clears the 3:1 a non-text graphic needs.
   58px square, so the touch target clears 44px at every width.
   The hairline of white at 22% is what keeps the disc readable as a disc when
   the page has scrolled onto the navy footer, where a navy button on a navy
   band would otherwise be only its green ring. On paper it is invisible. */
.lnch-btn{position:relative;display:grid;place-items:center;width:58px;height:58px;
  padding:0;border:0;border-radius:50%;cursor:pointer;
  background:var(--bg-dark);color:var(--white);
  box-shadow:var(--e3),0 0 0 1.5px rgba(255,255,255,.22),
             inset 0 0 0 2px var(--green-surface);
  transition:background 180ms var(--ease-snap),transform 180ms var(--ease-snap),
             box-shadow 180ms var(--ease-snap)}
@media(hover:hover){
  .lnch-btn:hover{background:var(--navy-lift);transform:translateY(-2px)}
}
.lnch-btn:active{transform:translateY(1px) scale(.98);transition-duration:60ms}
.lnch-btn[aria-expanded="true"]{background:var(--navy-deep)}

/* site.css gives every :focus-visible outline a border-radius of 2px, which on
   a round button turns it square the moment it is tabbed to. Same specificity,
   later file: the circle and the row corners keep their own radius. The green
   ring steps aside while the green focus outline is up, so the button never
   wears two rings at once. */
.lnch-btn:focus-visible{border-radius:50%;box-shadow:var(--e3)}
.lnch-opt:focus-visible{border-radius:var(--r-sm)}

/* The two glyphs share one grid cell and cross-fade: speech plates at rest,
   a cut cross while the card is open, so the button is also the close control. */
.lnch-glyph{grid-area:1/1;--s:26px;--sw:1.85;
  transition:opacity 160ms var(--ease-snap),transform 220ms var(--ease-swap)}
.lnch-glyph-shut{opacity:0;transform:rotate(-45deg) scale(.72)}
.lnch-btn[aria-expanded="true"] .lnch-glyph-open{opacity:0;transform:rotate(45deg) scale(.72)}
.lnch-btn[aria-expanded="true"] .lnch-glyph-shut{opacity:1;transform:none}

/* The idle state: four slow green rings, once, a beat after the page settles.
   Four and then silence - the header CTA already pulses forever, and two
   things breathing at the same time is a carnival, not a drawing. */
.lnch-ring{grid-area:1/1;width:58px;height:58px;border-radius:50%;pointer-events:none}
@media(prefers-reduced-motion:no-preference){
  .js .lnch-ring{animation:lnch-pulse 2600ms var(--ease-stone) 1600ms 4 both}
}
.lnch-btn[aria-expanded="true"] .lnch-ring{animation:none}
@keyframes lnch-pulse{
  0%{box-shadow:0 0 0 0 rgba(0,166,82,.45)}
  62%{box-shadow:0 0 0 15px rgba(0,166,82,0)}
  100%{box-shadow:0 0 0 0 rgba(0,166,82,0)}
}


/* P18 - THE CHOOSER CARD =====================================================
   White plaquette, opening from the button's corner. The drop shadow is a
   filter and not a box-shadow so it keeps following the sheet whatever shape
   the sheet takes. */
.lnch-card{width:min(344px,calc(100vw - 40px));
  filter:drop-shadow(0 12px 26px rgba(25,40,80,.22)) drop-shadow(0 2px 5px rgba(25,40,80,.10));
  opacity:0;transform:translateY(10px) scale(.97);transform-origin:100% 100%;
  transition:opacity 180ms var(--ease-stone),transform 230ms var(--ease-stone)}
.lnch-card[hidden]{display:none}
.lnch-card.is-open{opacity:1;transform:none}
.lnch-sheet{background:var(--white);color:var(--ink);border-radius:var(--r-md);
  padding:17px 17px 15px;box-shadow:inset 0 0 0 1px var(--line-soft)}

.lnch-eyebrow{margin:0;font-size:.62rem;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-faint);line-height:1.2}
.lnch-title{margin:5px 0 13px;font-size:1.02rem;font-weight:800;line-height:1.25;
  color:var(--ink);max-width:none}


/* P18 - THE TWO CHOICES ======================================================
   One row each, 56px tall, and nothing loads until one of them is pressed.
   The mitred tile is where the house shape shows up: the same cut top-right
   corner as .badge, at icon scale. */
.lnch-opts{display:flex;flex-direction:column;gap:8px}
.lnch-opt{display:flex;align-items:center;gap:12px;width:100%;min-height:56px;
  padding:9px 11px;text-align:left;text-decoration:none;cursor:pointer;
  font-family:inherit;color:var(--ink);background:var(--bg);
  border:1px solid var(--line);border-radius:var(--r-sm);
  transition:background 180ms var(--ease-snap),border-color 180ms var(--ease-snap),
             box-shadow 180ms var(--ease-snap)}
.lnch-opt[hidden]{display:none}
@media(hover:hover){
  .lnch-opt:hover{background:var(--bg-alt);border-color:var(--line-strong)}
}
.lnch-opt:active{background:var(--bg-alt)}

/* Used last in this browsing session: a green edge, nothing louder. The script
   also lands the focus here when the card opens. */
.lnch-opt.is-last{box-shadow:inset 3px 0 0 var(--green-surface)}

/* The cut is 8px and not the 10px of --miter: the shape is read against the
   width of the thing it is cut from, and a 10px bite out of a 40px square is
   a folded page corner, not the house's chamfer. */
.lnch-tile{--tile-cut:8px;display:flex;align-items:center;justify-content:center;
  width:40px;height:40px;flex:0 0 40px;background:var(--bg-alt);color:var(--ink);
  box-shadow:inset 0 0 0 1.5px var(--line);
  clip-path:polygon(0 0,calc(100% - var(--tile-cut)) 0,100% var(--tile-cut),100% 100%,0 100%);
  transition:background 180ms var(--ease-snap),box-shadow 180ms var(--ease-snap)}
.lnch-tile .ic{--s:22px;--sw:1.8}
@media(hover:hover){
  .lnch-opt:hover .lnch-tile{background:var(--green-wash);
    box-shadow:inset 0 0 0 1.5px var(--green-surface)}
}

.lnch-txt{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}
.lnch-name{font-weight:700;font-size:.95rem;line-height:1.2}
.lnch-sub{font-size:.775rem;line-height:1.32;color:var(--ink-soft)}
.lnch-go{--s:16px;--sw:2;color:var(--ink-faint);
  transition:transform 180ms var(--ease-snap),color 180ms var(--ease-snap)}
@media(hover:hover){
  .lnch-opt:hover .lnch-go{transform:translateX(2px);color:var(--green-ink)}
}


/* P18 - STATUS AND THE SAFETY NET ============================================
   .lnch-msg is the live region the script writes "Opening the chat..." into.
   .lnch-fail only appears when the third-party script never answered. */
.lnch-msg{margin:11px 0 0;font-size:.775rem;line-height:1.35;color:var(--ink-soft)}
.lnch-msg:empty{display:none}
.lnch-fail{margin:9px 0 0;font-size:.775rem;line-height:1.4;color:var(--ink-soft)}
.lnch-fail[hidden]{display:none}
.lnch-link{color:var(--green-ink);font-weight:700}


/* P18 - GOHIGHLEVEL'S OWN BUBBLE =============================================
   The widget draws its launcher inside a shadow root and exposes no ::part for
   it, so the only handle from outside is the data-active attribute it mirrors
   onto the host element. Hidden while closed means our button is the only
   launcher on the page; visible while open is the panel itself.
   The class is put on <html> by 20-launcher.js and only when the partial's
   data-hide-native flag is on, so this can be switched off without a rebuild
   of the stylesheet. */
.lnch-hide-lc chat-widget:not([data-active="true"]){visibility:hidden;pointer-events:none}


/* P18 - BY WIDTH =============================================================
   Under 900px the call bar owns the bottom of the screen: 72px of bar plus the
   safe area, inside the 76px site.css reserves on the body. The launcher sits
   16px above that reserve, so the clear gap to the top of the bar is 20px -
   wider than the 10px margin the script keeps around protected elements, which
   is what stops the two rules from arguing with each other. */
@media(max-width:899px){
  .lnch{bottom:calc(76px + env(safe-area-inset-bottom) + 16px);gap:10px}
  .lnch-btn,.lnch-ring{width:54px;height:54px}
  .lnch-glyph{--s:25px}
  .lnch-sheet{padding:15px 15px 13px}
  .lnch-title{font-size:.98rem;margin-bottom:11px}
}
@media(max-width:380px){
  .lnch-sub{font-size:.74rem}
}
@media print{
  .lnch{display:none}
}

/* ==== src/css/p-404.css ==== */
/* ============================================================================
   /404.html only (DESIGN-SYSTEM 7.12, TEMPLATE-GUIDE 6 row "404").
   The page has no page head, so its first section carries the H1 beside the
   shared P12 drawing sheet. What the shared blocks do not cover lives here,
   scoped to .pg-404 under the page prefix .pnf- (BUILD.md 3). Tokens only.
   ========================================================================== */

/* #not-found stands where every other inner page has its page head, so it
   keeps the head's rhythm (P02): the same air under the header, the datum on
   the same line, the H1 on the same step and measure. */
.pg-404 section.pnf-top{padding:clamp(32px,5vw,60px) 0 clamp(52px,6vw,88px)}
.pg-404 .pnf-copy .sec-eyebrow{margin-bottom:14px}
.pg-404 .pnf-copy h1{max-width:22ch;margin:0 0 .45em}
/* no one-word last line ("need." at 900px, "Road." at 960px) */
.pg-404 :is(.pnf-copy .lede,.pnf-help p){text-wrap:pretty}

/* The split is as tall as the copy: the photo floor of the fill rule
   (--fill-min) is for photographs, and here it only added empty board. The
   drawing rides in the middle of its sheet, which is also the container its
   labels are sized against. */
.pg-404 .pnf-split{--fill-min:0px}
.pg-404 .pnf-sheet{display:flex;flex-direction:column;justify-content:center;container-type:inline-size}
/* Stacked under 900px the column runs to 860px and P12 caps the sheet at
   720: the drawing came out 540px tall on a tablet. Held near its desktop
   size instead (the .slab-plan does the same under 980px).
   The stack stays on the foundation's own 899px and is NOT pulled up to
   1023px. From 900 to 1023 the sheet does keep the height of the taller title
   column (395px of sheet around a 243px drawing at 900), but stacking there
   was measured worse: at 1000px the sheet takes its 480 cap and leaves about
   456px of the row unused beside it, half the screen white, against 51-76px
   of slack inside a sheet that is drawn as a drafting sheet anyway. */
@media(max-width:899px){.pg-404 .pnf-sheet{max-width:480px}}

/* Callouts sized against the sheet, not the viewport (the .cst-lbl pattern of
   the cost plate). The viewBox is 360 wide, so P12's 11 user units grow and
   shrink with the sheet: they read at 12px only while the sheet is near 400.
   Three steps hold the label between 11 and 13px on screen at every width.
   Measured content box of the sheet -> label on screen:
     420+  stacked under 900px, the sheet takes its full 480 cap (box 444-448)
           -> 10 units = 12.4px. This step is NOT dead code: it only looks
           unreachable if you measure the two-column range alone.
     ---   two columns, 1100 to 1440 (box 375-411) -> P12's 11 = 11.7-12.6px.
     370-  the narrow two-column band and the phone (box 308-354) -> 13 =
           11.1-12.8px.
     300-  the smallest phones -> 14. */
@container (min-width:420px){.pg-404 .pnf-lbl{font-size:10px}}
@container (max-width:370px){.pg-404 .pnf-lbl{font-size:13px}}
@container (max-width:300px){.pg-404 .pnf-lbl{font-size:14px}}
.pg-404 .pnf-leader{stroke-width:1.2}

/* Eight links, two rows of four. The foundation grid (auto-fit, 210px) left
   a three-and-one row between 600 and 960px: two by two there, one column
   under 521px (the foundation's own phone rule), every tile as tall as its
   row, and no second top margin under the section head.
   Four per row from 1080px, which is where the home's own .svc-index already
   goes four-up: at 1080 each tile is about 246px and "Countertop Installation"
   still holds one line. Kept off below that, where the label wraps. */
.pg-404 .pnf-index{margin-top:0}
@media(min-width:521px){.pg-404 .pnf-index{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(min-width:1080px){.pg-404 .pnf-index{grid-template-columns:repeat(4,minmax(0,1fr))}}
.pg-404 .pnf-index a{height:100%}

/* #help: from 960px the facts start on the paragraph's first line (the
   list's own top margin and first-row padding would drop them 23px). Under
   960px the main column is too narrow for the two buttons on one line and the
   side one for the address, so the note block stacks and the facts drop their
   hairline and sit under the buttons. "I-95" never breaks at its hyphen. */
@media(min-width:960px){
  .pg-404 .pnf-help .sr-facts{margin-top:0}
  .pg-404 .pnf-help .sr-row:first-child{padding-top:0}
}
.pg-404 .pnf-nowrap{white-space:nowrap}
@media(max-width:959px){
  .pg-404 .pnf-help{grid-template-columns:minmax(0,1fr)}
  .pg-404 .pnf-help .duo-side{border-left:0;padding-left:0}
}

/* ==== src/css/p-_styleguide.css ==== */
/* /_styleguide/ only - demo helpers, scoped to the page (BUILD.md prefix rule) */
.pg-_styleguide .sg-label{position:relative;background:var(--bg-alt);border-block:1px solid var(--line);padding:10px 0}
.pg-_styleguide .sg-label .note{margin:0;color:var(--ink);max-width:none}
.pg-_styleguide .sg-h1{font-weight:800;font-size:clamp(2.05rem,4.7vw,3.15rem);line-height:1.04;
  letter-spacing:-.03em;color:var(--white);max-width:18ch;margin:0 0 .45em;text-wrap:balance}
.pg-_styleguide .sg-h1.sg-h1-article{font-size:clamp(2.35rem,3.55vw,2.85rem);line-height:1.05;
  letter-spacing:-.028em;max-width:24ch}
.pg-_styleguide .sg-thumbs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
.pg-_styleguide .sg-thumb{display:block;width:100%;padding:0;border:0;border-radius:var(--r-sm);overflow:hidden;
  aspect-ratio:4/3;background:var(--bg-dark)}
.pg-_styleguide .sg-thumb img{width:100%;height:100%;object-fit:cover;transition:transform 600ms var(--ease-stone)}
@media(hover:hover){.pg-_styleguide .sg-thumb:hover img{transform:scale(1.04)}}
@media(max-width:600px){.pg-_styleguide .sg-thumbs{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* #sg-icons - the Stone Line set, three sizes per card, then the set on navy */
.pg-_styleguide .sg-icons{list-style:none;margin:0;padding:0;max-width:none;display:grid;
  grid-template-columns:repeat(auto-fill,minmax(186px,1fr));gap:14px}
.pg-_styleguide .sg-icon{border:1px solid var(--line);border-radius:var(--r-sm);padding:16px 16px 14px;background:var(--bg)}
.pg-_styleguide .sg-icon.sg-new{border-color:var(--ink)}
.pg-_styleguide .sg-icon .fine{margin:6px 0 0;color:var(--ink-soft)}
.pg-_styleguide .sg-icon-sizes{display:flex;align-items:flex-end;gap:14px;margin:0 0 12px;color:var(--ink)}
.pg-_styleguide .sg-icon-name{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;margin:0;max-width:none;font-weight:700}
.pg-_styleguide .sg-icon-name code{font-size:.85rem;white-space:nowrap}
.pg-_styleguide .sg-s40 .ic{--s:40px;--sw:1.5}
.pg-_styleguide .sg-s24 .ic{--s:24px;--sw:1.75}
.pg-_styleguide .sg-s20 .ic{--s:20px;--sw:1.9}
.pg-_styleguide .sg-s16 .ic{--s:16px;--sw:2}
.pg-_styleguide .sg-icons-navy{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;margin:18px 0 0;
  padding:18px 20px;border-radius:var(--r-sm);background:var(--bg-dark);color:var(--white)}

/* ==== src/css/p-about.css ==== */
/* p-about.css - /about/ only. One subject: the proportion of the storefront
   photograph in the page head.
   ============================================================================
   1 - the head photograph ----------------------------------------------------
   .ph-shot (00-foundation-pages.css, P02) is 4:3. At 1081px and up the head
   grid gives the right column a 461px slot beside a copy column that runs
   561px tall here, so a 4:3 frame stopped 215px short and opened a pocket of
   empty navy under the picture - the hole /financing/ closed on its drawing
   with .fin-sheet, and the last one still published on this site.

   19:23 (0.826) is the slot, and it is also the crop that was cut for it
   (1900x2300 of A-00-recorte-nativo.jpg): the frame fills the column without
   scaling the photograph and without object-fit throwing away the lettering.

   Below 1081px the head is a single column, the picture runs the full width of
   the wrap and landscape is the right frame, so the foundation 4:3 comes back
   and object-fit:cover takes the middle band of the same file. Scoped to this
   page: /service-areas/ puts a photograph in the same slot beside a shorter
   copy column, where 4:3 still fits. */
.pg-about .page-head .ab-shot{aspect-ratio:19/23}
@media(max-width:1080px){
  .pg-about .page-head .ab-shot{aspect-ratio:4/3}
}


/* The showroom split fills instead of sticking (see the note in body.html).
   site.css caps a filling media column at --fill-max (720px) and this text
   column runs past 900px, the same case .kb-media and .fab-media answer on the
   home with exactly this line. */
.pg-about .ab-fill{--fill-max:none}

/* #how-we-work is a title beside a six-item list: at the foundation .9fr/1.1fr
   the title column is 470px wide for three short lines and ends 400px above
   the list. A narrower title column gives the list the room and shrinks the
   empty half. Same idea as .hgrid on the quartz page, different proportion. */
@media(min-width:900px){
  .pg-about .ab-hgrid{grid-template-columns:minmax(0,.62fr) minmax(0,1.38fr)}
}

/* ==== src/css/p-backsplash-installation-boynton-beach.css ==== */
/* ============================================================================
   /backsplash-installation-boynton-beach/ - page file (scope .pg-backsplash-
   installation-boynton-beach). One true one-off plus two small typesetting
   fixes; everything else on the page is template markup.

   1. The slab layout plate (T14 .slab-plan) was drawn for paper, beside the
      cost table of the quartz page. On this page it is the drawing sheet of
      the navy pillar (#same-slab), where the quartz page puts its edge sheet.
      So the plate here:
        - takes the pillar sheet's skin (the drafting-board tint and margins of
          the quartz page's .fab-edges sheet, no card shadow on the stone);
        - keeps its annotation ink: section.dark .note would paint the caption
          and the title-block labels white on the light sheet, and the .panel
          re-ink would grey the keys;
        - lays the drawing beside its title block from 900px, one cell per row,
          so the drawing keeps the plate's own scale (its type is sized for a
          ~450px plate) instead of stretching across the whole measure. Under
          900px the template's own title block rules apply (2 x 2, then 1).
   2. #heights: the balanced H2 must not break "Full-Height" on its hyphen.
   3. #cuts: the two practical notes stand clear of the list's last rule.
   ========================================================================== */
.pg-backsplash-installation-boynton-beach .bs-sheet{margin:clamp(30px,4vw,54px) 0 0;
  padding:clamp(16px,2vw,26px);background:var(--bg-alt);border:0;box-shadow:none}
/* (0,3,0) beats section.dark .note (0,2,1) on the light sheet */
.pg-backsplash-installation-boynton-beach .bs-sheet .note{color:var(--ink-faint)}
/* the keys keep the plate's ink, as on paper: (0,3,2) ties the .panel re-ink on navy and comes later */
.pg-backsplash-installation-boynton-beach .bs-sheet ol.sp-keys li{color:var(--ink)}
.pg-backsplash-installation-boynton-beach .bs-sheet .tblock dd{text-wrap:balance}
.pg-backsplash-installation-boynton-beach .bs-sheet-grid{display:grid;grid-template-columns:minmax(0,1fr);
  gap:4px 0;width:100%}
.pg-backsplash-installation-boynton-beach .bs-sheet-draw{display:flex;flex-direction:column;gap:16px;min-width:0}
@media(min-width:900px){
  .pg-backsplash-installation-boynton-beach .bs-sheet-grid{grid-template-columns:repeat(2,minmax(0,1fr));
    column-gap:clamp(28px,3.6vw,52px)}
  .pg-backsplash-installation-boynton-beach .bs-sheet .tblock{grid-template-columns:minmax(0,1fr);
    margin:0;align-self:end}
  .pg-backsplash-installation-boynton-beach .bs-sheet .tblock-cell{border-left:0;padding:14px 0 12px}
  .pg-backsplash-installation-boynton-beach .bs-sheet .tblock-cell + .tblock-cell{border-top:1px solid var(--line)}
}

/* #heights: "Full-Height" never splits on its hyphen in the balanced H2 */
.pg-backsplash-installation-boynton-beach .bs-nowrap{white-space:nowrap}
/* #faq: the continuation block is the same list, so it starts without a rule */

/* #cuts: the two practical notes stand clear of the list's last rule */
.pg-backsplash-installation-boynton-beach .bs-notes{margin-top:clamp(16px,1.8vw,22px)}
.pg-backsplash-installation-boynton-beach .bs-notes .fine{margin:0 0 .6em}
.pg-backsplash-installation-boynton-beach .bs-notes .fine:last-child{margin-bottom:0}

/* ==== src/css/p-bathroom-vanity-tops-boynton-beach.css ==== */
/* ============================================================================
   /bathroom-vanity-tops-boynton-beach/ - page file (scope .pg-bathroom-vanity-tops-boynton-beach)
   ----------------------------------------------------------------------------
   Five one-offs on shared blocks. Four go through a modifier class of this
   page; the fifth (.rw-sec) cannot, because the reviews widget is a shared
   partial with no slot for a page class - it is scoped to .pg- like every
   other selector here. Three of them are the ones the
   kitchen page carries (.kit-mats, .kit-lead, .kit-callout, .kit-plan);
   promotion to the template is asked in NOTES.md, and this file goes away
   once it lands.
   .vt-mats     P04 .rel-grid.four holding the four material cards with the
                T17 chips and CTA. From 1001 to 1199px the four-up cards are
                about 245px wide: the CTA and its rule broke over two lines
                and the chips stacked. Two by two until four cards fit (the
                foundation already goes two-up at 1000px).
   .vt-lead     one lead-in sentence between a section head and the block
                under it (the material cards, the sink schedule): without air
                under it the sentence read as part of the first card or cell.
                It takes the gap the filter bar uses above a grid. Nothing
                else: text-wrap:balance was dropped so this one-off stays
                identical to the kitchen's .kit-lead (balanced lines made the
                sentence three short lines under a wider .sec-sub, and the
                section opening lost its left-aligned rhythm).
   .vt-callout  T10 .callout with a sentence longer than the line beside the
                badge: the text dropped under the badge and left the badge
                alone on its line. It now wraps beside the badge wherever
                30ch fit, and never ends on a one-word line.
   .rw-sec      the ONE selector here that is not a modifier of this page, and
                the reason is that its markup is a shared partial this page
                cannot add a class to. The reviews widget is the middle
                section of the paper band #projects -> #reviews -> #faq, but
                render_reviews only offers a seamed band on the drafting
                board (" alt band-mid"), and a board here would sit one white
                section away from the #materials/#sinks/#remnants band, which
                TEMPLATE-GUIDE 4.2 forbids. So the surface stays paper and the
                page gives it the band's own padding, the two clamps of T01
                section.band-mid, verbatim: measured at 1440 the rule that
                closes #projects sat 225px above the REVIEWS eyebrow instead
                of the ~124px the rest of the band uses. Promotion of a paper
                band surface to build.py is asked in NOTES.md; this rule goes
                away the day it lands.
   .vt-plan     T14 .slab-plan beside a T11 .hgrid text column that runs
                longer than the sheet: the sheet rides along with the
                reading on a screen wide and tall enough (the guard T14 uses
                for the cost plate), so no empty column is left under it.
                On a phone the two-column key grid broke the longer keys
                over two lines beside one-line keys: one key per line.
   ========================================================================== */
@media(min-width:1001px) and (max-width:1199px){
  .pg-bathroom-vanity-tops-boynton-beach .vt-mats{grid-template-columns:repeat(2,minmax(0,1fr))}
}
.pg-bathroom-vanity-tops-boynton-beach .rw-sec{padding-top:clamp(30px,3.6vw,52px);
  padding-bottom:clamp(26px,3.4vw,48px)}
.pg-bathroom-vanity-tops-boynton-beach .vt-lead{margin:0 0 clamp(22px,2.6vw,32px)}
.pg-bathroom-vanity-tops-boynton-beach .vt-callout > span:last-child{flex:1 1 30ch;min-width:0;text-wrap:balance}
@media(min-width:900px) and (min-height:760px){
  .pg-bathroom-vanity-tops-boynton-beach .vt-plan{position:sticky;top:calc(var(--header-h) + 24px)}
}

/* ==== src/css/p-blog.css ==== */
/* /blog/ only - scoped to the page (BUILD.md prefix rule).
   The topic reading lists: the ruled numbered list of T11 (.numlist), one
   guide per row. The whole row is the link, the way .post-link covers a card:
   the anchor text stays the exact title, its ::after spans the row. Title,
   date in the annotation voice and the home's link rule on one line; where
   the column is narrow (900-1099px beside the heading, under 600px) the date
   and the rule drop under the title together, never the rule alone.
   Under the list, a "Related pages" label over the home's .svc-index chips:
   two per row only where both labels fit on one line, and every chip of a
   row as tall as its neighbour. */
/* H1: "Boynton Beach" stays whole from 600px up (in the 62ch column of the
   .ph-plain head every width from 600 to 1440 splits the city name), and lets
   go below that. Unbreakable on a phone the H1 takes a 4th line and the second
   head button ends up under the fixed .callbar; broken it is 3 lines and the
   row clears the bar at 390 and at 360. Same trick as .pnf-nowrap / .bs-nowrap,
   only reversed by width. */
.pg-blog .ph-copy h1 .bl-nw{white-space:normal}
@media(min-width:600px){.pg-blog .ph-copy h1 .bl-nw{white-space:nowrap}}

.pg-blog .bl-list li{position:relative}
.pg-blog .bl-row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:"t d r";
  align-items:center;gap:4px 18px}
.pg-blog .bl-link{grid-area:t;color:var(--ink);font-weight:700;line-height:1.4;text-decoration:none}
.pg-blog .bl-link::after{content:"";position:absolute;inset:0}
.pg-blog .bl-list li:hover .bl-link{color:var(--green-ink);text-decoration:underline;
  text-decoration-thickness:2px;text-underline-offset:4px}
.pg-blog .bl-date{grid-area:d;margin:0;white-space:nowrap}
.pg-blog .bl-row .mt-rule{grid-area:r;color:var(--green-ink)}
@media(min-width:900px) and (max-width:1099px),(max-width:599px){
  .pg-blog .bl-row{grid-template-columns:auto minmax(0,1fr);grid-template-areas:"t t" "d r";gap:6px 16px}
}
@media(prefers-reduced-motion:no-preference){
  .pg-blog .bl-row .mt-rule :is(.lr-rule,.lr-head){transition:transform 220ms var(--ease-snap)}
  .pg-blog .bl-list li:hover .mt-rule :is(.lr-rule,.lr-head){transform:translateX(4px)}
}
.pg-blog .bl-rel{margin:clamp(24px,2.8vw,34px) 0 0}
.pg-blog .bl-chips{margin-top:12px;grid-template-columns:repeat(auto-fit,minmax(262px,1fr))}
.pg-blog .bl-chips a{height:100%}

/* ==== src/css/p-cabinetry.css ==== */
/* ============================================================================
   /cabinetry/ - page file (scope .pg-cabinetry)
   ----------------------------------------------------------------------------
   One-offs on shared blocks, each through a modifier class of this page (no
   template selector is redeclared). Promotion of .cab-doors to the template
   as a neutral ".swatch-grid.is-tall" is asked in NOTES.md (the sinks page
   shows upright product photos too); this block goes away once it lands.

   .cab-doors   T05 .swatch-grid holding cabinet doors instead of slabs. A
                door is upright (3:5): at the slab's 2:1 frame only the flat
                middle of the door would show, and shaker, raised panel and
                slab doors would look alike. Upright frames three across
                would be 600px tall each, so the grid runs five columns on a
                desktop (four, then three below) and each card borrows the
                grid's rows (subgrid):
                in one row, every name, credit, description and ask line
                starts on the same line even when one name wraps. On a phone
                the template's snap rail stays, a card and a half in view.
   .cab-fin     the financing line and its disclaimer under the rail, as one
                note: two small paragraphs, tight.
   .cab-facts   the showroom's address and hours in the note beside the
                text: the directions line sits under the address, softer.
   .cab-plan    T14 .slab-plan with four keys, two of them long: one key per
                line where the plate is narrow.
   ========================================================================== */

/* .cab-doors ------------------------------------------------------------- */
/* the column count keeps every card at least as wide as its credit line
   ("Manufacturer photo", ~190px in the note voice): 5 from 1100px, 4 from
   900px, 3 from 560px; the phone rail shows a card and a half */
.pg-cabinetry .cab-doors{grid-template-columns:repeat(5,minmax(0,1fr));
  gap:clamp(30px,2.8vw,40px) clamp(14px,1.6vw,20px)}
@media(min-width:900px) and (max-width:1099px){
  .pg-cabinetry .cab-doors{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media(min-width:560px) and (max-width:899px){
  .pg-cabinetry .cab-doors{grid-template-columns:repeat(3,minmax(0,1fr))}
}
.pg-cabinetry .cab-doors .swatch-shot{aspect-ratio:3/5}
.pg-cabinetry .cab-doors .swatch-none .ic{--s:40px}
/* the ask line does not fit one line beside a door: let the rule follow the
   last word instead of standing beside a two-line block */
.pg-cabinetry .cab-doors .swatch-ask{display:block;text-wrap:balance}
.pg-cabinetry .cab-doors .swatch-ask .mt-rule{display:inline-block;vertical-align:-3px;margin-left:8px}
@media(min-width:560px){
  @supports (grid-template-rows:subgrid){
    /* five rows, not four: the name and the credit each get their own, so a
       name that wraps to two lines ("Liberty Shaker Karamel") no longer
       pushes its "Manufacturer photo" line below the rest of the row */
    .pg-cabinetry .cab-doors .swatch{display:grid;grid-row:span 5;grid-template-rows:subgrid;row-gap:0}
    .pg-cabinetry .cab-doors .swatch[hidden]{display:none}
    .pg-cabinetry .cab-doors .swatch-head{display:grid;grid-row:span 2;grid-template-rows:subgrid;row-gap:0}
    .pg-cabinetry .cab-doors .swatch-ask{align-self:end;justify-self:start}
  }
}
@media(max-width:559px){
  .pg-cabinetry .cab-doors .swatch{flex-basis:60%}
  /* the snap rail is a flex row, so subgrid never applies here and the cards
     of the rail are all "one row": reserve the taller of the two head lines
     on every card, which is what subgrid does per row on the grid. Without
     it, a name that wraps ("Liberty Shaker Karamel") pushes its own credit
     and descriptor 23px below the neighbour's, and the two-line credit of a
     Fabuwood card pushes its descriptor another 16px. Name: 2 lines x 1.25 =
     2.5em; credit: 2 lines x 1.3 = 2.6em. The ask line is already bottom
     aligned (margin-top:auto on equal-height flex items) */
  .pg-cabinetry .cab-doors .swatch-name{min-height:2.5em}
  .pg-cabinetry .cab-doors .swatch-credit{min-height:2.6em}
}

/* .cab-fin --------------------------------------------------------------- */
.pg-cabinetry .cab-fin .fine{margin:0;max-width:78ch}
.pg-cabinetry .cab-fin .fine + .fine{margin-top:.45em}

/* .cab-facts ------------------------------------------------------------- */
.pg-cabinetry .cab-facts{margin:0}
.pg-cabinetry .cab-facts-sub{display:block;font-weight:400;font-size:.92rem;line-height:1.5;color:var(--ink-soft)}

/* .cab-plan -------------------------------------------------------------- */
@media(max-width:1199px){
  .pg-cabinetry .cab-plan .sp-keys{grid-template-columns:minmax(0,1fr)}
}

/* ==== src/css/p-countertop-installation-boynton-beach.css ==== */
/* ============================================================================
   /countertop-installation-boynton-beach/ - page CSS (scope .pg-countertop-installation-boynton-beach)
   ----------------------------------------------------------------------------
   Only what the template blocks do not give. Tokens only, no ID selector, no
   !important, nothing redeclared on a foundation selector (every rule hangs
   on a class of this page: .ins-*).
   ========================================================================== */

/* 1 - A sub-block that opens with its own h3 inside running text
   (#templating, #waterfall-backsplash, #workmanship). The foundation zeroes
   every heading margin (h3{margin:0}), so an h3 set after a paragraph, or
   over a .numlist, sits on it with no air. .ins-sub takes the air the T01
   seam gives a sub-block, and its h3 the .duo h3 spacing. */
.pg-countertop-installation-boynton-beach .ins-sub{margin-top:clamp(24px,2.8vw,36px)}
.pg-countertop-installation-boynton-beach .sec-head + .ins-sub,
.pg-countertop-installation-boynton-beach .hgrid > .ins-sub{margin-top:0}
.pg-countertop-installation-boynton-beach .ins-sub h3{margin:0 0 .6em}
.pg-countertop-installation-boynton-beach .ins-sub > :last-child{margin-bottom:0}
/* the checkpoint label ("Space check.") reads as a label: the list itself is
   already set at 600, so "bolder" alone was invisible */
.pg-countertop-installation-boynton-beach .ins-sub .numlist b{font-weight:800}

/* the gallery's head inside #workmanship: after the seam, before the frames */
.pg-countertop-installation-boynton-beach .ins-gal-head{margin-bottom:clamp(22px,2.6vw,32px)}
.pg-countertop-installation-boynton-beach .ins-gal-head p{color:var(--ink-soft)}

/* 2 - The slab layout sheet in the #waterfall-backsplash split. Inside a
   .col-media the foundation pins the figcaption to the bottom of the frame
   as the navy title block (like the home's #fabrication sheet), so the sheet
   keeps room for it under the key list. Stacked (under 980px) the sheet
   stops at the width #cost gives the same plate, instead of a drawing the
   size of the screen. */
.pg-countertop-installation-boynton-beach .ins-plan{padding-bottom:66px}
@media(max-width:979px){
  .pg-countertop-installation-boynton-beach .ins-plan{max-width:600px}
}

/* 3 - "Full-Height" never breaks at its own hyphen in the section title */
.pg-countertop-installation-boynton-beach .ins-keep{white-space:nowrap}

/* ==== src/css/p-countertop-remnants-boynton-beach.css ==== */
/* ============================================================================
   /countertop-remnants-boynton-beach/ - page file (scope .pg-countertop-remnants-boynton-beach)
   ----------------------------------------------------------------------------
   Three one-offs on shared blocks, each through a modifier class of this page
   (no template selector is redeclared). Promotion to the template is asked in
   NOTES.md; this file goes away once it lands.
   .rm-steps    T11 .numlist holding steps, not habits: every row opens with
                a short bold lead. At the list's own 600 weight the lead did
                not read apart from its sentence, so the lead takes a line of
                its own in ink (like the h3 over a .sched-cell) and the
                sentence drops to body weight in the soft ink.
   .rm-callout  T10 .callout with two sentences instead of one: the text
                wrapped under the badge and left one word on its last line.
                It now sits beside the badge wherever 30ch fit next to it,
                and still drops under the badge on a phone.
   .rm-plan     T14 .slab-plan with three keys, two of them long: in the
                two-column key grid they broke over two or three lines at
                every width under 1200px. One key per line.
   .rm-piece    the leftover piece inside the T14 .slab-plan drawing. Same
                green line as the template's .sp-seam and the same draw, but
                .sp-seam means a PLANNED SEAM: reusing that name for the
                remnant would make a shared class carry two meanings, and the
                fill would have to be an inline rgba() literal in the markup.
                The template promotion asked in NOTES.md is a neutral .sp-piece
                beside .sp-seam; this rule goes away with it.
   ========================================================================== */
.pg-countertop-remnants-boynton-beach .rm-steps li > span{font-weight:400;color:var(--ink-soft)}
.pg-countertop-remnants-boynton-beach .rm-steps b{display:block;margin:0 0 3px;font-weight:800;color:var(--ink)}
.pg-countertop-remnants-boynton-beach .rm-callout > span:last-child{flex:1 1 30ch;min-width:0}
.pg-countertop-remnants-boynton-beach .rm-plan .sp-keys{grid-template-columns:minmax(0,1fr)}
.pg-countertop-remnants-boynton-beach .rm-piece{fill:rgba(0,166,82,.14);stroke:var(--green-surface);
  stroke-width:3;stroke-linecap:butt}
@media(prefers-reduced-motion:no-preference){
  .js .pg-countertop-remnants-boynton-beach .slab-plan[data-draw] .rm-piece{stroke-dasharray:100;stroke-dashoffset:100}
  .js .pg-countertop-remnants-boynton-beach .slab-plan[data-draw].is-in .rm-piece{
    transition:stroke-dashoffset 420ms var(--ease-swap) 700ms;stroke-dashoffset:0}
}

/* ==== src/css/p-countertops-installation-in-palm-beach-gardens.css ==== */
/* ============================================================================
   /countertops-installation-in-palm-beach-gardens/ - curation only.
   Four one-offs the shared blocks do not cover. Every selector is scoped to
   .pg-countertops-installation-in-palm-beach-gardens and every value is a
   foundation value, copied from the rule it stands next to, so the page keeps
   the rhythm of the home and of /quartz-countertops-boynton-beach/.
   ========================================================================== */

/* 23/09: the .pbg-facts override that stacked label over value is gone. It was
   the only city page that did not render the shared .sr-facts row, and two
   shorter labels ("Single-family and townhomes", "Owner-occupied") fit the
   foundation's 92px label column, so the five cities now read as one set. */

/* The numbered gate-and-HOA steps sit under their own small heading, and the
   closing line under the list. Headings carry margin:0 in the foundation, and
   .numlist opens on a rule, so both need their air declared here. */
.pg-countertops-installation-in-palm-beach-gardens .pbg-steps h3{
  margin:clamp(22px,2.4vw,30px) 0 14px}
.pg-countertops-installation-in-palm-beach-gardens .pbg-steps .numlist + p{
  margin:clamp(18px,2vw,24px) 0 0}

/* The four material cards are a sub-block of #outdoor-kitchens, opened by the
   seam datum: the same margin the template gives .duo and .hgrid after a seam. */
.pg-countertops-installation-in-palm-beach-gardens .pbg-stones{
  margin-top:clamp(24px,2.8vw,36px)}
.pg-countertops-installation-in-palm-beach-gardens .pbg-stones h3{
  margin:0 0 clamp(18px,2vw,24px)}

/* Two up in the 1001-1199 window, exactly like the two sibling pages that hit
   the same wrap (p-kitchen-countertops-boynton-beach.css and
   p-bathroom-vanity-tops-boynton-beach.css). The template sized the four-up
   CTA for "EXPLORE QUARTZITE" (17 characters): the card's CTA breaks in two
   between 1001 and 1199px, where the four-up card is at its narrowest. From
   1200px up the four cards fit four across with "SEE BACKSPLASHES" (16), so
   the page goes back to the house's four-up instead of staying two-up alone
   at every desktop width. Measured at 1200/1240/1280/1366/1440. */
@media(min-width:1001px) and (max-width:1199px){
  .pg-countertops-installation-in-palm-beach-gardens .pbg-stones .rel-grid.four{
    grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* The line under the gallery, with the voice and the air the template gives
   .rv-more on a page that closes its gallery with reviews. */
.pg-countertops-installation-in-palm-beach-gardens .pbg-gal-note{
  margin:clamp(20px,2.4vw,28px) 0 0;max-width:none;color:var(--ink-soft)}

/* ==== src/css/p-countertops-installation-in-west-palm-beach.css ==== */
/* p-countertops-installation-in-west-palm-beach.css - CITY page.

   The only thing this page adds to the template. The navy pillar #rentals
   carries two named sub-blocks under .pillar-head ("Durable surfaces" and
   "Planned turnarounds"), and T12 has no step between the pillar head and a
   sub-block: .pillar-head ends with no bottom margin above 1100px, a bare h3
   has margin 0, and .hgrid only inherits a top margin when a .datum-seam
   precedes it. Two seam rules inside one pillar would cut the stone into three
   slabs, so the page uses one spacer class instead, on the same scale as the
   seam it replaces.

   .wpb-sub > .tl then replaces the rail's own generous top margin: the h3 is
   the rail's label and has to sit with it, not a whole band away.

   Asked for the template in
   src/pages/countertops-installation-in-west-palm-beach/NOTES.md: if another
   page needs the same step, it belongs in 10-template.css under a neutral
   name. */

.pg-countertops-installation-in-west-palm-beach .wpb-sub{margin-top:clamp(34px,4.4vw,58px)}
.pg-countertops-installation-in-west-palm-beach .wpb-sub > .tl{margin-top:clamp(18px,2vw,26px)}

/* #materials closes with three paragraphs under the four cards (the outdoor
   note, the financing line, its fine print). A paragraph carries no top
   margin, so the first one sat on the shadow of the card row. */
.pg-countertops-installation-in-west-palm-beach .rel-grid + p{margin-top:clamp(26px,3vw,38px)}

/* #rentals: the pillar H2 is the longest heading the page was given verbatim
   by pages.json ("Rental and Investment Properties: Durable Surfaces, Planned
   Turnarounds", 70 characters). site.css caps .sec-head.lead h2 at 20ch, which
   here is 511px at 1440 - 11px short of "Rental and Investment" (522px), so the
   balancer spent two whole lines on two short orphans ("Rental and" /
   "Investment") and the heading ran to 5 lines and 268px. Measured in the
   browser at 1440/1280/1100/1024/900: at 26ch the first line holds "Rental and
   Investment", the heading is 4 lines and 215px at 1440, and the longest line
   leads instead of trailing. The cap still applies (the column is narrower than
   26ch above 1100), so nothing about the measure of the other headings changes;
   the page's other .sec-head.lead (#get-estimate) is deliberately left alone
   (the override hangs off .wpb-lead, on the #rentals head only: verify.py does
   not allow an id selector in a page stylesheet).
   Asked for the template in NOTES.md: if another page inherits a 70-character
   H2, the 20ch cap is what should move, in site.css/10-template.css. */
.pg-countertops-installation-in-west-palm-beach .wpb-lead h2{max-width:26ch}

/* ==== src/css/p-financing.css ==== */
/* p-financing.css - /financing/ only. One subject: the shop drawing that
   stands in the page head where the other .ph-media pages put a photograph.
   ============================================================================
   1 - the sheet -------------------------------------------------------------
   .ph-media (00-foundation-pages.css, P02) is built around a 4:3 photograph.
   Two things were measured with a 4:3 sheet here:

     at 1440px the head copy column runs 590px and the sheet was 339px, so
     328px of empty navy opened under the drawing - the pocket of air this
     redesign removes everywhere else;

     under 1081px the head grid is a single column, so the sheet took the whole
     wrap: a 1,000px-wide drawing above the fold at 1024px.

   The modifier class leaves the foundation rule alone and gives the sheet the
   proportion of the drawing plus a ceiling. It is scoped to this page because
   /about/ and /service-areas/ put a real photograph in the same slot, where
   4:3 is the right frame. */
.pg-financing .page-head .fin-sheet{aspect-ratio:4/5}
@media(max-width:1080px){
  .pg-financing .page-head .fin-sheet{max-width:440px}
}

/* 1-bis - the 900-1080px band, after the head became photo-first (23/09/2026)
   ---------------------------------------------------------------------------
   The 440px ceiling above was written for a NAVY head: from 900 to 1080px the
   foundation collapses .ph-media to one column, the sheet sat left at 440px and
   the rest of the row was navy - and navy beside navy is invisible.

   With a photograph as the ground that same emptiness became a 430x570px panel
   of bare slab to the right of the drawing, and the head grew to 1,345px tall
   at 900px. Measured, not guessed: it was the one width where this head read as
   a picture with text dumped on it.

   So in that band only, and only when the head actually carries a photograph,
   the two columns come back: the copy plate keeps the reading measure and the
   sheet fills the right of the frame. Nothing here reaches the phone, where the
   photograph is a band at the top and the stone is the surface again, so the
   440px ceiling still governs below 900px. */
@media(min-width:900px) and (max-width:1080px){
  .pg-financing .page-head.ph-photo .hero-grid{grid-template-columns:minmax(0,1fr) minmax(0,.78fr);
    align-items:center}
  .pg-financing .page-head.ph-photo .fin-sheet{max-width:none}
}

/* 2 - the type on the sheet -------------------------------------------------
   The drawing scales with its column, so its annotation must not scale with
   it: below 560px the sheet is about 320px wide and 11.5 user units would
   render at 9px. Same move, same breakpoint and same reason as .sp-lbl on the
   slab layout plate (10-template.css, T14). The sizes are user units of the
   viewBox, not pixels; the dimension breaks in body.html are cut wide enough
   for the larger step.

   Since 23/09 the same condition happens a second time, higher up: in the
   900-1000px band the two columns of 1-bis put the sheet at 353-395px, the same
   width the phone gives it. Same rule, same values, one more range in the query
   - above 1000px the sheet is back to full scale and needs nothing. */
.pg-financing .fin-lbl{fill:currentColor;fill-opacity:.82;font-weight:800;
  font-size:11.5px;letter-spacing:1.2px}
.pg-financing .fin-tb-dt{fill:currentColor;fill-opacity:.55;font-weight:800;
  font-size:9px;letter-spacing:1.5px}
.pg-financing .fin-tb-dd{fill:currentColor;font-weight:700;font-size:11.5px}
@media(max-width:560px),(min-width:900px) and (max-width:1000px){
  .pg-financing .fin-lbl{font-size:12.5px;letter-spacing:1px}
  .pg-financing .fin-tb-dt{font-size:10.5px;letter-spacing:1.2px}
  .pg-financing .fin-tb-dd{font-size:13.5px}
}

/* ==== src/css/p-granite-countertops-boynton-beach.css ==== */
/* p-granite-countertops-boynton-beach.css - MATERIAL page.

   The only thing this page adds to the template, and it exists because of one
   measured defect in #finishes.

   T12 aligns the two columns of .pillar-head by their end (align-items:end,
   >=1100px). That reads well when the columns are close in height - on the
   reference /quartz-countertops-boynton-beach/ #process they start on the same
   line. Here the right column (.pillar-intro, two paragraphs) is 168px taller
   than the left (eyebrow + a two-line H2) at 1100px, so the left column drops
   to the bottom of the row and the green datum is left alone over empty navy:
   the measured space between the datum and the eyebrow is 212px at 1100 and
   136px at 1440, against 32-61px on the other eleven sections of the page.

   Shortening the copy does not fix it: measured in the browser, dropping the
   whole redundant clause of the R14 paragraph is worth 59px of the 168px
   difference at 1100 (30px of 89px at 1440), which would still leave a 147px
   gap; and that clause is the hedge that keeps the finish claim inside R14
   while R14 is unconfirmed.
   So the fix is where the defect is - the alignment - and it is scoped to this
   page.

   Asked for the template in
   src/pages/granite-countertops-boynton-beach/NOTES.md: if another material
   page ends up with the same imbalance, .pillar-head should start its columns
   on the same line in 10-template.css and this file goes away. */

@media(min-width:1100px){
  .pg-granite-countertops-boynton-beach .pillar-head{align-items:start}
  .pg-granite-countertops-boynton-beach .pillar-head .pillar-intro{padding-bottom:0}
}

/* ==== src/css/p-how-custom-countertop-fabrication-works.css ==== */
/* ============================================================================
   /how-custom-countertop-fabrication-works/ - page file (after 10-template)
   ----------------------------------------------------------------------------
   Only what is specific to this post is left here. The shared fixes it used to
   carry (the article table of contents, the comparison sheet inside .prose and
   the annotation lists of a drawing) were promoted to 10-template.css T19 in
   the 23/09 synchronisation round.
   ========================================================================== */

/* On a phone the slab plate is ~270-300px wide, so the drawing scales to about
   0.6 and its keynote digits and dimension label fall under 9px (T14 already
   raises their font size). Each keytag and the label grow around their own
   centre instead (fill-box), so nothing moves off its leader line; the keys
   list goes to one column so "Sink and faucet cutouts" keeps one line. */
@media(max-width:560px){
  .pg-how-custom-countertop-fabrication-works .prose .slab-plan .sp-keytag :is(circle,text),
  .pg-how-custom-countertop-fabrication-works .prose .slab-plan .sp-lbl{transform-box:fill-box;transform-origin:center}
  .pg-how-custom-countertop-fabrication-works .prose .slab-plan .sp-keytag :is(circle,text){transform:scale(1.35)}
  .pg-how-custom-countertop-fabrication-works .prose .slab-plan .sp-lbl{transform:scale(1.2)}
}
@media(max-width:479px){
  .pg-how-custom-countertop-fabrication-works .prose .sp-keys{grid-template-columns:minmax(0,1fr)}
}

/* tel: links in the main column never break inside the number (the shared
   "Prefer to talk first? Call (561) 200-5555" line and the call in the last
   paragraph sit one character from the wrap at 360px). Same rule as the
   sibling posts. */
.pg-how-custom-countertop-fabrication-works main a[href^="tel:"]{white-space:nowrap}

/* ==== src/css/p-how-to-save-money-on-countertops.css ==== */
/* /how-to-save-money-on-countertops/ only. The table-of-contents width fix this
   file used to carry was promoted to 10-template.css T19.
   tel: links in the main column never break inside the number: at 390px the
   panel line "Call (561) 200-5555" split as "200-" / "5555". */
.pg-how-to-save-money-on-countertops main a[href^="tel:"]{white-space:nowrap}

/* ==== src/css/p-kitchen-countertops-boynton-beach.css ==== */
/* ============================================================================
   /kitchen-countertops-boynton-beach/ - page file (scope .pg-kitchen-countertops-boynton-beach)
   ----------------------------------------------------------------------------
   Six one-offs on shared blocks, each through a modifier class of this page
   (no template selector is redeclared). Promotion to the template is asked in
   NOTES.md; this file goes away once it lands.
   .kit-mats     P04 .rel-grid.four holding the four material cards with the
                 T17 chips and CTA. From 1001 to 1199px the four-up cards are
                 about 245px wide: "EXPLORE QUARTZITE" and its rule broke over
                 two lines and the chips stacked. Two by two until four
                 cards fit (the foundation already goes two-up at 1000px).
   .kit-lead     one lead-in sentence between the section head and a T09
                 .sched: without air under it the sentence read as part of
                 the first cell. It takes the gap the filter bar uses above
                 a grid.
   .kit-callout  T10 .callout with a sentence longer than the line beside the
                 badge: from 900 to ~1300px the text dropped under the badge
                 and the badge stood alone on its line. It now wraps beside
                 the badge wherever 30ch fit (same fix as .rm-callout on the
                 remnants page).
   .kit-plan     T14 .slab-plan keys: on a phone the two-column key grid
                 broke "Seating overhang" over two lines beside a one-line
                 key. One key per line under 561px (the remnants page does
                 the same at every width with .rm-plan).
   .kit-nobr     keeps "in-house" whole in the second page-head tick: at
                 360-390px it split after the hyphen ("in-" / "house").
   .kit-isl      T11 .hgrid in #islands carrying text on the left and the
                 .slab-plan sheet on the right (the Remnants recipe pairing,
                 section 6 of the guide). The sheet keeps its natural height
                 while three paragraphs run beside it, so from 900 to 1099px
                 the wider 1.1fr column emptied out under the drawing: at
                 900px the sheet ended around 400px above the text and the
                 reading column fell to ~355px (about 42 characters a line),
                 the tightest measure on the page. One column until 1100px,
                 where the leftover is ~155px: the sheet drops under the text
                 exactly as it already does on a phone.
   ========================================================================== */
@media(min-width:1001px) and (max-width:1199px){
  .pg-kitchen-countertops-boynton-beach .kit-mats{grid-template-columns:repeat(2,minmax(0,1fr))}
}
.pg-kitchen-countertops-boynton-beach .kit-lead{margin:0 0 clamp(22px,2.6vw,32px)}
.pg-kitchen-countertops-boynton-beach .kit-callout > span:last-child{flex:1 1 30ch;min-width:0}
.pg-kitchen-countertops-boynton-beach .kit-nobr{white-space:nowrap}
@media(max-width:1099px){
  .pg-kitchen-countertops-boynton-beach .kit-isl{grid-template-columns:minmax(0,1fr)}
}

/* ==== src/css/p-marble-countertops-boynton-beach.css ==== */
/* ============================================================================
   /marble-countertops-boynton-beach/ - one rule.
   ========================================================================== */

/* The slab photograph at the top of the #slabs side column. .duo-side is a
   text column, so the frame needs its own rounding, its own bottom air and a
   caption in the note voice; everything else (lightbox button, focal) is
   shared. */
.pg-marble-countertops-boynton-beach .mb-slab{margin:0 0 clamp(16px,2vw,22px)}
.pg-marble-countertops-boynton-beach .mb-slab img{width:100%;height:auto;display:block;
  border-radius:var(--r-md);box-shadow:var(--e1)}
.pg-marble-countertops-boynton-beach .mb-slab figcaption{margin:8px 0 0}

/* ==== src/css/p-our-stock.css ==== */
/* ============================================================================
   /our-stock/ - the slab catalog (pages.css, page part)
   ----------------------------------------------------------------------------
   The .stk- prefix is the one this page may declare unscoped (BUILD.md 3,
   tools/verify.py PAGE_PREFIXES), so the catalog can later be reused as a strip
   on another page. Everything else is scoped with .pg-our-stock.

   House rules of the file: no ID selector, no !important, tokens only (no hex
   at all here), no property redeclared on a foundation selector - a modifier
   class of our own instead. The filter bar and the slab viewer only appear when
   src/js/p-our-stock.js has run and added .stk-on: without the script every
   slab is visible and each photograph links to its 1600px file.

   S1  availability note (.stk-note) and the "slab gone" line (.stk-gone)
   S2  filter bar: material x colour, a disclosure on a phone (.stk-f*)
   S3  the card: drafting board, number plaque, name, label, chips, ask link
   S4  empty state (.stk-empty)
   S5  the slab viewer (.stk-lb-*), built from the page's <template>
   S6  small page one-offs (steps column, suppliers column)
   ========================================================================== */


/* S1 - AVAILABILITY NOTE ======================================================
   The first thing under the H2: when the photographs were taken and what to do
   before driving over. A drafting-board sheet, not an alert box. */
.stk-note{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start;
  gap:14px clamp(18px,2.2vw,26px);margin:0 0 clamp(24px,2.8vw,34px);
  padding:clamp(16px,1.8vw,22px) clamp(18px,2.2vw,26px);
  background-color:var(--bg-alt);background-image:var(--mesh);background-size:36px 36px;
  border-radius:var(--r-md)}
.stk-note > .badge{flex:0 0 auto}
.stk-note p{margin:0;max-width:76ch;font-size:.94rem;line-height:1.6;color:var(--ink-soft)}
@media(max-width:699px){.stk-note{grid-template-columns:minmax(0,1fr)}}

.stk-gone{margin:0 0 clamp(20px,2.4vw,28px);padding:12px 16px;color:var(--ink);
  background:var(--green-wash);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%);
  max-width:none}


/* S2 - FILTER BAR =============================================================
   Two groups of the template's mitred chips (.fbar-chip), one value per group,
   and the two add up. The label rides in the annotation voice beside its row.
   From 700px the disclosure is always open and its summary is not drawn; below
   that it is a real <details> that opens the two rows. */
.stk-filters{display:none;margin:0 0 clamp(22px,2.6vw,32px)}
.stk-filters.stk-on{display:block}
.stk-fdet{border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.stk-fsum{display:flex;align-items:center;justify-content:space-between;gap:12px;
  min-height:52px;padding:0 2px;margin:0;list-style:none;cursor:pointer;
  font-weight:800;font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink)}
.stk-fsum::-webkit-details-marker{display:none}
.stk-fsum::after{content:"";width:8px;height:8px;margin:-4px 4px 0 0;flex:0 0 8px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(45deg);transition:transform 220ms var(--ease-swap)}
.stk-fdet[open] .stk-fsum::after{transform:rotate(-135deg);margin-top:4px}
.stk-fsum-n{font-variant-numeric:tabular-nums;color:var(--green-ink)}
.stk-fsum-n:empty{display:none}
.stk-fgroup{display:grid;grid-template-columns:78px minmax(0,1fr);align-items:start;
  gap:8px 18px;padding:12px 0}
.stk-fgroup + .stk-fgroup{border-top:1px solid var(--line-soft)}
.stk-flabel{margin:0;padding-top:16px;color:var(--ink)}
.stk-chips{margin:0}
.stk-count{margin:12px 0 0;color:var(--ink-faint)}
@media(min-width:700px){
  /* the summary is only a phone control: on a laptop the two rows are the bar */
  .stk-fsum{display:none}
  .stk-fdet{border-top:0}
}
@media(max-width:699px){
  .stk-fgroup{grid-template-columns:minmax(0,1fr);gap:6px;padding:10px 0 14px}
  .stk-flabel{padding-top:0}
  .stk-fgroups{padding-bottom:6px}
}


/* S3 - THE CARD ===============================================================
   A slab is a drawing, not a mood photo: the picture sits WHOLE on the board
   (object-fit:contain), so the edge of the stone and its real proportion stay
   visible, and the board's mesh shows in the margin. The mitred plaque carries
   the number, which stays the reference a customer quotes on the phone even now
   that the owner's sheet has given every slab a name (STOCK-CATALOG-PLAN 17).
   The photograph link covers the card through its ::after, so
   the label and the chips open the slab too; "Ask about this slab" rides above
   it as the second target. */
.stk-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(28px,2.8vw,38px) clamp(16px,1.8vw,24px);list-style:none;margin:0;padding:0;max-width:none}
@media(max-width:899px){.stk-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:559px){.stk-grid{grid-template-columns:minmax(0,1fr)}}

.stk-card{position:relative;margin:0;min-width:0;display:flex;flex-direction:column}
.stk-card[hidden]{display:none}

.stk-shot{display:block;text-decoration:none;color:inherit;cursor:zoom-in}
.stk-shot::after{content:"";position:absolute;inset:0;z-index:2}
.stk-frame{display:block;position:relative;overflow:hidden;aspect-ratio:16/10;
  border-radius:var(--r-sm);box-shadow:var(--e1);
  background-color:var(--bg-alt);background-image:var(--mesh);background-size:36px 36px;
  transition:box-shadow 280ms var(--ease-stone),transform 280ms var(--ease-stone)}
.stk-frame::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;
  pointer-events:none;box-shadow:inset 0 0 0 1px rgba(25,40,80,.13)}
.stk-frame img{width:100%;height:100%;object-fit:contain}
.stk-frame .mat-name{position:absolute;left:0;bottom:0;z-index:2;
  padding:8px 15px 8px 13px;background:rgba(255,255,255,.94);color:var(--ink);
  font-weight:800;font-size:.78rem;letter-spacing:.06em;line-height:1;
  clip-path:polygon(0 0,calc(100% - var(--miter)) 0,100% var(--miter),100% 100%,0 100%)}
@media(hover:hover){
  .stk-card:hover .stk-frame{transform:translateY(-3px);box-shadow:var(--e2)}
  .stk-card:hover .stk-title{color:var(--green-ink)}
}

/* the annotation line breaks at the middot, never inside "Photographed Sep 2026" */
.stk-meta{margin:14px 0 0}
.stk-meta span{white-space:nowrap}
.stk-title{margin:5px 0 0;font-weight:800;font-size:1.02rem;line-height:1.25;
  letter-spacing:-.005em;color:var(--ink);transition:color 180ms var(--ease-snap)}
/* the reading of the photograph, under the name the owner gave the slab: a
   different voice from the title, so nobody mistakes it for part of the name */
.stk-look{margin:4px 0 0;font-size:.92rem;line-height:1.45;color:var(--ink-soft);max-width:34ch}
.stk-specs{padding-top:10px}
/* the ask line speaks in the card-CTA voice of the swatch grid, and sits above
   the photograph's overlay so it stays its own target */
.stk-ask{position:relative;z-index:4;align-self:flex-start;display:inline-flex;align-items:center;
  gap:10px;min-height:44px;padding-top:6px;font-weight:800;font-size:.72rem;letter-spacing:.12em;
  text-transform:uppercase;line-height:1.2;color:var(--green-ink);text-decoration:none}
.stk-ask .mt-rule{width:34px}
.stk-ask:hover{color:var(--green-deep);text-decoration:underline;text-underline-offset:4px}
@media(prefers-reduced-motion:no-preference){
  .stk-ask:hover .mt-rule .lr-rule,.stk-ask:hover .mt-rule .lr-head,
  .stk-ask:focus-visible .mt-rule .lr-rule,.stk-ask:focus-visible .mt-rule .lr-head{transform:translateX(4px)}
}


/* S4 - EMPTY STATE ============================================================
   The same sheet as the availability note, so the two "read this" blocks of the
   section are one family. The number never breaks across two lines. */
.stk-empty{padding:clamp(22px,2.6vw,30px);border-radius:var(--r-md);
  background-color:var(--bg-alt);background-image:var(--mesh);background-size:36px 36px}
.stk-empty p{margin:0;max-width:60ch;font-weight:600;color:var(--ink)}
.stk-empty a{white-space:nowrap}
.stk-empty .btn-row{margin-top:16px}


/* S5 - THE SLAB VIEWER ========================================================
   Same vocabulary as the shared lightbox (P06): navy backdrop, the same 48px
   navy buttons (.lbx-btn), the same counter. What this one adds is the title
   block: the slab number, the name, the reading of the photograph, and one row
   per fact - material, thickness, finish, colour, pattern. A fact the owner's
   sheet did not carry reads "Ask us" (only AC-10's finish, today): a row is
   never left blank, because a blank row is an invitation to invent data. */
.stk-lb{width:min(1180px,94vw);max-width:none;max-height:94vh;padding:0;border:0;
  background:transparent;overflow:visible;color:var(--ink)}
.stk-lb::backdrop{background:rgba(25,40,80,.94)}
.stk-lb-sheet{display:grid;grid-template-columns:minmax(0,1fr) 340px;margin:0;max-height:94vh;
  background:var(--bg);border-radius:var(--r-md);overflow:hidden;box-shadow:var(--e3)}
.stk-lb-stage{position:relative;min-width:0;background:var(--bg-dark);
  display:flex;align-items:center;justify-content:center;min-height:220px}
.stk-lb-stage img{width:auto;max-width:100%;height:auto;max-height:86vh;object-fit:contain;margin:0 auto}
.stk-lb-stage .hero-marks{inset:14px;display:block}
.stk-lb-panel{display:flex;flex-direction:column;align-items:flex-start;min-width:0;
  padding:clamp(20px,2vw,26px);overflow-y:auto}
.stk-lb-stamp{background:var(--ink);color:var(--white);box-shadow:none;margin:0}
.stk-lb-label{margin:12px 0 0;font-weight:800;font-size:1.06rem;line-height:1.3;
  letter-spacing:-.005em;color:var(--ink);max-width:none}
.stk-lb-look{margin:6px 0 0;max-width:38ch}
.stk-lb-views{display:flex;gap:8px;margin:16px 0 0;flex-wrap:wrap}
/* the datasheet grew a "Photographed" row with the owner's sheet, and that
   label is wider than the 92px label track the foundation sets, so this dl -
   and only this dl - gets a wider one. Below 420px the foundation stacks the
   row into one column anyway. */
.stk-lb-spec{width:100%;margin:18px 0 0}
.stk-lb-spec .sr-row{grid-template-columns:118px minmax(0,1fr)}
/* pages.css loads after site.css, so the wider track above would also beat the
   420px rule that stacks a datasheet row on a phone: put the stack back */
@media(max-width:420px){.stk-lb-spec .sr-row{grid-template-columns:minmax(0,1fr)}}
.stk-lb-ask{color:var(--ink-faint)}
.stk-lb-acts{width:100%;margin:auto 0 0;padding-top:20px}
.stk-lb-call{margin-top:10px}
.stk-lb-hint{margin:10px 0 0;text-align:center}
.stk-lb-prev{left:-64px;top:50%;margin-top:-24px}
.stk-lb-next{right:-64px;top:50%;margin-top:-24px}
.stk-lb-close{right:0;top:-58px}
@media(max-width:1300px){
  .stk-lb-prev{left:8px}
  .stk-lb-next{right:8px}
}
@media(max-width:899px){
  .stk-lb{width:100vw;max-height:100vh}
  .stk-lb-sheet{grid-template-columns:minmax(0,1fr);border-radius:0;max-height:100vh;
    overflow-y:auto}
  .stk-lb-stage{min-height:0}
  .stk-lb-stage img{max-height:52vh}
  .stk-lb-close{right:8px;top:8px}
  .stk-lb-prev,.stk-lb-next{top:auto;bottom:8px;margin-top:0}
  .stk-lb-prev{left:8px}
  .stk-lb-next{right:56px}
}
@media(prefers-reduced-motion:no-preference){
  .stk-lb[open]{animation:stk-lb-in 220ms var(--ease-stone)}
}
@keyframes stk-lb-in{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}


/* S6 - PAGE ONE-OFFS ========================================================== */
/* the numbered steps and the line of links under them share the right column
   of the .hgrid, so the list keeps the heading beside it. The lead-in of each
   step is a title: the foundation sets b to 600, the same weight as the row. */
.stk-steps .numlist b{font-weight:800}
.pg-our-stock .stk-steps > p{margin:18px 0 0;max-width:68ch}
/* the quartz and suppliers columns hold the right column of their .hgrid on
   their own: first paragraph flush with the heading, the rest on the text
   rhythm, and the ghost button a step further down */
.pg-our-stock .stk-note-col > p{margin:0;max-width:64ch}
.pg-our-stock .stk-note-col > p + p{margin-top:16px}
.pg-our-stock .stk-note-col > .btn-row{margin-top:22px}

/* ==== src/css/p-privacy-policy.css ==== */
/* /privacy-policy/ ==============================================================
   The registered A2P text marks its list items with a literal "=>" (bold on the
   live page). It is part of the text, so it stays. Inside .prose the shared
   marker (the mitred square) would stand next to it, so these lists hang the
   "=>" in the marker gutter instead: the text keeps one left edge, the "=>"
   reads as the bullet, and it takes the link green of the prose. */
.pg-privacy-policy .pp-list{padding-left:1.75em}
.pg-privacy-policy .pp-list > li::before{content:none}
.pg-privacy-policy .pp-mark{position:absolute;left:-1.75em;top:0;font-weight:800;color:var(--green-ink);
  white-space:nowrap}
/* the address block of section 10 reads as one card of four lines, not four
   paragraphs */
.pg-privacy-policy .pp-contact p{margin:0 0 .25em}

/* The numbered heading of a legal document is a label, not a headline. The 30ch
   cap of .prose h2 (00-foundation-pages.css P07) broke two of the ten in half
   from 900px up - "4. Cookies and / Tracking Technologies" and "5. How We Share
   / Your Information" - while "3. How We Use Your Information" fitted on one
   line and 500px of paper sat empty to the right, so the break read as a defect.
   Scoped here because P07 is shared (D21); request 3 of NOTES.md asks for
   .prose.legal h2{max-width:none} in the foundation, which would delete this
   rule and fix /terms-and-conditions/ in the same line. */
.pg-privacy-policy .prose.legal h2{max-width:none}

/* Second head button, 900-1080px. The shared rule hides .hs-call there because a
   form card (960-1080) or the green .hs-cta-lead (900-959) takes the slot on the
   commercial heads. A legal page has neither, the callbar is gone from 900 and
   the header's Free Estimate only starts at 1040, so without this the band
   900-1039px has no visible route to /contact/ outside the Company dropdown and
   the footer. Request 4 of NOTES.md asks the template owner for a neutral slot
   (.hs-second) that would replace this. */
@media(min-width:900px) and (max-width:1080px){
  .pg-privacy-policy .page-head .hs-call{display:inline-flex}
}

/* ==== src/css/p-projects.css ==== */
/* ============================================================================
   /projects/ - the project gallery (pages.css, page part)
   ----------------------------------------------------------------------------
   .prj- is the one prefix this page may declare unscoped (BUILD.md 3,
   tools/verify.py PAGE_PREFIXES), so the filtered gallery can later be reused
   as a strip on another page. Everything else is scoped with .pg-projects.

   House rules of the file: no ID selector, no !important, tokens only (no hex
   at all here), no property redeclared on a foundation selector - a modifier
   class of our own instead. Every behaviour comes from scripts that already
   ship: the chips are pages.js T04, the viewer is pages.js P06 and the
   slab-cut is site.js. This page has no JS file.

   G1  the filtered gallery grid (.prj-*, DESIGN-SYSTEM 7.4)
   G2  the per-photo action link (.prj-ask)
   G3  the label over the "work by type" cards (.prj-label)
   G4  the start-to-finish strip: spacing, and four columns instead of three
   ========================================================================== */


/* G1 - THE GALLERY GRID =======================================================
   Not .gal-grid: that one turns into a horizontal snap rail on a phone, and a
   gallery is for seeing many at once (DESIGN-SYSTEM 7.4). Three columns, two
   from 1000px down, and two on a phone as well. The frame is 4:3 with the
   .gal-item radius, no border: the weight is in the photograph. A filtered-out
   item is [hidden] by pages.js T04, so it also leaves the lightbox list. */
.prj-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(20px,2.4vw,30px) clamp(12px,1.6vw,18px);
  list-style:none;margin:0;padding:0;max-width:none}
@media(max-width:1000px){.prj-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* QA final 23/09: nine photographs in two columns leave the ninth alone in a
   half-empty last row (three columns divide nine evenly, two do not). The last
   card takes the whole row when the count is odd, so the gallery closes on a
   deliberate wide frame instead of a leftover. Add a tenth photo and the
   :nth-child(odd) test stops matching on its own - nothing to undo. */
@media(max-width:1000px){
  .prj-grid>.prj-item:last-child:nth-child(odd){grid-column:1/-1}
  .prj-grid>.prj-item:last-child:nth-child(odd) .prj-open{aspect-ratio:16/9}
}

.prj-item{margin:0;min-width:0;display:flex;flex-direction:column}
.prj-item[hidden]{display:none}

.prj-open{display:block;width:100%;padding:0;border:0;cursor:zoom-in;overflow:hidden;
  border-radius:var(--r-sm);background:var(--bg-alt);aspect-ratio:4/3}
.prj-open img{display:block;width:100%;height:100%;object-fit:cover;
  transition:transform 600ms var(--ease-stone)}
@media(hover:hover){.prj-open:hover img{transform:scale(1.05)}}

/* Two columns on a phone, so the card sheds what does not fit.
   The line goes at 560px, exactly as DESIGN-SYSTEM 7.4 sets it.
   The ask link goes earlier, at 700px: the label and its rule need about 300px
   on one line, and the two-column grid only reaches that from 700px up. Below
   it the label broke into two or three lines with the arrow stranded at the
   right edge. The photograph is still a tap target, and the form is one screen
   down. */
@media(max-width:560px){.prj-item .pj-line{display:none}}
@media(max-width:699px){.prj-item .prj-ask{display:none}}


/* G2 - ASK ABOUT ONE LIKE THIS ================================================
   The card CTA voice of .swatch-ask, in this page's own class (the stock and
   swatch links keep theirs). It writes the caption into the "Tell us about the
   project" box through data-prefill (pages.js P14): no new <select> option. */
.prj-ask{align-self:flex-start;display:inline-flex;align-items:center;gap:10px;min-height:44px;
  margin-top:auto;padding-top:8px;font-weight:800;font-size:.72rem;letter-spacing:.12em;
  text-transform:uppercase;line-height:1.2;color:var(--green-ink);text-decoration:none}
.prj-ask .mt-rule{width:34px}
.prj-ask:hover{color:var(--green-deep);text-decoration:underline;text-underline-offset:4px}
@media(prefers-reduced-motion:no-preference){
  .prj-ask:hover .mt-rule .lr-rule,.prj-ask:hover .mt-rule .lr-head,
  .prj-ask:focus-visible .mt-rule .lr-rule,.prj-ask:focus-visible .mt-rule .lr-head{transform:translateX(4px)}
}


/* G3 - THE WORK-BY-TYPE SHEETS ================================================
   The label is an annotation, not a heading: the four sheets carry h3s of their
   own and the page must not grow a fifth H2 the pages.json outline does not
   have.

   The sheet is .rel-card's geometry without .rel-card's contract. P04 says it
   plainly: "One link per card: the title carries the anchor and its ::after
   covers the card." Here the anchors have to be the exact phrases of the
   internal link plan ("kitchen countertops", "vanity tops", "backsplash
   installation"), they sit mid-sentence, and one of the four sheets links
   nowhere at all - so a whole-card link would be both wrong and a lie under the
   hover lift. NOTES.md asks the template owner for a .rel-card variant with
   inline links; until then this page keeps its own. The layout is the shared
   .rel-grid.four, so the four columns break exactly like every other grid. */
.pg-projects .prj-label{margin:0 0 clamp(14px,1.8vw,22px)}

.prj-type{position:relative;margin:0;display:flex;flex-direction:column;gap:8px;height:100%;
  padding:20px 22px 18px;background:var(--bg);border:1px solid var(--line-soft);
  border-radius:var(--r-md);box-shadow:var(--e1)}
.prj-type > .ic{--s:28px;--sw:1.6;color:var(--ink);margin:0 0 4px}
.prj-type h3{margin:0;font-size:1.06rem;line-height:1.25}
.prj-type p{margin:0;font-size:.92rem;line-height:1.55;color:var(--ink-soft);max-width:none}

/* The four sheets stretch to the tallest of the row (height:100%), so without
   a foot the slack piles up under the last line and the short sheets read as
   empty boxes: measured at 1440 and 1240, 146px of it under #vanities, 94 under
   #kitchens, 70 under #backsplash. The .rel-card answer is .mt-cta with
   margin-top:auto - the slack moves to the middle and the last lines of the
   row close level. Same move here, on the closing line of each sheet (the one
   that carries the link, where there is one), plus the hairline that tells the
   reader the gap above it is deliberate. */
.prj-type .prj-type-last{margin-top:auto;padding-top:12px;border-top:1px solid var(--line-soft)}


/* G4 - THE START-TO-FINISH STRIP ==============================================
   The JOB-A case study sits under its own h3 as one sub-block; geometry, tone
   and behaviour stay in the foundation (.gal-grid.pj-grid: square frames, and
   a snap rail below 900px).

   The strip is four frames, not three - countertop set, backsplash cut,
   backsplash in, finished corner - and the foundation grid is three columns,
   which would leave the fourth frame alone on a second row. .prj-strip states
   its own column count above the rail breakpoint: four across from 1100px
   (268px a frame at 1180, well under the 480px ceiling the curation rule puts
   on archive photography) and two-by-two between 900 and 1099, where four
   would land at ~210px and the captions under them would wrap three deep.
   Below 900px nothing is said: the foundation rail is the right answer there
   for four frames as much as for three. */
.pg-projects .prj-steps{margin-top:clamp(28px,3.4vw,44px)}
.pg-projects .prj-steps h3{margin:0 0 clamp(16px,2vw,24px)}
/* 23/09: four frames across read as four stamps of 268px, too small for a
   sequence the section's own H2 promises. The strip now takes the template's
   .gal-lead arrangement (T15) with one more row: the first frame leads at
   about 676px and the other three stack beside it in work order. T15 itself
   spans two rows for a three-photo gallery, so the third row is declared here
   and nothing shared changes. */
@media(min-width:1100px){
  .prj-strip.gal-lead{grid-template-columns:minmax(0,1.62fr) minmax(0,1fr);
    grid-template-rows:repeat(3,auto);gap:22px}
  .prj-strip.gal-lead .pj-lead{grid-row:1 / span 3}
}
/* 900-1099px keeps the two-by-two square set: four frames would land at about
   210px and a lead frame at 47vw leaves its three companions at 150px. The
   T15 defaults have to be undone by name, because .pj-grid.gal-lead outweighs
   a lone .prj-strip. */
@media(min-width:900px) and (max-width:1099px){
  .prj-strip.gal-lead{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:auto;gap:24px 22px}
  .prj-strip.gal-lead .pj-lead{grid-row:auto}
  .prj-strip.gal-lead .pj-work .gal-item{aspect-ratio:1/1;flex:0 0 auto}
}

/* ==== src/css/p-quartzite-countertops-boynton-beach.css ==== */
/* ============================================================================
   /quartzite-countertops-boynton-beach/ - page part of pages.css
   ----------------------------------------------------------------------------
   One block, one reason. The #slabs section publishes the SEVEN quartzite slabs
   the owner confirmed on the slab form he returned on 23/09, and seven is prime
   against the three columns of the shared .swatch-grid (T05): 3 + 3 + 1 leaves
   the last row two thirds empty and the strip reads unfinished.

   So the first card runs the full width of the grid and the other six close two
   complete rows - the same "one lead, then the rest" idiom the gallery of
   #projects already uses (.gal-lead / .pj-lead). It also groups the stone by
   itself: the lead is Taj Mahal quartzite (slab AC-12) with the other three Taj
   Mahal slabs on the row under it, then the three Cristallo slabs close the
   strip.

   House rules: every selector scoped to .pg-quartzite-countertops-boynton-beach,
   no ID selector, no !important, no hex (no colour is declared here at all),
   nothing redeclared on a foundation selector - .qz-lead is a modifier of our
   own that rides on .swatch.

   Q1  the lead card spans the grid       (from 560px: below it the shared grid
                                           is a snap rail and every card is one
                                           slide, so the modifier stops there)
   Q2  its frame goes wide                (only from 900px: at 560-899px the
                                           grid is two columns and the default
                                           2:1 frame is already the right shape)
   ========================================================================== */

/* Q1 ---------------------------------------------------------------------- */
@media(min-width:560px){
  .pg-quartzite-countertops-boynton-beach .qz-lead{grid-column:1/-1}
}

/* Q2 ---------------------------------------------------------------------- */
@media(min-width:900px){
  .pg-quartzite-countertops-boynton-beach .qz-lead .swatch-shot{aspect-ratio:3/1}
}

/* ==== src/css/p-service-areas.css ==== */
/* P-SERVICE-AREAS =============================================================
   One block, for the five city cards of #cities.

   Each card carries the two figures of the map list, the county and the drive
   from the shop, as .mt-spec tags. Side by side inside a three-column
   .rel-grid, "Palm Beach County" plus a time lands within a pixel or two of
   the card between 1000 and 1440px: four cards wrapped the pair onto two lines
   and the Broward card, whose county is shorter, did not, so one card in the
   row ended a line higher than its neighbours. Stacking the pair makes the
   five read identically at every width, and turns the tags into the small
   spec column the card wants anyway.

   Nothing here redeclares a foundation property: .mt-specs sets display,
   flex-wrap, gap, margin, padding and max-width, never flex-direction or
   align-items. The class is this page's own and rides next to .mt-specs.
   ========================================================================== */
.pg-service-areas .sa-tags{flex-direction:column;align-items:flex-start}

/* ==== src/css/p-sinks.css ==== */
/* ============================================================================
   /sinks/ - page file (scope .pg-sinks)
   ----------------------------------------------------------------------------
   Two one-offs on shared blocks, each through a modifier class of this page
   (no template selector is redeclared).

   .snk-styles  T05 .swatch-grid holding four sink styles instead of eighteen
                slab colours. Four items in the template's three columns would
                leave a single orphan card in the second row, so the grid runs
                four across once the wrap is at full width (one closed row) and
                two across below that (two closed rows). 1180px is where a
                quarter column still holds "Ask about this style" on one line;
                narrower than that the ask line wrapped and its rule drifted
                away from the last word. The card head also stays stacked at
                every width: a style name and "Manufacturer photo" do not fit
                one line in a 260px column, and the template's 1200px
                side-by-side head would wrap on some cards of a row and not on
                others.
   .snk-pair    T15 gallery holding two job photos under the split instead of
                the usual lead-and-two. Landscape 4:3 frames, and the seam on
                the header of the band sets the gap above them.
   .snk-visit   24/09 - the block of #showroom, the page's own two-column row.
                It is deliberately NOT a .split: above 900px a .split stretches
                its media column to the text's height, and below 900px the
                foundation gives that photo a 16/10 letterbox. The subject here
                is a standing slab with four sink models down its face, so both
                of those crops throw three of the four away. This row keeps the
                photograph's own 3:4 at every width and lets the text ride at
                the middle of it, the way .split .col-text already rides at the
                middle of a taller photo. No foundation selector is redeclared.
   ========================================================================== */

/* .snk-styles ------------------------------------------------------------ */
.pg-sinks .snk-styles{grid-template-columns:repeat(2,minmax(0,1fr))}
@media(min-width:1180px){
  .pg-sinks .snk-styles{grid-template-columns:repeat(4,minmax(0,1fr));
    gap:clamp(26px,2.6vw,34px) clamp(16px,1.7vw,22px)}
}
.pg-sinks .snk-styles .swatch-head{flex-direction:column;align-items:flex-start;
  justify-content:flex-start;gap:3px}

/* .snk-pair -------------------------------------------------------------- */
@media(min-width:900px){
  .pg-sinks .snk-pair{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}
  .pg-sinks .snk-pair .gal-item{aspect-ratio:4/3}
}
.pg-sinks .datum-seam + .snk-pair{margin-top:clamp(24px,2.8vw,36px)}

/* 23/09 - the four manufacturer photos are four different treatments (a white
   bowl on white, a farmhouse on dark wood, a stainless styled with a plant, a
   black composite on black), and the white one had no edge against the paper.
   The template's frame is kept; only its mat and its hairline are deepened, on
   this page's modifier class, so the four cards read as one set. */
.pg-sinks .snk-styles .swatch-shot::after{box-shadow:inset 0 0 0 1px var(--line-strong)}

/* .snk-visit -------------------------------------------------------------- */
.pg-sinks .snk-visit{display:grid;grid-template-columns:minmax(0,1fr);
  gap:clamp(24px,3.2vw,40px)}
.pg-sinks .snk-visit-text > :first-child{margin-top:0}
.pg-sinks .snk-visit-text > :last-child{margin-bottom:0}
/* The frame is the button, not the image: .gal-open arrives from T15 with
   height:100%, which is meant for a .gal-item that already has a ratio. Here
   there is no such parent, so the ratio is put on the button and the picture
   fills it. line-height:0 is not cosmetic - a <button> lays its content out in
   a centred anonymous box, and the two whitespace nodes around the <img> were
   adding 64px of empty line box that the figcaption then fell through. */
.pg-sinks .snk-visit-shot{margin:0;display:flex;flex-direction:column}
.pg-sinks .snk-visit-shot .gal-open{flex:0 0 auto;height:auto;aspect-ratio:3/4;
  line-height:0;overflow:hidden;border-radius:var(--r-md);background:var(--bg-alt)}
.pg-sinks .snk-visit-shot img{display:block;width:100%;height:100%;object-fit:cover}

/* Under 900px the block stacks, copy first. A 3:4 frame at full phone width is
   350x467 at 390px - one screenful with the caption under it. The cap keeps a
   tablet from blowing the frame past 430x573. */
@media(max-width:899px){
  .pg-sinks .snk-visit-shot{max-width:430px;margin-inline:auto}
}

/* Two columns from 900px. The photograph takes the narrower half: at 1440 it
   measures 466x621, which is the 3:4 file's own 1020px at a sane density. The
   copy is 525px there, so the frame sets the row height (661px with its
   caption) and the copy rides at the middle of it -
   .split .col-text does the same thing next to a filled media column, and this
   row is not a .split, so the DS 3.4 ban on align-items:center does not reach
   it (that ban exists to stop a SHORT media column floating in a tall text
   column; here the media is the tall one). */
@media(min-width:900px){
  .pg-sinks .snk-visit{grid-template-columns:minmax(0,1.12fr) minmax(0,.88fr);
    gap:var(--gap-split);align-items:center}
}

/* ==== src/css/p-terms-and-conditions.css ==== */
/* /terms-and-conditions/ ========================================================
   Two small needs of the registered A2P text, both on the prose of this page.
   1. Section 9 links the privacy policy with its full absolute URL as the link
      text (as filed). The markup gives it two <wbr> (after "//" and ".com/")
      and keeps "privacy-policy/" in one piece: a URL broken at its own hyphen
      reads as a hyphenated word. Below ~270px of column even
      "americancountertopsdesign.com/" would not fit, so as a last resort the
      host part may break anywhere instead of widening the page.
   2. The address block of section 11 reads as one card of four lines, not four
      paragraphs (the same treatment as the /privacy-policy/ contact block). */
.pg-terms-and-conditions .tc-url{overflow-wrap:anywhere}
.pg-terms-and-conditions .tc-nobr{white-space:nowrap}
.pg-terms-and-conditions .tc-contact p{margin:0 0 .25em}

/* The numbered heading of a legal document is a label, not a headline. The 30ch
   cap of .prose h2 (00-foundation-pages.css P07) plus the global text-wrap:
   balance broke three of the eleven in half from 900px up - "2. User
   Representation / & Age Restriction", "3. Consent to / Receive Messages" and
   "7. Message / Frequency and Rates" - with ~200px of paper empty to the right,
   and "7. Message" alone on a line reads as a truncated title in a numbered
   document. Scoped here because P07 is shared (D21); request 3 of NOTES.md asks
   for .prose.legal h2{max-width:none} in the foundation, which would delete
   this rule and the twin of it in p-privacy-policy.css in one line. */
.pg-terms-and-conditions .prose.legal h2{max-width:none}

/* Second head button, 900-1080px. The shared rule hides .hs-call there because a
   form card (960-1080) or the green .hs-cta-lead (900-959) takes the slot on the
   commercial heads. A legal page has neither, the callbar is gone from 900 and
   the header's Free Estimate only starts at 1040, so without this the band
   900-1039px has no visible route to /contact/ outside the Company dropdown and
   the footer - and /contact/ is an internal_links_out of pages.json. Same rule
   as p-privacy-policy.css; request 5 of NOTES.md asks the template owner for a
   neutral slot (.hs-second) that would replace both. */
@media(min-width:900px) and (max-width:1080px){
  .pg-terms-and-conditions .page-head .hs-call{display:inline-flex}
}

/* ==== src/css/p-thank-you.css ==== */
/* ============================================================================
   /thank-you/ only - scoped to .pg-thank-you under the page prefix .ty-
   (BUILD.md 3). One rule: where the H1 of this page breaks.

   The H1 is "Thank You - We Got Your Request". Left alone it wraps as
   "Thank You - We / Got Your Request" at every width, which leaves "We"
   hanging after the em dash. Holding the second clause together moves the
   break to the dash, but only while the clause fits on one line: measured on
   the built page, it needs about 355px, which the head measure reaches at a
   400px viewport. From 480px up (the phone/tablet line the rest of the page
   CSS already uses) it fits with room to spare and the H1 is two lines; below
   it the clause is left to wrap, which is also two lines - holding it there
   would make three, with a two-word orphan in the middle.

   This lives here, not in a style attribute, because it is layout and layout
   belongs in a stylesheet (BUILD.md 3). NOTES.md asks the template owner for a
   neutral line-break utility, which /blog/ needs too; when it exists, this
   file goes away.
   ========================================================================== */
@media(min-width:480px){
  .pg-thank-you .page-head h1 .ty-h1-nb{display:inline-block}
}
