/* ============================================================
   thing.homes Materials - catalogue stylesheet
   Editorial, industrial, spec-first. Extends the main site's
   tokens: ink #1e2033 on warm canvas, indigo accents, Fraunces
   display over General Sans, rounded-full actions, soft shadows.
   ============================================================ */

:root {
  --canvas: #FAF8F4;
  --card: #FFFFFF;
  --ink: #1e2033;
  --ink-deep: #0f0f1a;
  --ink-btn: #1e2033;
  --ink-btn-hover: #2a2d45;
  --muted: #565a6e;
  --faint: #8a8da0;
  --line: #E8E4DA;
  --line-strong: #D9D4C6;
  --indigo: #4f46e5;
  --indigo-bright: #6366f1;
  --indigo-wash: #EEF0FE;
  --emerald: #10b981;
  --emerald-deep: #059669;
  --emerald-ink: #047857;   /* 5.48:1 with white: the only green legal under a text label */
  --shadow-card: 0 1px 2px rgba(16,24,40,0.07), 0 4px 12px rgba(16,24,40,0.06);
  --shadow-lift: 0 18px 40px rgba(15,23,42,0.16), 0 4px 10px rgba(15,23,42,0.08);
  --radius: 16px;
  --radius-lg: 24px;
  --font-display: "Fraunces", Georgia, serif;
  --font-sans: "General Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --container: 1180px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  font-feature-settings: "ss01" 1, "cv01" 1;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; display: block; }
a { color: inherit; }
.figure, .spec-table td, .fact-val { font-variant-numeric: tabular-nums; }

.wrap { max-width: var(--container); margin: 0 auto; padding: 0 20px; }
@media (min-width: 640px) { .wrap { padding: 0 32px; } }

/* ---------- type ---------- */
.display {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.08;
  margin: 0;
}
h1.display { font-size: clamp(34px, 5.4vw, 62px); }
h2.display { font-size: clamp(26px, 3.4vw, 40px); }
h3.display { font-size: clamp(21px, 2.4vw, 27px); }

.microlabel {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}
.microlabel.on-dark { color: #9297b8; }

.lede { font-size: clamp(16px, 1.6vw, 19px); line-height: 1.65; color: var(--muted); max-width: 62ch; }

/* ---------- scroll progress hairline ---------- */
#progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 0;
  background: var(--indigo); z-index: 90;
}

/* ---------- header ----------
   No backdrop-filter: it is the single most expensive property on weak
   or software-rendered machines and a sticky bar repaints it every frame. */
.site-header {
  position: sticky; top: 0; z-index: 60;
  background: rgba(250,248,244,0.97);
  border-bottom: 1px solid var(--line);
}
.site-header .bar {
  display: flex; align-items: center; gap: 14px;
  height: 64px;
}
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; min-width: 0; }
.brand img { width: 30px; height: 30px; }
.brand .word { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; white-space: nowrap; }
.brand .word em { font-style: normal; color: var(--indigo-bright); }
.brand .dept {
  margin-left: 12px; padding-left: 14px; border-left: 1px solid var(--line-strong);
  font-size: 12px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
.header-nav { margin-left: auto; display: flex; align-items: center; gap: 20px; }
.header-nav a.navlink {
  font-size: 14px; font-weight: 500; color: var(--muted);
  text-decoration: none;
}
.header-nav a.navlink:hover { color: var(--ink); }
@media (max-width: 720px) {
  .brand .dept { display: none; }
  .header-nav a.navlink { display: none; }
}

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-sans); font-size: 14px; font-weight: 600;
  border-radius: 999px; padding: 11px 22px; min-height: 44px;
  text-decoration: none; cursor: pointer; border: 0;
  transition: background-color .2s cubic-bezier(.2,0,0,1), border-color .2s, transform .2s cubic-bezier(.2,0,0,1);
}
.btn-primary { background: var(--ink-btn); color: #fff; }
.btn-primary:hover { background: var(--ink-btn-hover); }
.btn-ghost { background: transparent; color: var(--ink); border: 1px solid var(--line-strong); }
.btn-ghost:hover { border-color: var(--faint); background: #fff; }
.btn-light { background: #fff; color: var(--ink); }
.btn-light:hover { background: var(--indigo-wash); }
.btn-wa { background: var(--emerald); color: #fff; }
.btn-wa:hover { background: var(--emerald-deep); }

/* ---------- stage rail ---------- */
.stage-rail { border-bottom: 1px solid var(--line); background: var(--canvas); }
.stage-rail .rail {
  display: flex; gap: 0; overflow-x: auto; scrollbar-width: none;
}
.stage-rail .rail::-webkit-scrollbar { display: none; }
.stage-stop {
  flex: 1 0 auto; display: flex; align-items: baseline; gap: 10px;
  padding: 14px 22px 13px 0; margin-right: 22px;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  text-decoration: none; white-space: nowrap;
}
.stage-stop .num { font-family: var(--font-display); font-size: 15px; color: var(--faint); }
.stage-stop .nm { font-size: 14px; font-weight: 600; color: var(--muted); }
.stage-stop .rng { font-size: 12px; color: var(--faint); }
.stage-stop:hover .nm { color: var(--ink); }
.stage-stop.active { border-bottom-color: var(--indigo); }
.stage-stop.active .nm { color: var(--ink); }
.stage-stop.active .num { color: var(--indigo); }

/* ---------- hero (home): a photograph carries it ---------- */
.hero {
  position: relative; overflow: hidden;
  background: var(--ink-deep); color: #fff;
}
.hero .hero-media { position: absolute; inset: 0; overflow: hidden; }
.hero .hero-media img.hero-drift {
  width: 100%; height: 100%; object-fit: cover; object-position: center 62%;
  transform: scale(1.04); /* headroom for the slow scroll drift */
}
/* legibility scrim: deep ink from the left where the type sits */
.hero .hero-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(78deg, rgba(13,14,24,0.92) 0%, rgba(13,14,24,0.82) 34%, rgba(13,14,24,0.42) 62%, rgba(13,14,24,0.18) 100%),
    linear-gradient(to top, rgba(13,14,24,0.55) 0%, rgba(13,14,24,0) 30%);
}
.hero .inner { padding-top: 88px; padding-bottom: 76px; position: relative; z-index: 1; }
@media (min-width: 900px) { .hero .inner { padding-top: 128px; padding-bottom: 108px; } }
.hero .hero-copy { max-width: 640px; }
.hero h1.display { color: #fff; }
.hero .lede { color: rgba(235,237,248,0.88); }
.hero .microlabel { color: rgba(235,237,248,0.66); }
.hero .btn-ghost { color: #fff; border-color: rgba(255,255,255,0.45); background: rgba(15,15,26,0.25); }
.hero .btn-ghost:hover { border-color: #fff; background: rgba(15,15,26,0.45); }
.hero .btn-primary { background: #fff; color: var(--ink); }
.hero .btn-primary:hover { background: var(--indigo-wash); }
.hero-claims { display: flex; flex-wrap: wrap; gap: 12px 28px; margin-top: 36px; }
.hero-claims .claim { font-size: 14px; font-weight: 500; color: rgba(235,237,248,0.85); display: flex; align-items: center; gap: 9px; }
.hero-claims .claim svg { width: 17px; height: 17px; color: #A5B4FC; flex: 0 0 auto; }
.hero-credit {
  /* the ink, not the box, sits on the 20px gutter line: this link carries
     2px of horizontal padding from the phone tap-target rule below, so the
     offset is 18 there and 20 here. Found by the first both-engine sweep
     (2026-09-07): it was the only ink on the whole catalogue inside the
     gutter, at 16px on all 24 cells of the home page. */
  position: absolute; right: 20px; bottom: 10px; z-index: 2;
  font-size: 11px; color: rgba(235,237,248,0.5); text-decoration: none;
}
.hero-credit:hover { color: rgba(235,237,248,0.85); }

/* ---------- sections ---------- */
.section { padding-top: 72px; padding-bottom: 72px; }
@media (min-width: 900px) { .section { padding-top: 96px; padding-bottom: 96px; } }
.section-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 40px; }
.section-head .lede { margin-top: 14px; }

/* ---------- stage group headers on home ---------- */
.stage-block { border-top: 1px solid var(--line); padding-top: 44px; margin-top: 56px; }
.stage-block:first-of-type { border-top: 0; margin-top: 0; padding-top: 0; }
.stage-head { display: flex; align-items: baseline; gap: 18px; margin-bottom: 26px; }
.stage-head .big {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(40px, 5vw, 64px); line-height: 1; color: var(--line-strong);
}
.stage-head h2 { margin: 0; }
.stage-head .sub { font-size: 14px; color: var(--faint); }

/* ---------- category grid ---------- */


.cat-card {
  position: relative; display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
  text-decoration: none; overflow: hidden;
  transition: transform .3s cubic-bezier(.2,0,0,1), box-shadow .3s cubic-bezier(.2,0,0,1), border-color .3s;
}
.cat-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: #b9bccb; }
.cat-card .media { aspect-ratio: 16/9; background: #E6E0D2; border-bottom: 1px solid var(--line); overflow: hidden; }
.cat-card .media img, .cat-card .media svg { width: 100%; height: 100%; object-fit: cover; }
.cat-card .media img { transition: transform .45s cubic-bezier(.2,0,0,1); }
.cat-card:hover .media img { transform: scale(1.035); }
.cat-card .body { padding: 20px 22px 22px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.cat-card .code { font-size: 11px; font-weight: 600; letter-spacing: 0.14em; color: var(--faint); }
.cat-card h3 { margin: 0; font-family: var(--font-display); font-weight: 500; font-size: 21px; letter-spacing: -0.01em; }
.cat-card p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--muted); }
.cat-card .foot { margin-top: auto; padding-top: 12px; font-size: 13px; font-weight: 600; color: var(--indigo); display: flex; align-items: center; gap: 6px; }

/* brand-origin tag: plain bordered tag, no dot, no tint */
.origin-tag {
  display: inline-block; font-size: 11px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  border: 1px solid var(--line-strong); border-radius: 4px;
  padding: 3px 8px; color: var(--muted); background: transparent;
  width: max-content;
}
.origin-tag.house { border-color: var(--indigo); color: var(--indigo); }

/* ---------- product grid + cards ---------- */
/* ---------- card grids ----------
   Flex rather than grid so a short final row CENTRES instead of stranding a card
   against dead space on the right, which reads as a missing item. Counts that
   divide badly into three are given two columns instead, so four cards form a
   full 2x2 block rather than a row of three and one orphan. Full rows still fill
   the line exactly, so nothing shifts on the grids that were already correct. */
.prod-grid, .cat-grid { display: flex; flex-wrap: wrap; gap: 18px; justify-content: center; }
.prod-grid > *, .cat-grid > * { flex: 0 1 100%; }
@media (min-width: 640px) {
  .prod-grid > *, .cat-grid > * { flex-basis: calc((100% - 18px) / 2); }
}
@media (min-width: 1000px) {
  .prod-grid > *, .cat-grid > * { flex-basis: calc((100% - 36px) / 3); }
  /* two columns read as complete for these counts; three would orphan */
  .prod-grid[data-count="2"] > *, .cat-grid[data-count="2"] > *,
  .prod-grid[data-count="4"] > *, .cat-grid[data-count="4"] > * { flex-basis: calc((100% - 18px) / 2); }
  /* a lone card is given real presence instead of a third of a row */
  .prod-grid[data-count="1"] > *, .cat-grid[data-count="1"] > * { flex-basis: calc((100% - 18px) / 2); }
}

.prod-card {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
  text-decoration: none; overflow: hidden;
  transition: transform .3s cubic-bezier(.2,0,0,1), box-shadow .3s cubic-bezier(.2,0,0,1), border-color .3s;
}
.prod-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: #b9bccb; }
.prod-card .media { aspect-ratio: 4/3; background: #E6E0D2; border-bottom: 1px solid var(--line); overflow: hidden; }
.prod-card .media img, .prod-card .media svg { width: 100%; height: 100%; object-fit: cover; }
.prod-card .media img { transition: transform .45s cubic-bezier(.2,0,0,1); }
.prod-card:hover .media img { transform: scale(1.035); }
.prod-card .body { padding: 18px 20px 20px; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.prod-card h3 { margin: 0; font-family: var(--font-display); font-weight: 500; font-size: 19px; letter-spacing: -0.01em; }
.prod-card .kicker { font-size: 13.5px; line-height: 1.5; color: var(--muted); }
.prod-card .unit { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line); font-size: 12.5px; font-weight: 500; color: var(--faint); display: flex; justify-content: space-between; gap: 10px; align-items: center; }
.prod-card .unit .u { color: var(--muted); }

/* ---------- category page header: text left, photograph right ---------- */
.cat-hero { border-bottom: 1px solid var(--line); position: relative; overflow: hidden; }
.cat-hero .inner { padding-top: 56px; padding-bottom: 52px; position: relative; z-index: 1; }
@media (min-width: 900px) { .cat-hero .inner { padding-top: 72px; padding-bottom: 64px; } }
.cat-hero .split { display: grid; gap: 36px; align-items: center; }
@media (min-width: 960px) { .cat-hero .split { grid-template-columns: 6fr 5fr; gap: 56px; } }
.cat-hero .photo {
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--line-strong); box-shadow: var(--shadow-lift);
  aspect-ratio: 4/3; background: var(--card);
}
.cat-hero .photo img, .cat-hero .photo svg { width: 100%; height: 100%; object-fit: cover; display: block; }
.cat-hero .folio {
  position: absolute; right: -10px; top: 8px;
  font-family: var(--font-display); font-weight: 400; line-height: 1;
  font-size: clamp(120px, 16vw, 220px); color: transparent;
  -webkit-text-stroke: 1px var(--line-strong);
  user-select: none; pointer-events: none;
}
@media (min-width: 960px) { .cat-hero .folio { display: none; } } /* the photo takes its place */
.cat-hero .crumbs { display: flex; flex-wrap: wrap; gap: 8px; font-size: 13px; color: var(--faint); margin-bottom: 22px; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--ink); text-decoration: underline; }
.crumbs .sep { color: var(--line-strong); }
.crumbs .sep::before { content: "/"; }
.cat-hero .stageline { margin-top: 0; margin-bottom: 14px; }
.cat-hero .lede { margin-top: 18px; }
.cat-hero .brandline { margin-top: 22px; font-size: 14px; color: var(--muted); max-width: 62ch; }
.cat-hero .brandline strong { font-weight: 600; color: var(--ink); }

/* ---------- product page ---------- */
.prod-hero { border-bottom: 1px solid var(--line); }
.prod-hero .inner { padding-top: 44px; padding-bottom: 64px; }
.prod-layout { display: grid; gap: 40px; margin-top: 34px; }
@media (min-width: 960px) {
  .prod-layout { grid-template-columns: 5fr 6fr; gap: 64px; align-items: start; }
  .prod-media-col { position: sticky; top: 96px; }
}
.prod-media {
  border: 1px solid var(--line-strong); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--card); box-shadow: var(--shadow-lift);
}
.prod-media img, .prod-media svg { width: 100%; height: auto; display: block; }
.media-caption { font-size: 12px; color: var(--faint); margin-top: 10px; }

.badge-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.badge {
  font-size: 12px; font-weight: 600; color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 6px 13px; background: #fff;
}

.fact-caveat { font-size: 12.5px; color: var(--faint); margin-top: 10px; margin-bottom: 0; max-width: 62ch; }

.spec-block h2, .spec-block h3 { margin-top: 0; margin-bottom: 14px; }
.spec-table { width: 100%; border-collapse: collapse; }
.spec-table tr { border-top: 1px solid var(--line); }
.spec-table tr:last-child { border-bottom: 1px solid var(--line); }
.spec-table th {
  text-align: left; vertical-align: top; padding: 11px 18px 11px 0;
  font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--faint); width: 38%;
}
.spec-table td { padding-top: 11px; padding-bottom: 11px; font-size: 14.5px; color: var(--ink); }

.variant-list { display: flex; flex-direction: column; gap: 10px; }
.variant {
  border: 1px solid var(--line); border-radius: 12px; background: var(--card);
  padding: 13px 16px; display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: baseline;
}
.variant .vname { font-weight: 600; font-size: 14.5px; }
.variant .vspec { font-size: 13.5px; color: var(--muted); }

.site-note {
  border-left: 2px solid var(--indigo); padding: 4px 0 4px 18px;
  margin-top: 34px; margin-bottom: 0; font-size: 15px; color: var(--muted);
}
.site-note .who { display: block; font-size: 12px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); margin-bottom: 6px; }

.cta-panel {
  margin-top: 40px; border-radius: var(--radius-lg);
  background: var(--ink-deep); color: #fff; padding: 28px;
  display: flex; flex-wrap: wrap; gap: 18px; align-items: center; justify-content: space-between;
}
.cta-panel .t { font-family: var(--font-display); font-size: 21px; font-weight: 500; margin: 0; }
.cta-panel .s { font-size: 13.5px; color: #9297b8; margin-top: 6px; margin-bottom: 0; max-width: 46ch; }
.cta-panel .actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* ---------- related ---------- */
.related { padding-top: 64px; padding-bottom: 88px; }
.related h2 { margin-bottom: 26px; }

/* ---------- trust band (home) ---------- */
.trust-band { background: var(--ink-deep); color: #fff; }
.trust-band .inner { padding-top: 80px; padding-bottom: 80px; }
.trust-grid { display: grid; gap: 36px; margin-top: 48px; }
@media (min-width: 800px) { .trust-grid { grid-template-columns: repeat(3, 1fr); gap: 48px; } }
.trust-item .n { font-family: var(--font-display); font-size: 17px; color: #A5B4FC; }
.trust-item h3 { font-family: var(--font-display); font-weight: 500; font-size: 21px; margin-top: 10px; margin-bottom: 10px; }
.trust-item p { margin: 0; font-size: 14.5px; line-height: 1.65; color: #9297b8; }

/* ---------- how-to-buy strip ---------- */
.steps { counter-reset: step; display: grid; gap: 26px; margin-top: 44px; }
@media (min-width: 800px) { .steps { grid-template-columns: repeat(4, 1fr); } }
.step { border-top: 2px solid var(--line-strong); padding-top: 16px; }
.step .sn { font-family: var(--font-display); font-size: 16px; color: var(--indigo); }
.step h3 { font-size: 16px; font-weight: 600; margin-top: 8px; margin-bottom: 8px; }
.step p { margin: 0; font-size: 14px; color: var(--muted); line-height: 1.6; }

/* ---------- footer ---------- */
.site-footer { background: var(--ink-deep); color: #fff; border-top: 1px solid rgba(255,255,255,0.06); }
.site-footer .inner { padding-top: 64px; padding-bottom: 40px; }
.foot-grid { display: grid; gap: 40px; }
@media (min-width: 900px) { .foot-grid { grid-template-columns: 4fr 5fr 3fr; gap: 56px; } }
.site-footer .word { font-size: 20px; font-weight: 600; }
.site-footer .word em { font-style: normal; color: #818cf8; }
.site-footer p.about { font-size: 14px; line-height: 1.7; color: #9297b8; max-width: 44ch; margin-top: 14px; margin-bottom: 0; }
.foot-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px; }
.foot-col .microlabel { margin-bottom: 14px; display: block; }
.foot-col ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.foot-col a { font-size: 13.5px; color: #c6c9dd; text-decoration: none; }
.foot-col a:hover { color: #fff; text-decoration: underline; }
.foot-contact { display: flex; flex-direction: column; gap: 12px; }
.foot-contact a { font-size: 14.5px; color: #e6e8f4; text-decoration: none; }
.foot-contact a:hover { text-decoration: underline; }
.foot-base {
  margin-top: 52px; padding-top: 22px; border-top: 1px solid rgba(255,255,255,0.08);
  display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between;
  font-size: 12.5px; color: #7a7e9c;
}
.foot-base a { color: #9297b8; text-decoration: none; }
.foot-base a:hover { color: #fff; }

/* ---------- floating enquiry ---------- */
.float-stack {
  position: fixed; z-index: 70;
  right: calc(20px + env(safe-area-inset-right)); /* on the gutter line, measured both edges */
  bottom: calc(20px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 10px;
}
.float-btn {
  width: 54px; height: 54px; border-radius: 999px; border: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 18px rgba(15,23,42,0.22);
  transition: transform .2s cubic-bezier(.2,0,0,1), background-color .2s;
  color: #fff; text-decoration: none;
}
.float-btn:hover { transform: translateY(-2px); }
.float-btn svg { width: 26px; height: 26px; }
/* the labels are for the phone bar only; the desktop stack stays icon-only */
.float-label { display: none; }
/* WHITE ON EMERALD-500 IS 2.54:1. As an icon-only circle this button was judged by the 3:1
   non-text rule; the phone bar gives it a visible "WhatsApp" LABEL, which moves it to the 4.5:1
   text rule and it fails. --emerald-ink is emerald-700, 5.48:1 with white, computed not guessed.
   The lighter green stays the hover and the desktop circle s fill. */
.float-wa { background: var(--emerald-ink); }
.float-wa:hover { background: var(--emerald-deep); }
.float-call { background: var(--ink-btn); }
.float-call:hover { background: var(--ink-btn-hover); }

/* ---------- quote modal ---------- */
.modal-root { position: fixed; inset: 0; z-index: 100; display: none; }
.modal-root.open { display: block; }
.modal-scrim { position: absolute; inset: 0; background: rgba(15,15,26,0.55); }
.modal-card {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(480px, calc(100vw - 28px)); max-height: calc(100vh - 40px); overflow: auto;
  background: var(--card); border-radius: var(--radius-lg); box-shadow: var(--shadow-lift);
  padding: 28px;
}
.modal-card h2 { font-family: var(--font-display); font-weight: 500; font-size: 24px; margin-top: 0; margin-bottom: 4px; }
.modal-card .sub { font-size: 13.5px; color: var(--muted); margin-top: 0; margin-bottom: 20px; }
.modal-close {
  position: absolute; top: 14px; right: 14px; width: 36px; height: 36px;
  border: 0; background: transparent; cursor: pointer; border-radius: 10px;
  color: var(--faint); font-size: 22px; line-height: 1;
}
.modal-close:hover { background: var(--canvas); color: var(--ink); }
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
.field input, .field textarea {
  width: 100%; border: 1px solid var(--line-strong); border-radius: 10px;
  background: #fff; color: var(--ink); font-family: var(--font-sans);
  font-size: 15px; padding: 11px 13px;
}
.field input:focus, .field textarea:focus {
  outline: none; border-color: var(--indigo-bright);
  box-shadow: 0 0 0 3px rgba(99,102,241,0.18);
}
.field textarea { resize: vertical; min-height: 74px; }
.field .hint { font-size: 12px; color: var(--faint); margin-top: 5px; }
.field.error input { border-color: #dc2626; }
.field .err-msg { display: none; font-size: 12px; color: #dc2626; margin-top: 5px; }
.field.error .err-msg { display: block; }
.modal-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; }
.modal-actions .btn { width: 100%; }
.mailto-line { text-align: center; font-size: 13px; color: var(--muted); margin-top: 4px; margin-bottom: 0; }
.mailto-line a { color: var(--indigo); font-weight: 600; text-decoration: none; }
.mailto-line a:hover { text-decoration: underline; }

/* ---------- reveal motion ---------- */
.reveal { opacity: 0; transform: translateY(14px); }
.reveal.in {
  opacity: 1; transform: none;
  transition: opacity .55s cubic-bezier(.2,0,0,1), transform .55s cubic-bezier(.2,0,0,1);
  transition-delay: var(--d, 0ms);
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
  .reveal.in { transition: none; }
  .cat-card, .prod-card, .float-btn, .cat-card .media img, .prod-card .media img { transition: none; }
  .cat-card:hover, .prod-card:hover { transform: none; }
  .cat-card:hover .media img, .prod-card:hover .media img { transform: none; }
  .hero .hero-media img.hero-drift { transform: none; }
}

/* ---------- focus + a11y ---------- */
:focus-visible { outline: 2px solid var(--indigo); outline-offset: 2px; }
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: #fff; padding: 10px 18px; border-radius: 0 0 10px 0;
}
.skip-link:focus { left: 0; }

/* ---------- credits page ---------- */
.credits-list { margin-top: 30px; display: flex; flex-direction: column; }
.credit-row { border-top: 1px solid var(--line); padding-top: 14px; padding-bottom: 14px; display: grid; gap: 4px 24px; font-size: 14px; }
@media (min-width: 720px) { .credit-row { grid-template-columns: 2fr 3fr 2fr; } }
.credit-row:last-child { border-bottom: 1px solid var(--line); }
.credit-row .f { font-weight: 600; }
.credit-row .l { color: var(--muted); }
.credit-row a { color: var(--indigo); text-decoration: none; }
.credit-row a:hover { text-decoration: underline; }

/* wide tables scroll inside, never the page */
.table-scroll { overflow-x: auto; }

/* (the catalogue-plate rules that stood here were retired on 2026-09-07:
   every slot is a photograph and the build fails if one is missing) */

/* ============================================================
   PHONE (<= 700px). Measured at 390 on a touch context, not
   eyeballed from a screenshot: the footer and credits links were
   17 to 21px tall against a 44px thumb, the hero took 79% of the
   first screen before a single product appeared, and the quote
   modal filled 804px of an 844px viewport with a 36px close
   button. These rules fix what a thumb and an eye actually meet.
   ============================================================ */
@media (max-width: 700px) {
  /* every navigational link gets a real thumb target without
     changing how it looks: padding does the work, the negative
     margin keeps the visual rhythm */
  .foot-col a, .foot-contact a, .foot-base a, .credit-row a, .crumbs a, .media-caption a, .hero-credit {
    display: inline-block;
    padding: 12px 2px;
    margin-top: -12px; margin-bottom: -12px;
    min-height: 44px;
    line-height: 20px;
    box-sizing: content-box;
  }
  .foot-col ul { gap: 2px; }
  .brand { padding-top: 6px; padding-bottom: 6px; }

  /* the hero was eating the whole first screen, so nothing below it
     was discoverable without a scroll of faith */
  .hero .inner { padding-top: 52px; padding-bottom: 44px; }
  .hero-claims { gap: 10px 18px; margin-top: 24px; }
  .hero-claims .claim { font-size: 13px; }

  /* the stage rail scrolls sideways; say so with a fade rather than
     leaving the cut-off card looking like a rendering fault */
  .stage-rail .rail {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
    padding-right: 24px;
  }

  /* modal: it fitted, but only just, and shrank further the moment a
     keyboard opened */
  .modal-card { padding: 22px 18px; max-height: calc(100vh - 88px); width: calc(100vw - 24px); }
  .modal-close { width: 44px; height: 44px; top: 8px; right: 8px; }
  .field { margin-bottom: 12px; }
  .field textarea { min-height: 60px; }

  /* the floating pair sits over the last card on short pages, so the
     page ends above them rather than behind them */
  /* clearance for the phone bottom bar (8 + 48 + 8 + safe area) */
  body { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
}

/* An image slot must never read as white nothing while its file is in flight.
   Each slot carries a 16px version of its own photograph as an inline
   background (data/lqip.json, written by qa/pipeline/lqip.js) over this warm
   stone fallback, so a reader scrolling ahead of the network sees the
   photograph's own colours rather than an empty box. The real image paints over
   it on decode. */
.prod-media { background-color: #E6E0D2; }

/* ============================================================
   OVERFLOW HARDENING on the product template.
   A reported horizontal overflow did not reproduce: 62 product
   pages measured at 360, 375, 390 and 412 gave scrollWidth equal
   to clientWidth in all 248 combinations, and the template has no
   fixed pixel widths. These rules close the latent routes anyway,
   so the page cannot overflow whatever the content or engine.
   ============================================================ */

/* Grid and flex children default to min-width:auto, which refuses to shrink
   below their content. This is the most common cause of mobile overflow and
   the one thing the template had no defence against. */
.prod-layout > *, .prod-media-col, .spec-block, .variant, .fact, .cta-panel > * { min-width: 0; }

/* A long unbroken token in a spec value can never push the row wider than the
   column. The longest in the data today is 16 characters; this holds if a
   future one is 60. */
.spec-table th, .spec-table td, .variant .vspec, .variant .vname,
.fact .fact-val, .media-caption, .prod-card .kicker { overflow-wrap: anywhere; }

/* A table wide enough to need scrolling now says so, with the same edge fade
   the stage rail uses, rather than looking cut off. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (max-width: 700px) {
  .table-scroll {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
  }
  /* the fade is a lie if there is nothing to scroll to, so it only applies
     while the table actually overflows its box */
  .table-scroll:not([data-scrollable]) { -webkit-mask-image: none; mask-image: none; }
}

/* Belt and braces: nothing in the page body may exceed the viewport. */
.prod-hero, .prod-layout, .related, .cta-panel { max-width: 100%; }

/* ============================================================
   TEXT SCALE (owner report 2026-09-07: product pages overflow on
   his phone; measured 0 of 248 at 100% text). Reproduced by name:
   Android/Samsung "Text scaling" at 175% and 200% (Chrome allows up
   to 200%) overflows every width including 412, on 15 of 60 cells.
   Root causes, from text-node measurement on the live pages: the
   product title's longest word cannot wrap, the footer's two 1fr
   columns have a min-content floor, the "House and named brands"
   pill cannot shrink, and the fixed call/WhatsApp stack then moves
   with the widened layout viewport. Text scale cannot be queried
   in CSS, so these rules are unconditional and safe at 100%.
   ============================================================ */
.display, h1, h2, h3, .kicker, .lede, .crumbs, .microlabel, .fact-label,
.origin-tag, .badge, .site-footer p.about, .foot-col a, .foot-contact a,
.site-footer .word, .site-note, .cta-panel p, .cta-panel h2 {
  overflow-wrap: anywhere; hyphens: auto; -webkit-hyphens: auto;
}
.origin-tag, .badge { max-width: 100%; white-space: normal; }
.foot-grid > *, .foot-col, .foot-cols > *, .cta-panel > *, .prod-hero .wrap > * { min-width: 0; }
@media (max-width: 720px) {
  /* the sticky bar wraps rather than pushing the button past the edge */
  .site-header .bar { height: auto; min-height: 64px; flex-wrap: wrap; padding-top: 8px; padding-bottom: 8px; row-gap: 8px; }
  .site-header .bar .btn { white-space: normal; text-align: center; }
  .btn { white-space: normal; }
}

/* ============================================================
   GUTTERS (owner report 2026-09-07, described precisely: product
   pages ran edge to edge on his phone). Measured by comparison:
   text ink at x=0 on the category hero AND the product hero at 360
   and 390, while the card grid below kept 20px. Cause: every
   ".x .inner { padding: T 0 B }" shorthand above targets the same
   element as .wrap and reset its horizontal 20px (32px from 640)
   to 0; on desktop the centred container hid it, except that hero
   text sat 32px left of the grids beneath. All of them are now
   padding-top/padding-bottom, so .wrap's gutter survives on the
   home hero, category hero, product hero, trust band, footer and
   the phone header. Never write a padding shorthand on a .wrap.
   Three prior passes measured overflow and never measured gutters;
   the owner's description was the instrument that named this.
   ============================================================ */

/* ============================================================
   GUTTER LINT (owner ruling 2026-09-07). A padding or margin
   shorthand whose horizontal value is zero, on a block that
   carries text, is a gutter defect waiting to happen: it recurred
   six times in this stylesheet, once on the day the fix was
   written. build.js now fails on any such shorthand in this file.
   Vertical-only intent is written as padding-top / padding-bottom
   (margin-top / margin-bottom). There is no exception marker; the
   longhand is always available.
   ============================================================ */

/* ============================================================
   PRODUCT PAGE HIERARCHY (owner ruling 2026-09-07: the page was a
   wall of text; a buyer wants what it is, what grade, how to get a
   rate). First screen: photograph, name, one line of grade, one
   button. Everything else below, one highlighted element per
   screen; the spec block reads label muted, value strong; prose
   under a Details disclosure the buyer can skip.
   ============================================================ */
.prod-hero .inner { padding-top: 18px; padding-bottom: 36px; }
.prod-hero .crumbs { display: flex; flex-wrap: wrap; gap: 8px; font-size: 13px; color: var(--faint); margin-bottom: 18px; }
.prod-layout { margin-top: 0; }
.prod-buy h1.display { font-size: clamp(30px, 7.6vw, 52px); }
.prod-buy .grade { font-size: 16px; line-height: 1.5; color: var(--muted); margin-top: 10px; margin-bottom: 0; }
.btn-rate { margin-top: 22px; width: 100%; min-height: 54px; font-size: 16px; }
@media (min-width: 640px) { .btn-rate { width: auto; min-width: 280px; } }
@media (max-width: 720px) {
  /* on a phone the first screen is the hero and nothing else: it fills the
     viewport below the sticky bar, so photo, name, grade and the button
     are the only things a buyer meets before scrolling */
  .prod-hero .inner { min-height: calc(100svh - 64px); }
  .prod-hero .crumb-root, .prod-hero .crumb-root + .sep { display: none; }
  /* the hero button is the one dominant element; the bar's copy of it
     steps aside on product pages */
  .page-product .site-header .bar .btn { display: none; }
}
.prod-body { border-bottom: 1px solid var(--line); }
.prod-body .wrap { padding-top: 26px; padding-bottom: 56px; }
.prod-cols-main { max-width: 680px; }
@media (min-width: 960px) { .prod-cols { display: grid; grid-template-columns: 5fr 6fr; gap: 64px; } .prod-cols-main { grid-column: 2; max-width: none; } }
.prod-body .badge-row { margin-top: 0; }
.spec-title {
  font-family: var(--font-sans); font-size: 12px; font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--faint); margin-top: 30px; margin-bottom: 6px;
}
.spec-block .spec-table th { width: 34%; font-size: 11px; letter-spacing: 0.1em; color: var(--faint); padding-top: 12px; padding-bottom: 12px; }
.spec-block .spec-table td { font-size: 15.5px; font-weight: 600; color: var(--ink); line-height: 1.4; padding-top: 12px; padding-bottom: 12px; }
.prod-details { margin-top: 30px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.prod-details summary {
  list-style: none; cursor: pointer; padding-top: 16px; padding-bottom: 16px;
  font-size: 15px; font-weight: 600; color: var(--ink); display: flex; justify-content: space-between; align-items: center; min-height: 44px;
}
.prod-details summary::-webkit-details-marker { display: none; }
.prod-details summary::after { content: "+"; font-size: 20px; color: var(--faint); }
.prod-details[open] summary::after { content: "\2013"; }
.prod-details .intro { font-size: 15.5px; line-height: 1.65; color: var(--muted); margin-top: 0; margin-bottom: 18px; }
.prod-details h3 { font-size: 12px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); margin-top: 18px; margin-bottom: 8px; }
.prod-details .variant-list { margin-bottom: 18px; }
.prod-details .site-note { margin-top: 0; margin-bottom: 20px; }
.prod-body .cta-panel { margin-top: 34px; }
.prod-body .cta-panel .btn { white-space: normal; }

/* ---------- the quote modal, the checkout page: direct ---------- */
.modal-card { padding: 22px 22px 18px; }
.modal-card .q-for { font-size: 15px; color: var(--muted); margin: 0 44px 16px 0; line-height: 1.4; }
.modal-card .q-for strong { color: var(--ink); font-weight: 600; }
.field-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; }
.q-more { margin-top: 2px; margin-bottom: 0; }
.q-more summary { list-style: none; cursor: pointer; font-size: 13.5px; font-weight: 600; color: var(--indigo); padding-top: 10px; padding-bottom: 10px; min-height: 44px; display: flex; align-items: center; }
.q-more summary::-webkit-details-marker { display: none; }
.q-more[open] summary { padding-bottom: 6px; }
.q-more .field textarea { min-height: 64px; }
.modal-actions { margin-top: 12px; gap: 8px; }
.mailto-line { margin: 0; }
@media (max-width: 720px) {
  .modal-card { padding: 18px 16px 14px; }
  .field { margin-bottom: 10px; }
}
/* tap targets measured under 44px on the redesigned page: the one-word photo
   credit link and the email alternative under the send button */
.media-caption a { display: inline-block; padding-right: 10px; margin-right: -10px; }
.mailto-line a { display: inline-flex; align-items: center; min-height: 44px; padding-left: 8px; padding-right: 8px; }
/* the section label keeps its air above (the generic .spec-block h2 rule
   was winning on specificity); on a phone the hero group sits centred in the
   first screen rather than leaving all of the air below the button */
.spec-block .spec-title { margin-top: 30px; }
/* the floating call stack steps out of the first screen while the hero's own
   button is on screen (site.js toggles .float-stack.yield); with scripts off
   the stack simply shows, and the hero group stays top-aligned so the two do
   not meet at 844 */
.float-stack.yield { opacity: 0; pointer-events: none; transform: translateY(12px); }
.float-stack { transition: opacity .25s, transform .25s; }

/* ---------- the quote card under a software keyboard ---------- */
.modal-root.kb .modal-card { top: var(--vv-top, 8px); transform: translate(-50%, 0); max-height: var(--vv-h, calc(100vh - 16px)); }
.modal-card { scroll-padding-top: 12px; scroll-padding-bottom: 96px; }
.modal-card .modal-actions { position: sticky; bottom: -1px; background: var(--card); padding-top: 10px; padding-bottom: 6px; margin-bottom: -6px; }

@media (max-width: 700px) {
  /* 18 + the 2px tap-target padding = ink on the 20px line */
  .hero-credit { right: 18px; }
}

/* ============================================================
   ITEM 4 (owner's ordered plan, 2026-09-07): on a phone the
   floating call and WhatsApp buttons become a BOTTOM BAR, out of
   the content column entirely. The round stack was 54 px wide
   sitting on the 20 px gutter line, over the content while a
   reader scrolled, which is what the owner was seeing as the
   right margin being missing. The main site already carries this
   pattern on /estimate (#mobile-bar, with its round button
   hidden below md), so the catalogue follows it rather than
   inventing a second one. The desktop stack is unchanged.
   ============================================================ */
@media (max-width: 700px) {
  .float-stack {
    left: 0; right: 0; bottom: 0;
    flex-direction: row; gap: 10px;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
    background: rgba(250,248,244,0.94);
    -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
    border-top: 1px solid var(--line);
  }
  .float-btn {
    flex: 1 1 0; min-width: 0; width: auto; height: 48px; min-height: 48px;
    border-radius: 12px; gap: 9px;
    font-family: var(--font-sans); font-size: 14.5px; font-weight: 600;
    box-shadow: none;
  }
  .float-btn:hover { transform: none; }
  .float-btn svg { width: 20px; height: 20px; flex: none; }
  .float-label { display: inline; }
  /* the bar sits outside the content column, so it never covers anything and
     has no reason to yield to the hero button the way the round stack did */
  .float-stack.yield { opacity: 1; pointer-events: auto; transform: none; }
  /* NOT behind the quote dialog. The stack is z-index 70 and .modal-root is 100 with a 0.55 scrim,
     so the bar shows through dimmed and a tap on it hits .modal-scrim, whose handler CLOSES the
     form (site.js). A labelled, app-chrome-looking control that dismisses the thing you are filling
     in is worse than no control. :has() is unsupported on old engines, where this degrades to
     exactly today s behaviour. */
  body:has(.modal-root.open) .float-stack { display: none; }
}
