/* ============================================================
   ASSEMBLY LINE - MAIN.CSS (COMPONENTS) v1.1.0
   Generic, reusable components. Every value here references a
   system.css token. Raw hex and raw px for colour or type are
   defects; px is permitted only for 1-2px borders/hairlines and
   pixel-locked details. Load AFTER system.css:
     <link rel="stylesheet" href="/css/system.css">
     <link rel="stylesheet" href="/css/main.css">
   Client-specific one-off components do NOT go in this file -
   put them in a separate site.css loaded after this one.

   THE LADDER (see SYSTEM.md):
   Page > Partial > Section > Container > Layout > Component,
   with utilities and tokens cross-cutting every level.
   ============================================================ */

/* ============ GLOBAL RESET & BASE ============ */
*, *::before, *::after { box-sizing: border-box; }
html, body {
  margin: 0;
  padding: 0;
  background-color: var(--surface-1);
  color: var(--ink-2);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
  scroll-behavior: smooth;
}
body {
  font-size: var(--text-body);
  font-weight: 400;
  line-height: var(--leading-ui);
}
h1, h2, h3, h4, h5, h6, p, blockquote, figure { margin: 0; padding: 0; }
p { line-height: var(--leading-body); }
a { text-decoration: none; color: inherit; }
/* height: auto keeps the aspect ratio when max-width narrows an image that
   carries width/height attributes. Those attributes are there so the browser
   reserves the box before the file arrives; any class that sizes an image
   outranks this element rule. */
img, video { max-width: 100%; height: auto; display: block; }

/* ============ FOCUS STATES ============ */
*:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}
.swap-btn:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  /* Square, following the buttons. A pill ring around a square button reads
     as a halo rather than a focus state. */
  border-radius: 0;
}
.form-input:focus-visible,
.form-select:focus-visible { outline: none; }

/* ============ TYPOGRAPHY CLASS API ============ */
/* Heading sizing lives in system.css (tags + .size-* + .t-hero).
   These classes cover non-heading text roles only. */
.duotone { display: flex; flex-direction: column; }
.duotone .lead { color: var(--ink-1); }
.duotone .soft { color: var(--grey-3); }

.subhead {
  font-size: var(--text-subhead);
  font-weight: 400;
  line-height: var(--leading-subhead);
}
.body-large  { font-size: var(--text-body-lg); font-weight: 400; line-height: var(--leading-body); color: var(--ink-2); }
.body-medium { font-size: var(--text-body);    font-weight: 400; line-height: var(--leading-body); color: var(--ink-2); }
.body-small  { font-size: var(--text-body-sm); font-weight: 400; line-height: var(--leading-body); color: var(--ink-2); }
.body-xs     { font-size: var(--text-body-xs); font-weight: 400; line-height: var(--leading-body); color: var(--ink-3); }

.inline-icon {
  display: inline-block;
  vertical-align: middle;
  height: 1.2em;
  width: auto;
  margin: 0 0.25rem;
  position: relative;
  top: -2px;
}
.caption {
  font-size: var(--text-caption);
  font-weight: 700;
  color: var(--ink-1);
}
.label {
  font-size: var(--text-label);
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--brand-3);
}
.micro {
  font-size: var(--text-micro);
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ============ HONEYPOT ============
   Off-canvas rather than display:none. A field that is never rendered is
   trivial for a bot to spot and skip, and skipping it is what gets the
   submission through. Paired with aria-hidden and tabindex="-1" in the
   markup, so assistive tech and the tab order never reach it. */
.form-hp {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ============ SKIP LINK ============
   First focusable element on every page, in the static HTML ahead of the
   injected header so it works before components.js has run. Off-canvas
   until it receives keyboard focus, then drops in above the fixed nav
   (nav is z-index 100). Targets <main id="main" tabindex="-1">; the
   tabindex lets every browser move focus into the landmark. */
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 110;
  padding: var(--space-sm) var(--space-md);
  background: var(--accent);
  color: var(--on-dark);
  font-weight: 700;
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: none;
  outline: 2px solid var(--on-dark);
  outline-offset: -4px;
}
main:focus { outline: none; }

/* ============ TEXT / COLOR UTILITIES ============ */
.text-ink { color: var(--ink-1); }
.text-secondary { color: var(--ink-2); }
.text-muted { color: var(--ink-3); }
.text-accent { color: var(--accent); }
.text-white { color: var(--on-dark); }
.text-success { color: var(--status-ok); }
.text-error { color: var(--status-error); }
.text-warning { color: var(--status-warn); }
.text-rating { color: var(--rating); }
.bg-surface-1 { background-color: var(--surface-1); }
.bg-surface-2 { background-color: var(--surface-2); }
.bg-surface-3 { background-color: var(--surface-3); }
.bg-accent-tint { background-color: var(--accent-tint); }

/* ============ SPACING UTILITIES ============ */
.mt-xs { margin-top: var(--space-xs); }
.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-ml { margin-top: var(--space-ml); }
.mt-lg { margin-top: var(--space-lg); }
.mt-xl { margin-top: var(--space-xl); }
.mb-xs { margin-bottom: var(--space-xs); }
.mb-sm { margin-bottom: var(--space-sm); }
.mb-md { margin-bottom: var(--space-md); }
.mb-ml { margin-bottom: var(--space-ml); }
.mb-lg { margin-bottom: var(--space-lg); }
.mb-xl { margin-bottom: var(--space-xl); }
.pt-0 { padding-top: 0 !important; }
.pb-0 { padding-bottom: 0 !important; }

/* ============ LAYOUT UTILITIES ============ */
.flex-center { display: flex; align-items: center; justify-content: center; flex-direction: column; }
.flex { display: flex; }
.justify-center { justify-content: center; }
.gap-xs { gap: var(--space-xs); }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-ml { gap: var(--space-ml); }
.gap-lg { gap: var(--space-lg); }
.text-center { text-align: center; }
.w-full { width: 100%; }
.max-w-full { max-width: 100%; }

/* Reading blocks - cap AND center a standalone text block.
   (measure-* in system.css caps heading width WITHOUT centering.) */
.reading { max-width: var(--container-reading); margin-left: auto; margin-right: auto; }
.reading-narrow { max-width: var(--reading-narrow); margin-left: auto; margin-right: auto; }

/* ============ RADIUS UTILITIES ============ */
.rounded-card { border-radius: var(--radius-card); }
.rounded-ui { border-radius: var(--radius-ui); }
.rounded-pill { border-radius: var(--radius-pill); }
.rounded-top { border-radius: var(--radius-card) var(--radius-card) 0 0; }

/* ============ SECTION (level 3) ============ */
/* One per band. Owns surface + vertical rhythm. Never nested. */
.section {
  position: relative;
  width: 100%;
  padding: calc(var(--space-section) + 0.5rem) 0;
  background-color: var(--surface-1);
  color: var(--ink-2);
}
/* Trims 4rem off a section's standard top padding, for a section following
   one that already carries generous bottom padding. Reached in two steps:
   2rem was previously applied here as an inline style, and a second 2rem was
   asked for on top of it. The inline style is gone; this is the whole value.
   Declared AFTER .section deliberately: .section sets `padding` as a
   shorthand, so anything meant to override it has to sit below it. */
.section.pt-trim { padding-top: calc(var(--space-section) + 0.5rem - 4rem); }
/* A short band whose content brings its own padding (e.g. transparent cards). */
.section.section-tight { padding-block: var(--space-lg); }

/* Padding utilities. They live here, BELOW .section, on purpose: .section sets
   `padding` as a shorthand at the same specificity, so a utility declared above
   it lost on source order and did nothing on a section (the /privacy/ and
   /terms/ bodies carried pt-xl pb-xl and still got the full section padding).
   pt-0 and pb-0 stay with the spacing utilities above; they use !important. */
.pt-xs { padding-top: var(--space-xs); }
.pt-sm { padding-top: var(--space-sm); }
.pt-md { padding-top: var(--space-md); }
.pt-ml { padding-top: var(--space-ml); }
.pt-lg { padding-top: var(--space-lg); }
.pt-xl { padding-top: var(--space-xl); }
.pb-xs { padding-bottom: var(--space-xs); }
.pb-sm { padding-bottom: var(--space-sm); }
.pb-md { padding-bottom: var(--space-md); }
.pb-ml { padding-bottom: var(--space-ml); }
.pb-lg { padding-bottom: var(--space-lg); }
.pb-xl { padding-bottom: var(--space-xl); }
.pt-fluid-xl { padding-top: var(--space-fluid-xl); }
.pb-fluid-xl { padding-bottom: var(--space-fluid-xl); }

/* ============ CONTAINERS (level 4) ============ */
/* One per section, first child. Owns horizontal measure.
   Tiers: wide 1800 / default 1530 / narrow 1200 / reading 900. */
.container,
.container-wide,
.container-narrow,
.container-reading {
  margin: 0 auto;
  width: calc(100% - (var(--gutter) * 2));
}
.container         { max-width: var(--container-normal); }
.container-wide    { max-width: var(--container-wide); }
.container-narrow  { max-width: var(--container-narrow); }
.container-reading { max-width: var(--container-reading); }

/* ============ SURFACE MODIFIERS ============ */
/* Pair with .section: <section class="section surface-dark">.
   Each sets background and auto-corrects text for contrast. */
.surface-base   { background-color: var(--surface-1); }
.surface-canvas { background-color: var(--surface-2); }
.surface-muted  { background-color: var(--surface-3); }

.surface-dark { background-color: var(--surface-dark); color: var(--on-dark-2); }
.surface-dark h1, .surface-dark h2, .surface-dark h3,
.surface-dark h4, .surface-dark h5, .surface-dark h6,
.surface-dark .t-hero { color: var(--on-dark); }
.surface-dark .text-secondary { color: var(--on-dark-2); }

.surface-accent { background-color: var(--accent); color: var(--on-dark-2); }
.surface-accent h1, .surface-accent h2, .surface-accent h3,
.surface-accent h4, .surface-accent h5, .surface-accent h6,
.surface-accent .t-hero { color: var(--on-dark); }
.surface-accent .text-secondary, .surface-accent .label { color: var(--on-dark-2); }

.surface-brand-a { background-color: var(--brand-5); color: var(--on-dark-2); }
.surface-brand-b { background-color: var(--brand-7); color: var(--on-dark-2); }
.surface-brand-a h1, .surface-brand-a h2, .surface-brand-a h3,
.surface-brand-a h4, .surface-brand-a h5, .surface-brand-a h6,
.surface-brand-b h1, .surface-brand-b h2, .surface-brand-b h3,
.surface-brand-b h4, .surface-brand-b h5, .surface-brand-b h6 { color: var(--on-dark); }

.surface-brand-highlight { background-color: var(--brand-6); color: var(--ink-1); }

/* Grid pattern modifier */
.bg-grid-pattern {
  background-image:
    linear-gradient(to right, var(--grid-line) 2px, transparent 2px),
    linear-gradient(to bottom, var(--grid-line) 2px, transparent 2px);
  background-size: 17.5rem 17.5rem;
  background-position: center top;
}
.surface-dark.bg-grid-pattern,
.surface-accent.bg-grid-pattern,
.surface-brand-a.bg-grid-pattern,
.surface-brand-b.bg-grid-pattern,
.surface-muted.bg-grid-pattern {
  background-image:
    linear-gradient(to right, var(--grid-line-on-dark) 2px, transparent 2px),
    linear-gradient(to bottom, var(--grid-line-on-dark) 2px, transparent 2px);
}
.surface-brand-highlight.bg-grid-pattern {
  background-image: none;
  position: relative;
}
@media (min-width: 64rem) {
  .surface-brand-highlight.bg-grid-pattern::before {
    content: "";
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: 50vw;
    background-image:
      linear-gradient(to right, var(--grid-line) 2px, transparent 2px),
      linear-gradient(to bottom, var(--grid-line) 2px, transparent 2px);
    background-size: 17.5rem 17.5rem;
    background-position: left top;
    z-index: 0;
    pointer-events: none;
  }
}

/* ============ TEXT UTILITIES ============ */
.line-clamp-3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.surface-brand-highlight .container,
.surface-brand-highlight .container-wide,
.surface-brand-highlight .container-narrow { position: relative; z-index: 1; }

/* ============ CTA PATTERN DECORATION ============
   The source PNG is opaque black line-art on transparency.
   brightness(0) invert(1) recolours it to white; alpha is preserved,
   so only the lines are affected. Tune --pattern-opacity to taste. */
.has-pattern-deco { position: relative; --pattern-opacity: 0.09; }
.has-pattern-deco::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url('/assets/images/pattern-background.png');
  background-position: top right;
  background-repeat: no-repeat;
  background-size: 42% auto;
  filter: brightness(0) invert(1);
  opacity: var(--pattern-opacity);
  z-index: 0;
  pointer-events: none;
}
.has-pattern-deco .container { position: relative; z-index: 1; }

/* Hairlines */
.has-top-border { border-top: 1px solid var(--border-1); }
.has-bottom-hairline { border-bottom: 1px solid var(--border-1); }
.has-bottom-hairline-dark { border-bottom: 1px solid var(--border-on-dark); }

/* ============ LAYOUTS (level 5) ============ */
/* 12-column grid */
.grid-12 { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--space-md); }
.col-span-1 { grid-column: span 1; }
.col-span-2 { grid-column: span 2; }
.col-span-3 { grid-column: span 3; }
.col-span-4 { grid-column: span 4; }
.col-span-5 { grid-column: span 5; }
.col-span-6 { grid-column: span 6; }
.col-span-7 { grid-column: span 7; }
.col-span-8 { grid-column: span 8; }
.col-span-9 { grid-column: span 9; }
.col-span-10 { grid-column: span 10; }
.col-span-11 { grid-column: span 11; }
.col-span-12 { grid-column: span 12; }
@media (max-width: 75rem) {
  .col-span-1, .col-span-2, .col-span-3, .col-span-4,
  .col-span-5, .col-span-6, .col-span-7, .col-span-8,
  .col-span-9, .col-span-10, .col-span-11, .col-span-12 { grid-column: span 12; }
}

/* Semantic grids */
.grid-2col { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); gap: var(--gutter); }
@media (min-width: 48rem) { #services-grid { grid-template-columns: 1fr 1.3fr; } }
.grid-3col { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gutter); }
@media (max-width: 64rem) { .grid-3col { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 48rem) { .grid-3col { grid-template-columns: 1fr; } }
.grid-4col { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17.5rem), 1fr)); gap: var(--gutter); }

/* ============ CARDS ============ */
.card-standard, .service-card, .feature-card, .post-card, .author-card, .contact-card {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  padding: var(--space-ml);
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--dur-fast) ease, border-color var(--dur-fast) ease, transform var(--dur-fast) ease;
  display: flex;
  flex-direction: column;
}
.card-standard:hover, .service-card:hover, .feature-card:hover,
.post-card:hover, .author-card:hover {
  box-shadow: var(--shadow-lift);
  border-color: var(--border-3);
  transform: translateY(-2px);
}
.card-dark {
  background: var(--surface-dark-2);
  border: 1px solid var(--border-on-dark);
  border-radius: var(--radius-card);
  padding: var(--space-ml);
  box-shadow: none;
  transition: background-color var(--dur-fast) ease, border-color var(--dur-fast) ease, transform var(--dur-fast) ease;
  display: flex;
  flex-direction: column;
}
.card-dark:hover {
  background: var(--surface-dark-3);
  border-color: var(--border-on-dark-strong);
  transform: translateY(-2px);
}
.card-dark h3, .card-dark h5, .card-dark h6 { color: var(--on-dark); }
.card-dark p { color: var(--on-dark-2); }
.card-dark .feature-card-icon { background: var(--surface-dark-3); color: var(--brand-1); }

/* Feature cards grid */
.feature-card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-xl); }
.feature-card-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3.5rem; height: 3.5rem;
  border-radius: 50%;
  background: var(--brand-3);
  color: var(--on-dark);
}
/* Stroke geometry lives here, not on each icon, so every feature card in
   every client site draws at the same weight. Icons are 24x24 outline paths
   (Lucide geometry) and take their colour from .feature-card-icon. */
.feature-card-icon svg {
  width: 1.5rem; height: 1.5rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
@media (max-width: 56.25rem) { .feature-card-grid { grid-template-columns: 1fr; gap: var(--space-lg); } }

/* Case study cards */
.grid-case-studies { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gutter); }
.case-card {
  --text-h2: var(--step-2);
  --text-h3: var(--step-2);
  --text-h5: var(--step-2);
  --leading-h2: var(--leading-h6);
  --leading-h3: var(--leading-h6);
  --leading-h5: var(--leading-h6);
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  overflow: hidden;
  transition: box-shadow var(--dur-fast) ease, border-color var(--dur-fast) ease;
  display: flex;
  flex-direction: column;
  min-height: 30rem;
}
.case-card:hover { box-shadow: var(--shadow-lift); border-color: var(--border-3); }
.case-card-image { width: 100%; aspect-ratio: 16 / 10; background-color: var(--surface-3); overflow: hidden; }
.case-card-image img { width: 100%; height: 100%; object-fit: cover; }
.case-card-content { padding: var(--space-md) var(--space-md) var(--space-ml); display: flex; flex-direction: column; flex: 1; }
.case-card-meta { font-size: var(--step-0); font-weight: 700; color: var(--ink-3); }
.case-card .caption { color: var(--accent); margin-top: auto; }
@media (max-width: 64rem) { .grid-case-studies { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 36.25rem) { .grid-case-studies { grid-template-columns: 1fr; } }

/* Blog cards */
.grid-blog { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 21.25rem), 1fr)); gap: var(--gutter); }
.blog-card {
  background: var(--surface-1);
  border: 1px solid var(--border-1);
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--dur-fast) ease, border-color var(--dur-fast) ease, transform var(--dur-fast) ease;
  display: flex;
  flex-direction: column;
}
.blog-card:hover { box-shadow: var(--shadow-lift); border-color: var(--border-3); transform: translateY(-2px); }
.blog-card-image { width: 100%; aspect-ratio: 16 / 9; background-color: var(--surface-3); overflow: hidden; }
.blog-card-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.blog-card-body { padding: var(--space-ml); display: flex; flex-direction: column; flex: 1; }

/* ============ BUTTONS ============ */
/* Hovers are role-based only - never brand tokens in core. */
.btn-primary { background: var(--brand-3); color: var(--on-dark); border: none; border-radius: 0; cursor: pointer; transition: background var(--dur-fast) ease; }
.btn-primary:hover { background: var(--brand-1); }
.btn-ink { background: var(--ink-1); color: var(--on-dark); border: none; border-radius: 0; cursor: pointer; }
.btn-ink:hover { background: var(--accent); }
.btn-ghost { background: transparent; color: var(--ink-1); border: 1px solid var(--border-1); border-radius: 0; cursor: pointer; }
.btn-ghost:hover { background: var(--surface-3); border-color: var(--border-3); }
.btn-on-dark { background: var(--on-dark); color: var(--ink-1); border: none; border-radius: 0; cursor: pointer; }
.btn-on-dark:hover { background: var(--grey-1); }

/* Swap-button pattern (text swaps on hover) */
.swap-btn { position: relative; overflow: hidden; padding: 0 !important; text-decoration: none; display: inline-block; }
.swap-btn-inner { position: relative; display: block; transition: transform var(--dur-med) ease; }
.btn-cta .swap-btn-inner { padding: 0.7rem 1.7rem; text-align: center; }
.btn-cta .btn-text { font-size: calc(var(--text-nav) * 0.8); }
.surface-dark .btn-cta.btn-primary {
  background: var(--on-dark);
  color: var(--brand-3);
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.surface-dark .btn-cta.btn-primary:hover {
  background: var(--brand-3);
  color: var(--on-dark);
}
/* Square, not the --radius-pill it inherits from .btn-ghost. */
.btn-nav-cta.btn-ghost { background: var(--brand-1); border-width: 1px; border-color: var(--brand-1); color: var(--on-dark); border-radius: 0; transition: background var(--dur-med) ease, border-color var(--dur-med) ease, color var(--dur-med) ease; }
.btn-nav-cta.btn-ghost:hover { background: var(--brand-3); border-color: var(--brand-3); color: var(--on-dark); }
.btn-nav-cta .swap-btn-inner { padding: 0.7rem 1.7rem; }
.btn-nav-cta .btn-text { font-size: calc(var(--text-nav) * 0.8); }
.btn-text { display: block; white-space: nowrap; font-size: var(--text-body); font-weight: 700; transition: opacity var(--dur-med) ease, transform var(--dur-med) ease; }
.btn-default { opacity: 1; transform: translateY(0); }
.swap-btn:hover .btn-default, .swap-btn:focus-visible .btn-default { opacity: 0; transform: translateY(-100%); }
.btn-hover { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; opacity: 0; transform: translateY(100%); }
.swap-btn:hover .btn-hover, .swap-btn:focus-visible .btn-hover { opacity: 1; transform: translateY(0); }

/* ============ COMPONENT: PRODUCT TILE ============ */
.product-tile-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--gutter);
}
@media (max-width: 56.25rem) {
  .product-tile-grid { grid-template-columns: 1fr; }
}

/* Compact variant - a product index of many small tiles rather than a few
   full-height showpieces. Pair with .product-tile-grid. Declared after the
   media query above so its auto-fit survives at narrow widths and collapses
   on its own terms. */
.product-tile-grid-compact { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17.5rem), 1fr)); }
.product-tile-grid-compact .product-tile { min-height: 22rem; }
.product-tile-grid-compact .product-tile-content { padding: var(--space-ml) var(--space-md); }
.product-tile-grid-compact .product-tile-title { font-size: var(--step-1); }

.product-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 36rem;
  border-radius: var(--radius-ui);
  color: var(--on-dark);
  text-decoration: none;
  overflow: hidden;
}
/* On phones the tiles are already full-width in a single column, so the 36rem
   floor leaves them far taller than they are wide. Square them off instead.
   min-height must be reset or it would out-rank the aspect ratio. The compact
   variant is excluded: it keeps its own shorter 22rem floor. */
@media (max-width: 48rem) {
  .product-tile-grid:not(.product-tile-grid-compact) .product-tile {
    min-height: 0;
    aspect-ratio: 1;
  }
}
.product-tile-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transition: transform var(--dur-med) ease;
  z-index: 0;
}
.product-tile:hover .product-tile-bg {
  transform: scale(1.05);
}
.product-tile::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(2,27,52,0.3) 0%, rgba(2,27,52,0.8) 100%);
  z-index: 1;
  transition: opacity var(--dur-med) ease;
}
.product-tile:hover::before {
  opacity: 0.5;
}
.product-tile > *:not(.product-tile-bg) {
  position: relative;
  z-index: 2;
}
.product-tile-content {
  flex: 1;
  display: flex;
  flex-direction: column;   /* title above description, not beside it */
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  text-align: center;
  padding: var(--space-xl);
}
/* Optional second line under the title. */
.product-tile-desc {
  font-size: var(--text-body-sm);
  line-height: var(--leading-body);
  max-width: 32ch;
  color: var(--on-dark);
}
.product-tile-title {
  font-size: var(--step-5);
  /* 400 is Anton's only real weight. See .gs-tile__name. */
  font-weight: 400;
  line-height: 1.1;
  text-align: center;
  max-width: 15ch;
  color: var(--on-dark);
}
.product-tile-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 var(--space-xl) var(--space-md) var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-on-dark-strong);
}
.product-tile-link-text {
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.product-tile-icon {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 1px solid var(--border-on-dark-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.product-tile:hover .product-tile-icon {
  background: var(--brand-3);
  color: var(--on-dark);
  border-color: var(--brand-3);
}
.product-tile-icon svg {
  width: 12px;
  height: 12px;
  fill: currentColor;
}

/* ============ PARTIAL: NAVIGATION ============ */
/* Two skins on one partial, because the partial is shared by every page and
   cannot carry a per-page class of its own. The page opts in by setting
   data-nav on <body>; absent means the solid default.

     (default)              solid bar, ink links. Inner pages.
     data-nav="transparent" bar disappears, links go white. Pages whose first
                            section is a dark hero, currently the homepage.

   Both resolve to the same solid dark bar once .nav-scrolled lands, so the
   links stay legible once the hero has scrolled away under them.

   The bar is flush to the top edge and full width. It used to float as a
   rounded pill inset by --header-offset, which is why that token is now 0
   rather than deleted: --header-height is derived from it and the mobile
   panel and the hero paddings all measure against that. */
.site-nav {
  position: fixed;
  top: var(--header-offset);
  left: 0;
  width: 100%;
  z-index: 100;
  background: transparent;
  transition: color var(--dur-med) ease;
}
.site-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--nav-surface, var(--surface-1));
  border-bottom: 1px solid var(--nav-edge, var(--border-1));
  transition: background var(--dur-med) ease, border-color var(--dur-med) ease;
}
/* Transparent skin. The hairline stays, at on-dark strength, so the bar still
   has an edge against a busy photograph. */
body[data-nav="transparent"] .site-nav {
  --nav-surface: transparent;
  --nav-edge: var(--border-on-dark);
}
body[data-nav="transparent"] .site-nav .nav-links > a,
body[data-nav="transparent"] .site-nav .nav-dropdown-toggle { color: var(--on-dark); }
/* Restated at the skin's own specificity. The base .nav-links > a:hover rule
   scores (0,2,1) and loses to the skin's (0,3,1), so without this the links on
   the transparent nav never changed colour on hover at all, and the underline
   inherited white through currentColor. */
body[data-nav="transparent"] .site-nav .nav-links > a:hover,
body[data-nav="transparent"] .site-nav .nav-dropdown-toggle:hover { color: var(--brand-3); }
body[data-nav="transparent"] .site-nav .nav-links > a[aria-current="page"],
body[data-nav="transparent"] .site-nav .nav-dropdown-toggle[aria-current="page"] { color: var(--brand-3); }
body[data-nav="transparent"] .site-nav .mobile-toggle .hamburger-line { background: var(--on-dark); }
body[data-nav="transparent"] .site-nav .site-logo img { filter: brightness(0) invert(1); }
body[data-nav="transparent"] .site-nav .btn-nav-cta.btn-ghost {
  background: var(--brand-3); border-color: var(--brand-3); color: var(--on-dark);
}
body[data-nav="transparent"] .site-nav .btn-nav-cta.btn-ghost:hover {
  background: var(--brand-3-dark); border-color: var(--brand-3-dark);
}
/* Scrolled: one solid dark bar, whichever skin the page started on.
   The transparent skin has to be named again here rather than relying on
   .site-nav.nav-scrolled alone: `body[data-nav] .site-nav` scores (0,2,1) and
   out-ranks (0,2,0), so without this the homepage bar stayed transparent all
   the way down the page. */
.site-nav.nav-scrolled,
body[data-nav="transparent"] .site-nav.nav-scrolled {
  --nav-surface: var(--surface-dark);
  --nav-edge: var(--border-on-dark-strong);
}
.site-nav.nav-scrolled::before {
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.site-nav.nav-scrolled .nav-links > a,
.site-nav.nav-scrolled .nav-dropdown-toggle { color: var(--on-dark); }
.site-nav.nav-scrolled .nav-links > a:hover,
.site-nav.nav-scrolled .nav-dropdown-toggle:hover { color: var(--brand-3); }
.site-nav.nav-scrolled .nav-links svg { color: var(--on-dark); }
.site-nav.nav-scrolled .mobile-toggle .hamburger-line { background: var(--on-dark); }
.site-nav.nav-scrolled .site-logo img { filter: brightness(0) invert(1); }
.site-nav.nav-scrolled .nav-signin { color: var(--on-dark); }
.site-nav.nav-scrolled .nav-signin:hover { color: var(--accent-tint); }
.site-nav.nav-scrolled .btn-nav-cta.btn-ghost { background: var(--surface-1); border-color: var(--surface-1); color: var(--brand-1); }
.site-nav.nav-scrolled .btn-nav-cta.btn-ghost:hover { background: var(--brand-3); border-color: var(--brand-3); color: var(--on-dark); }
.header-inner {
  display: grid; align-items: center; grid-template-columns: 1fr auto 1fr;
  height: var(--header-bar-height);
  max-width: var(--container-wide);
  width: calc(100% - (var(--gutter) * 2));
  margin: 0 auto;
  padding: 0 var(--space-fluid-s);
}
.site-logo { justify-self: start; }
.site-logo img { height: 2.97rem; width: auto; }
.nav-group { display: flex; align-items: center; }
.nav-links { display: flex; align-items: center; gap: var(--space-ml); }
/* Top-level links only. These deliberately use `> a` plus the toggle rather
   than a blanket `.nav-links a`: the dropdown items are also descendants of
   .nav-links, and a blanket rule reaches into the menu and fights the item
   styling below. That is how the scrolled menu ended up painting red labels
   on its own red hover background. */
.nav-links > a,
.nav-dropdown-toggle {
  position: relative;
  font-size: calc(var(--text-label) * 1.1);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-1);
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  transition: color var(--dur-fast) ease;
}
.nav-links > a:hover,
.nav-dropdown-toggle:hover { color: var(--brand-3); }

/* Hover rule. Wipes in from the left rather than fading, so the direction of
   travel reads even at 2px. scaleX on a pseudo-element keeps it off the layout
   entirely: an animated border or width would reflow the whole bar on every
   hover. Sits below the text baseline, inside the bar's own height, so it
   never touches the hairline at the bottom of the nav. */
.nav-links > a::after,
.nav-dropdown-toggle::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.45rem;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-fast) ease;
}
.nav-links > a:hover::after,
.nav-links > a:focus-visible::after,
.nav-dropdown-toggle:hover::after,
.nav-dropdown-toggle:focus-visible::after { transform: scaleX(1); }

/* Current page. script.js stamps aria-current on the matching link after the
   partial is injected, so this is driven by the attribute that already carries
   the meaning for screen readers rather than by a second decorative class.
   The rule stays drawn here, so "you are here" survives the pointer moving
   away, which colour alone does not. */
.nav-links > a[aria-current="page"],
.nav-dropdown-toggle[aria-current="page"] { color: var(--brand-3); }
.nav-links > a[aria-current="page"]::after,
.nav-dropdown-toggle[aria-current="page"]::after { transform: scaleX(1); }
.nav-links svg { color: var(--brand-3); transition: color var(--dur-fast) ease; }

/* ============ NAV DROPDOWN ============ */
/* One menu, two skins. The bar is a light pill at the top of the page and
   turns dark once .nav-scrolled lands, so the menu has to follow it. Every
   difference between the two skins is a token declared here; the scrolled
   variant re-declares the tokens and nothing else. A third bar skin means one
   more token block, not another layer of overrides, which is what the earlier
   split between main.css and site.css turned into, each side reaching for
   !important until the scrolled menu painted red on red. */
.nav-dropdown { position: relative; }
.nav-dropdown-toggle { display: flex; align-items: center; cursor: pointer; }
.nav-dropdown-toggle svg { transition: transform var(--dur-fast) ease; }
.nav-dropdown:hover .nav-dropdown-toggle svg,
.nav-dropdown:focus-within .nav-dropdown-toggle svg { transform: rotate(180deg); }
.nav-dropdown-menu {
  --dd-surface:       var(--surface-1);
  --dd-label:         var(--brand-1);
  --dd-edge:          var(--border-1);
  --dd-hover-surface: var(--brand-3);
  --dd-hover-label:   var(--on-dark);

  position: absolute; top: 100%; left: 0; min-width: 14rem;
  background: var(--dd-surface);
  border: 1px solid var(--dd-edge);
  border-radius: var(--radius-ui);
  box-shadow: var(--shadow-lift);
  padding: 0;
  overflow: hidden;
  opacity: 0; visibility: hidden; transform: translateY(6px);
  transition: opacity var(--dur-fast) ease, transform var(--dur-fast) ease,
              visibility var(--dur-fast) ease, background var(--dur-med) ease,
              border-color var(--dur-med) ease;
  z-index: 100;
}
.nav-dropdown:hover .nav-dropdown-menu,
.nav-dropdown:focus-within .nav-dropdown-menu { opacity: 1; visibility: visible; transform: translateY(0); }
.nav-dropdown-menu a {
  display: block;
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--dd-edge);
  border-radius: 0;
  color: var(--dd-label);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 500;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.nav-dropdown-menu a:last-child {
  border-bottom: none;
}
.nav-dropdown-menu a:hover,
.nav-dropdown-menu a:focus-visible {
  background: var(--dd-hover-surface);
  color: var(--dd-hover-label);
}
/* Dark skin. Surface, label and edge only. The hover pill stays red-on-white
   in both skins so the menu reads the same however you arrived at it. */
.site-nav.nav-scrolled .nav-dropdown-menu {
  --dd-surface: var(--surface-dark-2);
  --dd-label:   var(--on-dark);
  --dd-edge:    var(--border-on-dark-strong);
}
.header-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-sm); justify-self: end; }
.nav-signin { font-size: var(--step-0); font-weight: 700; color: var(--ink-1); transition: color var(--dur-fast) ease; }
.nav-signin:hover { color: var(--accent); }
.mobile-toggle { display: none; background: none; border: none; cursor: pointer; padding: var(--space-xs); }
.hamburger-line { display: block; width: 1.75rem; height: 2.5px; background: var(--ink-1); margin-bottom: 0.375rem; transition: transform var(--dur-med) ease, opacity var(--dur-med) ease; }
.hamburger-line:last-child { margin-bottom: 0; }
.site-nav.is-open .hamburger-line:nth-child(1) { transform: translateY(0.53rem) rotate(45deg); }
.site-nav.is-open .hamburger-line:nth-child(2) { opacity: 0; }
.site-nav.is-open .hamburger-line:nth-child(3) { transform: translateY(-0.53rem) rotate(-45deg); }

/* Responsive nav - full-viewport mobile menu */
@media (max-width: 75rem) {
  .nav-links { display: none; }
  .mobile-toggle { display: block; }
  .site-nav.is-open { background: var(--surface-1); }
  .site-nav.is-open .nav-links {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: var(--header-height);
    left: 0; right: 0; bottom: 0;
    width: 100%;
    height: auto;
    background: var(--surface-1);
    padding: var(--space-xs) var(--space-md) var(--space-md);
    box-shadow: none;
    border-top: 1px solid var(--border-1);
    gap: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .site-nav.is-open .nav-links a {
    width: 100%;
    padding: var(--space-sm) 0;
    border-bottom: 1px solid var(--border-1);
    justify-content: flex-start;
  }
  /* Rows in the open panel are already divided by real borders. */
  .site-nav.is-open .nav-links > a::after,
  .site-nav.is-open .nav-dropdown-toggle::after { display: none; }
  /* In the mobile panel the submenu is part of the list, not a floating menu.
     It has to be unfolded rather than hovered: :hover never fires on touch, so
     while these four sat behind a hover rule they were unreachable on a phone:
     the only route to the geyser, fireplace, stove and braai pages was the
     parent link to /products/. Shown inline and indented, always open. */
  .site-nav.is-open .nav-dropdown { width: 100%; }
  .site-nav.is-open .nav-dropdown-menu {
    position: static;
    opacity: 1; visibility: visible; transform: none;
    width: 100%; min-width: 0;
    background: none; border: none; box-shadow: none;
    border-radius: 0;
    padding: 0 0 0 var(--space-md);
  }
  .site-nav.is-open .nav-dropdown-menu a { color: var(--ink-2); border-radius: 0; }
  .site-nav.is-open .nav-dropdown-menu a:hover { background: none; color: var(--brand-3); }
  /* The chevron promises a fold that is no longer there. */
  .site-nav.is-open .nav-dropdown-toggle svg { display: none; }

  /* The transparent skin turns links, hamburger and logo white for the photo
     hero behind them. The open mobile panel is --surface-1, so all three go
     invisible against it. site.css used to carry a fix for exactly this, keyed
     to a .nav-on-dark class nothing ever applied; now that the skin is real,
     so is the bug. Ink wins whenever the panel is open. */
  body[data-nav="transparent"] .site-nav.is-open .nav-links > a,
  body[data-nav="transparent"] .site-nav.is-open .nav-dropdown-toggle { color: var(--ink-1); }
  body[data-nav="transparent"] .site-nav.is-open .nav-links > a:hover,
  body[data-nav="transparent"] .site-nav.is-open .nav-dropdown-toggle:hover { color: var(--brand-3); }
  body[data-nav="transparent"] .site-nav.is-open .hamburger-line { background: var(--ink-1); }
  body[data-nav="transparent"] .site-nav.is-open .site-logo img { filter: none; }
  .header-actions .btn-nav-cta,
  .header-actions .nav-signin { display: none; }
}
.mobile-only-nav-item { display: none !important; }
@media (max-width: 75rem) { .mobile-only-nav-item { display: flex !important; } }

/* ============ PARTIAL: FOOTER ============ */
.site-footer {
  background-color: var(--brand-1);
  padding: var(--space-xl) 0 var(--space-lg);
  border-bottom: 10px solid var(--brand-3);
  border-top: 1px solid var(--border-on-dark-strong);
  position: relative;
  overflow: hidden;
}
.site-footer .container { position: relative; z-index: 1; }
.footer-inner { display: grid; grid-template-columns: 1fr 2fr; gap: 3.75rem; margin-bottom: 3.75rem; }
.footer-logo { display: flex; align-items: center; gap: var(--space-xs); margin-bottom: var(--space-md); }
.footer-logo img { height: 2.85rem; width: auto; filter: brightness(0) invert(1); }
.footer-email-link { font-size: var(--step-1); font-weight: 700; color: var(--accent); display: inline-block; margin-bottom: var(--space-xs); transition: color var(--dur-fast) ease; }
.footer-email-link:hover { color: var(--brand-3); }
/* Baseline, not the default stretch: the 064 number shares a span with its
   WhatsApp link, and that span's inherited line-height dropped its text below
   the 015 number beside it. */
.footer-contact { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-xs) var(--space-md); margin-top: var(--space-sm); }
.footer-contact a { font-size: var(--text-body-sm); font-weight: 700; color: var(--on-dark); transition: color var(--dur-fast) ease; }
.footer-contact a:hover,
.footer-contact a:focus-visible { color: var(--accent-tint); }
.footer-address { font-style: normal; font-size: var(--text-body-sm); color: var(--on-dark-2); margin-top: var(--space-xs); }
.footer-right { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); }
.footer-nav-col { display: flex; flex-direction: column; gap: var(--space-sm); }
.footer-col-label { font-size: var(--text-body-sm); font-weight: 700; text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--on-dark); margin-bottom: var(--space-xs); }
.footer-nav-col a { font-size: var(--text-body-sm); color: var(--on-dark-2); font-weight: 400; transition: color var(--dur-fast) ease; display: inline-block; }
/* No transform. Nudging a link sideways on hover moves the target out from
   under the pointer that is aiming at it, which is the one thing a hover
   state should not do. Colour carries the state instead. */
.footer-nav-col a:hover,
.footer-nav-col a:focus-visible { color: var(--on-dark); }
.footer-divider { height: 1px; background-color: var(--border-on-dark); margin-bottom: var(--space-ml); }
.footer-bottom { display: flex; justify-content: space-between; align-items: center; }
.footer-fineprint { font-size: var(--text-caption); font-weight: 400; color: var(--on-dark-2); }
a.footer-fineprint { color: var(--brand-5); }
a.footer-fineprint:hover { color: var(--on-dark); }
.footer-social-link { font-size: var(--text-body-sm); font-weight: 700; color: var(--on-dark); margin: 0 0.75rem; transition: transform var(--dur-fast) ease, color var(--dur-fast) ease; display: inline-block; }
.footer-social-link:hover { color: var(--brand-3); transform: translateY(-3px); }
.footer-bottom-right { display: flex; gap: var(--space-md); }
@media (max-width: 64rem) { .footer-inner { grid-template-columns: 1fr; gap: var(--space-lg); } }
@media (max-width: 48rem) {
  .footer-right { grid-template-columns: 1fr; }
  .footer-bottom { flex-direction: column; gap: var(--space-md); align-items: center; text-align: center; }
}

/* Floating WhatsApp button, bottom right on every page. Always visible: it is
   a contact route, so it should not wait for a scroll. It replaced the back to
   top button in the same corner. Clicks are tracked as click_to_whatsapp by
   the delegated handler in script.js. */
.whatsapp-float {
  position: fixed; bottom: var(--space-md); right: var(--space-md);
  width: 3.5rem; height: 3.5rem;
  border-radius: 50%;
  background: var(--whatsapp);
  color: var(--on-dark);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-lift);
  transition: transform var(--dur-med) ease, background-color var(--dur-fast) ease;
  z-index: 90;
}
.whatsapp-float svg { width: 1.875rem; height: 1.875rem; }
.whatsapp-float:hover,
.whatsapp-float:focus-visible { background: var(--whatsapp-dark); transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) {
  .whatsapp-float { transition: none; }
  .whatsapp-float:hover,
  .whatsapp-float:focus-visible { transform: none; }
}

/* ============ HERO: SERVICE / INNER PAGE ============ */
.inner-hero { padding-top: 10rem; }
.inner-hero .grid-12 { align-items: center; }
.service-hero { text-align: left; padding-top: 8.75rem; padding-bottom: 1.25rem; }
.service-hero .label { display: block; margin-bottom: 0.875rem; }
.service-hero h1 { margin: 0 0 var(--space-sm); --header-measure: 15ch; letter-spacing: var(--tracking-display); line-height: 1; }
.service-hero h1.two-line-header { --header-measure: 20ch; }
.service-hero .body-large { max-width: 45ch; margin: 0 0 var(--space-ml); }
.service-subhead { font-size: var(--text-subhead); font-weight: 400; line-height: var(--leading-subhead); max-width: 60ch; padding-bottom: 2rem; }
.hero-bottom-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 2rem; width: 100%; }
.hero-built-with { display: flex; align-items: center; gap: var(--space-sm); }
.hero-built-with .label { margin: 0; font-size: var(--text-label); text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; }
.built-with-logos { display: flex; align-items: center; gap: var(--space-md); transform: translateY(-5px); }
.surface-accent .hero-built-with .label { color: var(--on-dark); }
.surface-accent .built-with-logos img { height: 4.125rem; width: auto; filter: grayscale(100%) brightness(200%); opacity: 0.9; }
@media (max-width: 48rem) {
  .hero-bottom-row { flex-direction: column; align-items: flex-start; gap: var(--space-ml); }
  .built-with-logos { flex-wrap: wrap; }
  .surface-accent .built-with-logos img { height: 2.875rem; }
}

/* ============ HERO: FULL-BLEED (image background, centered) ============ */
/* Full-width. Pass the image via the --home-hero-background custom property,
   and optionally a portrait crop for <=48rem via --home-hero-background-mobile:
   <section class="section fullbleed-hero" style="--home-hero-background: url('...')">
   Falls back to --surface-dark when no image is set. Content is centered both
   axes and text is centered.

   Three layers, back to front:
     1. the section's own background-color (fallback)
     2. ::before - the photo, desaturated and dimmed so a busy, high-contrast
        shot stops competing with the headline
     3. ::after  - the legibility scrim: a vertical wash plus a soft vignette
        that keeps the centre calm where the copy sits
   Both pseudo-elements sit at z-index -1; `isolation: isolate` stops them
   escaping behind the section. Tune with the two knobs below. */
.fullbleed-hero {
  --hero-image-calm: saturate(0.72) brightness(0.93);
  --hero-scrim-strength: 0.28;
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--space-xl) 0;
  text-align: center;
  overflow: hidden;
  isolation: isolate;
  color: var(--on-dark);
  background-color: var(--surface-dark);
}
/* The photo itself, toned down. */
.fullbleed-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--home-hero-background, none);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  filter: var(--hero-image-calm);
}
/* Legibility scrim - sits above the image, below the content. */
.fullbleed-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg,
      rgba(4, 12, 24, calc(var(--hero-scrim-strength) * 0.85)) 0%,
      rgba(4, 12, 24, var(--hero-scrim-strength)) 50%,
      rgba(4, 12, 24, calc(var(--hero-scrim-strength) * 1.3)) 100%),
    radial-gradient(ellipse 75% 65% at 50% 50%,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, 0.16) 100%);
}
.fullbleed-hero .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}
.fullbleed-hero .label { color: var(--on-dark-2); margin: 0; }
.fullbleed-hero h1,
.fullbleed-hero .t-hero { color: var(--on-dark); --header-measure: 30ch; margin: 0; }
.fullbleed-hero .body-large { color: var(--on-dark-2); max-width: 52ch; margin: 0; }
.fullbleed-hero .hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
@media (max-width: 48rem) {
  .fullbleed-hero { min-height: 32rem; padding: var(--space-lg) 0; }
  /* Swap in a portrait-cropped art-directed image, if the page supplies one.
     Note this overrides background-image rather than reassigning
     --home-hero-background: that property is set inline on the section, and an
     inline custom property always beats a stylesheet rule, so reassigning it
     here would silently do nothing. Falls back to the desktop image. */
  .fullbleed-hero::before {
    background-image: var(--home-hero-background-mobile, var(--home-hero-background, none));
  }
  .fullbleed-hero .hero-actions {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    max-width: 18rem;
  }
}

/* ============ HERO: PHOTO (two column, display type) ============ */
/* A second hero alongside .fullbleed-hero, not a replacement: the other four
   pages still use that one. This is the homepage treatment, built around a
   photograph of a person rather than a scene.

   Structural values are measured off the reference build, so the odd looking
   numbers (1.22fr, 6.5vw, 105deg) are transcribed rather than invented.

   Red appears in exactly three places here and nowhere else: the button fill,
   the 3px trust rules, and the terminator band. A large red field on a gas
   company reads as hazard signage rather than brand, which is also why the
   band is 6px and not the reference's 110px. */
.photo-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Without a floor the hero shrinks as the viewport grows: a wider column
     stops the headline wrapping, so 768px rendered shorter than 360px did.
     The ceiling is the reference's measured 909px. */
  --photo-hero-min-base: 29.106rem;              /* 27.72rem plus 5% */
  --photo-hero-min: var(--photo-hero-min-base);
  /* The +20px is a flat offset on top of the fluid curve, not folded into
     the three terms: adding it to a vw term needs a calc anyway, and keeping
     it outside means the curve and the nudge stay independently readable. */
  min-height: calc(clamp(var(--photo-hero-min), 57.17vw, 59.06rem) + 20px);
  /* The one knob for scrim tuning. The four gradient stops scale off it in
     the ratios measured from the reference, so raising this darkens the left
     without moving where the wash clears. 0.70 is tuned for a photograph with
     a dark or busy left third, which is what the hero shot is briefed for.
     Against a bright placeholder it will not hold contrast: measure, do not
     assume. See --photo-hero-clear for where the wash ends. */
  --photo-hero-scrim: 0.50;
  --photo-hero-clear: 68%;
  /* Flat wash over the whole frame, and corner darkening. Separate from the
     directional scrim so the left-to-right falloff can stay tuned for the
     headline while overall depth is dialled independently. */
  --photo-hero-wash: 0.11;
  --photo-hero-vignette: 0.42;
  /* Photograph brightness. The scrim above is directional and clears on the
     right; this is the flat dial that pulls the whole frame down so copy over
     the photo's bright patches holds contrast. Measured on the shipped
     photograph: headline 2.40:1 and body 1.74:1 unfiltered; 0.72 clears the
     3.0 and 4.5 thresholds (CHANGELOG 0.8.5). Raise toward 1 for a darker
     photograph. */
  --photo-hero-brightness: 0.72;
  /* Optical lift for the headline. The block is vertically centred, but the
     display face carries a lot of clear space above the caps, so it reads lower
     than it measures. Shifted rather than margined so the aside beside it and
     the CTA below it do not move with it. */
  /* Fluid, not fixed. The lift that puts the headline where it belongs on a
     wide screen is roughly 10rem, but the hero is only 28rem tall at its
     floor, so the same 10rem drives the headline up into the fixed nav on a
     phone. This tracks the viewport between the two, and the clamp floor is
     what keeps it clear of the header at 360px. */
  /* The intercept term used to be 2.5rem, which made the lift 88px at 769px
     while the hero was only 466px tall: the headline sat 4px BEHIND the nav
     bar between roughly 769 and 900. Pure vw with the same 10rem ceiling
     keeps 1920 exactly where it was and lets the bottom of the range fall
     back to the 4rem floor, which is what buys that band its clearance. */
  --photo-hero-title-lift: clamp(4rem, 8.34vw, 10rem);
  /* Separate knob from the title lift. The aside is centred against the
     headline in the grid, which drops the support copy well below the
     headline's top edge. This raises the support copy and the trust block
     together so the spacing between them is preserved. To move the paragraph
     alone, put the transform on .photo-hero-support instead. */
  --photo-hero-aside-lift: 2.75rem;
  /* 120 -> 150 top, 30 -> 70 sides, 40 -> 70 bottom */
  padding: clamp(7.5rem, 7.8vw, 9.375rem)
           clamp(1.875rem, 3.6vw, 4.375rem)
           clamp(2.5rem, 3.6vw, 4.375rem);
}

/* A real <picture>, not a CSS background. The background version cannot carry
   srcset and is invisible to the LCP preloader, so the largest element on the
   page is the one the browser finds last. Do not convert this back. */
.photo-hero-media { position: absolute; inset: 0; z-index: -2; }
.photo-hero-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  filter: brightness(var(--photo-hero-brightness));
}

/* Directional scrim. The reference gets away with a flat 20% black because its
   photograph is already dark upper left. This one has a face in the open right
   side, and a flat wash heavy enough to protect the headline would turn that
   face to mud, killing the exact trust signal the photograph is there for. So
   the gradient does its work on the left and has cleared by 68%.
   Re-tune the stops against the final photograph. */
.photo-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Layered front to back: directional scrim, bottom lift, corner vignette,
     then a flat wash under everything. */
  background:
    linear-gradient(105deg,
      rgba(0, 0, 0, var(--photo-hero-scrim)) 0%,
      rgba(0, 0, 0, calc(var(--photo-hero-scrim) * 0.66)) 32%,
      rgba(0, 0, 0, calc(var(--photo-hero-scrim) * 0.20)) 50%,
      rgba(0, 0, 0, calc(var(--photo-hero-scrim) * 0.03)) var(--photo-hero-clear)),
    linear-gradient(to top, rgba(0, 0, 0, var(--photo-hero-floor, 0.32)) 0%,
      rgba(0, 0, 0, 0) var(--photo-hero-floor-reach, 26%)),
    radial-gradient(ellipse 76% 66% at 50% 48%,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, calc(var(--photo-hero-vignette) * 0.35)) 62%,
      rgba(0, 0, 0, var(--photo-hero-vignette)) 100%),
    linear-gradient(rgba(0, 0, 0, var(--photo-hero-wash)),
                    rgba(0, 0, 0, var(--photo-hero-wash)));
}

.photo-hero-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.22fr) minmax(0, 1fr);
  /* Centre, not end. The columns are centred against each other in the
     reference, which is what puts the support copy where it sits. */
  align-items: center;
  gap: clamp(1.875rem, 3vw, 3.75rem);
  width: 100%;
  max-width: var(--container-wide);
  margin-inline: auto;
}

.photo-hero-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2.75rem, 6.5vw, 7.8rem);
  line-height: 1.05;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--on-dark-soft);
  text-wrap: balance;
  margin: 0 0 1rem 0;
  max-width: 16ch;
}
/* The lift moved from the headline to the whole left column, so the button
   travels with it instead of being left behind. Still a transform, so the
   aside beside it does not shift. */
.photo-hero-lead {
  transform: translateY(calc(var(--photo-hero-title-lift, 0rem) * -1));
}

/* ---- Aside: support copy and trust block ---- */
.photo-hero-aside {
  /* One multiplier for the support copy, the trust labels and the trust
     values, so they keep their relative sizes when the block is resized. */
  --photo-hero-aside-scale: 1.05;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: clamp(1.5rem, 2.4vw, 2.5rem);
  text-align: end;
  transform: translateY(calc(var(--photo-hero-aside-lift, 0rem) * -1));
}
.photo-hero-support {
  font-family: var(--font-body);
  font-size: calc(clamp(1.125rem, 1.3vw, 1.1875rem) * var(--photo-hero-aside-scale));
  line-height: 1.3;
  color: var(--on-dark-soft);
  max-width: 18.75rem;
  margin: 0;
}
.photo-hero-trust {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-width: 18.75rem;
}
.photo-hero-trust-item {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding-inline-end: var(--space-sm);
  border-inline-end: 3px solid var(--brand-3);
}
.photo-hero-trust-label {
  font-family: var(--font-body);
  font-size: calc(var(--text-label) * var(--photo-hero-aside-scale));
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--on-dark-2);
}
.photo-hero-trust-value {
  font-family: var(--font-display);
  font-size: calc(1.0625rem * var(--photo-hero-aside-scale));
  line-height: 1.25;
  text-transform: uppercase;
  color: var(--on-dark-soft);
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.photo-hero-trust-value a { color: inherit; text-decoration: none; }
.photo-hero-trust-value a:hover,
.photo-hero-trust-value a:focus-visible { color: var(--brand-3); }

/* ---- Square button variant. Zero radius is deliberate. ---- */
.btn-square {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.375;
  text-transform: uppercase;
  text-decoration: none;
  padding: 1.125rem 2.5rem;
  border: 0;
  border-radius: 0;
  background: var(--brand-3);
  color: var(--on-dark);
  transition: background-color var(--dur-fast) ease;
}
.btn-square:hover,
.btn-square:focus-visible { background: var(--brand-3-dark); }
.btn-square:focus-visible { outline: 3px solid var(--on-dark); outline-offset: 3px; }

/* ---- CTA ---- */
/* Sibling of the grid, not nested in the left column, so it survives the
   aside being hidden on mobile. */
/* Inside the lead column now, directly under the headline, rather than a
   sibling of the two-column row where it sat below the full height of the
   aside. align-self keeps it from stretching to the column width. */
.photo-hero-cta {
  display: inline-flex;
  align-self: flex-start;
  margin-block-start: var(--space-md);
}
.photo-hero-lead { display: flex; flex-direction: column; align-items: flex-start; }
/* Two anchors, one per breakpoint. The nav CALL NOW button links to
   /contactus/, not tel:, so hiding the aside on mobile would strip every call
   affordance off the screen on the device most of this traffic arrives on.
   The mobile CTA dials instead of routing to a form. */
.photo-hero-cta-call { display: none; align-self: flex-start; margin-block-start: var(--space-md); }

/* ---- Terminator band ---- */
.photo-hero-band {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  height: 27px;
  background: var(--brand-3);
}

/* ---- Mobile: headline and CTA only ---- */
@media (max-width: 48rem) {
  /* Taller on phones. Below this breakpoint the clamp floor is the only term
     that ever wins (60.5vw at 768px is 465px, well under the floor), so
     raising the floor is the whole change. Written as the multiplier rather
     than the resolved 41.58rem so the relationship to the desktop floor stays
     visible. */
  .photo-hero {
    --photo-hero-min: calc(var(--photo-hero-min-base) * 1.35);
    /* Bottom aligned, so the headline and call button sit below the
       technician's face instead of across it. In the mobile crop the face is
       at roughly 45% down the frame and the lower third is the cooker, which
       can take the text. The lift is zeroed because it is a transform: left
       on, it would pull the block back up into the face. */
    justify-content: flex-end;
    --photo-hero-title-lift: 0rem;
    /* The text now lands on bright stainless steel, so the bottom lift in
       .photo-hero::before is deepened and carried higher to hold contrast
       under the white headline. Re-measure if the photograph changes. */
    --photo-hero-floor: 0.7;
    --photo-hero-floor-reach: 48%;
  }
  .photo-hero-inner { grid-template-columns: 1fr; }
  /* Centred on phones only. The desktop layout sets the headline against the
     aside on the right, so centring there would break the pairing. */
  .photo-hero-lead { align-items: center; text-align: center; }
  /* Tighter to the headline than on desktop. 1.5rem minus 0.5rem is exactly
     --space-sm, so this stays a step on the spacing scale rather than a calc
     landing between two tokens. */
  .photo-hero-cta,
  .photo-hero-cta-call { align-self: center; margin-block-start: var(--space-sm); }
  /* Hidden, not removed: the numbers and registration stay in the source. */
  .photo-hero-aside { display: none; }
  .photo-hero-title { max-width: 100%; }
  .photo-hero-cta { display: none; }
  .photo-hero-cta-call { display: inline-flex; }
  /* Centred horizontally on the technician; any vertical crop comes off the
     extractor hood rather than the floor the text sits on. */
  .photo-hero-media img { object-position: 50% 30%; }
}

@media (prefers-reduced-motion: reduce) {
  .btn-square { transition: none; }
  /* Still appears, just without the wipe. */
  .nav-links > a::after,
  .nav-dropdown-toggle::after { transition: none; }
}

/* ============ COMPONENT: CONTACT BLOCK ============ */
/* Two columns: intro and details left, enquiry form right. Same 1290px
   measure as .lead-block. */
.contact-block {
  padding-block: clamp(3rem, 7.8vw, 9.375rem);
  padding-inline: clamp(1.25rem, 4vw, 2rem);
}
.contact-block-inner {
  max-width: 80.625rem;
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 3.4vw, 4.0625rem);
  align-items: start;
}
.contact-block-eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-label);
  line-height: 1;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--brand-3);
  margin: 0 0 1.5625rem;
}
.contact-block-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2rem, 3vw, 3.5625rem);
  line-height: 1.105;
  text-transform: uppercase;
  color: var(--ink-heading);
  max-width: 25rem;
  text-wrap: balance;
  margin: 0 0 1.75rem;
}
/* --sm is one step down (80%), for long product names that otherwise stack
   four lines deep in the quote column. */
.contact-block-title--sm { font-size: clamp(1.6rem, 2.4vw, 2.85rem); }
.contact-block-lead {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.625;
  color: var(--ink-2);
  max-width: 32.5rem;
  margin: 0 0 clamp(2rem, 2.9vw, 3.375rem);
}
.contact-details {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.625rem;
}
.contact-detail {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: 1.375rem;
  align-items: start;
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.625;
  color: var(--ink-2);
}
.contact-detail > span:last-child {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  align-items: flex-start;
}
.contact-detail a { color: var(--ink-1); text-decoration: none; }
/* "Prefer to talk?" row on product quote blocks: a quiet label, then the two
   numbers stacked as the things to tap. Number size is 75% of --step-1. */
.contact-talk-label { font-size: var(--text-body-sm); color: var(--ink-3); }
.contact-talk-numbers { display: flex; flex-direction: column; gap: 0.125rem; }
.contact-talk-numbers a { font-size: calc(var(--step-1) * 0.75); font-weight: 700; color: var(--ink-1); }
/* "Back to [range]" under the quote block's contact details. */
.contact-back-link {
  display: inline-block;
  margin-top: var(--space-lg);
  font-size: var(--text-body-sm);
  font-weight: 700;
  color: var(--brand-3);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.contact-back-link:hover,
.contact-back-link:focus-visible { color: var(--brand-3-dark); }
.contact-detail a:hover,
.contact-detail a:focus-visible { color: var(--brand-3); text-decoration: underline; }

/* Icons are CSS masks rather than inline SVG, matching .nav-phone-icon. Keeps
   the path data out of the markup and lets currentColor drive the fill. */
.contact-icon {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  display: block;
  background: var(--brand-3);
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.contact-icon-pin {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M20%2010c0%204.993-5.539%2010.193-7.399%2011.799a1%201%200%200%201-1.202%200C9.539%2020.193%204%2014.993%204%2010a8%208%200%200%201%2016%200%22%2F%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2210%22%20r%3D%223%22%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M20%2010c0%204.993-5.539%2010.193-7.399%2011.799a1%201%200%200%201-1.202%200C9.539%2020.193%204%2014.993%204%2010a8%208%200%200%201%2016%200%22%2F%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2210%22%20r%3D%223%22%2F%3E%3C%2Fsvg%3E");
}
.contact-icon-phone {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M22%2016.92v3a2%202%200%200%201-2.18%202%2019.79%2019.79%200%200%201-8.63-3.07%2019.5%2019.5%200%200%201-6-6%2019.79%2019.79%200%200%201-3.07-8.67A2%202%200%200%201%204.11%202h3a2%202%200%200%201%202%201.72c.13.96.36%201.9.7%202.81a2%202%200%200%201-.45%202.11L8.09%209.91a16%2016%200%200%200%206%206l1.27-1.27a2%202%200%200%201%202.11-.45c.9.34%201.85.57%202.81.7A2%202%200%200%201%2022%2016.92z%22%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M22%2016.92v3a2%202%200%200%201-2.18%202%2019.79%2019.79%200%200%201-8.63-3.07%2019.5%2019.5%200%200%201-6-6%2019.79%2019.79%200%200%201-3.07-8.67A2%202%200%200%201%204.11%202h3a2%202%200%200%201%202%201.72c.13.96.36%201.9.7%202.81a2%202%200%200%201-.45%202.11L8.09%209.91a16%2016%200%200%200%206%206l1.27-1.27a2%202%200%200%201%202.11-.45c.9.34%201.85.57%202.81.7A2%202%200%200%201%2022%2016.92z%22%2F%3E%3C%2Fsvg%3E");
}
.contact-icon-mail {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M22%207%2013.03%2012.7a1.94%201.94%200%200%201-2.06%200L2%207%22%2F%3E%3Cpath%20d%3D%22M2%206a2%202%200%200%201%202-2h16a2%202%200%200%201%202%202v12a2%202%200%200%201-2%202H4a2%202%200%200%201-2-2z%22%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M22%207%2013.03%2012.7a1.94%201.94%200%200%201-2.06%200L2%207%22%2F%3E%3Cpath%20d%3D%22M2%206a2%202%200%200%201%202-2h16a2%202%200%200%201%202%202v12a2%202%200%200%201-2%202H4a2%202%200%200%201-2-2z%22%2F%3E%3C%2Fsvg%3E");
}
.contact-icon-clock {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12%206v6l4%202%22%2F%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%2210%22%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12%206v6l4%202%22%2F%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%2210%22%2F%3E%3C%2Fsvg%3E");
}


@media (max-width: 60rem) {
  .contact-block-inner { grid-template-columns: minmax(0, 1fr); }
  .contact-block-title,
  .contact-block-lead { max-width: 100%; }
}

/* ============ COMPONENT: FORM ============ */
/* Extends the existing .form-input primitive rather than forking a second
   one. The only inherited value overridden is the radius: these controls sit
   beside .btn-square, and a 12px input next to a square button reads as two
   different systems. */
.form-field { display: flex; flex-direction: column; gap: 0.625rem; }
.form-row-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
}
.form-stack { display: flex; flex-direction: column; gap: 1.75rem; }
/* Body face, not the display face. Anton is a heavy condensed display cut
   drawn for headline sizes; at 17px uppercase its stems fall between pixels
   and the labels read as smudged. Tracking is added back so they still hold
   the uppercase rhythm of the rest of the form. */
.form-field .form-label {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-1);
  margin-bottom: 0;
}
.form-req { color: var(--brand-3); }
.form-field .form-input {
  border-radius: 0;
  padding: 0.8125rem 0.875rem;
  min-height: 3.0625rem;
  line-height: 1.4;
}
.form-input::placeholder {
  font-size: 0.9375rem;
  color: var(--grey-3);
  opacity: 1;   /* Firefox dims placeholders by default. */
}
.form-field .form-input:focus-visible {
  outline: 3px solid var(--brand-3);
  outline-offset: 2px;
  border-color: var(--brand-3);
  box-shadow: none;
}
/* Errors are carried by aria-invalid, which the live region announces. The
   border is reinforcement, never the only signal. */
.form-input[aria-invalid="true"] { border-color: var(--brand-3); border-width: 2px; }
.form-textarea { min-height: 8.75rem; resize: vertical; }


.form-status {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.5;
  margin: 0;
  color: var(--ink-2);
}
.form-status:empty { display: none; }
/* Submit reuses .btn-square. No second button primitive here. */
.form-stack .btn-square { align-self: flex-start; }

@media (max-width: 30rem) {
  .form-row-split { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .form-input { transition: none; }
}

/* ============ COMPONENT: RANGE GRID ============ */
/* Sub-ranges within a category: square image, solid dark caption bar under it.
   Same 1290px measure as .lead-block, .contact-block and .faq-block.

   Deliberately NOT the homepage .product-tile. That one is a chamfered
   portrait card with a left-aligned label and an arrow cue, and it marks a
   top-level category. This marks a sub-range one level down. The distinction
   only reads if it stays consistent, so: no chamfer here, and the two are
   never mixed on one page.

   Tile labels are link text, not headings. The section already owns an h2;
   adding an h5 per tile for its size would put the type scale ahead of the
   document outline. */
.range-grid {
  padding-block: clamp(3rem, 6vw, 7.5rem);
  padding-inline: clamp(1.25rem, 4vw, 2rem);
}
.range-grid-inner {
  max-width: 80.625rem;
  margin-inline: auto;
}
.range-grid-eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-label);
  line-height: 1;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-heading);
  margin: 0 0 1.5625rem;
}
.range-grid-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.75rem, 2.4vw, 2.75rem);
  line-height: 1.105;
  text-transform: uppercase;
  color: var(--ink-heading);
  text-wrap: balance;
  margin: 0 0 clamp(2rem, 2.6vw, 3.125rem);
}
/* Explicit repeat(3), not auto-fit: the column count is a design decision at
   each breakpoint, not something to be derived from available width. */
.range-grid-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.875rem;
}
@media (max-width: 64rem) { .range-grid-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 40rem) { .range-grid-list { grid-template-columns: minmax(0, 1fr); } }

.range-tile {
  display: flex;
  flex-direction: column;
  height: 100%;
  text-decoration: none;
  color: inherit;
}
.range-tile:focus-visible { outline: 3px solid var(--brand-3); outline-offset: 4px; }
.range-tile-media {
  display: block;
  aspect-ratio: 410 / 403;
  overflow: hidden;
}
.range-tile-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-med) ease;
}
.range-tile:hover .range-tile-media img,
.range-tile:focus-visible .range-tile-media img { transform: scale(1.04); }

/* flex: 1 is what keeps a row level. A two-line label grows the bar rather
   than the tile, so a short label beside a long one still ends flush. */
.range-tile-caption {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  min-height: 5.5rem;
  padding: 1.875rem;
  background: var(--surface-dark);
  transition: background-color var(--dur-fast) ease;
}
.range-tile-label {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.125rem, 1.5vw, 1.4375rem);
  line-height: 1.215;
  text-transform: uppercase;
  text-align: center;
  color: var(--on-dark-soft);
  text-wrap: balance;
}
/* The one red in this component. It is a hover state, so it is never on
   screen at rest and does not add to the page's standing red count. */
.range-tile:hover .range-tile-caption,
.range-tile:focus-visible .range-tile-caption { background: var(--brand-3); }

@media (prefers-reduced-motion: reduce) {
  .range-tile-media img,
  .range-tile-caption { transition: none; }
  .range-tile:hover .range-tile-media img { transform: none; }
}

/* ============ COMPONENT: LEAD BLOCK ============ */
/* Text-first lead-in: eyebrow, heading, two ruled body columns, wide image
   below. Registered in library/lead-block.html. Placed on no page template.

   Deliberately NOT unified with the photo hero or any page header:
     - centred 1290px measure, not near-full-bleed
     - 1px light rule, not a heavy dark one
     - two equal columns, not three

   The heading is an h2. This component does not own a page's h1. If it is
   ever used as a page opener the level is overridden at that point, not
   baked in here. */
.lead-block {
  padding-block: clamp(3rem, 7.8vw, 9.375rem) clamp(3rem, 5vw, 5rem);
  padding-inline: clamp(1.25rem, 4vw, 2rem);
}
.lead-block-inner {
  max-width: 80.625rem;   /* 1290px */
  margin-inline: auto;
}
.lead-block-eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-label);
  line-height: 1;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-heading);
  margin: 0 0 1.5625rem;
}
.lead-block-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2rem, 3vw, 3.5625rem);
  line-height: 1.105;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--ink-heading);
  /* Held to roughly half the measure so it wraps to two lines and leaves the
     right side open. Deliberate: removing this collapses the composition. */
  max-width: 38.75rem;    /* 620px */
  text-wrap: balance;
  margin: 0 0 clamp(2rem, 2.6vw, 3.125rem);
}
.lead-block-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.5rem, 1.6vw, 1.875rem);
}
/* The rule belongs to the columns only. The eyebrow and heading sit above it
   unruled. */
.lead-block-col {
  border-block-start: 1px solid var(--border-1);
  padding-block-start: clamp(1.5rem, 2.24vw, 2.6875rem);
}
.lead-block-col p {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.625;
  color: var(--ink-2);
  margin: 0;
}
.lead-block-media {
  margin: clamp(2rem, 2.6vw, 3.125rem) 0 0;
  aspect-ratio: 1290 / 527;
}
.lead-block-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
@media (max-width: 48rem) {
  .lead-block-cols { grid-template-columns: minmax(0, 1fr); }
  .lead-block-title { max-width: 100%; }
  /* 2.45:1 is a letterbox on a phone. Crop taller. */
  .lead-block-media { aspect-ratio: 4 / 3; }
}

/* Scroll-shrink media (hero video/image that scales on scroll) */
.scroll-shrink-media-wrapper { width: 100%; display: flex; justify-content: center; margin-top: var(--space-lg); }
.scroll-shrink-media {
  --scroll-progress: 0;
  --current-scale: calc(1 - (0.1156 * var(--scroll-progress)));
  --current-radius: calc(1.875rem * var(--scroll-progress));
  width: 100%; max-width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--grey-1);
  transform-origin: top center;
  transform: scale(var(--current-scale));
  border-radius: var(--current-radius);
  overflow: hidden;
  will-change: transform, border-radius;
}
.scroll-shrink-media img, .scroll-shrink-media video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ============ SPLIT CTA (two-panel) ============ */
.split-cta-container { display: grid; grid-template-columns: 1fr 1fr; width: 100%; }
.cta-half {
  background: var(--surface-1);
  padding: var(--space-xl) 3.75rem;
  text-decoration: none;
  display: flex; flex-direction: column; justify-content: space-between;
  min-height: 20rem;
  border-top: 1px solid var(--border-1);
  transition: background var(--dur-med) ease, padding-left var(--dur-med) ease;
  border-left: 2px solid transparent;
}
.cta-half:first-child { border-right: 1px solid var(--border-1); }
.cta-half:hover { background: var(--surface-3); border-left-color: var(--accent); padding-left: 4rem; }
.cta-half h2 { font-size: var(--step-3); line-height: var(--leading-h5); color: var(--ink-1); margin-bottom: var(--space-md); }
.cta-icon-box { font-size: 2rem; color: var(--accent); margin-bottom: auto; }
.cta-half p { margin-top: 2.5rem; font-weight: 700; color: var(--ink-1); }
@media (max-width: 48rem) {
  .split-cta-container { grid-template-columns: 1fr; }
  .cta-half:first-child { border-right: none; border-bottom: 1px solid var(--border-1); }
}

/* ============ STAT BAND ============ */
.stat-band { display: grid; grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr)); gap: var(--gutter); margin-top: var(--space-xl); border-top: 1px solid var(--border-1); padding-top: 3.75rem; }
.stat-number { font-size: var(--text-stat); font-weight: 700; color: var(--accent); line-height: 1; margin-bottom: 0.75rem; }
.stat-label { font-size: var(--text-body-sm); color: var(--ink-2); font-weight: 700; }
.surface-dark .stat-band, .surface-accent .stat-band { border-top-color: var(--border-on-dark); }
.surface-dark .stat-label, .surface-accent .stat-label { color: var(--on-dark-2); }

/* Horizontal stat band (case-study style) */
.case-stat-section { padding: clamp(1.875rem, 1.569rem + 1.306vw, 2.875rem) 0; }
.case-stat-wrapper { display: flex; align-items: center; justify-content: center; gap: 3.75rem; padding-top: 1.25rem; padding-bottom: 1.25rem; }
.case-stat-title { display: none; }
.case-stat-band { display: flex; align-items: center; gap: 4rem; flex-wrap: wrap; justify-content: center; }
.case-stat-unit { display: flex; flex-direction: column; align-items: center; text-align: center; }
.case-stat-number { font-size: calc(var(--step-5) * 0.6); font-weight: 900; color: var(--brand-3); line-height: 1; margin-bottom: 0.5rem; }
.case-stat-label { font-size: var(--text-label); text-transform: uppercase; letter-spacing: var(--tracking-caps); font-weight: 700; color: var(--ink-1); }
.surface-dark .case-stat-title { color: var(--on-dark); }
.surface-dark .case-stat-label { color: var(--on-dark-2); }
@media (max-width: 48rem) {
  .case-stat-wrapper { flex-direction: column; gap: var(--space-ml); }
  .case-stat-band { width: 100%; justify-content: space-between; }
}

/* ============ FAQ ACCORDION ============ */
/* Two columns: intro left, accordion right. Same 1290px measure as
   .lead-block and .contact-block.

   Replaces the previous card accordion, which was a rounded panel with a
   shadow and an --accent tint on open. The device here is a hairline rule and
   nothing else: no card, no background, no shadow.

   The old markup nested the heading INSIDE the button (button > h3), which
   puts a heading inside a control and gives screen readers a button whose
   accessible name comes from a heading. This is the other way round:
   h3 > button. The heading carries document structure so the questions show
   up in a heading scan, the button carries the control semantics so it is
   focusable, operable and announced as expanded or collapsed. Both are
   needed; neither alone does the job. */
.faq-block {
  /* One base value with the top given an extra 2rem. Written as a calc on a
     shared local rather than two separate clamps, so the fluid curve stays in
     one place and only the offset differs. */
  --faq-pad: clamp(3rem, 5vw, 5rem);
  padding-block: calc(var(--faq-pad) + 2rem) var(--faq-pad);
  padding-inline: clamp(1.25rem, 4vw, 2rem);
}
.faq-block-inner {
  max-width: 80.625rem;
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 555fr) minmax(0, 629fr);
  gap: clamp(2.5rem, 5.5vw, 6.5625rem);
  align-items: start;
}

/* ---- Intro ---- */
.faq-block-eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-label);
  line-height: 1;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--brand-3);
  margin: 0 0 1.5625rem;
}
.faq-block-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2rem, 3vw, 3.5625rem);
  line-height: 1.105;
  text-transform: uppercase;
  color: var(--ink-heading);
  text-wrap: balance;
  margin: 0 0 1.75rem;
}
.faq-block-lead {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.625;
  color: var(--ink-2);
  margin: 0;
}

/* ---- Accordion ---- */
.faq-item {
  border-block-end: 1px solid var(--border-1);
  margin-block-end: 1.875rem;
}
.faq-item:last-child { margin-block-end: 0; }
.faq-item-heading { margin: 0; }
.faq-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  width: 100%;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0 0 2.1875rem;
  text-align: start;
  cursor: pointer;
  color: var(--ink-heading);
  font: inherit;
}
.faq-question {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.1875rem;
  line-height: 1.265;
  text-transform: uppercase;
  color: inherit;
}
.faq-trigger:hover .faq-question,
.faq-trigger:focus-visible .faq-question { color: var(--brand-3); }
.faq-trigger:focus-visible { outline: 3px solid var(--brand-3); outline-offset: 4px; }

/* Icon drawn in CSS, not a glyph: nothing to translate, and it inherits
   colour from the trigger so hover and focus carry it for free. */
.faq-icon {
  position: relative;
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
}
.faq-icon::before,
.faq-icon::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  background: currentColor;
  transition: transform var(--dur-fast) ease, opacity var(--dur-fast) ease;
}
.faq-icon::before { width: 100%; height: 2px; }
.faq-icon::after { width: 2px; height: 100%; }
.faq-trigger[aria-expanded="true"] .faq-icon::after { transform: scaleY(0); opacity: 0; }

/* ---- Panel ---- */
/* hidden is applied by script.js on init, never hardcoded in the markup, so
   that a JS failure leaves every answer readable rather than every answer
   unreachable. */
.faq-panel[hidden] { display: none; }
.faq-answer {
  padding-block-end: 2.1875rem;
  max-width: 33.6875rem;
}
.faq-answer p {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.733;
  color: var(--ink-2);
  margin: 0;
}
.faq-answer p + p { margin-block-start: 0.875rem; }

@media (max-width: 60rem) {
  .faq-block-inner { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
  .faq-icon::before,
  .faq-icon::after { transition: none; }
}

/* ============ IMAGE + TEXT 50/50 (about-split pattern) ============ */
.about-header { max-width: 40rem; margin: 0 auto; text-align: center; }
/* For headlines that need to sit on one line at desktop; wraps normally below. */
.about-header-wide { max-width: var(--container-reading); }
.about-header .label { display: block; margin-bottom: 0.875rem; }
.about-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl); align-items: center; margin-top: var(--space-lg); }
/* Square corners, matching the buttons and the rest of the squared-off
   treatment. Width up from 90% to full column: at 90% the image sat inset
   from its own grid track for no reason the layout expresses. */
.about-photo { width: 100%; min-height: 28.75rem; border-radius: 0; overflow: hidden; background: var(--grey-1); }
.about-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.feature-grid { flex: 1; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm); }
@media (max-width: 62.5rem) { .about-split { grid-template-columns: 1fr; gap: var(--space-lg); } .about-photo { width: 100%; min-height: 20rem; } .feature-grid { width: 100%; } }
@media (max-width: 36.25rem) { .feature-grid { grid-template-columns: 1fr; } }

/* ============ HORIZONTAL TIMELINE ============ */
.timeline-horizontal { display: flex; justify-content: space-between; position: relative; margin-top: var(--space-xl); }
.timeline-line { position: absolute; top: 0.6875rem; left: 0; right: 0; height: 2px; background: var(--grey-1); z-index: 1; }
.timeline-step { flex: 1; position: relative; z-index: 2; padding-right: var(--space-md); }
.timeline-dot { width: 1.5rem; height: 1.5rem; border-radius: 50%; background: var(--accent); margin-bottom: var(--space-md); border: 4px solid var(--surface-1); }
@media (max-width: 48rem) {
  .timeline-horizontal { flex-direction: column; }
  .timeline-line { top: 0; bottom: 0; left: 0.6875rem; width: 2px; height: 100%; }
  .timeline-step { padding-right: 0; padding-left: var(--space-lg); margin-bottom: var(--space-lg); }
  .timeline-dot { position: absolute; left: 0; top: 0; }
}

/* ============ BEFORE / AFTER ============ */
.ba-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-md); }
@media (min-width: 48rem) { .ba-grid { grid-template-columns: 1fr 1fr; gap: var(--gutter); } }
.ba-block { display: flex; flex-direction: column; padding: 2.5rem; border-radius: var(--radius-card); }
@media (min-width: 64rem) { .ba-block { padding: var(--space-lg); } }
.ba-before { background-color: var(--surface-dark); }
.ba-before .label, .ba-before h4, .ba-before h3, .ba-before .ba-list li { color: var(--on-dark); }
.ba-after { background-color: var(--brand-3); }
.ba-after .label, .ba-after h4, .ba-after h3, .ba-after .ba-list li, .ba-after .body-small { color: var(--on-dark); }
.ba-list { list-style: none; padding: 0; margin: 0 0 var(--space-xl) 0; }
.ba-list:last-child { margin-bottom: 0; }
.ba-list li { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); padding: var(--space-md) 0; border-bottom: 1px solid var(--border-1); }
.ba-check-wrapper { display: flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; border-radius: 4px; flex-shrink: 0; }
.ba-before .ba-check-wrapper { background-color: var(--brand-3); color: var(--on-dark); }
.ba-after .ba-check-wrapper { background-color: var(--on-dark); color: var(--brand-3); }
.ba-before .ba-list li { border-bottom-color: var(--border-on-dark); }
.ba-after .ba-list li { border-bottom-color: var(--border-on-dark); }
.ba-list li:last-child { border-bottom: none; }
.ba-cta { margin-top: auto; }

/* ============ PRICING (clean grid) ============ */
.pricing-clean-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 0.75rem; max-width: 81.25rem; margin: 0 auto; }
@media (max-width: 48rem) { .pricing-clean-grid { gap: var(--space-md); margin-top: var(--space-md); margin-bottom: var(--space-md); } }
.pricing-clean-card { background: var(--surface-2); border-radius: var(--radius-card); padding: clamp(1.75rem, 1.367rem + 1.633vw, 3rem); display: flex; flex-direction: column; }
.pricing-clean-title { font-size: var(--step-1); font-weight: 700; color: var(--ink-1); margin-bottom: var(--space-ml); }
.pricing-clean-price { font-size: var(--step-1); font-weight: 700; color: var(--ink-1); line-height: 1; letter-spacing: -0.03em; margin-bottom: var(--space-ml); }
.pricing-clean-price span { font-size: var(--text-body-xs); font-weight: 700; color: var(--ink-3); letter-spacing: -0.01em; }
.pricing-clean-desc { font-size: var(--text-body-sm); color: var(--ink-3); margin-bottom: var(--space-ml); }
.pricing-clean-desc-fixed { min-height: 5.625rem; }
.pricing-clean-cta { width: 100%; text-align: center; margin-bottom: 2.5rem; }
.pricing-clean-features { list-style: none; padding: 0; margin: 0; }
.pricing-clean-features li { display: flex; align-items: flex-start; gap: 0.75rem; font-size: var(--text-body); color: var(--ink-2); margin-bottom: var(--space-sm); }
.pricing-clean-icon { width: 1.25rem; height: 1.25rem; border-radius: 50%; background: var(--ink-1); color: var(--on-dark); display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 0.375rem; }
.pricing-clean-card.pricing-clean-featured { border: 2px solid var(--accent); position: relative; }
.pricing-clean-badge { position: absolute; top: -0.875rem; left: 50%; transform: translateX(-50%); padding: 0.375rem 1rem; border-radius: var(--radius-pill); font-size: var(--text-micro); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; background-color: var(--accent-tint); color: var(--accent); }

/* ============ BLOG LISTING & ARTICLE ============ */
.blog-archive-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 21.25rem), 1fr)); gap: var(--gutter); }
.blog-post { position: relative; display: flex; flex-direction: column; background: var(--surface-1); border: 1px solid var(--border-1); border-radius: var(--radius-card); overflow: hidden; text-decoration: none; color: inherit; transition: box-shadow var(--dur-fast) ease, border-color var(--dur-fast) ease; }
.blog-post:hover { box-shadow: var(--shadow-lift); border-color: var(--border-3); }
.blog-post-image { width: 100%; aspect-ratio: 16 / 9; background: var(--surface-3); overflow: hidden; }
.blog-post-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.blog-post-meta { display: flex; align-items: center; gap: 0.75rem; padding: var(--space-md) var(--space-md) 0; }
.blog-post-eyebrow { font-size: var(--step-0); font-weight: 700; color: var(--ink-3); }
.blog-post-title { font-size: var(--text-subhead); font-weight: 700; line-height: 1.4; color: var(--ink-1); padding: 0.75rem var(--space-md) 0; }
.blog-post-excerpt { font-size: var(--text-body-sm); line-height: 1.7; color: var(--ink-3); padding: 0.75rem var(--space-md) 1.75rem; margin: 0; }

.blog-hero { display: flex; flex-direction: column; align-items: center; background: var(--surface-1); padding: clamp(10.625rem, 9.096rem + 6.531vw, 15.625rem) var(--gutter) clamp(3.75rem, 3.272rem + 2.041vw, 5.3125rem); text-align: center; }
.blog-hero-text { display: flex; flex-direction: column; align-items: center; max-width: 75rem; gap: var(--space-sm); --text-h1: var(--step-5); --leading-h1: var(--leading-h3); }
.blog-hero-text h1 { margin: 0; --header-measure: 24ch; color: var(--ink-1); }
.blog-hero-image { width: 100%; max-width: 62.5rem; margin: -3.75rem auto 0; padding: 0 var(--gutter); position: relative; z-index: 1; }
.blog-hero-image img { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; display: block; border-radius: var(--radius-card); box-shadow: var(--shadow-lift); }

.blog-body {
  --text-h2: var(--step-4); --leading-h2: var(--leading-h4);
  --text-h3: var(--step-3); --leading-h3: var(--leading-h5);
  max-width: var(--container-reading);
  margin: 0 auto;
  padding: var(--space-lg) var(--gutter) 1.75rem;
}
.blog-body h2 { margin-top: var(--space-xl); margin-bottom: var(--space-sm); letter-spacing: normal; color: var(--ink-1); }
.blog-body h3 { margin-top: var(--space-lg); margin-bottom: var(--space-sm); letter-spacing: normal; color: var(--ink-1); }
.blog-body p { margin-bottom: var(--space-md); color: var(--ink-2); }
.blog-body ul, .blog-body ol { margin-bottom: var(--space-md); padding-left: 1.5em; color: var(--ink-2); }
.blog-body blockquote { border-left: 3px solid var(--accent); padding-left: var(--space-md); margin: var(--space-lg) 0; color: var(--ink-3); font-style: italic; }
.blog-body img { width: 70%; max-width: 100%; border-radius: var(--radius-card); margin: var(--space-lg) auto; display: block; }
.blog-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 48rem) { .blog-body img { width: 100%; } }

.author-card { display: flex; align-items: flex-start; gap: 1.25rem; padding: var(--space-ml); background: var(--surface-2); width: 70%; margin: 4rem auto 0; border: 1px solid var(--border-1); border-radius: var(--radius-card); }
.author-card-avatar { width: 4.5rem; height: 4.5rem; border-radius: 50%; object-fit: cover; flex-shrink: 0; margin: 0; }
.author-card-label { font-family: var(--font-body); font-size: var(--text-label); font-weight: 700; letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--accent); }
@media (max-width: 48rem) { .author-card { flex-direction: column; align-items: center; text-align: center; width: 100%; } }

/* ============ BULLETED LIST ============ */
/* Body copy lists. Without this the only way to indent a <ul> is an inline
   style, which is how the homepage ended up with four of them. */
.list-bulleted { padding-left: 1.5rem; margin: 0; }
.list-bulleted li { margin-bottom: var(--space-xs); line-height: var(--leading-body); }
.list-bulleted li:last-child { margin-bottom: 0; }

/* ============ SPEC GRID ============ */
/* Label/value pairs for product detail pages. Markup is a <dl> with one
   <div> per pair, so each label stays glued to its value when the grid wraps. */
.spec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-md) var(--gutter);
  margin: 0;
}
.spec-grid > div { padding-top: var(--space-sm); border-top: 1px solid var(--border-1); }
.spec-grid dt {
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: var(--space-xs);
}
.spec-grid dd { margin: 0; font-size: var(--text-body); line-height: var(--leading-body); color: var(--ink-1); }

/* Brand red band: product Specifications sections, the About Us values row.
   All text white. White on --brand-3 is about 4.6:1, so small bold labels
   still clear AA; they stay full white rather than a muted tint for that
   reason. */
.surface-red { background-color: var(--brand-3); color: var(--on-dark); }
.surface-red :is(h1, h2, h3, h4, p, dt, dd) { color: var(--on-dark); }
.surface-red .spec-grid > div { border-top-color: rgba(255, 255, 255, 0.4); }

/* ============ PRODUCT CALLOUT ============ */
/* Boxed aside on product detail pages - install requirements, flue notes. Sits
   on the recessed surface with a crimson edge so it reads as a note attached to
   the copy, not as another section. */
.product-callout {
  background: var(--surface-3);
  border-left: 4px solid var(--brand-3);
  padding: var(--space-md) var(--space-ml);
  margin-top: var(--space-md);
}
.product-callout > :last-child { margin-bottom: 0; }

/* ============ PRODUCT GALLERY ============ */
/* Supporting product shots below the spec table. Same uncropped-edge treatment
   as .about-photo above it, so the two read as one page. */
.product-gallery {
  display: grid;
  /* auto-fill, not auto-fit: the empty tracks are kept, so a lone image stays
     one column wide instead of stretching across the row. A 400px dimension
     drawing was being blown up to 1300px that way. */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  /* 35% tighter than the standard gutter (30px to 19.5px), so the shots read
     as one set. */
  gap: calc(var(--gutter) * 0.65);
  margin: 0;
}
/* Contain, not cover: most shots are cut-outs on white and the drawings carry
   dimensions at their edges, so cropping cut off the product and the numbers.
   The frame keeps every row the same height. The section itself carries the
   colour (surface-canvas on SUPPORTING IMAGES), so the frame stays clear and a
   small drawing does not leave a white gap in a white section. */
.product-gallery figure { margin: 0; overflow: hidden; }
.product-gallery img { width: 100%; aspect-ratio: 800 / 452; object-fit: contain; display: block; }

/* A portrait product shot in .about-photo would be cropped top and bottom by
   that rule's object-fit: cover, cutting the unit in half. Modifier, so the
   pages already relying on cover are untouched.

   .about-photo also sets min-height but no definite height, so the img's
   height:100% resolves to auto and a portrait shot renders at full column width
   times its aspect - roughly 1190px tall. Sized to 70% and centred instead. */
.about-photo--contain {
  background: var(--surface-2);
  display: flex;
  align-items: center;
  justify-content: center;
}
.about-photo--contain img { width: 70%; height: auto; object-fit: contain; }
/* Landscape shots wider than the box (over about 1.24:1). The min-height above
   is sized for squarer photos, so a wide one sat at the top of an empty grey
   band. Dropping it lets the box fit the photo, which the row then centres. */
.about-photo--wide { min-height: 0; }

/* ============ PRODUCT DRAWING ============ */
/* Technical dimension drawings. These are line art, not photography - they must
   never be cropped to a tile, so the image is contained on a plain panel with
   space around it rather than dropped into .product-gallery beside the
   lifestyle shots. */
.product-drawing {
  background: var(--surface-1);
  padding: var(--space-lg);
  display: flex;
  justify-content: center;
}
.product-drawing img { width: 100%; max-width: 46rem; height: auto; display: block; }

/* ============ BREADCRUMB ============ */
.breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.breadcrumb a:hover { color: var(--accent); }
.breadcrumb .breadcrumb-sep { opacity: 0.5; }

/* ============ CONTACT FORM ============ */
.form-row { display: flex; flex-direction: column; margin-bottom: var(--space-md); }
.form-label { font-family: var(--font-body); font-weight: 700; color: var(--ink-1); margin-bottom: var(--space-xs); font-size: var(--text-body-sm); }
.form-input, .form-select { background: var(--surface-1); border: 1px solid var(--border-1); border-radius: var(--radius-ui); color: var(--ink-1); padding: var(--space-sm); width: 100%; font-family: var(--font-body); font-size: var(--step-0); transition: box-shadow var(--dur-fast) ease, border-color var(--dur-fast) ease; }
.form-input:focus, .form-select:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }

/* ============ LEGAL PAGES ============ */
.legal-subsection { padding-left: var(--space-md); border-left: 2px solid var(--border-1); }

/* ============ SCROLL REVEAL ============ */
.reveal { opacity: 0; transform: translateY(1.25rem); transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out); will-change: opacity, transform; }
.reveal.active { opacity: 1; transform: translateY(0); }

/* ============ TILE COMPONENT (AGP) ============ */
:root {
  --gs-chamfer: 2rem;
}

.gs-chamfer {
  clip-path: polygon(
    0 var(--gs-chamfer),
    var(--gs-chamfer) 0,
    100% 0,
    100% calc(100% - var(--gs-chamfer)),
    calc(100% - var(--gs-chamfer)) 100%,
    0 100%
  );
}

.gs-chamfer--alt {
  clip-path: polygon(
    0 0,
    calc(100% - var(--gs-chamfer)) 0,
    100% var(--gs-chamfer),
    100% 100%,
    var(--gs-chamfer) 100%,
    0 calc(100% - var(--gs-chamfer))
  );
}

.gs-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  text-decoration: none;
  color: inherit;
  outline: none;
}

.gs-tile:focus-visible .gs-tile__media {
  outline: 3px solid var(--accent-tint);
  outline-offset: 4px;
}

.gs-tile__media {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  position: relative;
  background-color: var(--surface-2);
}

.gs-tile__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 300ms ease;
  display: block;
}

.gs-tile:hover:not(.gs-tile--flat) .gs-tile__media img {
  transform: scale(1.05);
}

.gs-tile--square .gs-tile__media {
  clip-path: none;
}

.gs-tile__label {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: var(--space-md);
  z-index: 2;
  text-align: center;
}

.gs-tile:not(.gs-tile--flat) .gs-tile__label {
  background: rgba(0, 0, 0, 0.3);
}

.gs-tile--flat .gs-tile__label {
  position: static;
  width: auto;
  height: auto;
  padding: 0;
  background: none;
}

.gs-tile__name {
  /* --font-header resolves to Anton, so this is already the display face; the
     old system-ui fallback is left off because the token always resolves.
     letter-spacing was var(--tracking-wide), a token defined nowhere, so the
     declaration was invalid at computed-value time and the label rendered at
     normal tracking. A real value instead. */
  font-family: var(--font-header);
  font-size: var(--step-1);
  /* Anton has one face, 400. Asking for 700 gets a synthesised bold that
     smears the strokes, most visibly at this size. */
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--on-dark);
}

.gs-tile__cue {
  display: block;
  width: 2rem;
  height: 3px;
  background: var(--on-dark);
  transition: background-color 200ms ease;
}

.gs-tile:hover .gs-tile__cue {
  background: var(--brand-3);
}

.gs-tiles { padding: var(--space-xl) 0; }
.gs-tiles__head {
  margin-bottom: var(--space-xl);
  text-align: center;
}
.gs-tiles__eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: var(--space-xs);
}
.gs-tiles__title {
  font-family: var(--font-header);
  /* No font-size. Every other h2 on the site takes --text-h2 from the global
     heading scale; overriding this one to --step-3 made it visibly smaller
     than its neighbours for no stated reason. Inheriting keeps the section
     headings in one rhythm. */
  margin: 0;
}
/* A capped title (e.g. .reading on /products/) still centres: the margin: 0
   above would otherwise pin the narrower box to the left. */
.gs-tiles__title.reading { margin-inline: auto; }

.gs-tiles__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
}

@media (max-width: 64rem) {
  .gs-tiles__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 40rem) {
  .gs-tiles__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Block 6: flat colour variant ---------- */
.gs-tile__media--flat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  background: var(--brand-3); 
  padding: clamp(1.5rem, 2.6vw, 2.5rem);
  gap: var(--space-md);
}

.gs-tile__flat-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.gs-tile__flat-kicker {
  font-family: var(--font-body);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 0.65);
}

.gs-tile__flat-copy {
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  line-height: 1.35;
  color: var(--on-dark);
  max-width: 24ch;
}

.gs-tile--flat:hover .gs-tile__media--flat,
.gs-tile--flat:focus-visible .gs-tile__media--flat {
  background: var(--brand-1);
}

.gs-tile--flat.no-hover:hover .gs-tile__media--flat,
.gs-tile--flat.no-hover:focus-visible .gs-tile__media--flat {
  background: var(--brand-3);
}

.gs-tile--flat.no-hover:hover .gs-tile__cue,
.gs-tile--flat.no-hover:focus-visible .gs-tile__cue {
  background: var(--brand-3);
}

.gs-tile__media--flat { transition: background-color 200ms ease; }

@media (prefers-reduced-motion: reduce) {
  .gs-tile__media--flat { transition: none; }
}

/* ============ REVIEWS ============ */
.review-card { position: relative; }
.review-card::after {
  content: '\201D';
  position: absolute;
  top: 1rem;
  right: var(--space-md);
  font-size: 16rem;
  line-height: 1;
  color: var(--brand-1);
  opacity: 0.05;
  font-family: var(--font-accent-serif);
  pointer-events: none;
}

/* ============ REDUCED MOTION ============ */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .site-nav::before { transition: none !important; }
}

/* ============================================================
   PRODUCT PAGE HEADER (.gs-pagehead)
   ============================================================ */

.gs-pagehead {
  padding-inline: clamp(1.25rem, 3.6vw, 4.375rem);
  /* Top padding clears the fixed nav, then adds 1rem. The old value was a
     fluid 32-63px against an 88px bar, so the images ran underneath it at
     every width, by 25px at 1920 and 56px at 390. Derived from
     --header-height so it tracks the bar if that changes again. */
  padding-block: calc(var(--header-height) + 1rem) clamp(3rem, 5vw, 5rem);
  max-width: 120rem;
  margin-inline: auto;
}

/* ---------- Image row ---------- */

.gs-pagehead__media {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2.13fr);
  align-items: start;
  gap: clamp(1rem, 1.6vw, 1.875rem);
}

.gs-pagehead__media figure { margin: 0; }

.gs-pagehead__media-a { aspect-ratio: 555 / 437; }
.gs-pagehead__media-b { aspect-ratio: 1180 / 710; }

.gs-pagehead__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---------- Text row ---------- */

.gs-pagehead__body {
  display: grid;
  grid-template-columns: minmax(0, 555fr) minmax(0, 567fr) minmax(0, 567fr);
  gap: clamp(1.5rem, 2.4vw, 2.875rem);
  margin-block-start: clamp(2rem, 3.1vw, 3.75rem);
}

.gs-pagehead__eyebrow {
  font-family: var(--font-display);
  font-size: 0.875rem;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-heading);
  margin: 0 0 clamp(1.5rem, 2.4vw, 2.3125rem);
}

.gs-pagehead__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2rem, 3vw, 3.5625rem);
  line-height: 1.105;
  text-transform: uppercase;
  color: var(--ink-heading);
  text-wrap: balance;
  margin: 0;
}

/* Rule + top padding on body columns only. Never on the intro column. */
.gs-pagehead__col {
  border-block-start: 2px solid var(--ink-heading);
  padding-block-start: clamp(2.5rem, 2.9vw, 3.3125rem);
  margin-block-start: -1rem;
}

.gs-pagehead__col p {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.625;
  color: var(--ink-2);
  margin: 0;
}

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

@media (max-width: 64rem) {
  .gs-pagehead__body {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .gs-pagehead__intro { grid-column: 1 / -1; }
}

@media (max-width: 48rem) {
  .gs-pagehead__media { grid-template-columns: minmax(0, 1fr); }
  /* Both images, stacked, each at its own natural ratio. The previous rule
     hid the detail image outright and left the main one locked to its
     1180/710 box under object-fit: cover, so a phone showed one image and
     showed it cropped. Releasing the aspect-ratio and letting height follow
     the intrinsic size means neither is cut off. The width and height
     attributes stay on the <img>, so the browser still reserves the right
     space and nothing shifts as they load. */
  .gs-pagehead__media-a,
  .gs-pagehead__media-b { aspect-ratio: auto; }
  .gs-pagehead__media img { height: auto; }

  .gs-pagehead__body { grid-template-columns: minmax(0, 1fr); }
  .gs-pagehead__intro { grid-column: auto; }
}

/* Caret in the nav dropdown toggle. Lives here rather than as an inline
   style on the partial, which must stay free of hardcoded sizes. */
.nav-dropdown-caret { margin-left: 0.25rem; }

/* ---------- Partner Slider ---------- */
.partner-slider {
  width: 100%;
  overflow: hidden;
  padding: var(--space-md) 0;
  background-color: var(--surface-1);
  display: flex;
}

.partner-slider-track {
  display: flex;
  align-items: center;
  gap: 4rem;
  width: max-content;
  animation: slide 30s linear infinite;
}

.partner-logo {
  height: 60px;
  width: auto;
  object-fit: contain;
  filter: grayscale(100%);
  opacity: 0.7;
  transition: opacity 0.3s ease, filter 0.3s ease;
}

.partner-logo:hover {
  filter: grayscale(0%);
  opacity: 1;
}

@keyframes slide {
  0% {
    transform: translateX(0);
  }
  100% {
    /* 13 logos + 13 gaps roughly. We translate by -50% to reset the loop */
    transform: translateX(calc(-50% - 2rem));
  }
}

@media (max-width: 48rem) {
  .partner-logo {
    height: 40px;
  }
  .partner-slider-track {
    gap: 2rem;
  }
  @keyframes slide {
    0% {
      transform: translateX(0);
    }
    100% {
      transform: translateX(calc(-50% - 1rem));
    }
  }
}
