/* site.css — Ace Countertops content-page design system (served pages).
   Shares the landing's tokens: near-black + gold ramp, Segoe UI. Used by every
   aceserve-rendered page (server/templates/*). The one-page landing keeps its
   own landing.css; this styles /about, /services, /projects, /guides, etc. */
:root {
  --bg: #141414; --bg-deep: #0F0F0E; --surface: #1B1A17; --surface-2: #211F1B;
  --surface-hover: #262420; --fg: #F5F1E6; --fg-muted: #C8C1B2; --fg-subtle: #9B9483;
  --border: #2D2A24; --border-strong: #4A4232;
  --gold-hi: #F1E2A0; --gold: #D2AE5A; --gold-deep: #8C6E2E;
  --radius: 8px; --maxw: 1140px;
  --font: "Segoe UI", system-ui, -apple-system, sans-serif;
  --mono: ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace;
  --pad: clamp(1.25rem, 5vw, 4rem);
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 12px 34px rgba(0,0,0,.32);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; background: var(--bg); color: var(--fg); font-family: var(--font); line-height: 1.6; -webkit-font-smoothing: antialiased; }
h1, h2, h3, h4 { line-height: 1.14; letter-spacing: -.01em; margin: 0 0 .45em; font-weight: 700; }
a { color: var(--gold); text-decoration: none; }
a:hover { color: var(--gold-hi); text-decoration: underline; }
img { max-width: 100%; display: block; }
p { margin: 0 0 1em; }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad); }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 4px; }
.skip { position: absolute; left: -999px; background: var(--surface); padding: 10px 16px; border-radius: 8px; z-index: 100; }
.skip:focus { left: 12px; top: 12px; }
.center { text-align: center; }
.muted { color: var(--fg-muted); }

/* buttons */
.btn { display: inline-block; padding: 12px 22px; border-radius: var(--radius); font-weight: 600; border: 1px solid transparent; cursor: pointer; font-size: 15px; line-height: 1.2; transition: transform .12s ease, background .2s ease, border-color .2s; text-align: center; }
.btn:hover { transform: translateY(-2px); text-decoration: none; }
.btn-primary { background: linear-gradient(135deg, var(--gold-hi), var(--gold)); color: #201a0c; }
.btn-primary:hover { background: linear-gradient(135deg, #fff0bf, var(--gold-hi)); color: #201a0c; }
.btn-ghost { background: transparent; color: var(--fg); border-color: var(--border-strong); }
.btn-ghost:hover { border-color: var(--gold); color: var(--gold-hi); }
.btn-sm { padding: 9px 15px; font-size: 14px; }
/* .mainnav a / .site-foot a set light link colors at higher specificity than
   .btn-primary — gold buttons inside them must keep the dark label. */
.mainnav a.btn-primary, .site-foot a.btn-primary,
.mainnav a.btn-primary:hover, .site-foot a.btn-primary:hover { color: #201a0c; }

/* header */
.site-head { position: sticky; top: 0; z-index: 50; background: rgba(20,20,20,.86); backdrop-filter: saturate(1.4) blur(10px); border-bottom: 1px solid var(--border); }
.head-row { display: flex; align-items: center; gap: 20px; padding-block: 13px; } /* padding-block: the shorthand would zero .wrap's side padding */
.brand { display: flex; align-items: center; gap: 10px; }
.brand:hover { text-decoration: none; }
.brand img { height: 22px; width: auto; }
.mainnav { margin-left: auto; }
.mainnav a { color: var(--fg); font-weight: 500; font-size: 15px; }
.mainnav a:hover, .mainnav a[aria-current=page] { color: var(--gold-hi); text-decoration: none; }
.navlist { display: flex; gap: 22px; align-items: center; list-style: none; margin: 0; padding: 0; }
.navitem { position: relative; }

/* Group triggers are real buttons (disclosure pattern) — the menus open on
   click/Enter/Space and on ArrowDown, never on hover, so they are usable by
   keyboard and touch alike. Visibility is driven straight off aria-expanded,
   which means the ARIA state and the pixels can never disagree. */
.navbtn { display: inline-flex; align-items: center; gap: 6px; background: none; border: 0; padding: 6px 0; margin: 0;
  font: inherit; font-weight: 500; font-size: 15px; color: var(--fg); cursor: pointer; }
.navbtn:hover, .navbtn[aria-expanded=true], .navbtn[data-section=current] { color: var(--gold-hi); }
.navbtn .caret { width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor; opacity: .7; transition: transform .15s ease; }
.navbtn[aria-expanded=true] .caret { transform: rotate(180deg); }
.navbtn:focus-visible, .navtoggle:focus-visible, .mainnav a:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 4px; }

.submenu { display: none; position: absolute; top: 100%; left: -14px; min-width: 15rem; z-index: 60;
  list-style: none; margin: 8px 0 0; padding: 8px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 18px 40px rgba(0,0,0,.45); }
.navbtn[aria-expanded=true] + .submenu { display: block; }
.submenu li { margin: 0; }
.submenu a { display: block; padding: 9px 12px; border-radius: 7px; font-weight: 400; white-space: nowrap; }
.submenu a:hover, .submenu a:focus-visible { background: rgba(210,174,90,.12); }
.submenu a[aria-current=page] { background: rgba(210,174,90,.16); }
.navcta { margin-left: 4px; }

.navtoggle { display: none; margin-left: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; color: var(--fg); font-size: 18px; }

/* No JS: nothing can toggle aria-expanded, so lay every group out in flow
   rather than leaving four dead buttons. The footer already links the whole
   site, but the header must not be a trap either. */
.nojs .navbtn { cursor: default; }
.nojs .navbtn .caret { display: none; }
.nojs .submenu { display: block; position: static; min-width: 0; margin: 0; padding: 0 0 0 10px;
  background: none; border: 0; box-shadow: none; }

@media (max-width: 860px) {
  .mainnav { display: none; position: absolute; top: 100%; left: 0; right: 0; background: var(--surface);
    border-bottom: 1px solid var(--border); padding: 8px var(--pad) 16px; max-height: calc(100vh - 60px); overflow-y: auto; }
  .site-head[data-open=true] .mainnav, .nojs .mainnav { display: block; }
  .navlist { flex-direction: column; gap: 0; align-items: stretch; }
  .navitem { position: static; }
  .navbtn { width: 100%; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid var(--border); }
  .submenu { position: static; min-width: 0; margin: 0 0 8px; padding: 4px 0 4px 12px;
    background: none; border: 0; border-bottom: 1px solid var(--border); border-radius: 0; box-shadow: none; }
  .submenu a { padding: 10px 0; white-space: normal; }
  .navcta { margin: 12px 0 0; }
  .navcta .btn { display: block; text-align: center; }
  .navtoggle { display: block; }
}

/* breadcrumbs */
.crumbs { font-size: .85rem; color: var(--fg-subtle); padding-block: 18px 0; } /* padding-block: the shorthand would zero .wrap's side padding */
.crumbs a { color: var(--fg-subtle); }
.crumbs a:hover { color: var(--gold); }
.crumbs .sep { margin: 0 7px; opacity: .5; }
.crumbs .here { color: var(--fg-muted); }

/* hero / page head */
.phead { padding: 54px 0 30px; position: relative; overflow: hidden; }
.phead::before { content: ""; position: absolute; inset: 0; z-index: -1; background: radial-gradient(60% 90% at 85% -20%, rgba(210,174,90,.14), transparent 70%); }
.eyebrow { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 12.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; background: rgba(210,174,90,.12); color: var(--gold-hi); margin-bottom: 14px; }
.phead h1 { font-size: clamp(2rem, 5vw, 3.2rem); margin: 0 0 .2em; }
.phead .tag { font-size: clamp(1.02rem, 2.2vw, 1.25rem); color: var(--fg-muted); max-width: 60ch; }
.cta-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 24px; }

/* sections */
main section { padding: 30px 0; }
main section:last-of-type { padding-bottom: 64px; }
section h2 { font-size: clamp(1.4rem, 3vw, 2rem); }
.lead { color: var(--fg-muted); margin: 0 0 26px; max-width: 62ch; font-size: 1.05rem; }
.grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 270px), 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fill, minmax(min(100%, 330px), 1fr)); }
/* ── product / color card grid ───────────────────────────────────────────────
   FOUR columns on desktop, stepping 4 -> 3 -> 2 -> 1. This is a fixed column
   COUNT, not an auto-fill of a min width: auto-fill let a wide viewport stretch
   a single card to 761px and upscale a 160px source 4.75x, which is what made
   the catalog look blurry. A fixed count means the card width is always known,
   so `sizes` can be accurate and the browser can pick a sensible source.
   `minmax(0,1fr)` (never a bare 1fr) so a long product name cannot force the
   track wider than its share and push the page into horizontal overflow. */
.catgrid { display: grid; gap: 20px; grid-template-columns: repeat(4, minmax(0, 1fr)); align-items: start; }
@media (max-width: 1100px) { .catgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .catgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; } }
@media (max-width: 460px)  { .catgrid { grid-template-columns: 1fr; } }

/* Card media is a fixed 4:3 box with the image cropped to fill it, so every
   card in the row is the same height regardless of the source aspect ratio —
   the harvest mixes square swatches, wide slabs and portrait door shots. */
.catgrid .cardx { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.catgrid .cardx picture, .catgrid .cardx > img { display: block; margin: 0; }
.catgrid .cardx img.thumb { width: 100%; aspect-ratio: 4 / 3; height: auto; object-fit: cover; margin: 0; border-radius: 0; background: #1b1b1b; }
.catgrid .cardx .cardbody { padding: 14px 16px 18px; }
.catgrid .cardx h3 { margin: 0 0 4px; font-size: 1.02rem; line-height: 1.3; }
.catgrid .cardx p { font-size: .88rem; }
.catgrid .cardx .pill { margin: 0 0 8px; }

.cardx { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px; box-shadow: var(--shadow); transition: transform .15s ease, border-color .2s ease; display: block; color: inherit; }
a.cardx:hover { transform: translateY(-3px); border-color: var(--border-strong); text-decoration: none; }
.cardx h3 { margin: 0 0 7px; font-size: 1.16rem; }
.cardx p { margin: 0; color: var(--fg-muted); font-size: .95rem; }
.cardx .more { display: inline-block; margin-top: 12px; color: var(--gold); font-weight: 600; font-size: .9rem; }
.pill { display: inline-block; padding: 3px 11px; border-radius: 20px; font-size: 12px; font-weight: 600; background: rgba(210,174,90,.13); color: var(--gold-hi); margin-bottom: 10px; }

/* page hero image band */
.pagehero { position: relative; margin-bottom: 8px; }
.pagehero picture, .pagehero-img { display: block; width: 100%; }
.pagehero-img { height: clamp(220px, 40vh, 460px); object-fit: cover; }
.pagehero::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(20,20,20,.15), rgba(20,20,20,.55)); }

/* thumbs */
.thumb { aspect-ratio: 4/3; height: auto; width: 100%; object-fit: cover; border-radius: 6px; margin-bottom: 14px; background: var(--surface-2); }
.thumb picture { display: block; }
.thumb-sm { aspect-ratio: 1/1; height: auto; border-radius: 6px; }
.cardx picture img.thumb { margin-bottom: 14px; }
.prodhero { aspect-ratio: 16/11; height: auto; }

/* offer band */
.band { background: linear-gradient(135deg, var(--surface-2), var(--surface)); border: 1px solid var(--border-strong); border-radius: 14px; padding: 30px; display: flex; gap: 22px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.band h3 { font-size: 1.5rem; margin: 0 0 6px; color: var(--gold-hi); }
.band p { margin: 0; color: var(--fg-muted); }

/* stats / areas */
.stats { display: flex; gap: 40px; flex-wrap: wrap; margin: 8px 0 4px; }
.stat b { font-size: 2rem; display: block; color: var(--gold); }
.stat span { color: var(--fg-muted); font-size: .88rem; }
.areas { display: flex; gap: 8px; flex-wrap: wrap; }
.areas span { padding: 5px 13px; border: 1px solid var(--border); border-radius: 20px; font-size: .85rem; background: var(--surface); color: var(--fg-muted); }

/* prose / article */
.prose { max-width: 72ch; }
.prose h2 { font-size: 1.4rem; margin: 1.5em 0 .4em; }
.prose h3 { font-size: 1.15rem; margin: 1.3em 0 .35em; }
.prose p { margin: 0 0 1.1em; color: var(--fg); }
.prose ul, .prose ol { margin: 0 0 1.1em; padding-left: 1.3em; color: var(--fg-muted); }
.prose li { margin: .3em 0; }
.byline { color: var(--fg-subtle); font-size: .9rem; margin-bottom: 10px; }

/* reviews — /reviews mirrors Ace's Google reviews, so the source line and the
   reply block are content, not chrome (server/templates/reviews.html). The
   grid track is minmax(min(340px,100%),1fr): a bare 340px minimum overflows a
   320px viewport, which is a horizontal scrollbar on the narrowest phones. */
.rv-headline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rv-headline strong { color: var(--gold-hi); font-size: 1.25rem; }
.rv-source { color: var(--fg-subtle); font-size: .9rem; margin-top: -.4em; }
.rv-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); align-items: start; }
.review { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
.stars { color: var(--gold); letter-spacing: 2px; }
.review .who { color: var(--fg-subtle); font-size: .88rem; margin-top: 8px; }
.rv-top { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.rv-who { font-weight: 650; }
.rv-date { color: var(--fg-subtle); font-size: .85rem; margin-left: auto; }
.rv-body p { color: var(--fg-muted); margin: 0 0 .8em; }
.rv-body p:last-child { margin-bottom: 0; }
.rv-reply { margin-top: 14px; padding: 12px 15px; background: var(--surface-2); border-left: 2px solid var(--gold); border-radius: 0 8px 8px 0; }
.rv-reply-head { color: var(--gold-hi); font-size: .82rem; font-weight: 650; margin-bottom: 6px; }
.rv-reply p { color: var(--fg-muted); font-size: .93rem; margin: 0 0 .6em; }
.rv-reply p:last-child { margin-bottom: 0; }
.rv-attrib { color: var(--fg-subtle); font-size: .8rem; margin: 12px 0 0; }

/* faq */
.faq { max-width: 74ch; }
.faq details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; margin-bottom: 12px; padding: 2px 20px; }
.faq summary { cursor: pointer; padding: 16px 0; font-weight: 600; font-size: 1.04rem; list-style: none; display: flex; justify-content: space-between; gap: 16px; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; color: var(--gold); font-size: 1.4rem; line-height: 1; transition: transform .2s; }
.faq details[open] summary::after { transform: rotate(45deg); }
.faq .ans { padding: 0 0 18px; color: var(--fg-muted); }

/* forms */
.form-card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 26px; box-shadow: var(--shadow); max-width: 640px; }
.field { margin-bottom: 15px; }
.field label { display: block; font-weight: 600; font-size: .9rem; margin-bottom: 6px; }
.field input, .field textarea { width: 100%; font: inherit; font-size: 15px; padding: 11px 13px; border-radius: 8px; border: 1px solid var(--border-strong); background: var(--bg-deep); color: var(--fg); }
.field textarea { min-height: 96px; resize: vertical; }
.formmsg { margin-top: 12px; font-size: .95rem; color: var(--gold-hi); }
/* zip-first service-area gate (assets/js/leadform.js adds .zip-gated; a no-JS
   visitor never gets the class, so the whole form stays visible for them). */
.form-card.zip-gated .lf-rest { display: none; }
/* two-up first/last row (2026-08-14 form upgrade) */
.lf-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 460px) { .lf-row2 { grid-template-columns: 1fr; gap: 0; } }
.lf-opt { color: var(--fg-subtle); font-weight: 400; font-size: .85em; }
/* project-interest icon picker: checkbox cards on the site's gold tokens.
   Selected = gold border + gold-tinted surface + gold icon; the state has to
   read at a glance, not just on the tiny native checkbox (which is hidden). */
.ipick { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
@media (max-width: 560px) { .ipick { grid-template-columns: 1fr 1fr; } }
.iopt { position: relative; }
.iopt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.iopt label { display: flex; flex-direction: column; align-items: center; gap: 7px;
  cursor: pointer; text-align: center; padding: 13px 6px 11px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--bg-deep);
  font-weight: 600; font-size: .82rem; color: var(--fg-muted); line-height: 1.15; }
.iopt .iic { width: 26px; height: 26px; color: var(--fg-subtle); flex: none; }
.iopt input:checked + label { border-color: var(--gold); background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--gold); color: var(--fg); }
.iopt input:checked + label .iic { color: var(--gold); }
.iopt input:focus-visible + label { outline: 2px solid var(--gold); outline-offset: 2px; }
.zipstatus { margin: 8px 0 0; min-height: 1.3em; font-size: .95rem; color: var(--fg-muted); }
.zipstatus[data-state="ok"] { color: var(--gold-hi); }
.zipstatus[data-state="out"] { color: #E4A9A0; }

/* definition list (specs) */
.dl { display: grid; grid-template-columns: max-content 1fr; gap: 8px 20px; margin: 0; }
.dl dt { color: var(--fg-subtle); }
.dl dd { margin: 0; }

/* footer */
.site-foot { border-top: 1px solid var(--border); margin-top: 12px; padding: 46px 0 40px; color: var(--fg-muted); font-size: .92rem; background: var(--bg-deep); }
.foot-grid { display: grid; gap: 28px; grid-template-columns: 1.6fr repeat(4, 1fr); }
@media (max-width: 960px) { .foot-grid { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 620px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 400px) { .foot-grid { grid-template-columns: 1fr; } }
.site-foot h4 { font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; color: var(--fg); margin-bottom: 12px; }
.site-foot a { color: var(--fg-muted); display: block; padding: 3px 0; }
.site-foot a:hover { color: var(--gold); }
.site-foot .brand img { height: 24px; margin-bottom: 12px; }
.foot-legal { margin-top: 30px; padding-top: 18px; border-top: 1px solid var(--border); display: flex; gap: 16px; flex-wrap: wrap; justify-content: space-between; align-items: center; font-size: .82rem; color: var(--fg-subtle); }
.foot-legal .links a { display: inline; margin-right: 14px; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; scroll-behavior: auto; } }

/* ── page_sections renderer (templates/sections.html) ─────────────────────── */
.pg-toc { border-bottom: 1px solid var(--border); background: var(--bg-deep); }
.pg-toc ol { display: flex; flex-wrap: wrap; gap: 4px 22px; list-style: none; margin: 0; padding: 12px 0; font-size: .88rem; }
.pg-toc a { color: var(--fg-muted); }
.pg-toc a:hover { color: var(--gold-hi); }
.pg-sec { padding: 34px 0 6px; }
.pg-sec h2 { margin-bottom: 6px; }
.pg-sec .sub { color: var(--fg-subtle); margin-bottom: 18px; max-width: 70ch; }
.pg-sec p { color: var(--fg-muted); max-width: 74ch; }
/* A LIST IS NOT PROSE. 74ch is the right measure for a paragraph and the wrong
   one for a list of short items: inside the 1140px column it left the lists
   666px wide with ~470px of dead space beside them, which is what read as
   "left justified and not taking up proper width". Headings already span the
   column, so the mismatch was visible on every section-bearing page.
   Prose keeps its measure — widening paragraphs to 1140px would be a worse
   defect, not a fix. */
.pg-sec ul, .pg-sec ol { color: var(--fg-muted); padding-left: 1.2em; }
.pg-sec li { margin-bottom: .4em; }
/* A bare list that IS the section body flows into columns once there is room
   for two readable ones — `> ul` only, so grouped content below is untouched. */
@media (min-width: 1000px) {
  .pg-sec .pg-body > ul:not(.cardgrid):not(.steps),
  .pg-sec .pg-body > ol:not(.cardgrid):not(.steps) { columns: 2; column-gap: 46px; }
  .pg-sec .pg-body > ul:not(.cardgrid):not(.steps) > li,
  .pg-sec .pg-body > ol:not(.cardgrid):not(.steps) > li { break-inside: avoid; }
  .pg-sec .pg-body > h3 { break-after: avoid; }
}

/* ── SECTION PRIMITIVE: labeled groups ──────────────────────────────────────
   Emitted by groupsHTML() in tools/lib/sections.mjs. Any "several named
   buckets, each holding a short list" content uses it — pairings, sizes and
   finishes, rated applications — so all of them lay out identically on every
   page type instead of each inventing a shape.

   auto-fill/minmax rather than a fixed column count: the same rule serves a
   two-group section and a nine-group one, and it degrades to a single column
   on a phone with no breakpoint of its own. */
.pg-groups {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  gap: 26px 46px;
  margin-top: 18px;
}
.pg-group h3 { margin: 0 0 8px; font-size: 1rem; letter-spacing: .01em; }
.pg-group .pg-group-note { margin: 0 0 8px; color: var(--fg-subtle); font-size: .93rem; max-width: none; }
/* Inside a group the list is already in its own column — it must not be
   re-columned by the rule above, and it must not carry a reading measure. */
.pg-group ul, .pg-group ol { columns: auto; max-width: none; margin: 0; }
.pg-group li { margin-bottom: .5em; }
/* faq kind reuses the .faq details look */
.pg-sec .qa { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; margin-bottom: 12px; padding: 2px 20px; max-width: 74ch; }
.pg-sec .qa summary { cursor: pointer; padding: 14px 0; font-weight: 600; list-style: none; display: flex; justify-content: space-between; gap: 16px; }
.pg-sec .qa summary::-webkit-details-marker { display: none; }
.pg-sec .qa summary::after { content: "+"; color: var(--gold); font-size: 1.3rem; line-height: 1; transition: transform .2s; }
.pg-sec .qa[open] summary::after { transform: rotate(45deg); }
.pg-sec .qa p { padding-bottom: 6px; }
/* tables */
.pg-sec .tablewrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; max-width: 100%; }
.pg-sec table { border-collapse: collapse; width: 100%; font-size: .93rem; }
.pg-sec th { text-align: left; padding: 11px 14px; background: var(--surface); color: var(--fg); border-bottom: 1px solid var(--border-strong); white-space: nowrap; }
.pg-sec td { padding: 10px 14px; border-bottom: 1px solid var(--border); color: var(--fg-muted); }
.pg-sec tbody tr:last-child td { border-bottom: 0; }
.pg-sec .footnote { font-size: .82rem; color: var(--fg-subtle); margin-top: 8px; }
/* steps */
.pg-sec .steps { list-style: none; padding: 0; counter-reset: step; max-width: 74ch; }
.pg-sec .steps > li { counter-increment: step; position: relative; padding: 0 0 18px 52px; }
.pg-sec .steps > li::before { content: counter(step); position: absolute; left: 0; top: 0; width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: rgba(210,174,90,.14); color: var(--gold-hi); font-weight: 700; }
.pg-sec .steps h3 { margin: 4px 0 4px; font-size: 1.05rem; }
.pg-sec .stepmeta { font-size: .82rem; color: var(--fg-subtle); margin-bottom: 4px; }
.pg-sec .steptip { font-size: .9rem; color: var(--fg-subtle); border-left: 2px solid var(--gold); padding-left: 10px; }
/* cards + gallery */
/* Section cards. Same 4/3/2/1 rhythm as the catalog grid so the whole site
   shares one card cadence rather than each section inventing its own. A card
   WITH an image lets the image run edge-to-edge (padding moves to .cardbody);
   a card without one keeps its padding and reads as a text card. */
.pg-sec .cardgrid { list-style: none; padding: 0; margin: 0; display: grid; gap: 18px; grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: none; }
@media (max-width: 1100px) { .pg-sec .cardgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .pg-sec .cardgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; } }
@media (max-width: 460px)  { .pg-sec .cardgrid { grid-template-columns: 1fr; } }
.pg-sec .cardgrid .card { margin: 0; }
.pg-sec .cardgrid .card a {
  display: flex; flex-direction: column; height: 100%; color: var(--fg);
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 16px; overflow: hidden; transition: transform .15s ease, border-color .2s ease;
}
.pg-sec .cardgrid .card.has-img a { padding: 0; }
.pg-sec .cardgrid .card a:hover { border-color: var(--gold); text-decoration: none; transform: translateY(-2px); }
/* A card image may be a <picture> (pictureHTML in tools/lib/sections.mjs) or a
   bare <img>. <picture> is inline by default, which adds a baseline line-box
   under the image inside the flex card — block removes it and lets the <img>
   resolve width:100% against the card, not against an inline box. */
.pg-sec .cardgrid picture { display: block; }
.pg-sec .cardgrid img { width: 100%; aspect-ratio: 16/10; height: auto; object-fit: cover; margin: 0; display: block; background: var(--surface-2); }
.pg-sec .cardgrid .cardbody { display: block; }
.pg-sec .cardgrid .card.has-img .cardbody { padding: 14px 16px 16px; }
.pg-sec .cardgrid h3 { font-size: 1.02rem; margin: 0 0 5px; line-height: 1.32; }
.pg-sec .cardgrid p { font-size: .89rem; margin: 0; color: var(--fg-muted); max-width: none; }
.pg-sec .cardgrid .pill { margin: 0 0 8px; }
.pg-sec .cardgrid .cardmore { display: inline-block; margin-top: 10px; color: var(--gold); font-weight: 600; font-size: .86rem; }
.pg-sec .cardgrid .card a:hover .cardmore { color: var(--gold-hi); }
.pg-sec .gallery { list-style: none; padding: 0; display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); max-width: none; }
.pg-sec .gallery img { width: 100%; aspect-ratio: 4/3; height: auto; object-fit: cover; border-radius: 10px; }
.pg-sec .gallery figcaption { font-size: .82rem; color: var(--fg-subtle); padding-top: 6px; }
/* cta kind buttons (generator emits .btn / .btn.ghost) */
.pg-sec .ctarow { display: flex; gap: 12px; flex-wrap: wrap; }
.pg-sec .ctarow .btn { background: linear-gradient(135deg, var(--gold-hi), var(--gold)); color: #201a0c; }
.pg-sec .ctarow .btn.ghost { background: transparent; color: var(--fg); border-color: var(--border-strong); }
.pg-sec .ctarow .btn.ghost:hover { border-color: var(--gold); color: var(--gold-hi); }
/* The lead form now renders inside every `get-a-quote` CTA section
   (templates/sections.html). .form-card carries no margin of its own — it was
   only ever used as the whole content of a column — so without this it butts
   straight against the paragraph above it. */
.pg-sec--cta .form-card { margin-top: 22px; }
.pg-sec .cite { font-size: .8rem; color: var(--fg-subtle); }

/* ── project portfolio (server/projects.go, templates/project.html) ────────── */
.proj-hero figure { margin: 0; max-width: 900px; }
.proj-img { width: 100%; height: auto; border-radius: 10px; display: block; background: var(--surface-2); }
.proj-hero figcaption, .proj-pair figcaption, .proj-videos figcaption { font-size: .82rem; color: var(--fg-subtle); padding-top: 8px; }
.proj-facts { margin: 22px 0 18px; }
.proj-body p { max-width: 68ch; }
/* minmax(0,1fr) not 1fr: a bare 1fr lets a wide image push the page wider than
   the viewport, which is the horizontal-spill failure the QA gate rejects. */
.proj-pairs { display: grid; gap: 22px; }
.proj-pair { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.proj-pair figure { margin: 0; min-width: 0; }
.proj-pair .pill { margin-bottom: 8px; }
.proj-videos { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.proj-videos figure { margin: 0; min-width: 0; }
/* Walkthroughs are shot on a phone and most of them are PORTRAIT. `width:100%`
   alone made a 720x1280 clip render ~1800px tall in a 1012px column — one video
   taller than the rest of the page put together. Cap the HEIGHT and let the
   width follow the clip's own aspect ratio, so portrait and landscape are both
   sized by the dimension that actually constrains them. No object-fit: the box
   matches the video, so there is nothing to letterbox. */
.proj-videos video {
  width: auto; height: auto;
  max-width: 100%; max-height: 70vh;
  margin-inline: auto;
  border-radius: 10px; background: #000; display: block;
}
.proj-gallery img { border-radius: 10px; }

/* ── answer-first block (tools/build-answer-first.mjs, kind="answer") ───────
   The 40–60 word direct answer that opens every substantive page. It is
   CONTENT, not a callout: no panel, no background fill, no icon — a hairline
   gold rule, a bolded question, and body text one step larger than the page's.

   Position: page_sections render wherever a template puts {{template
   "sections" .}}, which on most templates is well below the fold and on
   templates/article.html is BELOW the whole guide body. An answer nobody
   reaches is not an answer, so #main is flexed and the block is ordered up to
   sit directly under the <h1>. Scoped by :has() so a page WITHOUT an answer
   block keeps the exact block layout it has today, and so browsers without
   :has() simply leave the section in document flow rather than breaking. */
#main:has(> .pg-sec--answer) { display: flex; flex-direction: column; }
#main:has(> .pg-sec--answer) > .pagehero { order: -3; }
/* .phead on every template except the graph-driven catalog page, whose hero is
   .p-hero (templates/product.html) — both must outrank the answer block. */
#main:has(> .pg-sec--answer) > .phead,
#main:has(> .pg-sec--answer) > .p-hero { order: -2; }
#main:has(> .pg-sec--answer) > .pg-sec--answer { order: -1; }

.pg-sec--answer { padding: 4px 0 10px; }
.pg-sec--answer .af { border-left: 2px solid var(--gold); padding-left: 18px; max-width: 68ch; }
.pg-sec--answer .af-q { margin: 0 0 6px; font-weight: 700; color: var(--fg); font-size: 1rem; line-height: 1.35; max-width: none; }
.pg-sec--answer .af-a { margin: 0; color: var(--fg-muted); font-size: 1.08rem; line-height: 1.62; max-width: none; }
@media (max-width: 640px) {
  .pg-sec--answer .af { padding-left: 14px; }
  .pg-sec--answer .af-a { font-size: 1.02rem; }
}

/* ── layout utilities ────────────────────────────────────────────────────────
   These exist so no template needs a style="" attribute. Inline CSS is a build
   failure here (see .claude/pm/80-qa/inline-css-scan.mjs): it cannot be cached,
   cannot be overridden by a later stylesheet, is invisible to the design system,
   and is the reason the CSP still needs style-src 'unsafe-inline'.
   Truly dynamic values (a per-product measured color, a meter width) are passed
   as CSS CUSTOM PROPERTIES instead — data, not styling. */
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 6px; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 14px; }
.mt-4 { margin-top: 16px; }
.mt-5 { margin-top: 18px; }
.pb-4 { padding-bottom: 32px; }
.mx-auto { margin-inline: auto; }
.center-x { justify-content: center; }
.text-gold { color: var(--gold); }
.text-fg { color: var(--fg); }
.fs-102 { font-size: 1.02rem; }
.is-hidden { display: none; }
/* two-column split used by /contact and the catalog item page */
.split-2 { display: grid; gap: 28px; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); align-items: start; }
.split-3 { display: grid; gap: 26px; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 760px) { .split-2, .split-3 { grid-template-columns: minmax(0, 1fr); } }
.grid-swatches { margin-top: 16px; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
/* sitemap column list */
.col-list { columns: 2 11rem; margin: 0; padding-left: 1.1em; color: var(--fg-muted); }
.col-list li { break-inside: avoid; margin: .2em 0; }
/* brand lockup in the footer/sitemap header */
.logo-plain { width: auto; border-radius: 0; }
.brand-sub { font-size: 10.5px; color: var(--fg-subtle); letter-spacing: .6px; }

/* Dynamic values arrive as custom properties, never as a style declaration. */
.chip[style*="--chip"] , .chip { background: var(--chip, var(--surface-2)); }
.p-meter > span { width: var(--pct, 0%); }
.swatchdot { background: var(--dot, var(--surface-2)); }

/* bare fallback 404 (server/main.go notFound) — non-page requests only */
.err-page { display: grid; place-items: center; min-height: 100vh; margin: 0; }
.err-box { max-width: 34rem; text-align: center; padding: 2rem; }
.err-code { color: var(--gold); font-size: 3rem; }

/* Form honeypot (leadform in partials.html). Positioned off-screen rather than
   display:none or [hidden] — a scripted filler skips inputs it can tell are
   hidden, and filling this is what marks a submission as spam at formsd. It is
   aria-hidden + tabindex=-1 in the markup, so nothing reaches it by keyboard or
   screen reader. Do NOT "clean this up" into display:none. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Same treatment for generated page_sections tables (cost tables on services,
   catalog indexes and city pages carry the identical 4-5 column shape as the
   homepage one the QA reviewer flagged). See the note in landing/landing.css. */
.pg-sec .tablewrap { position: relative; -webkit-overflow-scrolling: touch; }
@media (max-width: 760px) {
  .pg-sec .tablewrap table { min-width: 34rem; }
  .pg-sec .tablewrap th:first-child,
  .pg-sec .tablewrap td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--surface);
    box-shadow: 1px 0 0 var(--border);
  }
  .pg-sec .tablewrap {
    background:
      linear-gradient(to left, rgba(20,20,20,0), rgba(20,20,20,.75)) right center / 26px 100% no-repeat;
    background-attachment: local;
  }
}

/* Color-hub nav on a catalog index (color_hub.go → listing.html). Inline row
   of links, not cards: it sits under the H1 as a way to narrow, and cards here
   would compete with the product grid below it. */
.hubnav { margin-top: .5rem; color: var(--fg-subtle); font-size: .94rem; }
.hubnav a { color: var(--fg); text-decoration: underline; text-underline-offset: 3px; }
.hubnav a:hover { color: var(--gold); }

/* Catalog page navigation (catalog_pagination.go → listing.html). Plain links
   laid out as a wrapping row, so it degrades to a readable list with CSS off
   and never needs a horizontal scroller on a phone. The current page is a
   <span>, styled to look like the pressed state rather than like a link — a
   link to the page you are on is the one entry a reader can only be confused
   by. Tap targets are 44px so the row is usable with a thumb. */
.pagenav { margin-top: 2rem; border-top: 1px solid rgba(245,241,230,.1); padding-top: 1.25rem; }
.pagenav-count { color: var(--fg-subtle); font-size: .92rem; margin: 0 0 .75rem; }
.pagenav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.pagenav-list a,
.pagenav-here,
.pagenav-gap {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0 .7rem;
  border-radius: var(--radius); font-size: .95rem; line-height: 1;
}
.pagenav-list a { border: 1px solid rgba(245,241,230,.16); color: var(--fg); }
.pagenav-list a:hover { border-color: var(--gold); color: var(--gold-hi); text-decoration: none; }
.pagenav-here { border: 1px solid var(--gold); background: var(--surface-2); color: var(--gold-hi); font-weight: 600; }
.pagenav-gap { color: var(--fg-subtle); min-width: 1.5rem; padding: 0; }
.pagenav-step { font-weight: 600; }
