/* ============================================================
   Mooney Vision Solutions - components
   Phase 2. Sits on top of tokens.css, which owns every color
   and both typefaces. Nothing here introduces a new color; if
   a value looks hard-coded it is a neutral alpha, not a brand
   color.

   Class names marked (contract) are required by catalog-filter.js
   and must not be renamed without editing that file too.
   ============================================================ */

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

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

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  color: var(--g6);
  background: var(--warm-white);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--navy);
  line-height: 1.2;
  margin: 0 0 .5em;
  font-weight: 600;
}

a { color: var(--accent-ink); }
a:hover { color: var(--navy); }

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

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

.wrap { max-width: 1160px; margin: 0 auto; padding: 0 28px; }
.wrap-narrow { max-width: 820px; margin: 0 auto; padding: 0 28px; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--navy); color: #fff; padding: 12px 20px; border-radius: 0 0 6px 0;
}
.skip-link:focus { left: 0; color: #fff; }

/* ---- Header ------------------------------------------------ */

.site-header {
  background: #fff;
  border-bottom: 1px solid var(--g2);
  position: sticky; top: 0; z-index: 40;
}
.site-header .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; min-height: 78px;
}
/* Nav lockup - Phase 4. The full logo is a contained lockup: the wordmark
   sits inside the vesica with mostly empty space above and below it, so
   scaling it to header height kept the arcs and destroyed the type. The arcs
   run as the mark and the wordmark is set alongside in DM Sans, which is the
   register Shari's own wordmark is drawn in. See phase2.py header(). */
.site-logo { display: flex; align-items: center; gap: 12px; text-decoration: none; }
.site-logo img { height: 34px; width: auto; }
.wordmark { display: flex; flex-direction: column; line-height: 1; }
.wm-name {
  font-family: var(--font-body); font-weight: 700; font-size: 18.5px;
  color: var(--navy); letter-spacing: -.2px;
}
.wm-tag {
  font-size: 8.8px; font-weight: 500; letter-spacing: 1.1px;
  text-transform: uppercase; color: var(--g4); margin-top: 5px;
}
.site-logo:hover .wm-name { color: var(--accent-ink); }

.site-nav ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; gap: 30px; align-items: center;
}
.site-nav a {
  font-size: 14.5px; font-weight: 500; color: var(--g7);
  text-decoration: none; padding: 6px 0;
  border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: var(--navy); border-bottom-color: var(--accent); }
.site-nav a[aria-current="page"] { color: var(--navy); border-bottom-color: var(--accent); }

.nav-cta {
  background: var(--navy); color: #fff !important;
  padding: 10px 20px !important; border-radius: 6px;
  border-bottom: none !important; font-weight: 600;
}
.nav-cta:hover { background: var(--accent-ink); }

.nav-toggle {
  display: none; background: none; border: 1px solid var(--g2);
  border-radius: 6px; padding: 9px 12px; cursor: pointer; color: var(--navy);
  font-size: 15px; font-family: inherit;
}

/* ---- Page head --------------------------------------------- */

.page-head {
  background: var(--navy);
  color: var(--on-navy);
  padding: 62px 0 56px;
}
.page-head h1 { color: #fff; font-size: 40px; margin-bottom: 14px; }
.page-head p { max-width: 62ch; margin: 0; font-size: 16.5px; }

/* The one recurring brand device: the lens arc from Shari's logo,
   drawn in CSS rather than shipped as an image so it scales. Used
   only on dark section heads, never as page decoration. */
.page-head { position: relative; overflow: hidden; }
.page-head::after {
  content: ""; position: absolute; right: -120px; top: 50%;
  width: 420px; height: 240px; transform: translateY(-50%);
  border: 2px solid var(--accent-light); opacity: .22;
  border-radius: 50%; pointer-events: none;
}

.crumbs { font-size: 13px; margin-bottom: 18px; color: rgba(255,255,255,.6); }
.crumbs a { color: var(--accent-on-navy); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; color: var(--accent-on-navy); }
.crumbs span { padding: 0 8px; opacity: .5; }

/* Phase 6.1. Product breadcrumbs sit on white; the four rules above are for
   the navy hero pages and set a white-on-dark colour with the on-navy accent
   for links, which on a product page renders near-invisible. The generator
   patched over that with style="color:var(--g4)" on the <nav>, inline - and
   the deployed policy is style-src 'self' with no unsafe-inline, so in
   production that attribute is blocked and the fallback is the navy rule. The
   breadcrumbs were going to ship unreadable on all 37 product pages.

   The colour is --g5 rather than --g4: both pass on white, and --g5 is the
   darker of the two, which matters because breadcrumb text is 13px. Links
   take --navy-mid, 7.0:1 on white, rather than --accent-on-navy which is
   tuned for the opposite background. */
.product-hero .crumbs { color: var(--g5); }
.product-hero .crumbs a { color: var(--navy-mid); text-decoration: underline; }
.product-hero .crumbs a:hover,
.product-hero .crumbs a:focus-visible { color: var(--navy); }
/* The separator takes --g4 rather than --g3. tokens.css declares --g3 as a
   non-text value at 3.7:1 on white, and the "/" between breadcrumb items is
   rendered text whether or not it carries meaning. --g4 is 6.2:1. */
.product-hero .crumbs span { opacity: 1; color: var(--g4); }

/* ---- Catalog filter bar (contract) ------------------------- */

.catalog-controls { background: #fff; border-bottom: 1px solid var(--g2); padding: 26px 0 22px; }

.catalog-search { position: relative; max-width: 460px; margin-bottom: 20px; }
.catalog-search input {
  width: 100%; font-family: inherit; font-size: 15.5px; color: var(--td);
  padding: 12px 42px 12px 16px;
  border: 1px solid var(--g2); border-radius: 8px; background: var(--g1);
}
.catalog-search input:focus { background: #fff; border-color: var(--accent); outline: none; }
#catalogSearchClear {
  display: none; position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 28px; height: 28px; border: none; border-radius: 50%; background: var(--g2);
  cursor: pointer; color: var(--g6); font-size: 15px; line-height: 1;
  align-items: center; justify-content: center;
}

.filter-row { display: flex; gap: 14px; align-items: baseline; flex-wrap: wrap; margin-bottom: 12px; }
.filter-row > h2 {
  font-family: var(--font-body); font-size: 12.5px; font-weight: 600;
  color: var(--g4); margin: 0; flex: 0 0 74px; letter-spacing: .2px;
}
.pill-set { display: flex; gap: 8px; flex-wrap: wrap; }

.catalog-pill {
  font-family: inherit; font-size: 13.5px; font-weight: 500;
  padding: 7px 15px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--g2); background: #fff; color: var(--g6);
}
.catalog-pill:hover { border-color: var(--accent); color: var(--accent-ink); }
.catalog-pill.active {
  background: var(--navy); border-color: var(--navy); color: #fff;
}

.catalog-status { display: flex; gap: 16px; align-items: center; margin-top: 6px; min-height: 26px; }
#resultsCount { font-size: 13.5px; color: var(--g4); }
#catalogReset {
  font-family: inherit; font-size: 13.5px; background: none; border: none;
  color: var(--accent-ink); cursor: pointer; text-decoration: underline; padding: 0;
}

/* ---- Category dividers (contract) -------------------------- */

.cat-divider { grid-column: 1 / -1; margin: 44px 0 4px; scroll-margin-top: 100px; }
.cat-divider:first-child { margin-top: 8px; }
.cat-divider h2 {
  font-size: 25px; margin: 0 0 6px;
  display: flex; align-items: baseline; gap: 14px;
}
.cat-divider h2::after {
  content: ""; flex: 1; height: 1px; background: var(--g2);
}
.cat-divider p { margin: 0; font-size: 14.5px; color: var(--g4); max-width: 64ch; }

/* ---- Product grid (contract: .pc, data-* attributes) ------- */

.product-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 26px; padding: 34px 0 72px;
}
.product-grid.filter-active { gap: 26px; }

.pc {
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--g2); border-radius: 10px;
  overflow: hidden; text-decoration: none;
  transition: border-color .15s ease, transform .15s ease;
}
.pc:hover { border-color: var(--accent); transform: translateY(-2px); }

.pc-media { background: var(--cream); padding: 22px; }
/* Every vendor render is composited onto pure white by
   fetch_product_images.py. Multiply blending drops that white into the cream
   tile so the device floats on the panel instead of sitting in a white box
   inside it. Only safe because the backgrounds are known to be pure white. */
.pc-media img { width: 100%; aspect-ratio: 1; object-fit: contain; mix-blend-mode: multiply; }

.pb { padding: 20px 22px 22px; display: flex; flex-direction: column; flex: 1; }
.pbr { font-size: 11.5px; font-weight: 700; letter-spacing: 1.4px; color: var(--accent-ink); margin-bottom: 8px; }
.pb h3 { font-size: 19px; margin: 0 0 9px; }
.pb p { margin: 0 0 16px; font-size: 14px; line-height: 1.6; color: var(--g6); }

.ptags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.ptg {
  font-size: 11px; font-weight: 600; color: var(--g5);
  background: var(--g1); border: 1px solid var(--g2);
  padding: 4px 9px; border-radius: 4px;
}

.catalog-no-results {
  grid-column: 1 / -1; text-align: center; padding: 60px 20px;
  background: #fff; border: 1px dashed var(--g2); border-radius: 10px;
}
.catalog-no-results p { margin: 0 0 18px; }
.catalog-clear-filters {
  font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  background: var(--navy); color: #fff; border: none; padding: 10px 22px; border-radius: 6px;
}

/* ---- Product page ------------------------------------------ */

.product-hero { padding: 48px 0 56px; background: #fff; border-bottom: 1px solid var(--g2); }
.product-hero .wrap { display: grid; grid-template-columns: 1fr 1.15fr; gap: 52px; align-items: start; }

.ph-media { background: var(--cream); border-radius: 12px; padding: 34px; }
.ph-media img { width: 100%; aspect-ratio: 1; object-fit: contain; mix-blend-mode: multiply; }

.ph-brand { font-size: 11.5px; font-weight: 700; letter-spacing: 1.5px; color: var(--accent-ink); margin-bottom: 10px; }
.ph-body h1 { font-size: 36px; margin-bottom: 6px; }
.ph-tagline { font-size: 17px; color: var(--g5); margin: 0 0 22px; font-style: italic; }
.ph-body > p { font-size: 16px; margin: 0 0 22px; }

.ph-tags { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 26px; }

.btn {
  display: inline-block; font-weight: 600; font-size: 15px; text-decoration: none;
  padding: 13px 26px; border-radius: 7px;
}
.btn-primary { background: var(--navy); color: #fff; }
.btn-primary:hover { background: var(--accent-ink); color: #fff; }
.btn-secondary { border: 1px solid var(--g2); color: var(--navy); background: #fff; }
.btn-secondary:hover { border-color: var(--accent); color: var(--navy); }
.btn-row { display: flex; gap: 12px; flex-wrap: wrap; }

.section { padding: 54px 0; }
.section + .section { border-top: 1px solid var(--g2); }
.section h2 { font-size: 27px; margin-bottom: 22px; }

.feature-list { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 44px; }
.feature-list li {
  break-inside: avoid; padding-left: 26px; position: relative;
  margin-bottom: 13px; font-size: 15px;
}
.feature-list li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 11px; height: 6px; border-left: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent); transform: rotate(-45deg);
}

/* Practice fit. First two entries carry the argument, so they get
   the full width; the rest are supporting detail in two columns. */
.practice-fit { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; }
.pf-item {
  background: #fff; border: 1px solid var(--g2);
  border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0; padding: 20px 22px;
}
.pf-item:nth-child(1), .pf-item:nth-child(2) { grid-column: 1 / -1; }
.pf-item h3 { font-size: 16.5px; margin: 0 0 7px; font-family: var(--font-body); font-weight: 700; }
.pf-item p { margin: 0; font-size: 14.5px; }

.spec-table { width: 100%; border-collapse: collapse; font-size: 14.5px; }
.spec-table th, .spec-table td { text-align: left; padding: 12px 16px; border-bottom: 1px solid var(--g2); vertical-align: top; }
.spec-table th { width: 34%; font-weight: 600; color: var(--navy); background: var(--g1); }
.spec-table tr:last-child th, .spec-table tr:last-child td { border-bottom: none; }
.spec-wrap { border: 1px solid var(--g2); border-radius: 8px; overflow: hidden; background: #fff; }

.spec-note, .source-note {
  font-size: 13.5px; color: var(--g4); margin-top: 16px;
}

.ask-panel {
  background: #fff; border: 1px solid var(--g2); border-radius: 10px;
  padding: 26px 28px;
}
.ask-panel h2 { font-size: 21px; margin-bottom: 8px; }
.ask-panel p { margin: 0 0 18px; font-size: 15px; }

.related { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }

/* ---- CTA band ---------------------------------------------- */

.cta-band { background: var(--navy); color: var(--on-navy); padding: 58px 0; text-align: center; }
.cta-band h2 { color: #fff; font-size: 29px; }
.cta-band p { max-width: 56ch; margin: 0 auto 26px; }
.cta-band .btn-primary { background: var(--accent); color: #fff; }
.cta-band .btn-primary:hover { background: var(--accent-light); color: var(--navy); }
.cta-band .btn-secondary { background: transparent; border-color: rgba(255,255,255,.35); color: #fff; }
.cta-band .btn-secondary:hover { border-color: #fff; color: #fff; }
.cta-band .btn-row { justify-content: center; }

/* ---- Footer ------------------------------------------------ */

.site-footer { background: var(--td); color: rgba(255,255,255,.62); padding: 52px 0 30px; font-size: 14px; }
.site-footer .wrap { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 40px; }
/* Footer lockup - Phase 4. Same fault as the header: mvs-logo-inverse.png is
   the contained lockup with every pixel remapped to white, so at 40px the
   arcs and the wordmark merged into one white smear. Mark plus typeset
   wordmark, sized up from the nav because the footer has the room. */
.foot-lockup { display: flex; align-items: center; gap: 13px; margin-bottom: 16px; }
.foot-lockup img { height: 38px; width: auto; }
.foot-lockup .wm-name { color: #fff; font-size: 19px; }
.foot-lockup .wm-tag { color: rgba(255,255,255,.55); font-size: 9px; }
.site-footer h2 { color: #fff; font-family: var(--font-body); font-size: 13px; font-weight: 700; letter-spacing: .8px; margin-bottom: 14px; }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: 9px; }
.site-footer a { color: rgba(255,255,255,.72); text-decoration: none; }
.site-footer a:hover { color: #fff; }
/* Phase 4.1: .45 over --td computes to 4.46:1, under the 4.5:1 floor for
   text at this size. .50 is 5.19:1. The trademark attribution is the one
   paragraph on this site with a legal reason to be readable. */
.footer-legal {
  border-top: 1px solid rgba(255,255,255,.14); margin-top: 40px; padding-top: 22px;
  font-size: 12.5px; color: rgba(255,255,255,.50);
}

/* ---- Phase stub banner. Removed when the real page lands;
        verify.py fails the build if any survive to launch. --- */

.stub-banner {
  background: #FFF6E5; border-bottom: 1px solid #E8D5A8;
  padding: 14px 0; font-size: 14px; color: #6B4E00;
}
.stub-banner strong { color: #4A3600; }

/* ---- Responsive -------------------------------------------- */

@media (max-width: 980px) {
  .product-grid, .related { grid-template-columns: repeat(2, 1fr); }
  .product-hero .wrap { grid-template-columns: 1fr; gap: 32px; }
  .ph-media { max-width: 420px; }
  .site-footer .wrap { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 720px) {
  .wrap, .wrap-narrow { padding: 0 20px; }
  .nav-toggle { display: block; }
  .site-nav {
    display: none; position: absolute; top: 78px; left: 0; right: 0;
    background: #fff; border-bottom: 1px solid var(--g2); padding: 18px 20px 24px;
  }
  .site-nav.open { display: block; }
  .site-logo img { height: 28px; }
  .site-logo { gap: 10px; }
  /* Phase 4.1: the tagline rendered at 7.6px, below the size at which
     letter-spaced uppercase is readable on a phone. The name is what has to
     survive at this width; the tagline is decoration and is dropped rather
     than shrunk into illegibility. */
  .wm-name { font-size: 16px; }
  .wm-tag { display: none; }
  .site-nav ul { flex-direction: column; align-items: stretch; gap: 4px; }
  .site-nav a { padding: 11px 0; border-bottom: 1px solid var(--g2); }
  .nav-cta { text-align: center; margin-top: 12px; }
  .page-head { padding: 44px 0 40px; }
  .page-head h1 { font-size: 30px; }
  .page-head::after { display: none; }
  .product-grid, .related { grid-template-columns: 1fr; }
  .feature-list { columns: 1; }
  .practice-fit { grid-template-columns: 1fr; }
  .ph-body h1 { font-size: 28px; }
  .site-footer .wrap { grid-template-columns: 1fr; gap: 28px; }
  .filter-row > h2 { flex-basis: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  .pc:hover { transform: none; }
}

/* ---- Phase 7: touch targets, captions, print ------------------ */
/* The Phase 6 handoff measured interactive elements on a 390px viewport:
   footer links 18px tall, catalog pills 33px, the nav toggle 40px,
   breadcrumb links 17px, and the product links inside comparison tables
   18-26px. WCAG 2.2 sets 24px as the floor (2.5.8); 44px is the size a
   thumb hits reliably in a car park between calls, which is where this site
   gets read. Every fix here adds hit area with padding and negative margin
   so the visual rhythm of the page does not move. */
.nav-toggle { min-height: 44px; min-width: 44px; padding: 10px 14px; }
.catalog-pill { min-height: 44px; padding: 11px 16px; } /* Phase 7.1: 44px, as the note above promises */
.crumbs a { display: inline-block; padding: 8px 0; margin: -8px 0; }
.site-footer li { margin-bottom: 4px; }
.section-foot a { display: inline-block; padding: 12px 0; margin: -12px 0; }
.site-logo { min-height: 44px; }
.site-footer li a { display: inline-block; padding: 7px 0; }
.site-footer address a, .site-footer p a { display: inline-block; padding: 7px 0; margin: -7px 0; }
.footer-legal a { display: inline; padding: 0; margin: 0; }
.cmp-table a, .spec-table a, .also-list a, .lane-step a {
  display: inline-block; padding: 6px 0; margin: -6px 0;
}
/* A minimum target for any solitary link that is not running text. */
.btn { min-height: 44px; }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
@media print {
  .site-header, .site-footer, .cta-band, .nav-toggle, .btn-row, .skip-link { display: none !important; }
  body { color: #000; background: #fff; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 90%; }
  .product-hero, .page-head { background: none; color: #000; }
  .page-head h1, .product-hero h1 { color: #000; }
}

/* Phase 7: byline on the resource articles (phase5_resources.py) */
.byline { margin: 14px 0 0; font-size: 14px; color: rgba(255,255,255,.72); }
.byline-name { color: #fff; font-weight: 600; }
.byline time { white-space: nowrap; }

/* Phase 7: territory FAQ (phase41_pages.py) */
.faq-list { border-top: 1px solid var(--g2); }
.faq-item { border-bottom: 1px solid var(--g2); }
.faq-item summary { list-style: none; cursor: pointer; padding: 16px 40px 16px 0; position: relative; min-height: 44px; }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary h3 { margin: 0; font-size: 18px; color: var(--navy); }
.faq-item summary::after {
  content: ""; position: absolute; right: 6px; top: 22px; width: 10px; height: 10px;
  border-right: 2px solid var(--accent-ink); border-bottom: 2px solid var(--accent-ink);
  transform: rotate(45deg); transition: transform .15s;
}
.faq-item[open] summary::after { transform: rotate(-135deg); top: 26px; }
.faq-item summary:hover h3, .faq-item summary:focus-visible h3 { color: var(--accent-ink); }
.faq-item p { margin: 0 0 18px; max-width: 66ch; }

/* Phase 7: calculators (phase7_tools.py). Reuses the contact form field
   styles so the two calculators and the form read as one system. */
.calc-wrap { max-width: 760px; }
.calc .cf-row { margin-bottom: 18px; }
.calc .cf-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.calc label { font-weight: 600; font-size: 15px; margin-bottom: 6px; display: block; }
.calc input, .calc select {
  font: inherit; font-size: 16px; padding: 11px 13px; border: 1px solid var(--g2);
  border-radius: 7px; width: 100%; min-height: 44px; background: #fff; color: var(--navy);
}
.calc input:focus-visible, .calc select:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }
.calc-unit { display: flex; align-items: stretch; }
.calc-unit span {
  display: flex; align-items: center; padding: 0 12px; border: 1px solid var(--g2);
  border-right: 0; border-radius: 7px 0 0 7px; background: var(--cream); color: var(--g5);
}
.calc-unit input { border-radius: 0 7px 7px 0; }
.cf-help { display: block; font-size: 13.5px; color: var(--g5); margin-top: 6px; line-height: 1.45; }
.cf-opt { font-weight: 400; color: var(--g4); font-size: 13px; }
.calc-noscript { background: var(--cream); border-left: 4px solid var(--accent); padding: 14px 18px; }
.calc-results { margin-top: 36px; padding: 28px; background: var(--navy); color: var(--on-navy); border-radius: 10px; }
.calc-results h2 { color: #fff; font-size: 22px; margin: 0 0 18px; }
.calc-out { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 0; }
.calc-out div { background: rgba(255,255,255,.06); border-radius: 8px; padding: 14px; }
.calc-out dt { font-size: 12.5px; text-transform: uppercase; letter-spacing: .6px; color: rgba(255,255,255,.7); }
.calc-out dd { margin: 6px 0 0; font-size: 26px; font-weight: 600; color: #fff; font-family: var(--font-display); }
.calc-note { margin: 18px 0 0; font-size: 15.5px; }
.calc-how { margin-top: 18px; font-size: 14.5px; color: rgba(255,255,255,.8); }
.calc-how summary { cursor: pointer; color: #fff; padding: 8px 0; min-height: 44px; display: flex; align-items: center; }
/* Phase 7.1: the next step, directly under the result rather than three sections down. */
.calc-next { margin-top: 24px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,.18); }
.calc-next p { margin: 0 0 14px; font-size: 15.5px; color: rgba(255,255,255,.9); }
.nowrap { white-space: nowrap; }
.calc-how p { margin: 8px 0 0; }
/* 2026-09-08: the CMS reference table under the payback calculator. Reuses the
   comparison-table shape rather than inventing a third one. calc-num is right
   aligned and tabular so the dollar columns line up on the decimal. */
.calc-table { width: 100%; border-collapse: collapse; margin: 22px 0 16px; font-size: 15.5px; }
.calc-table caption { caption-side: top; text-align: left; font-size: 14.5px;
  color: var(--g4); padding-bottom: 10px; }
.calc-table th, .calc-table td { padding: 10px 12px; border-bottom: 1px solid var(--g2);
  text-align: left; vertical-align: top; }
.calc-table thead th { font-size: 13px; text-transform: uppercase; letter-spacing: .5px;
  color: var(--g4); border-bottom: 2px solid var(--g2); }
.calc-table .calc-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.calc-source { font-size: 14.5px; color: var(--g4); }
@media (max-width: 720px) {
  .calc .cf-row-2 { grid-template-columns: 1fr; }
  .calc-out { grid-template-columns: 1fr 1fr; }
  .calc-out dd { font-size: 22px; }
  /* The service column is the one that can go: the code names the row and the
     two money columns are the point of the table. */
  .calc-table { font-size: 14.5px; }
  .calc-table th:nth-child(2), .calc-table td:nth-child(2) { display: none; }
  .calc-table th, .calc-table td { padding: 9px 8px; }
}

/* ==== PHASE 3 HOME (generated by phase3_home.py) ==== */
/* ---- Hero -------------------------------------------------------------
   Phase 4.1. Phase 3 built this without a photograph specifically because
   both sibling sites use a scenic-photo-plus-scrim hero. Review reversed the
   decision. What did not change is the vesica: the portrait is still clipped
   to the construction from Shari's own mark rather than to the circle both
   siblings use, so the photograph sits behind the identity rather than
   replacing it.

   Photograph (Phase 16): Lake Superior from the Minnesota North Shore,
   Courtney Celley / USFWS, public domain. See IMAGE-CREDITS.md. */

.hero { position: relative; background: var(--navy); color: var(--on-navy);
        overflow: hidden; padding: 84px 0 92px; isolation: isolate; }
.hero-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 62% 42%; z-index: -2;
}
/* Left-weighted, not flat. A flat scrim dark enough to carry white text at
   4.5:1 takes the photograph out entirely, which is how you end up having
   paid for a hero image nobody can see. This holds ~92% navy under the
   headline and lets the sky through on the right. */
.hero-scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(96deg, rgba(11,27,71,.94) 0%, rgba(11,27,71,.88) 30%,
                    rgba(11,27,71,.56) 52%, rgba(11,27,71,.22) 72%,
                    rgba(11,27,71,.10) 100%),
    linear-gradient(to top, rgba(11,27,71,.62), rgba(11,27,71,.06) 52%);
}

/* ---- The lens (Phase 15) ----------------------------------------------
   Shari's portrait came off this page at her request; the mark that used to
   crop it is now drawn on its own over the open half of the photograph. See
   hero() in phase3_home.py for why the mark stays when the portrait goes.

   Placement is measured from the copy, not from the wrap. The span starts
   one gap to the right of the 620px copy column and runs to a quarter of
   the side margin past the wrap's edge, so on a wide screen the lens moves
   out over the field instead of sitting boxed inside the text column's
   grid. The lens is centred in that span and capped at 520px, which only
   binds above about 1450px wide; below that the span is narrower than the
   cap. The cap keeps it from outgrowing the headline on a large monitor. Vertically it
   is centred on 41%, just above the water line of the mn-calm-waters master
   at every desktop width (2000x857, cover, object-position 62% 42%), so it
   frames the treeline and its reflection.

   Colour. The upper arc runs from white at its lower left to the teal the
   rest of the hero uses for its eyebrow and links, which is the same
   direction the facets run in the logo (dark lower left, light upper
   right). The lower arc is plain white at .6 and quieter, again as in the
   logo, where it is the thinner and darker of the two. At .74 it read as
   beige over the gold field. White rather than the
   logo's own navy because the scrim puts navy over this side of the
   photograph at .10-.22, and a navy mark on a navy-tinted sky disappears in
   exactly the way Phase 5 kept finding accents disappearing. */
.hero-lens-layer { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.hero-lens {
  --side: max(0px, (100% - 1160px) / 2);
  position: absolute; top: 41%; transform: translateY(-50%);
  left: calc(var(--side) + 28px + 620px + 64px);
  right: max(28px, var(--side) / 2);
  width: auto; max-width: 520px; margin: 0 auto;
  aspect-ratio: 1572 / 772; height: auto; overflow: visible;
}
.lens-stop-a { stop-color: #fff; stop-opacity: .86; }
.lens-stop-b { stop-color: var(--accent-on-navy); stop-opacity: .96; }
.lens-upper { fill: url(#lens-upper); }
.lens-lower { fill: #fff; fill-opacity: .6; }

/* One column on the home page now; the landing page keeps two (its own
   .landing .hero-grid rule, phase8_landing.py). 620px is the width the copy
   column had beside the portrait at the full wrap, so the copy wraps where
   it always did. */
.home .hero-grid { grid-template-columns: minmax(0, 620px); }

.hero-grid {
  position: relative; display: grid; grid-template-columns: 1.35fr .65fr;
  gap: 56px; align-items: center;
}
.hero-eyebrow {
  font-size: 12.5px; letter-spacing: 1.4px; text-transform: uppercase;
  font-weight: 700; color: var(--accent-on-navy); margin: 0 0 18px;
}
.hero h1 { color: #fff; font-size: 48px; line-height: 1.12; margin: 0 0 22px; max-width: 19ch;
            text-wrap: balance; }
.hero-lede { font-size: 17.5px; max-width: 54ch; margin: 0 0 16px; color: #E4E8F0; }
.hero-lede + .hero-lede { margin-bottom: 30px; }
.hero .btn-secondary { background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.45); color: #fff; }
.hero .btn-secondary:hover { border-color: #fff; background: rgba(255,255,255,.14); color: #fff; }
.hero .btn-primary { background: var(--accent); color: #fff; }
.hero .btn-primary:hover { background: var(--accent-light); color: var(--navy); }
.hero-third { margin: 20px 0 0; font-size: 14.5px; color: #D3D9E4; }
.hero-third a { color: var(--accent-on-navy); }
.hero-third a:hover { color: #fff; }

/* Phase 16: .hero-defs and .lens-mask are gone. The landing page was the
   last reader of both (Phase 15 flag 1); its portrait came off at Shari's
   request, so nothing on the site crops a photograph to the vesica now. */
/* ---- Photographic bands ----------------------------------------------
   Same mechanism as the hero, used on the territory band and on the About
   and /territory heroes. .on-dark flips the map palette; see phase41_map.py. */

.band-photo { position: absolute; inset: 0; width: 100%; height: 100%;
              object-fit: cover; object-position: 50% 60%; z-index: -2; }
/* .84/.72 rather than .93/.88. Against the gold grass the darkest of these
   still puts white text at roughly 11:1, so the extra density was buying
   nothing except hiding the photograph the band exists to show. This is the
   value for a page hero, where the photograph is the only one on the page. */
.band-scrim { position: absolute; inset: 0; z-index: -1;
              background:
                linear-gradient(180deg, rgba(11,27,71,.84), rgba(11,27,71,.72)),
                linear-gradient(100deg, rgba(11,27,71,.30), rgba(11,27,71,0) 62%); }

/* ---- Section rhythm --------------------------------------------------
   Phase 5. The review note was that the page has "too much white background
   together" and goes flat straight after the hero. It did. The order was:

       hero            navy + photograph
       lanes           #fff
       featured        --cream, which is two steps off white
       room            #fff
       territory       navy + photograph
       cta             navy

   so the middle 1,500px - three consecutive sections, the whole product
   argument - rendered as one undifferentiated pale block, and the cream in
   the middle of it was too close to white to read as a division at all. The
   sibling sites get away with a long light run because they break it with
   full-bleed photographs every second section; this page had used both of its
   photographs before the run started.

   The fix is alternation rather than more decoration:

       hero            navy + photograph
       lanes           --warm-white, cards in white
       featured        navy
       room            pale blue tint
       territory       navy + photograph
       cta             navy

   No two light sections now touch, the two photographic bands are separated
   by four screens rather than sitting either side of one, and the page steps
   down in tone toward the closing call to action instead of arriving at it
   from a flat white field. */

.home .section { padding: 62px 0; }
.home .section-featured, .home .section-territory { padding: 72px 0 78px; }
/* The 1px divider exists to separate two light sections. Between a light and
   a dark one it draws a grey line across the top of a navy band. */
.home .section + .section.on-dark, .home .section.on-dark + .section { border-top: 0; }

/* ---- Entry lanes ------------------------------------------- */

.section-lede { max-width: 66ch; font-size: 16.5px; margin: -8px 0 30px; }

/* --cream, not --warm-white. The first attempt used --warm-white, which is
   #F8F7F5 - three points off white on every channel, and on the rendered page
   it read as white with a rounding error. If a band is there to break a light
   run it has to be visible in a screenshot. */
.section-lanes { background: var(--cream); }

.lane-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
/* Five lanes into a three-column grid: the last two take half the row each,
   which reads as deliberate rather than as a short final row. */
.lane-grid .lane:nth-child(4), .lane-grid .lane:nth-child(5) { grid-column: span 3 / span 3; }
@supports (grid-template-columns: subgrid) {
  .lane-grid { grid-template-columns: repeat(6, 1fr); }
  .lane-grid .lane { grid-column: span 2; }
  .lane-grid .lane:nth-child(4), .lane-grid .lane:nth-child(5) { grid-column: span 3; }
}

/* Phase 5 colour pass, replacing Phase 4.1's. That pass gave each card a 3px
   tinted top edge and called it done, which put the accent on the page at the
   width of a hairline - technically present, invisible in a screenshot. The
   accent now arrives as a filled icon well at 46px, and the cards sit on
   --warm-white so white is itself doing work rather than being the default. */
.lane {
  display: block; background: #fff; border: 1px solid var(--g2);
  border-radius: 10px; padding: 24px 24px 20px; text-decoration: none;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.lane:hover { border-color: var(--accent);
              transform: translateY(-2px); box-shadow: 0 10px 26px rgba(11,27,71,.10); }
.lane-icon {
  display: flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; border-radius: 12px; margin-bottom: 17px;
  /* Solid, not a 12% tint. The tint version was measured against the card and
     the card is white, so the well rendered as a very slightly green square -
     the same "technically present, invisible in a screenshot" failure the 3px
     top border had. Five solid accent tiles is the accent actually appearing
     on the page at a size a person notices. */
  background: var(--accent); color: #fff;
  transition: background .15s ease;
}
.lane-icon svg { width: 26px; height: 26px; }
.lane:hover .lane-icon { background: var(--navy); }
.lane h3 { font-size: 19px; margin: 0 0 10px; }
.lane p { margin: 0 0 14px; font-size: 15px; color: var(--g5); }
.lane-go {
  font-size: 13.5px; font-weight: 700; color: var(--accent-ink);
  font-family: var(--font-body);
}
.lane-go::after { content: " \2192"; }

/* ---- Featured card (contract: .pf, .pf-eyebrow, .pf-body) ---
   Phase 5 puts this band on navy. Every product render is composited onto
   pure white and blended into its tile with mix-blend-mode: multiply, so the
   cards cannot go dark - and they should not. White cards on navy is a
   stronger frame for a product photograph than white cards on cream, and it
   is the break the light run needed. */

.section-featured { background: var(--navy); color: var(--on-navy); }
.section-featured h2 { color: #fff; }
.section-featured .section-lede { color: #C9D0DC; }
.featured-wrap { margin-bottom: 26px; }
.pf {
  display: grid; grid-template-columns: 1fr 1.25fr; gap: 0;
  background: #fff; border: 1px solid var(--g2); border-radius: 10px;
  overflow: hidden; text-decoration: none; align-items: center;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* Phase 11. Three flagship cards, stacked media-over-copy, each at the
   width a third of the wrap gives it. The render sits on the cream well at
   the same height on all three so the IC200 (tall and narrow) and the Solix
   (wide and low) read as the same size of object. */
.flag-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
.pf-flag { grid-template-columns: 1fr; align-items: stretch; }
.pf-flag .pf-media { height: 300px; padding: 22px 28px; }
.pf-flag .pf-media img { max-height: 100%; }
.pf-flag .pf-body { padding: 26px 28px 28px; }
.pf-flag .pf-body h3 { font-size: 23px; }
.pf-flag .pf-body p { font-size: 15px; }
.section-sub { margin: 0 0 16px; font-size: 15.5px; color: #C9D0DC; }
.pf:hover { border-color: var(--accent); }
/* On navy a 1px --g2 border is invisible and a shadow does nothing. The ring
   is what separates the card from the band, and it brightens on hover. */
.section-featured .pf, .section-featured .pc {
  border-color: transparent; box-shadow: 0 0 0 1px rgba(255,255,255,.14),
                                         0 14px 34px rgba(4,10,30,.34);
}
.section-featured .pf:hover, .section-featured .pc:hover {
  box-shadow: 0 0 0 2px var(--accent-light), 0 18px 40px rgba(4,10,30,.42);
}
/* Phase 3.1: the product renders are square, so a 1fr media column made the
   featured card as tall as the image and left the copy floating in the top
   third of a very tall white panel. Capping the media height lets the card
   size to its content. */
.pf-media { background: var(--cream); padding: 26px; display: flex;
            align-items: center; justify-content: center; }
.pf-media img { mix-blend-mode: multiply; max-height: 240px; width: auto;
                max-width: 100%; object-fit: contain; }
.pf-body { padding: 30px 34px; }
.pf-eyebrow {
  font-size: 12px; letter-spacing: 1.1px; text-transform: uppercase;
  font-weight: 700; color: var(--accent-ink); margin-bottom: 9px;
}
.pf-body h3 { font-size: 25px; margin: 0 0 12px; }
.pf-body p { margin: 0 0 16px; font-size: 15.5px; color: var(--g5); }

/* Phase 11: the row under the flagships is two cards, each half the wrap,
   so it reads as a pair rather than as two orphans of a four-up grid. */
.home-grid { grid-template-columns: repeat(2, 1fr); padding: 0 0 8px; gap: 20px; }
/* At half the wrap a square render is 560px tall; cap it so the pair sits
   under the flagships rather than dwarfing them. */
.home-grid .pc-media img { aspect-ratio: auto; height: 250px; }
.section-foot { margin: 28px 0 0; font-size: 15px; font-weight: 600; }
.section-featured .section-foot a { color: var(--accent-on-navy); }
.section-featured .section-foot a:hover { color: #fff; }

/* ---- Four lines, one room ----------------------------------
   Phase 5: a pale blue tint rather than #fff, so the section reads as its own
   band between two dark ones instead of as the gap between them. The four
   rules take four values from the existing palette rather than four new
   colours - the point is that the four lines are distinct, and inventing a
   brand colour per manufacturer would be both off-system and, for two of
   them, wrong, since Marco and Eyefficient are the same company. */

.section-room { background: #EDF0F6; }
.room-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.room-item { border-top: 3px solid var(--accent); padding-top: 16px; }
.room-item:nth-child(1) { border-top-color: var(--navy); }
.room-item:nth-child(2) { border-top-color: var(--steel); }
.room-item:nth-child(3) { border-top-color: var(--accent); }
.room-item:nth-child(4) { border-top-color: var(--navy-mid); }
.room-item h3 { font-size: 18px; margin: 0 0 8px; }
.room-item p { margin: 0; font-size: 14.5px; color: var(--g5); }

/* ---- Territory band ----------------------------------------
   Phase 5 darkens the scrim here specifically, and only here. The review note
   was that the photograph is "too visible" - which it was, but not because
   .84/.72 is the wrong value for a photographic band in general. It is the
   right value on the About and /territory heroes, where the photograph is the
   only one on the page and is meant to be looked at. On the home page it is
   the second photograph in one scroll, sitting under a map that has to be
   read, and it was competing with both. Denser, and desaturated so the fall
   colour stops pulling the eye off the map: the photograph is now texture
   under the band rather than a picture behind it. */

.section-territory { position: relative; isolation: isolate;
                     color: var(--on-navy); }
.section-territory .band-photo { filter: saturate(.55) contrast(.94); }
.section-territory .band-scrim {
  background:
    linear-gradient(180deg, rgba(11,27,71,.945), rgba(11,27,71,.90)),
    linear-gradient(100deg, rgba(11,27,71,.32), rgba(11,27,71,0) 66%);
}
.section-territory h2 { color: #fff; }
.section-territory p { color: #D3D9E4; }
.terr-grid { display: grid; grid-template-columns: 1fr 1.05fr; gap: 52px;
             align-items: center; }
.terr-figure { max-width: 560px; width: 100%; justify-self: end; }
.section-territory .btn-row { margin-top: 26px; }
/* The base .btn-primary is navy, which on a navy band is a rectangle of the
   background with white text in it. Same fix the hero already carries. */
.section-territory .btn-primary { background: var(--accent); color: #fff; }
.section-territory .btn-primary:hover { background: var(--accent-light); color: var(--navy); }

/* The territory band and the closing call to action are both navy and meet
   with no visible edge, so the photograph appears to stop for no reason. One
   hairline is enough to make that a section change rather than a glitch. */
.home .section-territory + .cta-band { border-top: 1px solid rgba(255,255,255,.13); }

/* ---- Photographic heroes on a phone (Phase 13) ------------------------
   Every scenic master is a panorama, 2.3:1 to 3.5:1. On a desktop the hero
   is wider than it is tall, so object-fit: cover shows most of the picture
   and the scrim only has to be dense on the side the copy sits on. On a
   phone the same hero is a tall column - 390 x 1274 on the home page - and
   cover shows a 13%-wide sliver of the panorama, magnified two and a half
   times, with the copy over all of it. Phase 4.1 answered that by taking
   the mobile scrim to a flat .90/.94, which made the text safe and the
   photograph invisible: navy with a faint brown smear in it.

   The phone treatment below keeps the desktop's idea and turns it ninety
   degrees. Desktop: dense navy under the copy on one side, the photograph
   open on the other. Phone: the photograph open in a strip across the top,
   at a height where the panorama shows most of its width, fading to navy
   where the copy begins. The copy sits on the same .94-.96 navy it has on
   desktop and the picture is shown at a size where it is a picture.

   One mechanism, four heroes. --strip and --strip-scrim are set here once,
   at 980 so they exist for every page's own mobile breakpoint (About 860,
   Resources 900, landing 980). Each hero's mobile block sets its own
   padding-top and object-position and points its scrim at the variable;
   see phase4_about.py, phase5_resources.py and phase8_landing.py. Check 69
   holds the four to it. Nothing here reaches a viewport wider than 980px. */
@media (max-width: 980px) {
  :root {
    /* 62vw keeps the crop's proportion steady from 320 to 550; 340px caps
       it on tablets; 60vh keeps a landscape phone from spending most of
       its screen on the strip. */
    --strip: min(62vw, 60vh, 340px);
    /* Light over the strip - .14 at the top, .22 where the fade begins -
       then to .95 across the last 130px so the eyebrow lands on navy.
       Below the strip the hero's own navy shows through at .96, which is
       the same value the desktop scrim holds under the headline. */
    --strip-scrim: linear-gradient(180deg,
      rgba(11,27,71,.14) 0,
      rgba(11,27,71,.22) calc(var(--strip) - 130px),
      rgba(11,27,71,.95) calc(var(--strip) - 6px),
      rgba(11,27,71,.96) 100%);
  }
}

@media (max-width: 900px) {
  .hero { padding: calc(var(--strip) - 10px) 0 58px; }
  .hero-grid { grid-template-columns: 1fr; gap: 34px; }
  .hero h1 { font-size: 32px; max-width: none; }
  .hero-lede { font-size: 16.5px; }
  /* Phase 15. No lens on a phone: the photograph is a strip above the copy
     and the mark has nowhere to sit that is not on top of the text. */
  .hero-lens-layer { display: none; }
  /* The strip. Phase 16: 62% x keeps the hills and their reflection in
     the middle of the strip, with the dark pines on the left edge. */
  .hero-photo { top: 0; bottom: auto; height: var(--strip); object-position: 62% 50%; }
  .hero-scrim { background: var(--strip-scrim); }
  .home .section, .home .section-featured, .home .section-territory { padding: 46px 0 50px; }
  .lane-grid, .room-grid, .terr-grid { grid-template-columns: 1fr !important; }
  .home-grid { grid-template-columns: repeat(2, 1fr) !important; }
  .flag-grid { grid-template-columns: 1fr; }
  .pf-flag .pf-media { height: 240px; }
  .lane-grid .lane { grid-column: auto !important; }
  .terr-figure { justify-self: stretch; max-width: none; }
  .pf { grid-template-columns: 1fr; }
  .pf-body { padding: 24px; }
  .pf-body h3 { font-size: 21px; }
}
@media (max-width: 560px) {
  .home-grid { grid-template-columns: 1fr !important; }
}

/* ---- Territory map (phase41_map.py) ------------------------------------
   Every state in the territory is filled, one colour: navy on a light band,
   pale teal on the dark one. The -part and -county rules below only render
   if catalog.json's `partial` list is ever non-empty again; since 2026-09-07
   it is empty and the map is a single fill. */

.terr-map { display: block; width: 100%; height: auto; }
.terr-state { fill: none; stroke: var(--navy); stroke-width: 2.2;
              stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.terr-state-full { fill: var(--navy); stroke: var(--warm-white); stroke-width: 1.6; }
.terr-state-part { fill: var(--accent); stroke: var(--warm-white); stroke-width: 1.6; }
.terr-county { fill: none; stroke: rgba(255,255,255,.34); stroke-width: .7;
               vector-effect: non-scaling-stroke; }
.terr-county-on { fill: var(--accent-ink); stroke: rgba(255,255,255,.55); }

/* On the dark band the same map has to invert or it disappears, and the two
   fills have to separate from each other as well as from the navy behind
   them. --accent against --accent-ink was two dark teals a shade apart: both
   legible as "filled", neither distinguishable from the other, and the darker
   one sank into the band. The light map's contrast is navy-to-teal, so the
   dark map's is teal-to-pale-teal, running the hierarchy the other way. */
.on-dark .terr-state { stroke: rgba(255,255,255,.55); }
.on-dark .terr-state-full { fill: var(--accent-on-navy); stroke: rgba(11,27,71,.55); stroke-width: 1.6; }
.on-dark .terr-state-part { fill: var(--accent); stroke: rgba(11,27,71,.45); stroke-width: 1.6; }
.on-dark .terr-county { stroke: rgba(11,27,71,.30); }

.terr-keys { list-style: none; margin: 22px 0 0; padding: 0; display: flex;
             flex-wrap: wrap; gap: 10px 26px; font-size: 14.5px; }
.terr-key { display: flex; align-items: center; gap: 9px; }
.terr-key span { width: 15px; height: 15px; border-radius: 3px; flex: none;
                 border: 1.5px solid var(--navy); }
.terr-key-full span { background: var(--navy); }
.terr-key-part span { background: var(--accent); }
.terr-key-pending span { background: repeating-linear-gradient(45deg,
    var(--accent-glow), var(--accent-glow) 3px, transparent 3px, transparent 6px);
    border-style: dashed; }
.on-dark .terr-keys { color: var(--on-navy); }
.on-dark .terr-key span { border-color: rgba(255,255,255,.6); }
.on-dark .terr-key-full span { background: var(--accent-on-navy); }
.on-dark .terr-key-part span { background: var(--accent); }
/* ==== END PHASE 3 HOME ==== */
/* ==== PHASE 3.1 ==== */
/* One provenance block per product page instead of a raw URL in the spec note
   plus a second "Source page" section. The source name is the link; the URL
   itself is not printed, which keeps the source-of-truth benefit without
   making the page look like research notes. */
.provenance {
  border-left: 3px solid var(--accent);
  padding: 14px 0 14px 18px; margin: 8px 0 0;
}
.provenance p { margin: 0; font-size: 14.5px; color: var(--g5); }
/* Phase 5.1: was var(--ink), which is declared nowhere. --td is the ink value
   this was reaching for, and the only token in the ramp darker than --g8. */
.provenance strong { color: var(--td); }
.provenance-note { margin-top: 8px !important; font-size: 13.5px; }
/* ==== END PHASE 3.1 ==== */

/* ==== PHASE 4 ABOUT (generated by phase4_about.py) ==== */
/* ---- About hero (Phase 10 rebuild) -------------------------------------
   .band-photo / .band-scrim are defined in the home block. The photograph
   and scrim are the Phase 4.1 ones; the composition is new: framed portrait
   on the left, forward of the band and overhanging its bottom edge into the
   section below; copy and a derived facts strip on the right. The overhang
   is the whole idea - it is what makes the portrait read as a print laid on
   the page rather than a cut-out set into it - so the section that follows
   carries extra top padding to make room, and the hero itself has no bottom
   padding because the portrait is what ends it. */
.about-hero { position: relative; isolation: isolate; background: var(--navy);
              padding: 72px 0 0; }
.about-hero .band-photo { object-position: 50% 45%; }
/* Phase 15, at Shari's request: the portrait is smaller. One variable sets
   the print's width and everything that belongs to the object is derived
   from it - the mat, the offset teal frame, the overhang into the next
   section and the room that section makes for it - so the Phase 10
   composition keeps its proportions at the new size instead of carrying a
   320px print's overhang under a 272px one. The ratios are the Phase 10
   values divided by 320: mat 7, offset 16, overhang 72.

   272 is 15% narrower and 28% less area: "slightly smaller" was the
   brief, and 15% is enough to see side by side without changing what the
   hero is about. No other size was rendered; if Shari wants it smaller
   still, change --ah-w here and nothing else. */
.about { --ah-w: 272px;
         --ah-overhang: calc(var(--ah-w) * .225);
         --ah-mat: calc(var(--ah-w) * .022);
         --ah-offset: calc(var(--ah-w) * .05); }
.ah-grid { display: grid; grid-template-columns: var(--ah-w) 1fr; gap: 60px;
           align-items: center; }
.ah-copy { padding: 8px 0 80px; }
.ah-eyebrow {
  font-size: 11.5px; font-weight: 700; letter-spacing: 1.6px;
  text-transform: uppercase; color: var(--accent-light); margin: 0 0 14px;
}
.about-hero h1 { color: #fff; font-size: 50px; margin: 0 0 8px; line-height: 1.08; }
.ah-role { color: var(--accent-on-navy); font-size: 15px; font-weight: 600;
           letter-spacing: .2px; margin: 0 0 20px; }
.ah-lede { color: var(--on-navy); font-size: 17px; max-width: 54ch; margin: 0 0 26px; }
.about-hero .btn-secondary {
  background: transparent; border-color: rgba(255,255,255,.35); color: #fff;
}
.about-hero .btn-secondary:hover { border-color: var(--accent-light); color: #fff; }

/* The facts strip. Every number is derived in hero(); none is typed. */
.ah-facts { list-style: none; margin: 34px 0 0; padding: 22px 0 0;
            border-top: 1px solid rgba(255,255,255,.18);
            display: flex; flex-wrap: wrap; gap: 18px 40px; }
.ah-facts li { display: flex; flex-direction: column; min-width: 92px; }
.ah-facts strong { font-family: var(--font-display); font-weight: 500;
                   font-size: 34px; line-height: 1; color: #fff; }
.ah-facts span { font-size: 13px; color: var(--on-navy); margin-top: 6px;
                 letter-spacing: .2px; }

/* The framed print. A warm-white mat, a soft drop, and behind it - offset
   down and left - an open teal frame, the one brand gesture on the object.
   The image keeps its own edges; a print shows its backdrop. */
.ah-portrait { position: relative; margin: 0; align-self: end;
               margin-bottom: calc(-1 * var(--ah-overhang)); z-index: 1; }
.ah-frame { position: relative; background: var(--warm-white); padding: var(--ah-mat);
            border-radius: 4px;
            box-shadow: 0 30px 60px rgba(4,10,32,.45), 0 4px 14px rgba(4,10,32,.30); }
.ah-frame img { display: block; width: 100%; height: auto; border-radius: 2px;
                background: var(--navy-mid); }
.ah-frame::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  transform: translate(calc(-1 * var(--ah-offset)), var(--ah-offset));
  border: 2px solid var(--accent-on-navy); border-radius: 4px; opacity: .9;
}
/* Room for the overhang. Scoped to the section that follows the hero.
   The overhang plus the 60px the section had above its heading in Phase 10. */
.about .about-hero + .section { padding-top: calc(var(--ah-overhang) + 60px); }

.reason-note { font-size: 14.5px; color: var(--g5); margin-top: 12px;
               border-left: 3px solid var(--accent); padding-left: 14px; }

/* ---- Coverage (Phase 10) -----------------------------------------------
   Its own class. `section-territory` is the home page's navy band and its
   colour rules are not scoped; reusing the class here painted this
   section's text white on warm white. */
.section-coverage { background: var(--cream); }
.coverage-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 56px;
                 align-items: center; }
.coverage-copy h2 { margin-top: 0; }
.coverage-copy .section-lede { color: var(--navy); font-weight: 500;
                               font-size: 19px; margin-bottom: 14px; }
.coverage-copy p { max-width: 50ch; }
.coverage-copy .btn-row { margin-top: 26px; }
.section-coverage .terr-figure-lg { max-width: 640px; }

/* The marked gaps. Amber, matching the stub banner, because it is the same
   signal: this is not finished and must not ship like this. */
.needs-shari {
  border: 1px dashed #C08A2E; background: #FDF7EA;
  border-radius: 8px; padding: 18px 20px; margin: 22px 0;
}
.ns-flag {
  font-size: 10.5px; font-weight: 700; letter-spacing: 1.4px;
  text-transform: uppercase; color: #8A6014; margin: 0 0 8px;
}
.needs-shari h3 {
  font-size: 17px; margin: 0 0 6px; font-family: var(--font-display);
  color: var(--navy);
}
.needs-shari p { margin: 0; font-size: 15px; color: var(--g6); }

.reasons { list-style: none; margin: 0; padding: 0; counter-reset: r; }
.reasons li {
  counter-increment: r; padding: 0 0 0 52px; position: relative;
  margin: 0 0 30px;
}
.reasons li::before {
  content: counter(r, decimal-leading-zero);
  position: absolute; left: 0; top: 2px;
  font-family: var(--font-display); font-size: 20px; color: var(--accent);
}
.reasons h3 { font-size: 18.5px; margin: 0 0 7px; }
.reasons p { margin: 0; }

.section-proof { background: var(--cream); }
.proof-quote {
  border-left: 3px solid var(--accent); background: #fff;
  padding: 22px 26px; margin: 26px 0; border-radius: 0 8px 8px 0;
}
.proof-quote p { margin: 0 0 10px; font-size: 16px; color: var(--g7); }
.pq-src { font-size: 13.5px !important; color: var(--g4) !important; margin: 0 !important; }

.lc-cats {
  list-style: none; margin: 0 0 8px; padding: 0;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px 24px;
}
.lc-cats a {
  display: block; padding: 11px 14px; background: #fff;
  border: 1px solid var(--g2); border-radius: 7px;
  text-decoration: none; font-size: 15px; font-weight: 500;
}
.lc-cats a:hover { border-color: var(--accent); color: var(--navy); }

@media (max-width: 860px) {
  /* Phone photo strip; the mechanism and the variables are in the home
     block (phase3_home.py), Phase 13. 46% x centres the crop on the road
     and the butte behind it; the desktop copy sits over that part and the
     ridge on the right is the picture there. */
  .about-hero { padding-top: calc(var(--strip) - 10px); }
  .about-hero .band-photo { top: 0; bottom: auto; height: var(--strip); object-position: 46% 50%; }
  .about-hero .band-scrim { background: var(--strip-scrim); }
  /* The base .btn-primary is navy. On the desktop this hero's .84 scrim
     leaves enough photograph behind it to read as a button; on the phone
     the copy sits on .96 navy and it disappears into the band. Same fix
     the home hero and the territory band carry, scoped here so the desktop
     About page is untouched. */
  .about-hero .btn-primary { background: var(--accent); color: #fff; }
  .about-hero .btn-primary:hover { background: var(--accent-light); color: var(--navy); }
  .ah-grid { grid-template-columns: 1fr; gap: 28px; }
  /* Phase 15: 232 -> 196, the same 15%. The phone keeps its own overhang
     ratio (48/232) because the print sits centred under the facts strip
     there, not beside the copy. */
  .about { --ah-w: 196px; --ah-overhang: calc(var(--ah-w) * .207); }
  .ah-portrait { max-width: var(--ah-w); width: 100%;
                 margin: 0 auto calc(-1 * var(--ah-overhang)); order: 2; }
  .ah-copy { order: 1; padding-bottom: 8px; text-align: left; }
  .about-hero h1 { font-size: 36px; }
  .ah-facts { gap: 14px 26px; }
  .ah-facts strong { font-size: 28px; }
  .about .about-hero + .section { padding-top: calc(var(--ah-overhang) + 44px); }
  .coverage-grid { grid-template-columns: 1fr; gap: 28px; }
  .lc-cats { grid-template-columns: 1fr; }
  .reasons li { padding-left: 42px; }
}
/* ==== END PHASE 4 ABOUT ==== */
/* ==== PHASE 4.1 PAGES (generated by phase41_pages.py) ==== */
/* ---- Territory page --------------------------------------------------- */

.terr-page-grid { display: grid; grid-template-columns: 1.25fr .85fr; gap: 48px;
                  align-items: center; }
.terr-figure-lg { max-width: 100%; }
.terr-note { font-size: 14.5px; color: var(--g5); margin-top: 18px; }

.state-list { list-style: none; margin: 0 0 28px; padding: 0; }
.state-list li { display: flex; justify-content: space-between; align-items: baseline;
                 gap: 20px; padding: 13px 0; border-bottom: 1px solid var(--g2); }
.state-list strong { font-size: 17px; }
.state-list span { font-size: 14.5px; color: var(--accent-ink); font-weight: 600; }

.cty-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 28px; }
.cty-list { columns: 2; font-size: 14.5px; list-style: none; padding: 0; margin: 0; }
.cty-list li { padding: 2px 0; }

/* ---- Contact ---------------------------------------------------------- */

.contact-grid { display: grid; grid-template-columns: 1.4fr .6fr; gap: 44px;
                align-items: start; }
/* Phase 5. A grid item's default min-width is auto, i.e. min-content, and the
   min-content width of this column is set by the longest <option> in the
   product select - "Optovue Solix Essential" and friends. So the form column
   refused to go below 377px and the whole page scrolled sideways at 320 and
   390. The select is the only element on the site whose intrinsic width is
   driven by data, which is why this was the only page it happened on. */
.contact-grid > *, .cform > *, .cf-row > *, .cf-field { min-width: 0; }
/* The select itself also has to be told, or it sizes to its widest option and
   pushes its own (now shrinkable) column back out. */
.cf-field select { max-width: 100%; width: 100%; }
.cform { display: flex; flex-direction: column; gap: 20px; }
.cf-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.cf-field { display: flex; flex-direction: column; }
.cf-field label { font-size: 14.5px; font-weight: 600; margin-bottom: 7px;
                  font-family: var(--font-body); }
.cf-field label span[aria-hidden] { color: var(--accent-ink); }
.cf-opt { font-weight: 400; color: var(--g4); font-size: 13px; }
.cf-field input, .cf-field select, .cf-field textarea {
  font: inherit; font-size: 16px; padding: 11px 13px; border: 1px solid var(--g3);
  border-radius: 8px; background: #fff; color: var(--g6);
  transition: border-color .12s ease, box-shadow .12s ease;
}
/* Phase 5.1. The old rule leaned on a 1px border-colour change plus a 12%-alpha
   glow, which is under the 3:1 non-text contrast WCAG asks for and is close to
   invisible on a laptop screen in daylight. A solid 2px ring, offset so it does
   not sit on top of the border it is replacing, is unambiguous. */
.cf-field input:focus-visible, .cf-field select:focus-visible,
.cf-field textarea:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-glow);
}
/* :invalid alone paints every empty required field red before the visitor has
   touched anything, which reads as being told off for arriving. :user-invalid
   waits until they have actually interacted with it. */
.cf-field input:user-invalid, .cf-field textarea:user-invalid { border-color: #B3261E; }
.cf-help { font-size: 13.5px; color: var(--g4); margin: 7px 0 0; }
.cform-note { font-size: 15px; font-weight: 600; color: var(--accent-ink);
              background: var(--accent-glow); padding: 11px 14px; border-radius: 8px;
              margin: 0; }
.cf-consent { font-size: 13.5px; color: var(--g5); margin: 0; max-width: 62ch; }

/* Off-screen rather than display:none. Some bots skip fields that are
   display:none on the assumption that they are traps, which defeats the trap. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

.form-status { padding: 14px 16px; border-radius: 8px; margin-bottom: 20px;
               font-size: 15px; }
.form-status-ok { background: var(--accent-glow); color: var(--accent-ink);
                  border: 1px solid var(--accent); }
.form-status-error { background: #FDECEA; color: #8C1D18; border: 1px solid #E3B4AE; }
.form-status-busy { background: var(--cream); color: var(--g5); border: 1px solid var(--g2); }

.contact-aside { display: flex; flex-direction: column; gap: 20px; }
.ca-line { margin: 0 0 10px; font-size: 15.5px; }
.ca-line strong { display: block; font-size: 12.5px; letter-spacing: 1px;
                  text-transform: uppercase; color: var(--g4); margin-bottom: 3px;
                  font-family: var(--font-body); }
.ca-note { font-size: 14px; color: var(--g5); margin: 14px 0 0; }
.ca-steps { margin: 0; padding-left: 20px; font-size: 14.5px; color: var(--g5); }
.ca-steps li { margin-bottom: 9px; }

.prose h2 { font-size: 21px; margin: 32px 0 10px; }
.prose h2:first-child { margin-top: 0; }
.prose p { max-width: 68ch; }

@media (max-width: 900px) {
  .terr-page-grid, .contact-grid, .cf-row, .cty-grid { grid-template-columns: 1fr; }
  .cty-list { columns: 1; }
}
/* ==== END PHASE 4.1 PAGES ==== */
/* ==== PHASE 5 RESOURCES (generated by phase5_resources.py) ==== */
/* ---- Resources index -------------------------------------------------- */

/* Phase 10 hero. The photograph has the four faces in its left third, so the
   scrim runs light-to-dark left-to-right and the copy sits in the right
   column over the dense side. Below 860px the column goes full width and the
   scrim goes uniformly dense, because there is no longer a clear side. */
.res-hero { position: relative; isolation: isolate; background: var(--navy);
            padding: 96px 0 104px; color: var(--on-navy); }
/* Anchored left, not centred: the faces are in the left third of the crop
   and a centred cover crop on a wide viewport trims Washington. */
.res-hero .band-photo { object-position: 0% 40%; }
.res-hero .band-scrim {
  background:
    linear-gradient(90deg, rgba(11,27,71,.38) 0%, rgba(11,27,71,.52) 38%,
                    rgba(11,27,71,.90) 62%, rgba(11,27,71,.95) 100%),
    linear-gradient(180deg, rgba(11,27,71,.34), rgba(11,27,71,.10) 60%, rgba(11,27,71,.42));
}
.res-hero-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 580px); }
.res-hero-copy { grid-column: 2; }
.res-eyebrow { font-size: 11.5px; font-weight: 700; letter-spacing: 1.6px;
               text-transform: uppercase; color: var(--accent-light); margin: 0 0 14px; }
.res-hero h1 { color: #fff; font-size: 46px; line-height: 1.08; margin: 0 0 18px; }
.res-lede { color: #D3D9E4; font-size: 17px; margin: 0; max-width: 56ch; }

/* Groups: heading and lede on the left, cards on the right, so the page reads
   as three short shelves rather than one grid. */
.section-res { padding-bottom: 20px; }
.res-group { display: grid; grid-template-columns: 300px 1fr; gap: 40px 56px;
             align-items: start; padding: 36px 0; }
.res-group + .res-group { border-top: 1px solid var(--g2); }
.res-group-head { position: sticky; top: 90px; }
.res-group-head h2 { margin: 0 0 12px; }
.res-group-head .section-lede { margin: 0; font-size: 15.5px; color: var(--g5); }

.res-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; }
.res-card {
  display: flex; flex-direction: column; background: #fff;
  border: 1px solid var(--g2); border-radius: 10px; padding: 24px 26px 22px;
  text-decoration: none;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.res-card:hover { border-color: var(--accent); transform: translateY(-2px);
                  box-shadow: 0 10px 26px rgba(11,27,71,.10); }
.res-kind {
  align-self: flex-start; font-family: var(--font-body); font-size: 11.5px;
  font-weight: 700; letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--accent-ink); background: var(--accent-glow);
  padding: 4px 10px; border-radius: 5px; margin-bottom: 14px;
}
.res-card h3 { font-size: 21px; margin: 0 0 10px; }
.res-card p { margin: 0 0 14px; font-size: 15px; color: var(--g5); }
.res-covers { font-size: 13px; color: var(--g4); margin-bottom: 16px; display: block; }

/* The dark variant, written AFTER the rules it overrides. The first pass put
   this block above them, and `.res-card-wide p` and `.res-card p` have equal
   specificity, so source order won and the lede rendered in --g5 on navy -
   about 2.1:1, which is text that is technically present. */
.res-card.res-card-wide { grid-column: span 2; background: var(--navy);
                          border-color: var(--navy); }
.res-card.res-card-wide:hover { border-color: var(--accent-light); }
.res-card.res-card-wide h3 { color: #fff; }
.res-card.res-card-wide p { color: #C9D0DC; }
.res-card.res-card-wide .res-kind { background: rgba(255,255,255,.16); color: #fff; }
.res-card.res-card-wide .lane-go { color: var(--accent-on-navy); }

/* Calculators are tools, not reading; a teal rule and a cream field say so. */
.res-card.res-card-calc { background: var(--cream); border-color: var(--g2);
                          border-top: 3px solid var(--accent); }
.res-card.res-card-calc .res-kind { background: #fff; }

/* How the guides are written. */
.section-res-how { background: var(--navy); color: var(--on-navy); padding: 44px 0; }
.res-how { list-style: none; margin: 0; padding: 0; display: grid;
           grid-template-columns: repeat(3, 1fr); gap: 32px; }
.res-how li { display: flex; flex-direction: column; gap: 8px;
              border-left: 2px solid var(--accent); padding-left: 18px; }
.res-how strong { font-family: var(--font-display); font-weight: 500; font-size: 20px;
                  color: #fff; }
.res-how span { font-size: 14.5px; line-height: 1.55; }

/* ---- Comparison table -------------------------------------------------
   The row header column is sticky because the whole point of the table is
   reading across, and on a narrow screen a horizontally scrolled table with
   the labels gone is a grid of unattributed numbers. */

.cmp-wrap { overflow-x: auto; }
.cmp-table { min-width: 720px; }
.cmp-table th[scope="col"] { text-align: left; vertical-align: bottom;
                             padding-bottom: 12px; font-size: 15px; }
.cmp-table th[scope="col"] a { color: var(--navy); text-decoration: none;
                               display: block; font-weight: 700; }
.cmp-table th[scope="col"] a:hover { color: var(--accent-ink); }
.cmp-brand { display: block; font-size: 11.5px; font-weight: 700;
             letter-spacing: 1.1px; text-transform: uppercase;
             color: var(--accent-ink); margin-top: 4px; }
/* --g1, matching the base .spec-table row header, not #fff. The first pass
   set this to white, which on a phone gave a pinned column in one colour and
   an unpinned header cell above it in another. The shadow is what makes it
   read as pinned rather than as a rendering fault. */
.cmp-table th[scope="row"] { position: sticky; left: 0; background: var(--g1);
                             width: auto; min-width: 168px;
                             box-shadow: 1px 0 0 var(--g2); }
.cmp-table thead th:first-child { position: sticky; left: 0; background: var(--g1);
                                  box-shadow: 1px 0 0 var(--g2); }
.cmp-table th[scope="col"] { width: auto; background: #fff; }

/* Shown only where the table actually needs scrolling. A table that scrolls
   sideways with no indication that it does is a table people read the first
   two columns of. */
.cmp-hint { display: none; font-size: 14px; color: var(--g4); margin: 0 0 12px; }
@media (max-width: 860px) { .cmp-hint { display: block; } }
.cmp-table td { vertical-align: top; font-size: 14.5px; }
.cmp-none { color: var(--g4); font-style: italic; }

/* ---- What separates them ---------------------------------------------- */

.section-separates { background: var(--cream); }
.sep-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; }
.sep-item { background: #fff; border: 1px solid var(--g2);
            border-left: 3px solid var(--accent); border-radius: 8px;
            padding: 20px 22px; }
.sep-item h3 { font-size: 18px; margin: 0 0 9px; }
.sep-item h3 a { color: var(--navy); text-decoration: none; }
.sep-item h3 a:hover { color: var(--accent-ink); }
.sep-item p { margin: 0; font-size: 14.5px; color: var(--g5); }

/* ---- Named but not tabulated ------------------------------------------ */

.also-list { list-style: none; margin: 20px 0 0; padding: 0; }
.also-list li { padding: 14px 0; border-bottom: 1px solid var(--g2); }
.also-list a { font-weight: 700; display: block; margin-bottom: 4px; }
.also-list span { font-size: 14.5px; color: var(--g5); }

.prov-list, .alias-list { margin: 16px 0 20px; padding-left: 20px; font-size: 14.5px;
                          color: var(--g5); }
.prov-list li, .alias-list li { margin-bottom: 9px; }
.section-prov { background: var(--g1); }
.section-prov h3 { font-size: 18px; margin: 26px 0 8px; }

/* ---- Lane guide -------------------------------------------------------- */

.lane-steps { list-style: none; margin: 0; padding: 0; }
.lane-step { display: grid; grid-template-columns: 52px 1fr; gap: 20px;
             padding-bottom: 28px; margin-bottom: 28px;
             border-bottom: 1px solid var(--g2); }
.lane-step:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.ls-n { width: 44px; height: 44px; border-radius: 12px; background: var(--accent);
        color: #fff; display: flex; align-items: center; justify-content: center;
        font-family: var(--font-display); font-size: 21px; }
.ls-body h3 { font-size: 20px; margin: 6px 0 10px; }
.ls-body p { margin: 0 0 10px; font-size: 15.5px; color: var(--g5); }
.ls-links { font-size: 13.5px; color: var(--g4); }

.section-settle { background: var(--cream); }
.settle-list { list-style: none; margin: 0; padding: 0; }
.settle-list li { padding: 14px 0 14px 26px; position: relative; font-size: 15.5px;
                  color: var(--g5); border-bottom: 1px solid var(--g2); }
.settle-list li:last-child { border-bottom: 0; }
.settle-list li::before { content: ""; position: absolute; left: 0; top: 22px;
                          width: 9px; height: 9px; border-radius: 2px;
                          background: var(--accent); }
.settle-list strong { color: var(--td); }

@media (max-width: 900px) {
  .res-grid, .sep-grid { grid-template-columns: 1fr; }
  /* Phone photo strip; mechanism and variables in the home block
     (phase3_home.py), Phase 13. The faces are in the left third of the
     master, so 26% x holds all four in the crop at every phone width. This
     replaces a flat .86/.92 scrim under which the mountain was not there. */
  .res-hero { padding: calc(var(--strip) - 10px) 0 52px; }
  .res-hero .band-photo { top: 0; bottom: auto; height: var(--strip); object-position: 26% 50%; }
  .res-hero .band-scrim { background: var(--strip-scrim); }
  .res-hero-grid { grid-template-columns: 1fr; }
  .res-hero-copy { grid-column: 1; }
  .res-hero h1 { font-size: 34px; }
  .res-group { grid-template-columns: 1fr; gap: 18px; padding: 28px 0; }
  .res-group-head { position: static; }
  .res-how { grid-template-columns: 1fr; gap: 22px; }
  /* .res-card.res-card-wide, not .res-card-wide. Raising the specificity of
     the dark-variant block to fix a contrast bug quietly outranked this reset,
     so the wide card kept spanning two columns in a one-column grid and pushed
     47px off the right edge at 320. Specificity fixes have blast radius. */
  .res-card.res-card-wide { grid-column: auto; }
  .lane-step { grid-template-columns: 40px 1fr; gap: 14px; }
  .ls-n { width: 36px; height: 36px; font-size: 18px; }
}
/* ==== END PHASE 5 RESOURCES ==== */
/* === phase8: landing (generated by phase8_landing.py) === */
/* Landing page. Reuses .hero, .section-featured, .room-grid, .cform, .ask-panel
   from the rest of the site (Phase 16: no longer .lens-mask); the li-* rules below are only the
   parts no other page has. Campaign voice is allowed on this page and the
   email only (editor's decision, 2026-09-06). */
.landing .hero { padding: 72px 0 76px; }
.landing .hero-photo { object-position: 50% 60%; }
.landing .hero-grid { grid-template-columns: 1.25fr .75fr; align-items: center; }
.landing .hero h1 { font-size: 56px; max-width: 14ch; }
.landing .hero-lede strong.li-money { color: var(--accent-light); font-weight: 700; }
.li-stats { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.li-stats li { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.14);
  border-left: 3px solid var(--accent-light); border-radius: 0 8px 8px 0; padding: 16px 20px;
  display: grid; gap: 2px; }
.li-stats strong { font-family: var(--font-display); font-weight: 600; font-size: 34px;
  line-height: 1; color: #fff; }
.li-stats span { font-size: 13.5px; color: var(--on-navy); }
.li-kicker { font-size: 12.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent-ink); margin: 0 0 10px; }
.section-featured .li-kicker { color: var(--accent-light); }
.li-offer-grid { display: grid; grid-template-columns: 1.15fr .85fr; gap: 52px; align-items: center; }
.li-terms { list-style: none; margin: 0 0 26px; padding: 0; display: grid; gap: 14px; }
.li-terms li { border-left: 3px solid var(--accent); padding: 2px 0 2px 16px; font-size: 15px;
  color: var(--g5); }
.li-terms strong { display: block; font-size: 14px; color: var(--navy); margin: 0 0 3px; }
.li-device { margin: 0; background: var(--cream); border-radius: 12px; padding: 30px 30px 18px;
  text-align: center; }
.li-device img { mix-blend-mode: multiply; width: 100%; max-width: 420px; height: auto; }
.li-device figcaption { font-size: 13px; color: var(--g4); margin-top: 8px; }
.li-code-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin: 30px 0 22px; }
.li-code { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.14);
  border-top: 3px solid var(--accent-light); border-radius: 0 0 10px 10px; padding: 24px 26px; }
.li-code-tag { font-size: 12.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent-light); margin: 0 0 10px; }
.li-code h3 { color: #fff; font-size: 23px; margin: 0 0 12px; }
.li-code p { color: var(--on-navy); font-size: 15px; margin: 0 0 12px; }
.li-code .li-code-close { color: #fff; border-top: 1px solid rgba(255,255,255,.14); padding-top: 12px;
  margin-top: 4px; margin-bottom: 0; }
.li-fine { font-size: 13px; color: var(--g4); max-width: 80ch; margin: 18px 0 0; }
.section-featured .li-fine { color: var(--on-navy); }
.li-room-grid { grid-template-columns: repeat(3, 1fr); }
.li-numbers-grid { display: grid; grid-template-columns: 1.3fr .7fr; gap: 48px; align-items: center; }
.li-numbers-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.li-numbers-list li { background: #fff; border: 1px solid var(--g2); border-left: 3px solid var(--navy);
  border-radius: 0 8px 8px 0; padding: 14px 18px; display: grid; gap: 2px; }
.li-numbers-list strong { font-family: var(--font-display); font-weight: 600; font-size: 26px;
  color: var(--navy); line-height: 1.1; }
.li-numbers-list span { font-size: 13.5px; color: var(--g5); }
.li-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.li-tile { display: block; background: #fff; border: 1px solid var(--g2); border-radius: 10px;
  overflow: hidden; text-decoration: none; transition: border-color .15s ease, transform .15s ease,
  box-shadow .15s ease; }
.li-tile:hover { border-color: var(--accent); transform: translateY(-2px);
  box-shadow: 0 10px 26px rgba(11,27,71,.10); }
.li-tile-media { display: flex; align-items: center; justify-content: center; background: var(--cream);
  padding: 18px; }
.li-tile-media img { mix-blend-mode: multiply; max-height: 170px; width: auto; max-width: 100%; }
.li-tile-body { display: grid; gap: 3px; padding: 14px 18px 16px; }
.li-tile-body strong { color: var(--navy); font-size: 16px; }
.li-tile-body span { font-size: 13.5px; color: var(--g5); }
.li-quote { scroll-margin-top: 24px; }
.li-quote-grid { display: grid; grid-template-columns: .8fr 1.2fr; gap: 52px; align-items: start; }
/* Phase 16: no portrait here. Shari asked for her photograph off every
   COMPASS surface (this page and the email); check 71 holds it. The block
   leads with the same kicker the rest of the page uses instead. */
.li-note h2 { font-size: 27px; margin: 0 0 12px; }
.li-note p { color: var(--g6); }
.li-note .ask-panel { margin-top: 22px; }
@media (max-width: 980px) {
  .landing .hero-grid, .li-offer-grid, .li-numbers-grid, .li-quote-grid { grid-template-columns: 1fr; gap: 32px; }
  /* Phone photo strip; mechanism and variables in the home block
     (phase3_home.py), Phase 13. Set at 980 rather than 900 because that is
     where this hero goes single-column. The grass has no subject to centre
     on, so the crop stays where the desktop puts it. */
  .landing .hero { padding-top: calc(var(--strip) - 10px); }
  .landing .hero-photo { top: 0; bottom: auto; height: var(--strip); object-position: 50% 60%; }
  .landing .hero-scrim { background: var(--strip-scrim); }
  .landing .hero h1 { font-size: 44px; }
  .li-stats { grid-template-columns: repeat(3, 1fr); }
  .li-tiles { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
  /* padding-bottom only: the top is the strip's, set at 980 above. */
  .landing .hero { padding-bottom: 52px; }
  .landing .hero h1 { font-size: 36px; max-width: none; }
  .li-stats { grid-template-columns: 1fr; }
  .li-code-grid { grid-template-columns: 1fr; }
  .li-room-grid { grid-template-columns: 1fr !important; }
  .li-device { padding: 20px 20px 12px; }
}
/* === /phase8 === */
