/*
 * Services page — built from the design handoff.
 *
 * The handoff was written for a Next.js/React codebase; this site is static
 * HTML, so its components become server-generated markup and these rules.
 * Its prototype used inline styles throughout; per the handoff those are
 * deliberately not carried over.
 *
 * Everything is scoped under .sv-page so this page's near-black palette and
 * Barlow/IBM Plex typography cannot leak into the rest of the site, which
 * still uses the Kadence theme.
 */

/* =========================================================================
   PALETTE
   =========================================================================

   The site ships in dark (night) mode -- the original design handoff palette.
   Light mode is one attribute away:

       <html data-theme="light">

   Nothing else changes: no second stylesheet, no rebuild. Every colour on the
   site resolves from these tokens, which is what makes a swap enough, and
   theme.js flips the attribute for the header switch.

   The -rgb tokens hold bare triples so a rule can write
   rgba(var(--ink-rgb), 0.6) and get a muted tone that follows the theme.
   Before they existed there were 146 hardcoded colour literals across the
   page stylesheets, and every one would have stayed dark-on-dark.
   ========================================================================= */

:root {
  /* ---- dark (night) -- the default ---- */

  --bg: #14110f;
  --bg-rgb: 20, 17, 15;
  --bg-raised: #191614;
  --bg-raised-rgb: 25, 22, 20;
  --bg-hover: #1f1b18;
  --bg-image: #0d0b0a;
  --bg-input: #0f0d0c;

  --ink: #f4f1ec;
  --ink-rgb: 244, 241, 236;

  --accent: #c8352a;
  --accent-hover: #e04236;
  --accent-rgb: 200, 53, 42;
  --accent-soft: #ff8071;

  /*
   * The accent as SMALL TEXT, which is a different problem from the accent as
   * a surface. #c8352a on this page measures 3.58:1, and 3.43:1 on the raised
   * surface -- under the 4.5:1 WCAG AA needs below 24px. It affects every
   * eyebrow and every "read more" on the site.
   *
   * This is a lighter red that clears it at 5.35:1 on the raised surface. The
   * accent itself is untouched: as a button background with white text it is
   * fine, and as large display type it clears the 3:1 large-text threshold.
   */
  --accent-text: #ef5a4c;

  --ok: #3fa45b;
  --ok-rgb: 63, 164, 91;
  --ok-soft: #6fce8b;
  --warn: #d9a441;

  --on-accent: #ffffff;
  --on-accent-rgb: 255, 255, 255;

  --rule: rgba(244, 241, 236, 0.14);
  --rule-soft: rgba(244, 241, 236, 0.1);
  --border-idle: rgba(244, 241, 236, 0.22);

  --header-bg: rgba(20, 17, 15, 0.94);

  /*
   * The CTA band is a full-bleed block of colour, and a large area reads far
   * brighter than the same red does as a button.
   *
   * Measured against the page rather than picked by eye. Full --accent put the
   * band at 3.58x the page luminance -- a flare across the full width. #8c2219
   * was still 2.11x. This sits at 1.58x: present, clearly the brand red, but
   * no longer the brightest thing on a dark screen. White text on it measures
   * 11.9:1, so nothing was traded for it.
   *
   * The accent itself is untouched, so buttons and links keep their punch.
   */
  --band-bg: #6b1811;
  --band-ink: rgba(255, 255, 255, 0.96);
  --band-ink-soft: rgba(255, 255, 255, 0.78);
  --img-filter: saturate(0.85) contrast(1.05);
  --img-muted: grayscale(1) brightness(0.62) contrast(1.1);

  /* Light-on-dark tolerates far lower alpha, so these keep the original feel. */
  --text-faint: rgba(244, 241, 236, 0.5);
  --text-muted: rgba(244, 241, 236, 0.66);
  --text-soft: rgba(244, 241, 236, 0.8);
}

/* ---- light (day) ---- */
:root[data-theme="light"] {
  /* ---- light (day) -- opt in with <html data-theme="light"> ---- */

  /* Warm off-white, not pure white: this is an industrial brand, and a
     clinical white ground fights the condensed type and hairline rules. */
  --bg: #f4f1ec;
  --bg-rgb: 244, 241, 236;
  --bg-raised: #fbf9f6;
  --bg-raised-rgb: 251, 249, 246;
  --bg-hover: #ebe6de;
  --bg-image: #e2ddd4;
  --bg-input: #ffffff;

  --ink: #14110f;
  --ink-rgb: 20, 17, 15;

  /* Deepened from the handoff's #c8352a. That red was tuned to glow against
     near-black; on a light ground it needs more weight to hold contrast as
     body-adjacent text and as a link colour. */
  --accent: #b4271c;
  --accent-hover: #8f1d14;
  --accent-rgb: 180, 39, 28;
  /* Light mode already passes: #b4271c measures 5.74:1 on the page and 6.15:1
     on the raised surface, so small accent text needs no lightening here. */
  --accent-text: #b4271c;
  /* On dark this was a lighter tint of the accent. On light it has to go the
     other way -- darker, not lighter -- or it disappears. */
  --accent-soft: #94180f;

  --ok: #2f7d45;
  --ok-rgb: 47, 125, 69;
  --ok-soft: #276639;
  --warn: #9a6a12;

  --on-accent: #ffffff;
  --on-accent-rgb: 255, 255, 255;

  --rule: rgba(20, 17, 15, 0.16);
  --rule-soft: rgba(20, 17, 15, 0.11);
  --border-idle: rgba(20, 17, 15, 0.28);

  --header-bg: rgba(244, 241, 236, 0.92);

  /* A light page tolerates more red than a dark one -- the band is darker
     than its surroundings here, so it reads as weight rather than glare. */
  --band-bg: #942017;
  --band-ink: rgba(255, 255, 255, 0.97);
  --band-ink-soft: rgba(255, 255, 255, 0.82);

  /* The photography is dark, moody artwork. On a light ground it needs
     lifting, or the images read as holes punched through the page. */
  --img-filter: saturate(0.95) contrast(1.02) brightness(1.08);
  --img-muted: grayscale(1) brightness(0.95) contrast(1.03);

  /*
   * De-emphasised text, with a floor.
   *
   * Dark-on-light loses contrast much faster than light-on-dark: the same
   * rgba(ink, 0.45) that reads ~5:1 on near-black reads 2.97:1 here, under
   * the 4.5:1 WCAG AA minimum. Alpha is baked into each rule and cannot vary
   * by theme, so every muted colour resolves through these instead.
   *
   * Measured on the deployed page: faint 4.61:1, muted 5.55:1, soft 7.4:1.
   */
  --text-faint: rgba(20, 17, 15, 0.63);
  --text-muted: rgba(20, 17, 15, 0.72);
  --text-soft: rgba(20, 17, 15, 0.82);
}

.sv-page {
  --gutter: 40px;

  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: Barlow, system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.6;
}

/* ---- document ---- */

html { -webkit-text-size-adjust: 100%; }

body.sv-body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: Barlow, system-ui, -apple-system, sans-serif;
}

/* ---- sticky header ---- */

.sv-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 18px var(--gutter);
  background: var(--header-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule-soft);
}

.sv-brand { display: flex; align-items: baseline; gap: 10px; color: inherit; }

.sv-wordmark {
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 700;
  font-size: 26px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1;
}
.sv-wordmark-sm { font-size: 24px; margin: 0; }

.sv-brand-meta {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.sv-nav { display: flex; gap: 26px; flex-wrap: wrap; }
.sv-nav a {
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color .15s;
}
.sv-nav a:hover { color: var(--ink); }
.sv-nav a[aria-current="page"] { color: var(--ink); }

/* ---- menu button ---- */

/*
 * Hidden until nav.js marks the document ready. Until then the nav row is the
 * menu, so a failed script leaves working links rather than an inert button
 * next to nothing.
 */
.sv-burger { display: none; }

.sv-burger-bars {
  display: grid;
  gap: 4px;
  width: 20px;
}
.sv-burger-bars span {
  display: block;
  height: 2px;
  background: currentColor;
  transition: transform .18s ease, opacity .18s ease;
}

/* Three bars fold into a cross while the panel is open. */
.sv-burger[aria-expanded="true"] .sv-burger-bars span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.sv-burger[aria-expanded="true"] .sv-burger-bars span:nth-child(2) { opacity: 0; }
.sv-burger[aria-expanded="true"] .sv-burger-bars span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ---- day / night switch ---- */

.sv-theme {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px, the comfortable tap target, without looking like a chunky button. */
  width: 44px;
  height: 44px;
  flex: none;
  padding: 0;
  background: none;
  border: 1px solid var(--border-idle);
  color: var(--ink);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  transition: border-color .15s, color .15s;
}
.sv-theme:hover { border-color: var(--accent); color: var(--accent-text); }

/*
 * One glyph is shown at a time, and which one is driven by the theme rather
 * than by JavaScript. That means the icon is never briefly wrong: the boot
 * script sets the attribute before first paint, so the correct glyph is in
 * place on the very first frame, before theme.js has even loaded.
 */
.sv-theme-sun { display: inline; }
.sv-theme-moon { display: none; }
:root[data-theme="light"] .sv-theme-sun { display: none; }
:root[data-theme="light"] .sv-theme-moon { display: inline; }

.sv-phone {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 20px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background .15s;
}
.sv-phone:hover { background: var(--accent-hover); color: var(--on-accent); }
.sv-phone-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--on-accent); flex: none; }

/* ---- footer ---- */

.sv-footer {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 40px;
  align-items: end;
  padding: 52px var(--gutter) 44px;
  border-top: 1px solid var(--rule-soft);
}
.sv-copy { margin-top: 10px; font-size: 14px; color: var(--text-faint); }

.sv-footer-contact { display: flex; flex-wrap: wrap; gap: 28px; }
.sv-footer-contact p { display: block; }
.sv-footer-contact .sv-label { display: block; font-size: 10px; letter-spacing: 0.16em; }
.sv-footer-contact a {
  display: block;
  margin-top: 4px;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 20px;
  letter-spacing: 0.04em;
  color: var(--ink);
  transition: color .15s;
}
.sv-footer-contact a:hover { color: var(--accent-text); }

@media (max-width: 900px) {
  /*
   * flex-wrap is load-bearing: the menu panel is a flex item with width:100%,
   * and without wrapping it cannot take its own row -- it gets shrunk into
   * whatever space is left on the header line. Dropping this collapsed the
   * open panel to 108px jammed against the right edge.
   */
  .sv-topbar { flex-wrap: wrap; gap: 12px; padding: 12px var(--gutter); }

  /* Phone and switch shrink to leave room for the menu button on one row. */
  .sv-phone { padding: 10px 14px; font-size: 15px; }
  .sv-theme { width: 40px; height: 40px; font-size: 17px; }
  .sv-brand-meta { display: none; }

  /*
   * Collapsed menu. Only applies once nav.js has marked the document, so a
   * failed script leaves the plain wrapped nav instead of no menu at all.
   */
  [data-nav="ready"] .sv-burger {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    order: 2;
    padding: 10px 14px;
    background: none;
    border: 1px solid rgba(var(--ink-rgb), 0.28);
    color: var(--ink);
    font-family: "Barlow Condensed", sans-serif;
    font-weight: 600;
    font-size: 15px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
  }

  /*
   * display:none rather than a height animation. The 0fr/1fr grid trick needs
   * a single wrapper child, and this nav has six link children -- it would
   * only have collapsed the first row.
   *
   * The adjacent sibling selector is why nav.js does not need a class: the
   * button's own aria-expanded drives the panel.
   */
  [data-nav="ready"] .sv-nav { display: none; order: 4; width: 100%; margin: 0; }
  [data-nav="ready"] .sv-burger[aria-expanded="true"] + .sv-nav {
    display: grid;
    gap: 1px;
    background: var(--rule);
    margin: 4px 0 2px;
  }

  [data-nav="ready"] .sv-nav > a {
    min-height: 0;
    padding: 14px 16px;
    background: var(--bg);
    font-size: 16px;
    color: var(--text-soft);
  }
  [data-nav="ready"] .sv-nav > a[aria-current="page"] {
    color: var(--ink);
    box-shadow: inset 3px 0 0 var(--accent);
  }

  /* No script: keep the old wrapped row, with tappable targets. */
  .sv-nav { order: 4; width: 100%; gap: 4px 14px; }
  .sv-nav a { padding: 11px 0; }

  .sv-footer { grid-template-columns: 1fr; gap: 28px; padding: 40px var(--gutter); }
  .sv-footer-contact a { display: inline-block; padding: 6px 0; }
}

.sv-page *,
.sv-page *::before,
.sv-page *::after { box-sizing: border-box; }

.sv-page h1, .sv-page h2, .sv-page h3 {
  font-family: "Barlow Condensed", Barlow, sans-serif;
  text-transform: uppercase;
  margin: 0;
}

.sv-page p { margin: 0; }
.sv-page ul, .sv-page ol { margin: 0; padding: 0; list-style: none; }
.sv-page a { text-decoration: none; }

/* Sticky theme header sits above; keep anchor targets clear of it. */
.sv-page [id] { scroll-margin-top: 90px; }

/* ---- shared type ---- */

.sv-eyebrow {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0 0 14px;
}

.sv-eyebrow-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-faint);
  margin-bottom: 28px;
}
.sv-eyebrow-bar::before {
  content: "";
  width: 34px;
  height: 2px;
  background: var(--accent);
  flex: none;
}

.sv-h1 {
  font-weight: 700;
  font-size: clamp(46px, 6vw, 88px);
  line-height: 0.94;
  text-wrap: balance;
}
.sv-accent { color: var(--accent); }

.sv-h2 {
  font-weight: 700;
  font-size: clamp(32px, 3.8vw, 50px);
  line-height: 1;
}
.sv-h2-split { font-size: clamp(30px, 3.4vw, 44px); }

.sv-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0;
}
.sv-label-accent { color: var(--accent-text); }

/* ---- buttons: square everywhere, per the handoff ---- */

.sv-btns { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 34px; }

/*
 * background and color are declared here, not left to the variants.
 *
 * Without them a <button> falls back to the browser's own buttonface grey, so
 * .sv-btn-outline -- which sets a border and a colour but no background --
 * rendered as a light grey box with near-white text on it in dark mode:
 * completely unreadable. It went unnoticed because every other outline button
 * on the site is an <a>, and links have no default background.
 */
.sv-btn {
  display: inline-block;
  padding: 16px 30px;
  border: 1px solid transparent;
  background: none;
  color: var(--ink);
  -webkit-appearance: none;
  appearance: none;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 17px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}

.sv-btn-primary { background: var(--accent); color: var(--on-accent); }
.sv-btn-primary:hover { background: var(--accent-hover); color: var(--on-accent); }

.sv-btn-outline { border-color: var(--text-faint); color: var(--ink); }
.sv-btn-outline:hover { border-color: var(--ink); color: var(--ink); }

/*
 * Sits on the red band, so it is dark in BOTH themes -- it is not a
 * background-coloured button. Using var(--bg) here made it white-on-white
 * the moment the palette flipped to light.
 */
/*
 * Border is not decoration. Against the deepened band this button only
 * reaches 2.11:1, under the 3:1 WCAG asks for a control boundary, so without
 * an edge it reads as a hole rather than a button.
 */
.sv-btn-dark {
  background: #14110f;
  color: var(--on-accent);
  border-color: rgba(255, 255, 255, 0.3);
}
.sv-btn-dark:hover { background: #000; border-color: rgba(255, 255, 255, 0.55); color: var(--on-accent); }

.sv-btn-onred { border-color: rgba(var(--on-accent-rgb), 0.55); color: var(--on-accent); }
.sv-btn-onred:hover { border-color: var(--on-accent); background: rgba(var(--on-accent-rgb), 0.12); color: var(--on-accent); }

/* ---- 1. hero ---- */

.sv-hero {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  border-bottom: 1px solid var(--rule-soft);
}

.sv-hero-copy { padding: 80px 60px 60px var(--gutter); max-width: 860px; }

.sv-hero-body {
  margin-top: 26px;
  font-size: 19px;
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 50ch;
  text-wrap: pretty;
}

/* 1px grid gaps over a lighter ground read as hairline rules. This recurs
   throughout the design. */
.sv-trust {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: rgba(var(--ink-rgb), 0.12);
  margin-top: 56px;
}
.sv-trust li { background: var(--bg); padding: 18px 16px 18px 0; }
.sv-trust-label {
  display: block;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.sv-trust-desc {
  display: block;
  margin-top: 9px;
  font-size: 15px;
  line-height: 1.4;
  color: var(--text-soft);
}

.sv-hero-media {
  position: relative;
  min-height: 600px;
  border-left: 1px solid var(--rule-soft);
  background: var(--bg-image);
  overflow: hidden;
}
.sv-hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Portrait artwork in a landscape well: anchor high so faces survive. */
  object-position: 50% 22%;
  filter: var(--img-filter);
}
.sv-hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(var(--bg-rgb), 0.88) 0%, rgba(var(--bg-rgb), 0.1) 42%, rgba(var(--bg-rgb), 0.4) 100%);
}

/* ---- section scaffolding ---- */

.sv-section { padding: 80px var(--gutter); }
#situation { padding-bottom: 0; }
#services { padding: 64px var(--gutter) 88px; }

.sv-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px;
  flex-wrap: wrap;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--rule);
}
.sv-head-support {
  font-size: 16px;
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 36ch;
}

/* ---- 2. situation router ---- */

.sv-intents {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--rule);
  margin-top: 28px;
}

.sv-intent {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  padding: 26px 24px 26px 0;
  background: var(--bg);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: background .15s;
}
.sv-intent:hover { background: var(--bg-hover); }
.sv-intent[aria-pressed="true"] {
  background: var(--bg-hover);
  box-shadow: inset 3px 0 0 var(--accent);
  padding-left: 24px;
}

.sv-intent-top { display: flex; justify-content: space-between; gap: 14px; }
.sv-intent-label {
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 22px;
  line-height: 1.1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.sv-intent-mark {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid rgba(var(--ink-rgb), 0.28);
  color: var(--text-muted);
  font-size: 13px;
}
.sv-intent-mark::before { content: "\2192"; }
.sv-intent[aria-pressed="true"] .sv-intent-mark {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.sv-intent[aria-pressed="true"] .sv-intent-mark::before { content: "\2713"; }

.sv-intent-detail {
  display: block;
  margin-top: 12px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-muted);
}

.sv-intent-answer { display: none; }
.sv-intent[aria-pressed="true"] .sv-intent-answer {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid rgba(var(--ink-rgb), 0.16);
}
.sv-intent-answer-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sv-intent-answer-value {
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 17px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-text);
}

/* ---- 3. service switcher ---- */

.sv-tabs { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-top: 28px; }

.sv-tab {
  padding: 0;
  text-align: left;
  background: transparent;
  border: 1px solid rgba(var(--ink-rgb), 0.16);
  color: var(--text-muted);
  font: inherit;
  cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
.sv-tab:hover { border-color: var(--text-faint); }
.sv-tab[aria-selected="true"] {
  background: var(--bg-raised);
  border-color: var(--accent);
  color: var(--ink);
}

.sv-tab-thumb { display: block; height: 96px; overflow: hidden; background: var(--bg-image); }
.sv-tab-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 20%;
  filter: var(--img-muted);
  transition: filter .2s;
}
.sv-tab[aria-selected="true"] .sv-tab-thumb img { filter: var(--img-filter); }

.sv-tab-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 13px 14px 14px;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 19px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1.05;
}
.sv-tab-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(var(--ink-rgb), 0.22);
}
.sv-tab[aria-selected="true"] .sv-tab-dot { background: var(--accent); }

.sv-panel { margin-top: 12px; border: 1px solid var(--rule); background: var(--bg-raised); }

.sv-pane { display: grid; grid-template-columns: 0.82fr 1.18fr; }
.sv-pane[hidden] { display: none; }

.sv-pane-media { position: relative; min-height: 540px; background: var(--bg-image); overflow: hidden; }
.sv-pane-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;
  filter: var(--img-filter);
}
.sv-pane-caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: 28px;
  background: linear-gradient(0deg, rgba(13,11,10,0.94), rgba(13,11,10,0));
}
.sv-pane-index {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.sv-pane-title { margin-top: 8px; font-weight: 700; font-size: clamp(30px, 3.4vw, 44px); line-height: 1; }
.sv-pane-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.sv-pane-tags span {
  padding: 6px 11px;
  border: 1px solid rgba(var(--ink-rgb), 0.26);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.sv-pane-body { padding: 40px 40px 36px; border-left: 1px solid var(--rule); }
.sv-lede { font-size: 19px; line-height: 1.6; color: var(--text-soft); text-wrap: pretty; }

.sv-pane-grid {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 36px;
  margin-top: 32px;
  padding-top: 32px;
  border-top: 1px solid var(--rule);
}

.sv-included { display: grid; gap: 10px; margin-top: 16px; }
.sv-included li {
  display: grid;
  grid-template-columns: 8px 1fr;
  gap: 13px;
  align-items: start;
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-soft);
}
.sv-included li::before { content: ""; width: 8px; height: 8px; margin-top: 7px; background: var(--accent); }

.sv-meta { display: grid; gap: 22px; align-content: start; }
.sv-meta-value { margin-top: 9px; font-size: 15px; line-height: 1.55; color: var(--text-soft); }

.sv-pane-cta { margin-top: 34px; padding-top: 28px; border-top: 1px solid var(--rule); gap: 12px; }
.sv-pane-cta .sv-btn { padding: 15px 26px; }

/* ---- 4. NFPA schedule ---- */

.sv-schedule { background: var(--bg-raised); border-top: 1px solid var(--rule-soft); }
.sv-tiers { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--rule); margin-top: 28px; }
.sv-tier { background: var(--bg-raised); padding: 28px 24px 30px; }
.sv-tier-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.sv-tier-every { font-family: "Barlow Condensed", sans-serif; font-weight: 700; font-size: 34px; line-height: 1; text-transform: uppercase; }
.sv-tier-by {
  padding: 5px 10px;
  border: 1px solid rgba(var(--ink-rgb), 0.24);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
.sv-tier-title { margin-top: 16px; font-weight: 600; font-size: 20px; letter-spacing: 0.03em; }
.sv-tier-desc { margin-top: 10px; font-size: 15px; line-height: 1.55; color: var(--text-muted); }
.sv-footnote { margin-top: 22px; max-width: 88ch; font-size: 15px; line-height: 1.6; color: var(--text-faint); }

/* ---- 5. how it works + properties ---- */

.sv-split { display: grid; grid-template-columns: 0.9fr 1.1fr; border-top: 1px solid var(--rule-soft); }
.sv-split-left { padding: 76px 48px 76px var(--gutter); border-right: 1px solid var(--rule-soft); }
.sv-split-right { padding: 76px var(--gutter) 76px 48px; }
.sv-split .sv-h2 { margin-bottom: 34px; }

.sv-steps { display: grid; gap: 1px; background: var(--rule); }
.sv-steps li { display: grid; grid-template-columns: 44px 1fr; gap: 20px; padding: 24px 0; background: var(--bg); }
.sv-step-n { font-family: "Barlow Condensed", sans-serif; font-weight: 700; font-size: 30px; color: var(--accent-text); }
.sv-step-title {
  display: block;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 21px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.sv-step-desc { display: block; margin-top: 8px; font-size: 16px; line-height: 1.55; color: var(--text-muted); }

.sv-chips { display: flex; flex-wrap: wrap; gap: 10px; }
.sv-chips li {
  padding: 11px 16px;
  border: 1px solid rgba(var(--ink-rgb), 0.2);
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 500;
  font-size: 17px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.sv-split-close { margin-top: 26px; max-width: 52ch; font-size: 16px; color: var(--text-muted); }

/* ---- 6. FAQ ---- */

.sv-faq { display: grid; grid-template-columns: 0.7fr 1.3fr; gap: 56px; align-items: start; border-top: 1px solid var(--rule-soft); }
.sv-faq-support { margin-top: 18px; max-width: 34ch; font-size: 16px; color: var(--text-muted); }

.sv-faq-list { display: grid; gap: 1px; background: var(--rule); border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.sv-faq-row { background: var(--bg); }
.sv-faq-row h3 { margin: 0; }

.sv-faq-q {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  width: 100%;
  padding: 22px 0;
  background: none;
  border: 0;
  color: inherit;
  text-align: left;
  cursor: pointer;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 22px;
  letter-spacing: 0.02em;
  line-height: 1.15;
  text-transform: uppercase;
}

.sv-faq-icon {
  flex: none;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(var(--ink-rgb), 0.28);
  color: var(--text-muted);
  font-size: 18px;
  line-height: 1;
  transition: transform .18s, background .18s, color .18s, border-color .18s;
}
.sv-faq-q[aria-expanded="true"] .sv-faq-icon {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  transform: rotate(45deg);
}

.sv-faq-a { padding: 0 0 26px; max-width: 74ch; font-size: 16px; line-height: 1.6; color: var(--text-muted); text-wrap: pretty; }

/* ---- 7. CTA band ---- */

.sv-band {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 48px;
  padding: 60px var(--gutter);
  background: var(--band-bg);
  color: var(--band-ink);
}
.sv-band-title { font-weight: 700; font-size: clamp(30px, 3.4vw, 46px); line-height: 1.02; }
.sv-band-body { margin-top: 14px; max-width: 58ch; font-size: 18px; line-height: 1.55; color: var(--band-ink-soft); }
.sv-band .sv-btns { margin-top: 0; gap: 12px; }
.sv-band .sv-btn { padding: 16px 28px; }

/* ---- focus ---- */

.sv-page :focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ---- responsive: the handoff is desktop-only and marks this as guidance ---- */

@media (max-width: 1100px) {
  .sv-tabs { grid-template-columns: repeat(3, 1fr); }
  .sv-tiers { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 900px) {
  .sv-page { --gutter: 20px; }
  .sv-section { padding: 56px var(--gutter); }
  #services { padding: 48px var(--gutter) 64px; }

  .sv-hero { grid-template-columns: 1fr; }
  .sv-hero-media { order: -1; min-height: 42vh; border-left: 0; border-bottom: 1px solid var(--rule-soft); }
  .sv-hero-copy { padding: 48px var(--gutter); }
  .sv-trust { grid-template-columns: repeat(2, 1fr); margin-top: 40px; }
  .sv-trust li { padding: 16px 14px 16px 0; }

  .sv-intents { grid-template-columns: 1fr; }
  .sv-pane { grid-template-columns: 1fr; }
  .sv-pane-media { min-height: 280px; }
  .sv-pane-body { padding: 28px var(--gutter) 32px; border-left: 0; border-top: 1px solid var(--rule); }
  .sv-pane-grid { grid-template-columns: 1fr; gap: 26px; }

  .sv-split { grid-template-columns: 1fr; }
  .sv-split-left { padding: 56px var(--gutter); border-right: 0; border-bottom: 1px solid var(--rule-soft); }
  .sv-split-right { padding: 56px var(--gutter); }

  .sv-faq { grid-template-columns: 1fr; gap: 32px; }
  .sv-band { grid-template-columns: 1fr; gap: 28px; padding: 48px var(--gutter); }
  .sv-band .sv-btn { width: 100%; text-align: center; }
}

@media (max-width: 620px) {
  /* Tabs become a horizontal strip rather than shrinking to unreadable. */
  .sv-tabs {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
  }
  .sv-tab { flex: 0 0 60%; scroll-snap-align: start; }
  .sv-tiers { grid-template-columns: 1fr; }
  .sv-btns .sv-btn { width: 100%; text-align: center; }
  .sv-head { gap: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .sv-page * { transition: none !important; }
}

/* ---- services hub ---- */

/*
 * Guarded by min-width. This rule used to sit unguarded after the mobile
 * media query, so it re-applied two columns at every width and the hub hero
 * never collapsed on a phone -- measured at 412px wide it was still splitting
 * into a 216px image column and a 196px text column, which is why the copy
 * came out one or two words per line.
 */
@media (min-width: 901px) {
  .hub-hero { grid-template-columns: 1.05fr 0.95fr; }
}
.hub-hero .sv-hero-copy { padding-bottom: 72px; }

/*
 * The banner is a 3:1 panorama. The line pages use a 600px-tall image well,
 * which would crop it to roughly 1:1 and throw away most of the scene, so the
 * hub well is short and wide instead — the crop then costs about a quarter of
 * the width rather than three quarters of it.
 */
/*
 * The banner is 3:1 (2172x724). Without an aspect-ratio the well stretched to
 * whatever height the hero copy happened to be -- measured at 601x466, a 1.29:1
 * box, so object-fit: cover was throwing away 57% of the artwork.
 *
 * Pinning the well to the banner's own ratio means cover crops nothing at all,
 * and align-self stops the grid stretching it back out. This was previously set
 * only inside the mobile media query, which is why the desktop view was wrong.
 */
.hub-hero-media {
  min-height: 0;
  align-self: center;
  aspect-ratio: 2172 / 724;
}
.hub-hero-media img { object-position: center; }

.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 1px;
  background: var(--rule);
  margin-top: 28px;
}

.hub-card {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: inherit;
  transition: background .15s;
}
a.hub-card:hover { background: var(--bg-hover); }
/* A line with no page yet must not look clickable. */
.hub-card-static { cursor: default; }

/*
 * These four images are not the same shape -- three are 3:1 banners and one is
 * a 2:3 portrait. A fixed 190px well cropped every one of them differently, and
 * badly: the portrait lost about 58% of its height.
 *
 * All four are now 3:1 banners, so the well matches them exactly and contain
 * crops nothing while still filling the panel edge to edge.
 *
 * It was briefly 2:1: the extinguisher card had only a 2:3 portrait, which at
 * 3:1 contained down to 52x79 -- a postage stamp, worse than the crop it
 * replaced. Keep contain rather than cover, so a future image of a different
 * shape degrades to letterboxing instead of losing half its subject.
 */
.hub-card-media {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 1;
  padding: 10px;
  overflow: hidden;
  background: var(--bg-image);
}
.hub-card-media img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 100%;
  object-fit: contain;
  filter: var(--img-filter);
  transition: filter .2s;
}
.hub-card-static .hub-card-media img { filter: var(--img-muted); opacity: 0.72; }
.hub-card-media-empty { aspect-ratio: 3 / 1; background: var(--bg-image); }

.hub-card-body { display: flex; flex-direction: column; gap: 10px; padding: 24px 22px 26px; flex: 1; }

.hub-card-index {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  color: var(--text-faint);
}

.hub-card-name {
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 700;
  font-size: 26px;
  line-height: 1.05;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.hub-card-blurb { font-size: 15px; line-height: 1.55; color: var(--text-muted); }

.hub-card-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.hub-card-tags span {
  padding: 4px 9px;
  border: 1px solid rgba(var(--ink-rgb), 0.2);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.hub-card-more {
  margin-top: auto;
  padding-top: 6px;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.hub-card-more[data-status="soon"] { color: var(--text-faint); }

/* Planned lines still show their artwork, dimmed so they do not read as live. */
.hub-card-static .hub-card-media img { filter: var(--img-muted); }

/*
 * Wide media panes.
 *
 * The default pane puts the photo in a tall left column, which suits portrait
 * artwork. Where a line's photography is 3:1 banners, that column would crop
 * away most of each scene, so the pane stacks instead: image full width at its
 * own ratio, copy beneath.
 */
.sv-pane-wide { grid-template-columns: 1fr; }

.sv-pane-wide .sv-pane-media {
  min-height: 0;
  aspect-ratio: 2172 / 724;
}

.sv-pane-wide .sv-pane-body {
  border-left: 0;
  border-top: 1px solid var(--rule);
}

/* Two columns of copy under a full-width image, rather than one long measure. */
@media (min-width: 901px) {
  .sv-pane-wide .sv-pane-grid { grid-template-columns: 1.15fr 1fr; }
}

/* Footer legal link. Every page should offer a route to the privacy policy. */
.sv-legal { margin-top: 10px; font-size: 14px; }
.sv-legal a { color: var(--text-faint); text-decoration: underline; }
.sv-legal a:hover { color: var(--accent-text); }

/* ---- back to top ---- */

/*
 * Injected by to-top.js, so it does not exist without JavaScript. Hidden with
 * visibility + opacity rather than display so it can fade, and pointer-events
 * is switched off while hidden or it would swallow clicks on whatever sits
 * underneath it.
 */
.sv-totop {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  background: var(--bg-raised);
  border: 1px solid var(--border-idle);
  color: var(--ink);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity .2s ease, transform .2s ease, visibility .2s,
              background .15s, border-color .15s, color .15s;
  pointer-events: none;
}
.sv-totop.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
}
.sv-totop:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

@media (prefers-reduced-motion: reduce) {
  .sv-totop { transition: opacity .2s ease, visibility .2s; transform: none; }
}

@media (max-width: 900px) {
  .sv-totop { right: 16px; bottom: 16px; width: 46px; height: 46px; }
}

/* ---- credentials and hours ---- */

/*
 * Both blocks render only when content/company.json holds real facts. Until
 * then the markup is not emitted at all, so there is no empty heading and no
 * placeholder for anyone to mistake for a claim.
 */
.cred {
  padding: 34px var(--gutter) 38px;
  border-top: 1px solid var(--rule-soft);
}
.cred-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.cred-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 18px;
  border: 1px solid var(--rule);
}
.cred-label {
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.cred-detail {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.cred-insurance {
  margin: 18px 0 0;
  max-width: 68ch;
  font-size: 0.95rem;
  color: var(--text-muted);
}

.hours {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1px;
  background: var(--rule);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.hours-block { padding: 26px var(--gutter) 28px; background: var(--bg); }
.hours-emergency { background: var(--bg-raised); }
.hours-note { margin: 10px 0 0; font-size: 1.05rem; }
.hours-headline {
  margin: 10px 0 0;
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 700;
  font-size: 1.35rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.hours-body { margin: 8px 0 0; max-width: 60ch; font-size: 0.97rem; line-height: 1.6; color: var(--text-muted); }

/* Footer link row. The nav is already six items; a seventh crowds a phone, so
   About and Service areas live here — they still need internal links to be
   found at all. */
.sv-footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-top: 14px;
}
.sv-footer-nav a {
  font-family: "Barlow Condensed", sans-serif;
  font-weight: 600;
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.sv-footer-nav a:hover { color: var(--accent-text); }

/* =========================================================================
   SCROLL REVEAL
   =========================================================================
   SAFE BY CONSTRUCTION: this animates transform and nothing else.

   The first version faded opacity 0 -> 1. On the deployed page I measured
   sections that carried the reveal class, with both rules applying, still
   computing to opacity 0 -- the transition frozen at its start value. The
   child stagger was worse: an animation with backwards fill pins elements at
   their `from` state until it runs.

   Either one leaves a blank page if the browser never advances it, and no
   entrance effect is worth that on a site whose job is to be reachable.

   A stuck transform costs twelve pixels of position, which nobody will ever
   report. A stuck opacity costs the page. So there is no opacity here and no
   child stagger, and content is visible at every moment regardless of what
   does or does not run.

   Only applies once reveal.js sets data-reveal, and reveal.js refuses to set
   it at all for anyone who has asked for reduced motion.
   ========================================================================= */

[data-reveal="ready"] .sv-section,
[data-reveal="ready"] .sv-split,
[data-reveal="ready"] .bl-lead,
[data-reveal="ready"] .ar-hero,
[data-reveal="ready"] .ab-hero,
[data-reveal="ready"] [data-reveal-item] {
  transform: translateY(12px);
  transition: transform .55s cubic-bezier(.22, .61, .36, 1);
}
[data-reveal="ready"] .is-in { transform: none; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal="ready"] .sv-section,
  [data-reveal="ready"] .sv-split,
  [data-reveal="ready"] .bl-lead,
  [data-reveal="ready"] .ar-hero,
  [data-reveal="ready"] .ab-hero,
  [data-reveal="ready"] [data-reveal-item] {
    transform: none !important;
    transition: none !important;
  }
}

/*
 * Hero vignette. Applies to whatever artwork is in the panel: darkens the
 * corners so the image reads as a framed cinematic shot rather than a
 * rectangle dropped into a grid cell, and stops a busy photo fighting the
 * headline across the divide.
 *
 * Pure gradient over the existing scrim, no motion, nothing that can fail.
 */
.sv-hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 100% at 50% 45%, rgba(var(--bg-rgb), 0) 42%, rgba(var(--bg-rgb), 0.55) 100%),
    linear-gradient(90deg, rgba(var(--bg-rgb), 0.55) 0%, rgba(var(--bg-rgb), 0) 22%);
}

/* =========================================================================
   FILM GRAIN
   =========================================================================
   The artwork on this site is inked and heavily grained. The page around it
   was perfectly clean, so every image read as a photograph pasted onto a
   spreadsheet. This lays the same grain over the whole surface so the page and
   the pictures belong to each other.

   Generated by SVG feTurbulence rather than a bitmap: about 250 bytes inline
   instead of the 23KB a base64 noise tile costs on every page, and it is
   resolution independent so it does not soften on a retina screen.

   Fixed to the viewport, not the document, so it does not swim while you
   scroll. It lives on ::after with pointer-events: none, so it cannot
   intercept a click and never reaches a screen reader.
   ========================================================================= */

.sv-page::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  /* Barely there. At 0.05 it reads as texture; much past 0.08 it reads as a
     dirty screen. */
  opacity: 0.05;
  mix-blend-mode: overlay;
}

/* Light mode needs less: grain over an off-white ground shows far more
   readily than over near-black. */
:root[data-theme="light"] .sv-page::after { opacity: 0.03; }

/* =========================================================================
   EYE CANDY
   =========================================================================
   Decoration only. Nothing below carries content and nothing below can hide
   any — every effect is a pseudo-element, a filter, or a few pixels of
   transform. If none of it runs, the page is exactly the page.
   ========================================================================= */

/* ---- scroll progress ---- */

/*
 * Added by effects.js only on pages long enough to warrant it. scaleX from the
 * left edge, so the browser animates a transform rather than reflowing a width
 * sixty times a second.
 */
.sv-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 40;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}

/* ---- hero spotlight ---- */

/*
 * A soft light following the pointer across a large image panel. The pictures
 * on this site are lit scenes — a torch beam, a doorway, a strip light — so a
 * moving highlight suits them.
 *
 * effects.js adds .has-spotlight to every big media well: the homepage and
 * service hero, the blog lead, the article image, the About portrait and the
 * store photograph. It was bound to .sv-hero-media alone, so the blog — which
 * uses .bl-lead-media and .bl-post-media — had nothing.
 *
 * position: relative comes with the class rather than being assumed, because
 * three of those five panels did not have it.
 *
 * --on is flipped by effects.js on enter and leave, and the opacity transition
 * makes it fade rather than snap. The custom properties have defaults here so
 * the rule is valid before any pointer has moved.
 */
.has-spotlight { position: relative; }
.has-spotlight::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
    22rem 22rem at var(--mx, 50%) var(--my, 50%),
    rgba(var(--ink-rgb), 0.16) 0%,
    rgba(var(--ink-rgb), 0.06) 35%,
    transparent 70%
  );
  opacity: var(--on, 0);
  transition: opacity .35s ease;
}

/* ---- card hover ---- */

/*
 * The service cards had a background swap and nothing else. These lift a
 * couple of pixels, draw an accent edge along the top, and push into the
 * image — three cheap signals that beat one.
 *
 * translateY on a card is safe: if the transition never runs the card simply
 * sits where it always was.
 */
.hm-line,
.hub-card,
.ar-card,
.bl-card {
  position: relative;
  transition: background .18s ease, transform .18s ease;
}
.hm-line::after,
.hub-card::after,
.ar-card::after,
.bl-card::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .28s cubic-bezier(.22, .61, .36, 1);
}
.hm-line:hover,
.hub-card:hover,
.ar-card:hover,
.bl-card:hover { transform: translateY(-2px); }

.hm-line:hover::after,
.hub-card:hover::after,
.ar-card:hover::after,
.bl-card:hover::after { transform: scaleX(1); }

/* Image push on the cards that carry one. */
.hm-line-media img,
.hub-card-media img { transition: transform .5s cubic-bezier(.22, .61, .36, 1), filter .3s ease; }
.hm-line:hover .hm-line-media img,
.hub-card:hover .hub-card-media img { transform: scale(1.04); }

/* ---- button sheen ---- */

/*
 * A highlight that crosses the primary button on hover. Clipped by
 * overflow: hidden on the button itself, so it cannot escape onto the page.
 */
.sv-btn-primary { position: relative; overflow: hidden; }
.sv-btn-primary::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -60%;
  width: 45%;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.22), transparent);
  transform: skewX(-18deg);
  transition: left .55s cubic-bezier(.22, .61, .36, 1);
}
.sv-btn-primary:hover::after { left: 120%; }

/* ---- eyebrow rule ---- */

/* The bar beside a section eyebrow grows in when its section arrives. Purely
   a rule, never text, so a stuck scaleX(0) costs a decorative line. */
[data-reveal="ready"] .sv-eyebrow-bar::before {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .5s cubic-bezier(.22, .61, .36, 1) .1s;
}
[data-reveal="ready"] .is-in .sv-eyebrow-bar::before,
[data-reveal="ready"] .sv-eyebrow-bar.is-in::before { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .hm-line,
  .hub-card,
  .ar-card,
  .bl-card,
  .hm-line-media img,
  .hub-card-media img,
  .sv-btn-primary::after,
  .has-spotlight { position: relative; }
.has-spotlight::before { transition: none !important; }
  .hm-line:hover,
  .hub-card:hover,
  .ar-card:hover,
  .bl-card:hover { transform: none; }
  [data-reveal="ready"] .sv-eyebrow-bar::before { transform: none !important; transition: none !important; }
}
