@charset "UTF-8";
/* ==========================================================================
   RUSH WEBWORKS  |  site.css  |  reusable design system, v2

   Static CSS only. No build step, no preprocessor, no framework, no CDN.
   Fonts load via <link> in the head partial, never with @import here.

   HOW TO USE THIS FILE ON A NEW CLIENT
     1. Edit section 00 (BRAND). That is the whole reskin.
     2. Swap the logo SVG in the header and footer partials.
     3. Do not touch anything below section 00 to change how the site LOOKS.
        Sections 01 to 23 are the bones. They read section 00 and nothing else.

   TABLE OF CONTENTS
     00  BRAND            <- the only block you edit per client
     01  System tokens    (scale, motion, containers, derived from 00)
     02  Surface contexts (how a block knows what is legible on it)
     03  Reset
     04  Base typography
     05  Utilities
     06  Layout: wrap / section / grid
     07  Devices: slash-rule / eyebrow / chip / stat / credential strip
     08  Buttons
     09  Skip link
     10  Header, brand, nav, mobile drawer
     11  Hero
     12  Cards, services, split
     13  Quote band, CTA band
     14  Work grid, portfolio filter
     15  Gallery, lightbox
     16  Pillars, benefits, positions
     17  Forms
     18  Contact, breadcrumb, prose, error page
     19  Sticky mobile call bar
     20  Footer
     21  Reveal animation
     22  prefers-reduced-motion
     23  Print

   CONTRAST RULE, ENFORCED STRUCTURALLY
     A bright accent on white paper can be as low as 1.4:1. It is NEVER used
     for text on a light surface. Components that want an accent text colour
     use --accent-text, which resolves to --accent-on-light (the ink) on light
     surfaces and --accent-on-dark (the accent) on dark ones. Do not hard-code
     var(--accent) as a text colour anywhere below section 00.
   ========================================================================== */


/* ==========================================================================
   ==========================================================================
   00  BRAND
   ==========================================================================

   ###  EDIT ONLY THIS BLOCK TO RESKIN A SITE.  ###

   Everything under here is plumbing. If you find yourself editing a
   component to change a colour, a corner, an angle or a divider, stop:
   the token you want is in this block. Add one if it genuinely is not.

   ==========================================================================
   ========================================================================== */

:root {

  /* ------------------------------------------------------------------
     MIKE'S LIGHTS  |  Holiday Lighting Services  |  Vancouver, WA

     Every colour below was sampled off Mike's own flier (2026-10-05):
       logo red      #DD0A0F   banner red   #B90308
       night panel   #040D13   sky blue     #0F4A9A
       bulbs         red #DC251E  green #01DD5B  yellow #FDE442  blue #028EE8
       window glow   #FCC65D
     The flier is white brush lettering and red blocks on a night sky,
     with a strand of colour bulbs through the logo. The site keeps that:
     red is the FILL, warm bulb gold is the accent TEXT on dark, and the
     five bulb colours are the signature (group F).

     Recipe log for BRAND.md section 4:
       Anton + Figtree (Permanent Marker for the brush accents), skew -5,
       radius 8, LIGHTS divider (new, defined below), hero = drawn night
       scene with a live strand, no photo slideshow.
     ------------------------------------------------------------------ */

  /* A.  COLOUR ROLES */
  --accent:         #C90A10;   /* flier red, deep enough for white text: 5.9:1 */
  --accent-bright:  #E3141A;   /* hover. White on it is 4.8:1 */
  --accent-deep:    #8E0509;   /* borders, pressed */

  --ink:            #07111F;   /* the flier's night */
  --ink-2:          #0E1C30;
  --ink-3:          #1C2E49;

  --paper:          #FFFFFF;
  --paper-2:        #F1F5FA;   /* a cold snow tint, not a warm grey */
  --paper-3:        #DAE3EE;
  --paper-4:        #C0CCDB;

  --text-dark:      #07111F;
  --text-mid:       #47556B;   /* 7.6:1 on paper, 6.9:1 on paper-2 */
  --text-light:     #FFFFFF;
  --text-light-mid: #B4C2D6;   /* 10.5:1 on ink */

  /* The accent is a DARK red, so text on it is the paper, not the ink. */
  --on-accent:      var(--paper);
  --on-accent-mid:  #FFE1E1;   /* 4.8:1 on the accent */

  /* The accent is already red, so errors go darker and colder, and the
     error wording on every field is explicit. */
  --danger:         #7A1620;
  --danger-soft:    #F7E4E6;

  --ink-rgb:        7 17 31;
  --paper-rgb:      255 255 255;
  --accent-rgb:     201 10 16;


  /* B.  THE TWO CONTRAST GUARDS */
  --accent-on-light: var(--accent);   /* 5.9:1 on paper, 5.4:1 on paper-2 */
  /* Red on the night ink is 3.2:1, so accent TEXT on dark is the warm
     glow of a lit bulb instead. 13.3:1 on ink. */
  --accent-on-dark:  #FFD36E;


  /* C.  TYPE
     Anton is the condensed poster face off the flier's service list.
     Figtree is the body. Permanent Marker (group F) is the brush hand
     used for the wordmark and one hero phrase, never for running text. */
  --font-display: "Anton", "Arial Narrow", "Haettenschweiler", Impact, sans-serif;
  --font-body:    "Figtree", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;

  --display-weight:   400;
  --display-case:     uppercase;
  --display-tracking: .02em;

  --label-case:     uppercase;
  --label-tracking: .2em;


  /* D.  PERSONALITY */
  --skew:      -5deg;
  --unskew:    calc(var(--skew) * -1);
  --band-tilt: calc(var(--skew) * .14);

  --radius: 8px;

  --btn-radius:   var(--r-sm);
  --btn-height:   52px;
  --btn-pad-x:    1.6rem;
  --btn-font:     var(--font-display);
  --btn-case:     uppercase;
  --btn-tracking: .07em;

  --chip-radius: var(--r-sm);

  /* --- The LIGHTS divider recipe --------------------------------------
     This client's own divider, defined here so nothing below the fence
     changes. The short in-heading mark is five round bulbs on a wire:
     the clip-path cuts the shape, the texture paints one bulb colour per
     bulb, the sheen puts a highlight on each. It is drawn for the 168px
     mark at --rule-height: 14px, where each bulb is a true circle.
     The long form is a thin wire with colour dashes; the real full-width
     dividers on this site are the SVG strands (section 24). The recipes
     in section 07 (brush, solid, notch) are untouched. */
  --rule-lights: polygon(
      0% 50%,  .5% 32%,  99.5% 32%, 100% 50%,  99.5% 68%,  .5% 68%
  );
  --rule-lights-short: polygon(
    1.84% 42.1%, 2.04% 32.9%, 3.01% 14.6%, 4.53% 3.0%, 5.95% 0.0%, 7.38% 3.0%,
    8.90% 14.6%, 9.87% 32.9%, 10.07% 42.1%, 23.86% 42.1%, 24.06% 32.9%, 25.03% 14.6%,
    26.55% 3.0%, 27.98% 0.0%, 29.40% 3.0%, 30.92% 14.6%, 31.89% 32.9%, 32.09% 42.1%,
    45.89% 42.1%, 46.08% 32.9%, 47.05% 14.6%, 48.57% 3.0%, 50.00% 0.0%, 51.43% 3.0%,
    52.95% 14.6%, 53.92% 32.9%, 54.11% 42.1%, 67.91% 42.1%, 68.11% 32.9%, 69.08% 14.6%,
    70.60% 3.0%, 72.02% 0.0%, 73.45% 3.0%, 74.97% 14.6%, 75.94% 32.9%, 76.14% 42.1%,
    89.93% 42.1%, 90.13% 32.9%, 91.10% 14.6%, 92.62% 3.0%, 94.05% 0.0%, 95.47% 3.0%,
    96.99% 14.6%, 97.96% 32.9%, 98.16% 42.1%, 98.16% 57.9%, 97.96% 67.1%, 96.99% 85.4%,
    95.47% 97.0%, 94.05% 100.0%, 92.62% 97.0%, 91.10% 85.4%, 90.13% 67.1%, 89.93% 57.9%,
    76.14% 57.9%, 75.94% 67.1%, 74.97% 85.4%, 73.45% 97.0%, 72.02% 100.0%, 70.60% 97.0%,
    69.08% 85.4%, 68.11% 67.1%, 67.91% 57.9%, 54.11% 57.9%, 53.92% 67.1%, 52.95% 85.4%,
    51.43% 97.0%, 50.00% 100.0%, 48.57% 97.0%, 47.05% 85.4%, 46.08% 67.1%, 45.89% 57.9%,
    32.09% 57.9%, 31.89% 67.1%, 30.92% 85.4%, 29.40% 97.0%, 27.98% 100.0%, 26.55% 97.0%,
    25.03% 85.4%, 24.06% 67.1%, 23.86% 57.9%, 10.07% 57.9%, 9.87% 67.1%, 8.90% 85.4%,
    7.38% 97.0%, 5.95% 100.0%, 4.53% 97.0%, 3.01% 85.4%, 2.04% 67.1%, 1.84% 57.9%
  );
  --rule-lights-texture: linear-gradient(90deg,
    var(--wire) 0.00% 1.43%,
    var(--bulb-red) 1.43% 10.48%,
    var(--wire) 10.48% 23.45%,
    var(--bulb-green) 23.45% 32.50%,
    var(--wire) 32.50% 45.48%,
    var(--bulb-gold) 45.48% 54.52%,
    var(--wire) 54.52% 67.50%,
    var(--bulb-blue) 67.50% 76.55%,
    var(--wire) 76.55% 89.52%,
    var(--bulb-orange) 89.52% 98.57%,
    var(--wire) 98.57% 100%);
  --rule-lights-sheen:
    radial-gradient(circle 2.4px at 4.64% 30%, rgb(255 255 255 / .8) 0 60%, rgb(255 255 255 / 0) 100%),
    radial-gradient(circle 2.4px at 26.67% 30%, rgb(255 255 255 / .8) 0 60%, rgb(255 255 255 / 0) 100%),
    radial-gradient(circle 2.4px at 48.69% 30%, rgb(255 255 255 / .8) 0 60%, rgb(255 255 255 / 0) 100%),
    radial-gradient(circle 2.4px at 70.71% 30%, rgb(255 255 255 / .8) 0 60%, rgb(255 255 255 / 0) 100%),
    radial-gradient(circle 2.4px at 92.74% 30%, rgb(255 255 255 / .8) 0 60%, rgb(255 255 255 / 0) 100%);

  --rule-style:       var(--rule-lights);
  --rule-style-short: var(--rule-lights-short);
  --rule-texture:     var(--rule-lights-texture);
  --rule-sheen:       var(--rule-lights-sheen);

  --rule-height: 14px;

  --shadow-depth: 1.1;
  --section-rhythm: 1.05;
  --border-weight: 2px;

  --wrap:        1220px;
  --wrap-narrow: 800px;


  /* E.  TWO STRINGS AND ONE DATA URI */
  /* PLACEHOLDER origin until Mike picks a domain. Keep in step with
     ORIGIN in _build.py. */
  --print-origin: "https://mikeslightsvancouver.com";

  --select-caret: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2307111F' stroke-width='2' stroke-linecap='square'/%3E%3C/svg%3E");


  /* F.  THE LIGHTS  (this client's own palette, read by section 24 and by
         the divider recipe above) */
  --bulb-red:    #FF3A2F;
  --bulb-green:  #17D364;
  --bulb-gold:   #FFD42E;
  --bulb-blue:   #1E9BF0;
  --bulb-orange: #FF8A1E;
  --bulb-warm:   #FFDF9E;      /* warm white */
  --bulb-cool:   #E4F3FF;      /* cool white */
  --wire:        #2E7D4F;      /* strand wire, legible on snow and on night */
  --glow:        #FFD36E;      /* the warm text accent on dark */
  --night-1:     #040B16;      /* top of the sky */
  --night-2:     #0A1F45;
  --night-3:     #12407F;      /* the flier's blue, low on the horizon */
  --flag-navy:   #10254B;
  --font-brush:  "Permanent Marker", "Segoe Print", "Bradley Hand", "Comic Sans MS", cursive;
}

/* ==========================================================================
   ###  END OF THE BRAND BLOCK. NOTHING BELOW HERE IS PER-CLIENT.  ###
   ========================================================================== */


/* ==========================================================================
   01  SYSTEM TOKENS
   The scale, the motion, the containers, and everything derived from the
   personality tokens above. These are the bones. Changing them changes the
   system for every client, not one site.
   ========================================================================== */

:root {
  /* ---- Type scale ---- */
  --fs-body:   clamp(1rem, 0.955rem + 0.22vw, 1.0625rem);
  --fs-small:  0.875rem;
  --fs-micro:  0.78rem;
  --fs-lede:   clamp(1.05rem, 0.98rem + 0.5vw, 1.3rem);
  --fs-h1:     clamp(2rem, 1.35rem + 3.1vw, 3.5rem);
  --fs-hero:   clamp(2.1rem, 7.2vw, 5.1rem);
  --fs-h2:     clamp(1.7rem, 1.25rem + 2.1vw, 2.8rem);
  --fs-h3:     clamp(1.25rem, 1.1rem + 0.7vw, 1.6rem);
  --fs-h4:     clamp(1.05rem, 1rem + 0.3vw, 1.25rem);
  --fs-h5:     1rem;
  --fs-h6:     0.9rem;

  --lh-display: 1.02;
  --lh-head:    1.12;
  --lh-body:    1.62;

  /* ---- Spacing scale ---- */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;
  --sp-10: 8rem;

  /* Section padding, scaled by the rhythm personality token. */
  --section-y: calc(clamp(3rem, 7.5vw, 6rem) * var(--section-rhythm));

  /* ---- Radii, derived from --radius ----
     Capped with min() on purpose. A card, a form panel or a media box has
     real content in it: past about 22px the corner starts eating the top
     line of text, and at a pill value the card turns into a dome and clips
     its own title. So --radius drives the scale and the cap keeps any value,
     including a careless 999px, rendering as a finished site.
     Small controls that SHOULD be able to go fully round (buttons, chips)
     point at --r-pill directly through their own tokens in section 00. */
  --r-sm:   min(calc(var(--radius) * .5), 12px);
  --r-md:   min(var(--radius), 22px);
  --r-lg:   min(calc(var(--radius) * 2), 32px);
  --r-pill: 999px;

  /* ---- Accent bars, derived from --border-weight ----
     --accent-bar    card top rules, service card rules, underlines
     --accent-bar-lg form top rule, pillar left rule, footer top rule */
  --accent-bar:    calc(var(--border-weight) * 2);
  --accent-bar-lg: calc(var(--border-weight) * 3);

  /* ---- Shadows, derived from --shadow-depth ----
     At --shadow-depth: 0 the offset, the blur and the alpha all go to
     zero, so the shadows are genuinely gone rather than faintly present. */
  --sh-1: 0 calc(1px * var(--shadow-depth)) calc(2px * var(--shadow-depth)) rgb(var(--ink-rgb) / calc(.08 * var(--shadow-depth))),
          0 calc(2px * var(--shadow-depth)) calc(8px * var(--shadow-depth)) rgb(var(--ink-rgb) / calc(.05 * var(--shadow-depth)));
  --sh-2: 0 calc(6px * var(--shadow-depth)) calc(22px * var(--shadow-depth)) rgb(var(--ink-rgb) / calc(.13 * var(--shadow-depth)));
  --sh-3: 0 calc(16px * var(--shadow-depth)) calc(44px * var(--shadow-depth)) rgb(var(--ink-rgb) / calc(.20 * var(--shadow-depth)));
  --sh-dark: 0 calc(18px * var(--shadow-depth)) calc(48px * var(--shadow-depth)) rgb(0 0 0 / calc(.55 * var(--shadow-depth)));

  /* ---- Containers ----
     --wrap and --wrap-narrow are set in section 00, because the measure is a
     brand decision. Only the gutter is system. */
  --gutter: 20px;

  /* ---- Chrome heights ---- */
  --header-h: 64px;
  --call-bar-h: 58px;

  /* ---- Motion ---- */
  --t-fast: .16s ease;
  --t:      .24s ease;
  --t-slow: .42s cubic-bezier(.16, .84, .44, 1);
}

@media (min-width: 760px) {
  :root { --gutter: 24px; }
}

@media (min-width: 1000px) {
  :root { --header-h: 80px; }
}

@media (min-width: 1200px) {
  :root { --gutter: 32px; }
}


/* ==========================================================================
   02  SURFACE CONTEXTS
   Every block declares what is legible on it. Components read these, so a
   card, a stat or an eyebrow is automatically correct on paper, on ink and
   on an accent fill with no extra classes.

   --accent-text is the contrast guard in action. It is the ONLY way a
   component is allowed to ask for "the accent, as text".
   ========================================================================== */

:root,
.section,
.section-light,
.card,
.prose,
.form {
  --surface:        var(--paper);
  --raised:         var(--paper);
  --on-surface:     var(--text-dark);
  --on-surface-mid: var(--text-mid);
  --accent-text:    var(--accent-on-light);   /* legible accent on THIS surface */
  --hairline:       var(--paper-3);
  --ring:           var(--accent);
  --ring-halo:      var(--ink);
}

.section-light {
  --surface: var(--paper-2);
  --raised:  var(--paper);
}

.header,
.nav-drawer,
.hero,
.section-dark,
.quote-band,
.footer,
.lightbox {
  --surface:        var(--ink);
  --raised:         var(--ink-2);
  --on-surface:     var(--text-light);
  --on-surface-mid: var(--text-light-mid);
  --accent-text:    var(--accent-on-dark);
  --hairline:       var(--ink-3);
  --ring:           var(--accent);
  --ring-halo:      var(--ink);
}

.btn,
.nav-cta,
.cta-band,
.call-bar-primary,
.work-card-label,
.chip.is-solid,
.filter-btn[aria-pressed="true"] {
  --surface:        var(--accent);
  --raised:         var(--accent);
  --on-surface:     var(--on-accent);
  --on-surface-mid: var(--on-accent-mid);
  --accent-text:    var(--on-accent);
  --hairline:       var(--accent-deep);
  --ring:           var(--ink);        /* an accent ring is invisible on accent */
  --ring-halo:      var(--paper);
}


/* ==========================================================================
   03  RESET
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Safety net for the "zero horizontal scroll" promise. `clip` (not `hidden`)
     so it does not create a scroll container and break position:sticky.
     Nothing should actually need it: no component overflows at 320px. */
  overflow-x: clip;
}

/* Scroll lock. It has to sit on the root: html already owns the viewport's
   overflow here, so overflow:hidden on <body> alone would not hold it.
   site.js measures the scrollbar and sets --sbw so hiding it does not shift
   the layout sideways. (No scrollbar-gutter here on purpose: reserving a
   permanent gutter inflates documentElement.scrollWidth and would make an
   honest "zero horizontal overflow" audit read as a 15px overflow.) */
html.is-locked {
  overflow: hidden;
  padding-right: var(--sbw, 0px);
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ul, ol, fieldset, legend {
  margin: 0;
  padding: 0;
}

ul[class], ol[class] { list-style: none; }

img, svg, video, canvas, picture {
  display: block;
  max-width: 100%;
}

img, video { height: auto; }

input, button, textarea, select {
  font: inherit;
  color: inherit;
}

button { background: none; border: 0; }

a { color: inherit; }

table { border-collapse: collapse; }

fieldset { border: 0; min-width: 0; }

[hidden] { display: none !important; }

:target { scroll-margin-top: calc(var(--header-h) + 1.25rem); }

::selection {
  background: var(--accent);
  color: var(--on-accent);
}


/* ==========================================================================
   04  BASE TYPOGRAPHY
   ========================================================================== */

body {
  min-height: 100%;
  background: var(--paper);
  color: var(--text-dark);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Room for the sticky mobile call bar so it never covers the footer. */
  padding-bottom: calc(var(--call-bar-h) + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 721px) {
  body { padding-bottom: 0; }
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  text-transform: var(--display-case);
  letter-spacing: var(--display-tracking);
  line-height: var(--lh-head);
  color: var(--on-surface);
  overflow-wrap: break-word;
  text-wrap: balance;
}

h1 { font-size: var(--fs-h1); line-height: var(--lh-display); }
h2 { font-size: var(--fs-h2); line-height: 1.06; }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }
/* h5 and h6 open up a little past the display tracking, because at these
   sizes the display face gets cramped. Written as an offset from the token so
   they still follow a skin that tightens or loosens the tracking. */
h5 { font-size: var(--fs-h5); letter-spacing: calc(var(--display-tracking) + .03em); }
h6 { font-size: var(--fs-h6); letter-spacing: calc(var(--display-tracking) + .07em); }

p, li, dd, dt, blockquote, figcaption, label, td, th {
  overflow-wrap: break-word;
}

p + p { margin-top: 1em; }

strong, b { font-weight: 700; }

a {
  text-decoration-thickness: 2px;
  text-underline-offset: .18em;
  transition: color var(--t-fast), background-color var(--t-fast), border-color var(--t-fast);
}

/* Focus. An accent ring plus a hard halo, so it is visible on paper AND on
   ink. Surfaces that are themselves accent-filled flip --ring to the ink
   in section 02, because an accent ring on an accent fill is invisible. */
:focus { outline: none; }

:is(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--ring);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--ring-halo);
  border-radius: var(--r-sm);
}

hr {
  border: 0;
  height: 1px;
  background: var(--hairline);
  margin-block: var(--sp-6);
}


/* ==========================================================================
   05  UTILITIES
   ========================================================================== */

/* The slant. Put .skew on a heading or lockup, .unskew on the inner text run
   so the glyphs stay upright inside a skewed shape. At --skew: 0deg both are
   identity transforms and cost nothing. */
.skew   { transform: skewX(var(--skew)); }
.unskew { display: inline-block; transform: skewX(var(--unskew)); }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.text-center { text-align: center; }

.lede {
  font-size: var(--fs-lede);
  line-height: 1.5;
  color: var(--on-surface-mid);
  max-width: 62ch;
}

.muted { color: var(--on-surface-mid); }

/* Aspect-ratio helpers for .card-media, .split-media and .gallery-item */
.ratio-16x9 { --media-ratio: 16 / 9; }
.ratio-3x2  { --media-ratio: 3 / 2; }
.ratio-4x3  { --media-ratio: 4 / 3; }
.ratio-1x1  { --media-ratio: 1 / 1; }
.ratio-3x4  { --media-ratio: 3 / 4; }


/* ==========================================================================
   06  LAYOUT
   ========================================================================== */

.wrap,
.wrap-narrow {
  width: 100%;
  margin-inline: auto;
  /* padding-inline, never margin, so the gutter can never collapse */
  padding-inline: var(--gutter);
}

.wrap        { max-width: var(--wrap); }
.wrap-narrow { max-width: var(--wrap-narrow); }

.section {
  position: relative;
  padding-block: var(--section-y);
  background: var(--surface);
  color: var(--on-surface);
}

.section-dark  { background: var(--ink); color: var(--text-light); }
.section-light { background: var(--paper-2); color: var(--text-dark); }

/* Body copy on a dark band is muted. Written through :where() ON PURPOSE, so
   the whole selector has ZERO specificity and any component that paints its
   own colour wins without needing a patch.
   The trap this avoids: ".section-dark p" is (0,1,1) and beats a bare
   component class like ".eyebrow" or ".pillar-quote" at (0,1,0). A
   <p class="pillar-quote"> inside a dark band would silently compute to the
   muted grey and lose its accent. Do not unwrap these. */
:where(.section-dark, .footer) :where(p, li) { color: var(--on-surface-mid); }

.section-head {
  max-width: 64ch;
  margin-bottom: clamp(1.75rem, 4.5vw, 2.75rem);
}

.section-head > * + * { margin-top: var(--sp-3); }

.section-head.text-center {
  margin-inline: auto;
}

.section-head.text-center .eyebrow,
.section-head.text-center .slash-rule { margin-inline: auto; }

.section-head.text-center .lede { margin-inline: auto; }

/* --- Bare intro columns ---------------------------------------------------
   The reset zeroes every heading and list margin, and the shared components
   (.section-head, .split-body, .prose) are what normally put the rhythm back.
   A short intro column of bare children sitting directly inside .wrap-narrow
   has no such component, so a subhead and its chip row collapse flat against
   the paragraph above (measured gap: 0px). Restore the rhythm for that
   adjacency only. Scoped to DIRECT children, so headings inside .prose or
   .split-body are untouched.
--------------------------------------------------------------------------- */
.wrap-narrow > p + h2 { margin-top: clamp(2.25rem, 5vw, 3rem); }
.wrap-narrow > p + h3 { margin-top: clamp(2rem, 4.5vw, 2.75rem); }
.wrap-narrow > h2 + .chip-row,
.wrap-narrow > h3 + .chip-row { margin-top: var(--sp-4); }

/* --- Grid utilities. One column on phones, widening at the breakpoints. --- */
.grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: 1fr;
}

.grid-tight { gap: var(--sp-3); }
.grid-wide  { gap: clamp(1.5rem, 4vw, 3rem); }

@media (min-width: 560px) {
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 760px) {
  .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}


/* ==========================================================================
   07  DEVICES
   ========================================================================== */

/* --- THE DIVIDER RECIPES ---------------------------------------------------
   Three hand-authored treatments for .slash-rule. Pure CSS, no images.
   DO NOT EDIT THESE. Choose one in section 00, group D, or with a
   data-rule attribute / a .rule-* modifier class (both below).

   Each recipe is four values:
     -long      the clip-path for a full-width rule
     -short     the clip-path for the 168px in-heading accent mark
     -texture   an extra background layer painted over the accent fill
     -sheen     the ::after overlay that keeps it from reading as a flat slab
--------------------------------------------------------------------------- */
:root {

  /* BRUSH. A dry-brush sweep. Tapered at both ends, ragged along both
     edges, and pinched almost shut twice near the trailing end where the
     brush ran out of paint. The striations run along the sweep. */
  --rule-brush: polygon(
     0% 44%,   2% 26%,   7% 11%,  13% 21%,  21%  6%,  31% 16%,  40%  4%,
    50% 14%,  57%  6%,  61% 18%,  63% 44%,  66% 17%,  72%  7%,  79% 19%,
    84%  9%,  87% 41%,  90% 19%,  95% 10%, 100% 40%,
   100% 60%,  95% 90%,  90% 81%,  87% 59%,  84% 91%,  79% 81%,  72% 93%,
    66% 83%,  63% 56%,  61% 82%,  57% 94%,  50% 86%,  40% 96%,  31% 84%,
    21% 94%,  13% 79%,   7% 89%,   2% 74%,   0% 56%
  );
  /* The two dry pinches read as breaks at 168px, so the short mark gets a
     simpler single-stroke sweep with no pinch. */
  --rule-brush-short: polygon(
     0% 40%,   4% 19%,  12%  7%,  22% 20%,  34%  5%,  46% 18%,  58%  4%,
    70% 19%,  82%  7%,  92% 18%, 100% 36%,
   100% 64%,  92% 89%,  82% 78%,  70% 93%,  58% 80%,  46% 95%,  34% 79%,
    22% 92%,  12% 77%,   4% 89%,   0% 60%
  );
  --rule-brush-texture: repeating-linear-gradient(
    2deg,
    rgb(var(--ink-rgb) / 0)   0,
    rgb(var(--ink-rgb) / 0)   4.3px,
    rgb(var(--ink-rgb) / .34) 4.3px,
    rgb(var(--ink-rgb) / .34) 5px,
    rgb(var(--ink-rgb) / 0)   5px,
    rgb(var(--ink-rgb) / 0)   9.6px
  );
  /* The white in the sheens below is a specular highlight on a coloured
     object, not a surface colour, so it stays neutral rather than following
     --paper. It is the one white in this file that is meant to be white. The
     shade at the bottom of the brush sheen DOES follow the ink. */
  --rule-brush-sheen: linear-gradient(
    to bottom,
    rgb(255 255 255 / .32) 0%,
    rgb(255 255 255 / 0)   46%,
    rgb(var(--ink-rgb) / .16) 100%
  );

  /* SOLID. A clean tapered bar. Full height through the middle, narrowing
     to a third of its height at both ends. No texture, one soft highlight.
     Reads engineered. */
  --rule-solid: polygon(
      0% 34%,   6% 0%,  94% 0%, 100% 34%,
    100% 66%,  94% 100%, 6% 100%,   0% 66%
  );
  --rule-solid-short: polygon(
      0% 30%,  10% 0%,  90% 0%, 100% 30%,
    100% 70%,  90% 100%, 10% 100%,  0% 70%
  );
  --rule-solid-texture: none;
  --rule-solid-sheen: linear-gradient(
    to bottom,
    rgb(255 255 255 / .24) 0%,
    rgb(255 255 255 / 0)   62%
  );

  /* NOTCH. A stepped, crenellated bar: square teeth along the top, offset
     square teeth along the bottom. Flat colour, no sheen, no texture.
     Reads architectural and modular. */
  --rule-notch: polygon(
      0%   0%,  14%   0%,  14%  34%,  28%  34%,  28%   0%,  42%   0%,
     42%  34%,  56%  34%,  56%   0%,  70%   0%,  70%  34%,  84%  34%,
     84%   0%, 100%   0%,
    100% 100%,  86% 100%,  86%  66%,  72%  66%,  72% 100%,  58% 100%,
     58%  66%,  44%  66%,  44% 100%,  30% 100%,  30%  66%,  16%  66%,
     16% 100%,   0% 100%
  );
  --rule-notch-short: polygon(
      0%   0%,  26%   0%,  26%  38%,  52%  38%,  52%   0%,  78%   0%,
     78%  38%, 100%  38%,
    100% 100%,  74% 100%,  74%  62%,  48%  62%,  48% 100%,  22% 100%,
     22%  62%,   0%  62%
  );
  --rule-notch-texture: none;
  --rule-notch-sheen: none;
}

/* Per-page override. One attribute on <html> reskins every divider on the
   page without touching a single element's markup. */
[data-rule="brush"] {
  --rule-style:       var(--rule-brush);
  --rule-style-short: var(--rule-brush-short);
  --rule-texture:     var(--rule-brush-texture);
  --rule-sheen:       var(--rule-brush-sheen);
}

[data-rule="solid"] {
  --rule-style:       var(--rule-solid);
  --rule-style-short: var(--rule-solid-short);
  --rule-texture:     var(--rule-solid-texture);
  --rule-sheen:       var(--rule-solid-sheen);
}

[data-rule="notch"] {
  --rule-style:       var(--rule-notch);
  --rule-style-short: var(--rule-notch-short);
  --rule-texture:     var(--rule-notch-texture);
  --rule-sheen:       var(--rule-notch-sheen);
}

/* Per-element override, for the one divider on a page that should differ. */
.slash-rule.rule-brush {
  --rule-style:       var(--rule-brush);
  --rule-style-short: var(--rule-brush-short);
  --rule-texture:     var(--rule-brush-texture);
  --rule-sheen:       var(--rule-brush-sheen);
}

.slash-rule.rule-solid {
  --rule-style:       var(--rule-solid);
  --rule-style-short: var(--rule-solid-short);
  --rule-texture:     var(--rule-solid-texture);
  --rule-sheen:       var(--rule-solid-sheen);
}

.slash-rule.rule-notch {
  --rule-style:       var(--rule-notch);
  --rule-style-short: var(--rule-notch-short);
  --rule-texture:     var(--rule-notch-texture);
  --rule-sheen:       var(--rule-notch-sheen);
}


/* --- .slash-rule -----------------------------------------------------------
   The section divider. An accent band, slanted so its ends cut on a diagonal,
   shaped by whichever recipe is live. Not an <hr> semantically, though it can
   be written on one.
     <span class="slash-rule" aria-hidden="true"></span>
   Inside a .section-head, .hero-inner, .split-body or .card-body it renders
   short automatically (a 168px accent mark). Force either length with
   .is-short or .is-long. At --skew: 0deg it is a level rule, which is the
   correct look for a squared-up skin.
--------------------------------------------------------------------------- */
.slash-rule {
  display: block;
  position: relative;
  width: 100%;
  height: var(--rule-height);
  border: 0;
  margin: 0;
  transform: skewX(var(--skew));
  background: var(--rule-texture), var(--accent);
  clip-path: var(--rule-style);
}

/* A little modelling so the band is not a flat slab of colour. Recipes that
   want a flat band set --rule-sheen to none and this paints nothing. */
.slash-rule::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--rule-sheen);
}

/* Short accent mark. */
.section-head .slash-rule,
.hero-inner .slash-rule,
.split-body .slash-rule,
.card-body .slash-rule,
.slash-rule.is-short {
  width: 168px;
  max-width: 62%;
  clip-path: var(--rule-style-short);
}

/* Force full width back on, inside a context that would shorten it. */
.slash-rule.is-long {
  width: 100%;
  max-width: none;
  clip-path: var(--rule-style);
}

.slash-rule + * { margin-top: var(--sp-4); }

/* Clearance under a heading.
   .slash-rule resets its own margin so it can be dropped anywhere, including
   onto an <hr>. That reset is (0,1,0) and sits later in the file than the
   ".section-head > * + *" rhythm, so it silently wins and the mark ends up
   butting against the line box above it. With an uppercase display face that
   reads as a tight underline and nobody notices. The moment a skin sets
   --display-case: none the headings grow descenders, the headings run a very
   tight line-height (1.02 to 1.12) so the tail falls at the bottom of the
   box, and the tail of a "p" or a "g" lands on the mark. Two of the six
   pairings in section 00 and three of the six recipes in BRAND.md ask for
   sentence case, so this is the common path, not the exotic one.
   The gap is in em of the HEADING, so it scales with the heading rather than
   with body copy: bigger under a hero h1, smaller under a card h3. */
:is(h1, h2, h3, h4):has(+ .slash-rule) { padding-bottom: .2em; }

/* A heavier sweep used to cap a section. */
.slash-rule.is-band { height: calc(var(--rule-height) * 1.45); }


/* --- .eyebrow --------------------------------------------------------------
   Small label above a heading with an accent sweep mark. On dark surfaces the
   text itself is the accent. On light surfaces the text is ink and only the
   mark is accent, because a bright accent on paper is unreadable.
--------------------------------------------------------------------------- */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
  margin: 0 0 var(--sp-3);
  font-family: var(--font-body);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--label-tracking);
  line-height: 1.3;
  text-transform: var(--label-case);
  color: var(--accent-text);
}

.eyebrow::before {
  content: "";
  flex: none;
  width: 1.75rem;
  height: var(--accent-bar);
  background: var(--accent);
  transform: skewX(var(--skew));
}

/* An .eyebrow is usually a <p>. It keeps its accent inside a dark band only
   because the dark-band body rule in section 06 is written through :where()
   and therefore has zero specificity. If you ever unwrap that rule, this
   label goes muted grey on every dark section. */


/* --- .chip : a license or credential pill --------------------------------- */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: 34px;
  padding: .3rem .85rem;
  border: var(--border-weight) solid var(--hairline);
  border-radius: var(--chip-radius);
  background: transparent;
  color: var(--on-surface);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .55);
  text-transform: var(--label-case);
  white-space: nowrap;
  transform: skewX(var(--skew));
}

.chip > * { display: inline-block; transform: skewX(var(--unskew)); }

.section-dark .chip,
.hero .chip,
.footer .chip { border-color: var(--accent); }

.chip.is-solid {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* A .chip used as a LINK (a "services shown in this project" row) needs a real
   44px tap target and must not read as an underlined body-copy link. The pill
   keeps its type size, the control grows to clear the tap-target rule. Plain
   <span class="chip"> credential pills are untouched. The accent is a FILL
   here, never a text colour on a light surface. */
a.chip {
  min-height: 44px;
  color: var(--on-surface);
  text-decoration: none;
  transition: background-color var(--t-fast), border-color var(--t-fast),
              color var(--t-fast);
}

a.chip:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

a.chip:active {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--paper);
}

.section-dark a.chip:hover,
.hero a.chip:hover,
.footer a.chip:hover { border-color: var(--accent); }


/* --- .stat : big accent number over a small caption ----------------------- */
.stat-row {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
}

@media (min-width: 560px) {
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .stat-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.stat {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  min-width: 0;
  padding-left: var(--sp-4);
  border-left: var(--accent-bar) solid var(--accent);
}

.stat-num {
  min-width: 0;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(1.55rem, 1.1rem + 2.2vw, 3rem);
  line-height: 1.02;
  letter-spacing: var(--display-tracking);
  text-transform: var(--display-case);
  color: var(--accent-text);
  transform: skewX(var(--skew));
  transform-origin: left bottom;
  /* A licence number is a long unbreakable token. Let it break rather than
     blow the grid track out and put horizontal scroll on the whole page. */
  overflow-wrap: anywhere;
}

.stat-label {
  min-width: 0;
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: calc(var(--label-tracking) * .35);
  text-transform: var(--label-case);
  color: var(--on-surface-mid);
  overflow-wrap: break-word;
}


/* --- .credential-strip -----------------------------------------------------
   The thin band of trust chips that sits directly under the hero. It is a
   band, not a full .section, so the only thing it needs is a vertical rhythm
   of its own. Carry .section-dark on it for the surface context (which is
   what turns the chip borders accent).
     <div class="credential-strip section-dark"><div class="wrap">...
--------------------------------------------------------------------------- */
.credential-strip {
  padding-block: var(--sp-4);
  border-top: var(--accent-bar) solid var(--accent);
  border-bottom: 1px solid var(--hairline);
}

.credential-strip .chip-row { gap: var(--sp-2) var(--sp-4); }


/* --- .error-code : the oversized numeral on a 404 or 500 page --------------
   Reads the surface tokens, so the numeral is accent on ink and ink on paper,
   and would flip automatically on any other surface. inline-block so the skew
   only shifts the glyph box, never the full column width, which keeps the
   320px overflow audit clean.
--------------------------------------------------------------------------- */
.error-code {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(4.25rem, 20vw, 10rem);
  line-height: .92;
  letter-spacing: var(--display-tracking);
  color: var(--accent-text);
  transform: skewX(var(--skew));
}

.error-help { margin-top: var(--sp-5); }

.error-help a {
  color: var(--accent-text);
  font-weight: 600;
}


/* ==========================================================================
   08  BUTTONS
   The label sits in an inner <span> which is automatically un-skewed, so the
   type stays upright inside a slanted shape. At --skew: 0deg both transforms
   are the identity and the button is a plain rectangle.
   Markup: <a class="btn" href="/contact.html"><span>Request a Bid</span></a>
   ========================================================================== */

.btn,
.nav-cta,
.filter-btn,
.call-bar-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  max-width: 100%;
  min-height: var(--btn-height);
  padding: .7rem var(--btn-pad-x);
  border: var(--border-weight) solid var(--accent);
  border-radius: var(--btn-radius);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--btn-font);
  font-size: clamp(.86rem, .82rem + .16vw, .98rem);
  font-weight: var(--display-weight);
  letter-spacing: var(--btn-tracking);
  line-height: 1.1;
  text-transform: var(--btn-case);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transform: skewX(var(--skew));
  transition: background-color var(--t-fast), border-color var(--t-fast),
              color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}

/* Auto-unskew the label so authors never have to remember .unskew here. */
.btn > *,
.nav-cta > *,
.filter-btn > *,
.call-bar-link > * {
  display: inline-block;
  transform: skewX(var(--unskew));
}

.btn:hover,
.nav-cta:hover {
  background: var(--accent-bright);
  border-color: var(--accent-bright);
  color: var(--on-accent);
}

.btn:active,
.nav-cta:active,
.filter-btn:active,
.call-bar-link:active {
  transform: skewX(var(--skew)) translateY(2px);
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--paper);
  box-shadow: none;
}

/* Outlined variant. Inherits the surface it sits on. */
.btn-ghost {
  background: transparent;
  border-color: var(--on-surface);
  color: var(--on-surface);
  --ring: var(--accent);
  --ring-halo: var(--ink);
}

.section-dark .btn-ghost,
.hero .btn-ghost,
.footer .btn-ghost {
  border-color: var(--accent);
  color: var(--text-light);
}

.btn-ghost:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.btn-ghost:active {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--paper);
}

/* Inside an accent band an accent button is invisible. Flip to ink. */
.cta-band .btn,
.call-bar-primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--accent-on-dark);
}

.cta-band .btn:hover {
  background: var(--ink-2);
  border-color: var(--ink-2);
  color: var(--accent-bright);
}

/* The outlined button inside the accent band. It paints from --on-accent, NOT
   from --ink. --on-accent is the one colour the palette guarantees is legible
   on an accent fill, so this stays readable whether the client's accent is a
   bright amber (--on-accent is the ink, which is what the default renders) or
   a dark navy, forest or burgundy (--on-accent flips to the paper). Hard
   coding var(--ink) here measured 3.1:1 on a mid-blue accent.
   The hover pair is the exact inverse of the normal pair, so it carries the
   identical contrast ratio by construction. */
.cta-band .btn-ghost {
  background: transparent;
  border-color: var(--on-accent);
  color: var(--on-accent);
}

.cta-band .btn-ghost:hover {
  background: var(--on-accent);
  border-color: var(--on-accent);
  color: var(--accent);
}

.btn-lg {
  min-height: calc(var(--btn-height) + 8px);
  padding: .9rem calc(var(--btn-pad-x) + .5rem);
  font-size: clamp(.95rem, .9rem + .25vw, 1.1rem);
}

.btn-block {
  display: flex;
  width: 100%;
}

/* Wrapper for two or more buttons. The extra gap keeps slanted edges apart. */
.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  margin-top: var(--sp-5);
}

.btn-row.text-center { justify-content: center; }


/* ==========================================================================
   09  SKIP LINK
   ========================================================================== */

.skip-link {
  position: absolute;
  top: 0;
  left: var(--gutter);
  z-index: 300;
  transform: translateY(-140%);
  display: inline-flex;
  align-items: center;
  min-height: var(--btn-height);
  padding: .7rem 1.25rem;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .35);
  text-transform: var(--label-case);
  text-decoration: none;
  box-shadow: var(--sh-3);
  --ring: var(--ink);
  --ring-halo: var(--paper);
}

.skip-link:focus-visible {
  transform: translateY(0);
}


/* ==========================================================================
   10  HEADER / BRAND / NAV / DRAWER
   ========================================================================== */

.header {
  position: sticky;
  top: 0;
  z-index: 70;
  color: var(--text-light);
  border-bottom: 1px solid var(--ink-3);
  transition: box-shadow var(--t), border-color var(--t);
}

/* The translucent panel lives on a pseudo-element, NOT on .header itself.
   backdrop-filter makes an element the containing block for its
   position:fixed descendants, which would collapse .nav-drawer into the
   height of the header instead of the viewport. */
.header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgb(var(--ink-rgb) / .88);
  transition: background-color var(--t);
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .header::before {
    -webkit-backdrop-filter: blur(10px) saturate(130%);
    backdrop-filter: blur(10px) saturate(130%);
  }
}

.header.is-scrolled {
  border-bottom-color: var(--accent);
  box-shadow: var(--sh-dark);
}

.header.is-scrolled::before { background: var(--ink); }

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--header-h);
}

/* --- Brand lockup: logo mark + optional two-line wordmark ---
   Swap the inline SVG in the header and footer partials per client. The SVG
   should paint from --badge-ink and --badge-accent rather than hard-coded
   fills, so it follows the palette. --- */
/* flex: 0 1 auto plus min-width: 0 lets flexbox squeeze the brand's BOX when
   the nav is wide, but .wordmark is white-space: nowrap, so without the
   overflow below the text simply spills out of the squeezed box and paints on
   top of the first nav link. Measured on a 1280px laptop with a two word name
   and a three word kicker: 67px of spill, 51px of it sitting on the nav, and
   the page-level overflow audit stays clean the whole time because html has
   overflow-x: clip, so nothing warns you. Clip it here instead. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  min-height: 48px;
  padding-block: .25rem;
  text-decoration: none;
  color: var(--text-light);
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
}

.badge-mark {
  flex: none;
  width: 40px;
  height: 40px;
  /* The inline SVG reads these. Override per surface if ever needed. */
  --badge-ink:    var(--ink);
  --badge-accent: var(--accent);
}

@media (min-width: 1000px) {
  .badge-mark { width: 46px; height: 46px; }
}

.wordmark {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(.92rem, .82rem + .5vw, 1.15rem);
  line-height: 1;
  text-transform: var(--display-case);
  color: var(--text-light);
  transform: skewX(var(--skew));
  transform-origin: left center;
  white-space: nowrap;
}

/* The header has a real capacity limit: a brand lockup, five nav links, a
   phone number and a CTA button on one row. If the lockup runs out of room it
   now truncates rather than overlapping the nav.
   AN ELLIPSIS IN THE HEADER MEANS THE HEADER IS OVER CAPACITY. Fix the cause,
   do not style around it: shorten the name in the lockup, shorten or delete
   the kicker line, drop a nav item, or shorten the CTA label. The tightest
   combinations are an uppercase display face with a large --label-tracking
   and a four word CTA. */
.wordmark-line {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The small kicker line above the name (a region, a trade, a tagline).
   Tracked wider than the name, but off --label-tracking rather than a fixed
   value: a skin that runs tight micro-labels must not leave this one line
   wearing the previous skin's spacing. */
.wordmark-line:first-child {
  font-size: .7em;
  letter-spacing: calc(var(--label-tracking) * 1.1);
  color: var(--text-light-mid);
}

.wordmark-line:last-child {
  font-size: 1em;
  letter-spacing: var(--display-tracking);
}

/* Any single glyph inside the wordmark that should carry the accent. */
.wordmark-accent { color: var(--accent-on-dark); }

/* --- Nav. The same <nav> element is the inline bar on desktop and the
       slide-in drawer on phones, so there is only one copy of the markup. --- */
.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 1.6vw, 1.5rem);
}

.nav-list {
  display: flex;
  align-items: center;
  gap: clamp(.9rem, 1.4vw, 1.6rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  min-width: 44px;          /* short labels still clear the 44px target */
  padding: .25rem 0;
  border-bottom: 3px solid transparent;
  color: var(--text-light);
  font-size: clamp(.78rem, .74rem + .16vw, .86rem);
  font-weight: 600;
  letter-spacing: calc(var(--label-tracking) * .55);
  text-transform: var(--label-case);
  text-decoration: none;
  white-space: nowrap;
}

.nav-link:hover { color: var(--accent-on-dark); border-bottom-color: var(--accent-deep); }

.nav-link[aria-current="page"] {
  color: var(--accent-on-dark);
  border-bottom-color: var(--accent);
}

.nav-phone {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: 48px;
  padding: .25rem .25rem;
  color: var(--text-light);
  font-size: clamp(.85rem, .8rem + .2vw, .95rem);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .22);
  text-decoration: none;
  white-space: nowrap;
}

.nav-phone:hover { color: var(--accent-on-dark); }

.nav-phone svg { width: 16px; height: 16px; flex: none; fill: var(--accent); }

.nav-cta { padding: .6rem 1.15rem; min-height: 46px; }

/* --- Hamburger --- */
.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex: none;
  width: 48px;
  height: 48px;
  padding: 0;
  border: var(--border-weight) solid var(--ink-3);
  border-radius: var(--r-sm);
  background: var(--ink-2);
  color: var(--text-light);
  cursor: pointer;
}

.nav-toggle:hover { border-color: var(--accent); }

.nav-toggle-bar {
  display: block;
  width: 22px;
  height: 2px;
  background: currentColor;
  transition: transform var(--t), opacity var(--t-fast);
}

.nav-toggle[aria-expanded="true"] { border-color: var(--accent); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --- Mobile drawer (below 1000px) ---
   The closed drawer is display:none, not a panel parked off the right edge.
   A parked panel is position:fixed and still counts toward
   documentElement.scrollWidth, which would put a phantom 340px of horizontal
   overflow on every page. It opens with a keyframe instead of a transition
   because you cannot transition out of display:none.                       */
@media (max-width: 999.98px) {
  .nav-drawer {
    display: none;
    position: fixed;
    top: var(--header-h);
    right: 0;
    bottom: 0;
    left: auto;
    z-index: 60;
    width: min(88vw, 340px);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--sp-5) var(--sp-5)
             calc(var(--sp-8) + var(--call-bar-h) + env(safe-area-inset-bottom, 0px));
    background: var(--ink-2);
    border-left: 1px solid var(--ink-3);
    box-shadow: var(--sh-dark);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .nav-drawer.is-open {
    display: flex;
    animation: rw-drawer-in .3s cubic-bezier(.16, .84, .44, 1) both;
  }

  @keyframes rw-drawer-in {
    from { transform: translateX(100%); opacity: .4; }
    to   { transform: none;             opacity: 1; }
  }

  .nav-drawer .nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-bottom: var(--sp-5);
  }

  .nav-drawer .nav-link {
    min-height: 54px;
    padding: .5rem 0;
    border-bottom: 1px solid var(--ink-3);
    font-size: .95rem;
  }

  .nav-drawer .nav-link[aria-current="page"] {
    border-bottom-color: var(--accent);
    padding-left: .6rem;
    box-shadow: inset var(--accent-bar) 0 0 var(--accent);
  }

  .nav-drawer .nav-phone {
    min-height: 54px;
    font-size: 1.15rem;
    margin-bottom: var(--sp-3);
  }

  .nav-drawer .nav-cta { width: 100%; }

  /* Backdrop. Deliberately neutral black rather than tinted ink, so it dims
     the page rather than colouring it. */
  body.is-nav-open::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 58;
    background: rgb(0 0 0 / .58);
  }
}

@media (min-width: 1000px) {
  .nav-toggle { display: none; }
  .nav-drawer { display: flex; animation: none; }
}


/* ==========================================================================
   11  HERO
   ========================================================================== */

.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  /* Pull up under the sticky header, then pad the content back down. */
  margin-top: calc(var(--header-h) * -1);
  padding-top: calc(var(--header-h) + var(--sp-7));
  padding-bottom: clamp(2.5rem, 7vw, 5rem);
  min-height: min(88vh, 780px);
  min-height: min(88svh, 780px);
  background: var(--ink);
  color: var(--text-light);
  overflow: hidden;
}

.hero-slides {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: var(--ink);
}

.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.6s ease;
  will-change: opacity;
}

.hero-slide.is-active { opacity: 1; }

.hero-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
  transform-origin: 54% 46%;
}

/* Ken Burns belongs to the CROSS-FADING slideshow only, where each slide is
   swapped out at the end of its 7.6s run. Scoped to [data-hero] so it can
   never apply to a single static interior hero, which would animate once and
   then sit permanently at scale(1.12), cropping 12% off the photo forever. */
[data-hero] .hero-slide.is-active img {
  animation: rw-kenburns 7600ms ease-out both;
}

@keyframes rw-kenburns {
  from { transform: scale(1)    translate3d(0, 0, 0); }
  to   { transform: scale(1.12) translate3d(-1.1%, -0.9%, 0); }
}

/* Interior pages have one still hero. Give it a short settle-in that ENDS at
   natural scale, so the photo rests uncropped.
   NOTE: data-hero sits on .hero-slides, not on the .hero section, so this is
   scoped to the same element. Writing it as .hero:not([data-hero]) would also
   match the homepage (whose section carries no attribute) and, being more
   specific, would silently steal the slideshow's Ken Burns. */
.hero-slides:not([data-hero]) .hero-slide.is-active img {
  animation: rw-hero-settle 1100ms cubic-bezier(.16, .84, .44, 1) both;
}

@keyframes rw-hero-settle {
  from { transform: scale(1.055); }
  to   { transform: scale(1); }
}

/* Two-part scrim: a corner-to-corner wash plus a hard bottom fade, so the
   headline stays readable over every photo including the brightest one.
   Neutral black on purpose: a tinted scrim would recolour the photography. */
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(to bottom, rgb(0 0 0 / 0) 38%, rgb(0 0 0 / .85) 100%),
    linear-gradient(35deg, rgb(0 0 0 / .72) 0%, rgb(0 0 0 / .52) 46%, rgb(0 0 0 / .25) 100%);
}

.hero-inner {
  position: relative;
  width: 100%;
  max-width: 900px;
}

.hero-inner h1 {
  font-size: var(--fs-hero);
  line-height: .96;
  letter-spacing: var(--display-tracking);
  color: var(--text-light);
  overflow-wrap: break-word;
  text-wrap: balance;
  transform: skewX(var(--skew));
  transform-origin: left bottom;
  text-shadow: 0 3px 22px rgb(0 0 0 / .55);
  margin-bottom: var(--sp-4);
}

/* Slightly softened from pure white so it sits under the headline rather than
   competing with it, but much brighter than --text-light-mid, which is too
   grey to hold up over photography. */
.hero-inner .lede {
  color: rgb(var(--paper-rgb) / .92);
  max-width: 54ch;
  text-shadow: 0 2px 14px rgb(0 0 0 / .6);
}

.hero-inner .chip-row { margin-top: var(--sp-5); }

/* --- Compact interior hero -------------------------------------------------
   The same .hero component, with one still photo instead of a slideshow, so
   it does not need the full 88svh of the homepage. Scoped by the ABSENCE of
   the [data-hero] slideshow container rather than by a new class name, so it
   applies to any interior page that drops a single still into .hero and never
   touches the homepage. A browser without :has() simply keeps the tall hero,
   which still renders correctly.
--------------------------------------------------------------------------- */
.hero:not(:has([data-hero])) {
  min-height: 0;
  padding-bottom: clamp(2rem, 5.5vw, 3.25rem);
}

.hero:not(:has([data-hero])) .hero-inner h1 {
  font-size: var(--fs-h1);
}

.hero-dots {
  position: absolute;
  right: var(--gutter);
  bottom: calc(var(--gutter) * .75);
  z-index: 3;
  display: flex;
  gap: .35rem;
}

/* 44x44 hit area. The visible mark is the smaller ::before bar. */
.hero-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* The inactive marks are the paper colour at low alpha, not a fixed white, so
   an ivory or warm paper carries through here too. They sit over photography,
   which is why they are pale rather than a surface token. */
.hero-dot::before {
  content: "";
  width: 26px;
  height: 5px;
  background: rgb(var(--paper-rgb) / .42);
  transform: skewX(var(--skew));
  transition: background-color var(--t-fast), width var(--t-fast);
}

.hero-dot:hover::before { background: rgb(var(--paper-rgb) / .75); }

.hero-dot[aria-current="true"]::before,
.hero-dot.is-active::before {
  background: var(--accent);
  width: 34px;
}

@media (max-width: 559.98px) {
  .hero-dots { bottom: .25rem; right: .25rem; gap: 0; }
  /* Hit area stays 44x44. Only the visible bar shrinks. */
  .hero-dot::before { width: 17px; }
  .hero-dot[aria-current="true"]::before,
  .hero-dot.is-active::before { width: 24px; }
}


/* ==========================================================================
   12  CARDS / SERVICES / SPLIT
   ========================================================================== */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--raised);
  color: var(--on-surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: transform var(--t), box-shadow var(--t), border-color var(--t);
}

.section-dark .card,
.footer .card { background: var(--ink-2); border-color: var(--ink-3); }

.card-media {
  position: relative;
  display: block;
  aspect-ratio: var(--media-ratio, 4 / 3);
  background: var(--hairline);
  overflow: hidden;
}

.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--t-slow);
}

.card-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  flex: 1 1 auto;
  padding: clamp(1.1rem, 3vw, 1.75rem);
}

/* Styles itself, so it works on a real <h3> inside a card AND on a <span>
   inside a whole-card <a> where a heading would be awkward. */
.card-title {
  display: block;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--display-weight);
  line-height: var(--lh-head);
  letter-spacing: var(--display-tracking);
  text-transform: var(--display-case);
  color: var(--on-surface);
  overflow-wrap: break-word;
}

.card-text {
  color: var(--on-surface-mid);
  margin: 0;
}

.card-link {
  margin-top: auto;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  min-height: 44px;
  min-width: 44px;
  padding-top: .35rem;
  color: var(--accent-text);
  font-weight: 700;
  font-size: var(--fs-small);
  letter-spacing: calc(var(--label-tracking) * .55);
  text-transform: var(--label-case);
  text-decoration: none;
  border-bottom: 3px solid var(--accent);
}

.card-link::after { content: "\2192"; transition: transform var(--t-fast); }
.card-link:hover::after { transform: translateX(4px); }

/* --- Service card: a card with an accent top rule --- */
.service-card { border-top: var(--accent-bar) solid var(--accent); }

.service-card:hover,
.work-card:hover,
a.card:hover {
  transform: translateY(-4px);
  box-shadow: var(--sh-2);
  border-color: var(--accent-deep);
}

.service-card:hover .card-media img,
.work-card:hover .card-media img { transform: scale(1.05); }

/* --- Service list: ticked list of what is included --- */
.service-list {
  display: grid;
  gap: .65rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.service-list > li {
  position: relative;
  padding-left: 1.85rem;
  color: var(--on-surface-mid);
}

.service-list > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .55em;
  width: 13px;
  height: 13px;
  border-radius: var(--r-sm);
  background: var(--accent);
  transform: skewX(var(--skew));
}

.service-list.is-two-col { grid-template-columns: 1fr; }

@media (min-width: 760px) {
  .service-list.is-two-col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- Split: image beside text, stacks on phones --- */
.split {
  display: grid;
  gap: clamp(1.5rem, 4.5vw, 3.25rem);
  grid-template-columns: 1fr;
  align-items: center;
}

@media (min-width: 900px) {
  .split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .split.reverse .split-media { order: 2; }
  .split.reverse .split-body  { order: 1; }
}

.split-media {
  position: relative;
  aspect-ratio: var(--media-ratio, 4 / 3);
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--hairline);
}

.split-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Accent corner flag on split imagery. The negative offset is clipped by the
   parent's overflow, so it reads as a flush tab at --skew: 0deg. Only the
   inner end takes the radius, for the same reason as .work-card-label. */
.split-media::after {
  content: "";
  position: absolute;
  left: -14px;
  bottom: 18px;
  width: 92px;
  height: 12px;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--accent);
  transform: skewX(var(--skew));
}

.split-body { min-width: 0; }
.split-body > * + * { margin-top: var(--sp-4); }


/* ==========================================================================
   13  QUOTE BAND / CTA BAND
   ========================================================================== */

.quote-band {
  position: relative;
  padding-block: calc(clamp(3rem, 8vw, 5.5rem) * var(--section-rhythm));
  background: var(--ink);
  color: var(--text-light);
  overflow: hidden;
}

/* The accent cap. --band-tilt is derived from --skew, so at 0deg this is a
   level rule sitting 4px proud of the top edge, which is deliberate. */
.quote-band::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 10px;
  background: var(--accent);
  transform: skewY(var(--band-tilt)) translateY(-6px);
}

.quote-text {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(1.45rem, 1rem + 2.4vw, 2.6rem);
  line-height: 1.12;
  letter-spacing: var(--display-tracking);
  text-transform: var(--display-case);
  color: var(--text-light);
  transform: skewX(var(--skew));
  transform-origin: left center;
  margin: 0;
}

.quote-text em {
  font-style: normal;
  color: var(--accent-text);
}

.quote-cite {
  display: block;
  margin-top: var(--sp-4);
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-style: normal;
  font-weight: 600;
  letter-spacing: calc(var(--label-tracking) * .65);
  text-transform: var(--label-case);
  color: var(--text-light-mid);
}

/* --- CTA band: the reusable accent "Request a bid" strip --- */
.cta-band {
  position: relative;
  padding-block: calc(clamp(2.5rem, 6.5vw, 4.25rem) * var(--section-rhythm));
  background: var(--accent);
  color: var(--on-accent);
  overflow: hidden;
}

.cta-band::before,
.cta-band::after {
  content: "";
  position: absolute;
  left: -5%;
  width: 110%;
  height: 14px;
  background: var(--ink);
  opacity: .9;
}

.cta-band::before { top: -7px;    transform: skewY(var(--band-tilt)); }
.cta-band::after  { bottom: -7px; transform: skewY(var(--band-tilt)); }

.cta-inner {
  display: grid;
  gap: var(--sp-5);
  align-items: center;
  grid-template-columns: 1fr;
}

@media (min-width: 900px) {
  .cta-inner { grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-7); }
}

.cta-title {
  font-size: clamp(1.6rem, 1.1rem + 2.4vw, 2.6rem);
  line-height: 1.04;
  color: var(--on-accent);
  transform: skewX(var(--skew));
  transform-origin: left bottom;
  margin: 0;
}

.cta-text {
  margin-top: var(--sp-3);
  color: var(--on-accent-mid);
  font-weight: 500;
  max-width: 56ch;
}

.cta-band .btn-row { margin-top: 0; }


/* ==========================================================================
   14  WORK GRID / FILTER
   ========================================================================== */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem .7rem;
  margin-bottom: var(--sp-6);
}

.filter-btn {
  min-height: 46px;
  padding: .55rem 1.1rem;
  background: transparent;
  border-color: var(--hairline);
  color: var(--on-surface);
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .45);
}

.filter-btn:hover {
  border-color: var(--accent-deep);
  background: transparent;
  color: var(--on-surface);
}

.filter-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.filter-count {
  flex: 1 0 100%;
  margin: .25rem 0 0;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--on-surface-mid);
}

.work-grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .work-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.work-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  overflow: hidden;
  text-decoration: none;
  color: var(--on-surface);
  transition: transform var(--t), box-shadow var(--t), border-color var(--t);
}

.work-card .card-media { --media-ratio: 3 / 2; }

/* The corner tag. Its left bleed is clipped by the card's overflow, so at
   --skew: 0deg it reads as a flush-left tab rather than a broken offset.
   Only the two INNER corners take the radius: the outer pair is cut off by
   the card anyway, and rounding one end is what makes it read as a tag. */
.work-card-label {
  position: absolute;
  left: -6px;
  top: 14px;
  z-index: 2;
  padding: .3rem 1rem .3rem 1.2rem;
  border-radius: 0 var(--chip-radius) var(--chip-radius) 0;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-body);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .55);
  text-transform: var(--label-case);
  transform: skewX(var(--skew));
}

.work-card-label > * { display: inline-block; transform: skewX(var(--unskew)); }


/* ==========================================================================
   15  GALLERY / LIGHTBOX
   ========================================================================== */

.gallery {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 560px) {
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .gallery { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.gallery-item {
  margin: 0;
  min-width: 0;
}

.gallery-btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--hairline);
  cursor: zoom-in;
  overflow: hidden;
  position: relative;
  aspect-ratio: var(--media-ratio, 4 / 3);
}

.gallery-btn img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--t-slow), opacity var(--t-fast);
}

.gallery-btn:hover img { transform: scale(1.05); opacity: .92; }

.gallery-btn::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: 44px;
  height: 10px;
  border-radius: var(--r-sm) 0 0 var(--r-sm);
  background: var(--accent);
  transform: skewX(var(--skew)) translateX(6px);
  opacity: 0;
  transition: opacity var(--t-fast);
}

.gallery-btn:hover::after,
.gallery-btn:focus-visible::after { opacity: 1; }

/* --- Lightbox. Built and appended to <body> by site.js on first use. --- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  background: rgb(0 0 0 / .95);
  color: var(--text-light);
}

.lightbox.is-open { display: block; }

.lightbox-inner {
  position: absolute;
  inset: 0;
  display: grid;
  /* minmax(0, 1fr), not 1fr: a bare 1fr row has an auto minimum, so a tall
     portrait photo grew the row past the viewport and max-height: 100% then
     resolved against that oversized row. Found 2026-09-28 on a gallery of
     phone portraits (1200x2597); landscape galleries never showed it. */
  grid-template-rows: minmax(0, 1fr) auto;
  align-items: center;
  justify-items: center;
  gap: var(--sp-3);
  padding: clamp(3.5rem, 8vw, 4.5rem) clamp(.75rem, 4vw, 3.5rem) clamp(4rem, 9vw, 3.5rem);
}

.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
  width: auto;
  height: auto;
  object-fit: contain;
  border: 3px solid rgb(var(--accent-rgb) / .35);
  background: var(--ink);
}

.lightbox-caption {
  max-width: 68ch;
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--text-light-mid);
  text-align: center;
}

/* The hairline on these three is neutral white at low alpha on purpose. It
   separates the control from a 95% black backdrop, which is the one surface in
   the system that is not built out of the palette (see .lightbox above), so a
   palette colour would be the wrong tool. */
.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  padding: 0;
  border: var(--border-weight) solid rgb(255 255 255 / .28);
  border-radius: var(--r-sm);
  background: rgb(var(--ink-rgb) / .85);
  color: var(--text-light);
  font-family: var(--font-body);
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.lightbox-close { top: clamp(.5rem, 2vw, 1.25rem); right: clamp(.5rem, 2vw, 1.25rem); }
.lightbox-prev  { left: clamp(.5rem, 2vw, 1.25rem);  top: 50%; transform: translateY(-50%); }
.lightbox-next  { right: clamp(.5rem, 2vw, 1.25rem); top: 50%; transform: translateY(-50%); }

.lightbox-count {
  position: absolute;
  left: 50%;
  bottom: clamp(.6rem, 2.5vw, 1.25rem);
  transform: translateX(-50%);
  padding: .35rem .85rem;
  background: rgb(var(--ink-rgb) / .85);
  border: 1px solid rgb(255 255 255 / .2);
  border-radius: var(--r-sm);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .55);
  white-space: nowrap;
}

@media (max-width: 559.98px) {
  .lightbox-prev,
  .lightbox-next {
    top: auto;
    bottom: .6rem;
    transform: none;
    width: 48px;
    height: 48px;
  }
  .lightbox-prev { left: .6rem; }
  .lightbox-next { right: .6rem; }
  .lightbox-count { bottom: 1.1rem; }
}

/* The scroll lock itself lives on <html> (see the reset). This is here only
   so the class on <body> is meaningful to page authors reading the markup. */
body.is-locked { overscroll-behavior: contain; }


/* ==========================================================================
   16  PILLARS / BENEFITS / POSITIONS
   ========================================================================== */

.pillar-grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: 1fr;
}

@media (min-width: 760px) {
  .pillar-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1100px) {
  .pillar-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.pillar {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
  padding: clamp(1.25rem, 3.5vw, 2rem);
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-left: var(--accent-bar-lg) solid var(--accent);
  border-radius: var(--r-sm);
}

.section-dark .pillar {
  background: var(--ink-2);
  border-color: var(--ink-3);
  border-left-color: var(--accent);
}

.pillar-title {
  font-size: var(--fs-h4);
  color: var(--on-surface);
}

.pillar-quote {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(1.05rem, .95rem + .6vw, 1.35rem);
  line-height: 1.16;
  text-transform: var(--display-case);
  letter-spacing: var(--display-tracking);
  color: var(--accent-text);
  transform: skewX(var(--skew));
  transform-origin: left center;
  margin: 0;
}

/* Zero specificity for the same reason as the dark-band body rule in section
   06: a <p class="pillar-quote"> must keep its accent, not be repainted muted
   by its own container. */
:where(.pillar) :where(p) { color: var(--on-surface-mid); margin: 0; }

/* --- Benefits --- */
.benefit-grid {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 560px) { .benefit-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .benefit-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.benefit {
  display: flex;
  align-items: flex-start;
  gap: .8rem;
  min-height: 56px;
  padding: .85rem 1rem;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  font-weight: 600;
  color: var(--on-surface);
}

.section-dark .benefit { background: var(--ink-2); border-color: var(--ink-3); }

.benefit::before {
  content: "";
  flex: none;
  width: 12px;
  height: 12px;
  margin-top: .45em;
  border-radius: var(--r-sm);
  background: var(--accent);
  transform: skewX(var(--skew));
}

/* --- Open positions --- */
.position-list {
  display: grid;
  gap: 0;
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--hairline);
}

@media (min-width: 720px) {
  .position-list { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--sp-6); }
}

.position {
  display: flex;
  align-items: center;
  gap: .9rem;
  min-height: 60px;
  padding: .9rem 0;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(.95rem, .9rem + .3vw, 1.1rem);
  text-transform: var(--display-case);
  letter-spacing: var(--display-tracking);
  color: var(--on-surface);
}

.position::before {
  content: "";
  flex: none;
  width: 16px;
  height: 7px;
  border-radius: var(--r-sm);
  background: var(--accent);
  transform: skewX(var(--skew));
}


/* ==========================================================================
   17  FORMS
   ========================================================================== */

.form {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-top: var(--accent-bar-lg) solid var(--accent);
  border-radius: var(--r-md);
  padding: clamp(1.25rem, 4vw, 2.25rem);
}

.field { display: block; margin-bottom: var(--sp-5); }
.field:last-child { margin-bottom: 0; }

.field-row {
  display: grid;
  gap: 0 var(--sp-5);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.label {
  display: block;
  margin-bottom: .4rem;
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .35);
  text-transform: var(--label-case);
  color: var(--on-surface);
}

.label .required { color: var(--danger); }

.input,
.select,
.textarea {
  display: block;
  width: 100%;
  min-height: 50px;
  padding: .7rem .9rem;
  background: var(--paper);
  color: var(--text-dark);
  border: var(--border-weight) solid var(--paper-3);
  border-radius: var(--r-sm);
  font-size: 1rem;
  line-height: 1.4;
  transition: border-color var(--t-fast), background-color var(--t-fast);
}

.input:hover,
.select:hover,
.textarea:hover { border-color: var(--paper-4); }

.input:focus,
.select:focus,
.textarea:focus { border-color: var(--accent-deep); }

.textarea {
  min-height: 150px;
  resize: vertical;
}

.select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 2.75rem;
  background-image: var(--select-caret);
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 12px 8px;
}

.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--danger);
  background: var(--danger-soft);
}

.form-error {
  margin-top: .4rem;
  font-size: var(--fs-small);
  font-weight: 700;
  line-height: 1.4;
  color: var(--danger);
}

.form-error::before { content: "\26A0\FE0E  "; }

.form-note {
  margin-top: var(--sp-4);
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--on-surface-mid);
}

.form-note a { color: var(--text-dark); font-weight: 700; }

/* --- Checkbox group --- */
.checkgroup {
  display: grid;
  gap: .5rem;
  grid-template-columns: 1fr;
  border: 0;
  padding: 0;
  margin: 0;
}

@media (min-width: 560px) { .checkgroup { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.check {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  min-height: 48px;
  padding: .6rem .75rem;
  background: var(--paper);
  border: 1px solid var(--paper-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-small);
  line-height: 1.35;
  cursor: pointer;
  transition: border-color var(--t-fast), background-color var(--t-fast);
}

.check:hover { border-color: var(--accent-deep); }

/* accent-color stays the ink on purpose: a bright accent behind a white
   checkmark is unreadable at 20px. */
.check input[type="checkbox"],
.check input[type="radio"] {
  flex: none;
  width: 20px;
  height: 20px;
  margin: .1rem 0 0;
  accent-color: var(--ink);
  cursor: pointer;
}

.check:has(input:checked) {
  border-color: var(--accent-deep);
  box-shadow: inset 0 0 0 1px var(--accent-deep);
}


/* ==========================================================================
   18  CONTACT / BREADCRUMB / PROSE
   ========================================================================== */

.contact-grid {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3rem);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 1000px) {
  .contact-grid { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); }
}

.contact-detail {
  display: block;
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--hairline);
}

.contact-detail:first-child { padding-top: 0; }

.contact-detail dt,
.contact-detail .label {
  margin-bottom: .3rem;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: calc(var(--label-tracking) * .78);
  text-transform: var(--label-case);
  color: var(--on-surface-mid);
}

.contact-detail dd { margin: 0; }

.contact-detail a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(1.05rem, .95rem + .55vw, 1.4rem);
  letter-spacing: var(--display-tracking);
  text-transform: var(--display-case);
  color: var(--on-surface);
  text-decoration: none;
  border-bottom: 3px solid var(--accent);
  word-break: break-word;
}

.contact-detail a:hover { color: var(--accent-text); border-bottom-color: var(--accent-deep); }

.contact-detail p { margin: 0; color: var(--on-surface-mid); }

/* --- Breadcrumb --- */
.breadcrumb {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-small);
  color: var(--on-surface-mid);
}

.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem .55rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.breadcrumb li { display: inline-flex; align-items: center; gap: .55rem; }

.breadcrumb li + li::before {
  content: "/";
  color: var(--accent-deep);
  font-weight: 700;
}

.section-dark .breadcrumb li + li::before,
.hero .breadcrumb li + li::before { color: var(--accent); }

.breadcrumb a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  color: inherit;
  text-decoration: underline;
}

.breadcrumb a:hover { color: var(--accent-text); }

.breadcrumb [aria-current="page"] { color: var(--on-surface); font-weight: 600; }

/* --- Prose: long-form pages (privacy, terms, notes, articles) --- */
.prose {
  max-width: 72ch;
  color: var(--on-surface);
}

.prose > * + * { margin-top: 1.1em; }

.prose h2 { margin-top: 2.2em; font-size: var(--fs-h3); }
.prose h3 { margin-top: 1.8em; font-size: var(--fs-h4); }
.prose h2:first-child,
.prose h3:first-child { margin-top: 0; }

.prose p,
.prose li { color: var(--on-surface-mid); }

.prose ul,
.prose ol { padding-left: 1.35rem; }

.prose li + li { margin-top: .45em; }

.prose ul { list-style: none; padding-left: 0; }

.prose ul > li {
  position: relative;
  padding-left: 1.6rem;
}

.prose ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .62em;
  width: 11px;
  height: 11px;
  border-radius: var(--r-sm);
  background: var(--accent);
  transform: skewX(var(--skew));
}

/* A .chip-row nested inside .prose must not inherit the prose list marker.
   .prose ul > li gets an accent square bullet and a 1.6rem indent, which is
   right for a sentence list and wrong for a row of pills. Reset the marker,
   the indent and the stacked li spacing so .chip-row keeps its own flex row
   and its own gap. Ordinary .prose lists are untouched. */
.prose .chip-row > li { padding-left: 0; }
.prose .chip-row > li::before { content: none; }
.prose .chip-row > li + li { margin-top: 0; }

.prose a {
  color: var(--text-dark);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--accent-deep);
}

.prose a:hover { text-decoration-color: var(--ink); }

.prose strong { color: var(--on-surface); }


/* ==========================================================================
   19  STICKY MOBILE CALL BAR  (phones only, <= 720px)
   ========================================================================== */

.call-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 80;
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 3px solid var(--accent);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  box-shadow: 0 -8px 24px rgb(0 0 0 / .35);
}

.call-bar-link {
  min-height: var(--call-bar-h);
  padding: .6rem .5rem;
  border: 0;
  border-radius: 0;
  transform: none;               /* no slant, these are full-bleed halves */
  background: var(--ink);
  color: var(--text-light);
  font-size: clamp(.82rem, .76rem + .5vw, .95rem);
  letter-spacing: calc(var(--label-tracking) * .28);
  --ring: var(--accent);
  --ring-halo: var(--ink);
}

.call-bar-link > * { transform: none; }

/* Pressed state. The same accent-deep + paper pair every other control in the
   system uses, so both halves of the bar press the same way. */
.call-bar-link:active {
  transform: none;
  background: var(--accent-deep);
  color: var(--paper);
}

.call-bar-primary {
  background: var(--accent);
  color: var(--on-accent);
}

.call-bar-primary:active { background: var(--accent-deep); color: var(--paper); }

@media (min-width: 721px) {
  .call-bar { display: none; }
}


/* ==========================================================================
   20  FOOTER
   Every footer carries the Rush Webworks credit. Do not remove .footer-credit.
   ========================================================================== */

.footer {
  position: relative;
  background: var(--ink);
  color: var(--text-light);
  padding-top: calc(clamp(2.5rem, 6vw, 4rem) * var(--section-rhythm));
  padding-bottom: calc(clamp(1.5rem, 4vw, 2.5rem) * var(--section-rhythm));
  border-top: calc(var(--border-weight) * 3) solid var(--accent);
}

.footer-grid {
  display: grid;
  gap: var(--sp-6) var(--sp-5);
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .footer-grid { grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: var(--sp-6); }
}

.footer-col { min-width: 0; }

.footer-brand .brand { margin-bottom: var(--sp-4); }

.footer-brand p {
  color: var(--text-light-mid);
  font-size: var(--fs-small);
  max-width: 40ch;
}

.footer-head {
  margin-bottom: var(--sp-3);
  font-family: var(--font-body);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--label-tracking);
  text-transform: var(--label-case);
  color: var(--accent-text);
}

.footer-links {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  padding: .25rem 0;
  color: var(--text-light-mid);
  font-size: var(--fs-small);
  text-decoration: none;
}

.footer-links a:hover {
  color: var(--accent-on-dark);
  text-decoration: underline;
  text-decoration-color: var(--accent);
}

.footer-contact { display: grid; gap: .1rem; }

.footer-contact a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  color: var(--text-light);
  font-size: var(--fs-small);
  font-weight: 600;
  text-decoration: none;
  word-break: break-word;
}

.footer-contact a:hover { color: var(--accent-on-dark); }

.footer-contact .footer-tel {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(1.1rem, 1rem + .5vw, 1.35rem);
  letter-spacing: var(--display-tracking);
  color: var(--accent-text);
}

.footer-contact address {
  font-style: normal;
  color: var(--text-light-mid);
  font-size: var(--fs-small);
  padding-block: .5rem;
}

.footer .chip-row { margin-top: var(--sp-4); }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem var(--sp-5);
  margin-top: clamp(2rem, 5vw, 3rem);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--ink-3);
  font-size: var(--fs-small);
  color: var(--text-light-mid);
}

.footer-bottom p { margin: 0; color: var(--text-light-mid); }

.footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-legal a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  color: var(--text-light-mid);
  text-decoration: underline;
}

.footer-legal a:hover { color: var(--accent-on-dark); }

/* The studio credit. Markup contract:
     <p class="footer-credit">Site built by
       <a href="https://rushwebworks.com">Rush Webworks</a></p> */
.footer-credit { color: var(--text-light-mid); }

.footer-credit a {
  color: var(--accent-on-dark);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: rgb(var(--accent-rgb) / .45);
}

.footer-credit a:hover { text-decoration-color: var(--accent); }


/* ==========================================================================
   21  REVEAL ON SCROLL
   Content is visible by default. site.js adds .js-reveal to <html> only when
   IntersectionObserver exists AND the user allows motion, which is the only
   way .reveal ever becomes hidden. No-JS and reduced-motion users see
   everything immediately.
   ========================================================================== */

.reveal { opacity: 1; transform: none; }

.js-reveal .reveal {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity .65s ease,
    transform .65s cubic-bezier(.16, .84, .44, 1);
  transition-delay: calc(var(--i, 0) * 85ms);
  will-change: opacity, transform;
}

.js-reveal .reveal.in {
  opacity: 1;
  transform: none;
}


/* ==========================================================================
   22  REDUCED MOTION
   Kills every transition and animation in the stylesheet. This is not a
   courtesy, it is the accessibility floor. Do not weaken it.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: .001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  html { scroll-behavior: auto; }

  .hero-slide.is-active img { animation: none; transform: none; }
  .hero-slide { transition: none; }
  .nav-drawer.is-open { animation: none; }
  .js-reveal .reveal { opacity: 1; transform: none; }
  .card-media img,
  .gallery-btn img { transition: none; }
}


/* ==========================================================================
   23  PRINT
   ========================================================================== */

@media print {
  .header,
  .nav,
  .nav-drawer,
  .nav-toggle,
  .skip-link,
  .call-bar,
  .hero-slides,
  .hero-scrim,
  .hero-dots,
  .lightbox,
  .filter-bar,
  .cta-band,
  .btn,
  .btn-row,
  .nav-cta { display: none !important; }

  html { overflow-x: visible; }

  body {
    padding-bottom: 0;
    background: #fff !important;
    color: #000 !important;
    font-size: 11pt;
    line-height: 1.45;
  }

  *,
  *::before,
  *::after {
    background: transparent !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
    transform: none !important;
    filter: none !important;
  }

  .section,
  .section-dark,
  .section-light,
  .quote-band,
  .footer,
  .hero {
    background: #fff !important;
    color: #000 !important;
    padding-block: 12pt;
    min-height: 0 !important;
    margin-top: 0 !important;
  }

  .card,
  .pillar,
  .form,
  .benefit,
  .work-card {
    border: 1pt solid #999 !important;
    break-inside: avoid;
  }

  h1, h2, h3, h4 { break-after: avoid; color: #000 !important; }

  img { max-width: 100% !important; break-inside: avoid; }

  /* Accent marks print as solid black bars. The divider drops its clip-path
     so a torn brush edge does not turn into a row of black specks on paper. */
  .slash-rule,
  .eyebrow::before,
  .service-list > li::before,
  .prose ul > li::before,
  .benefit::before,
  .position::before,
  .split-media::after { background: #000 !important; }

  .slash-rule {
    clip-path: none !important;
    height: 3pt !important;
  }

  .slash-rule::after { display: none !important; }

  a { text-decoration: underline; }

  a[href^="http"]::after,
  a[href^="mailto:"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }

  a[href^="tel:"]::after { content: ""; }

  /* --print-origin is set once in section 00. */
  a[href^="/"]::after {
    content: " (" var(--print-origin) attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }

  .footer-bottom { border-top: 1pt solid #999; }

  @page { margin: 14mm; }
}


/* ==========================================================================
   24  SITE: MIKE'S LIGHTS
   ==========================================================================
   Per-site additions. Everything above this banner is the stencil, verbatim
   (_src/make_css.py asserts it). Edit this file in _src/site-block.css.

     24.1  Stencil corrections for a dark accent
     24.2  The logo lockup
     24.3  Strands (the hanging bulbs)
     24.4  Home hero: night sky, snow, skyline
     24.5  Interior page hero
     24.6  The veteran patch and the veteran band
     24.7  Service cards with the red medallions
     24.8  The house (.scene) and its five looks
     24.9  The scroll story
     24.10 The display designer
     24.11 Photo frames, before and after
     24.12 Service area, FAQ, small parts
     24.13 Footer
     24.14 Preview markers (stand-ins waiting on Mike)
     24.15 Reduced motion and print
   -------------------------------------------------------------------------- */


/* --- 24.1  Stencil corrections for a dark accent ---------------------------
   The stencil assumes a bright accent. This one is a deep red with white
   text on it, which trips three things below the fence:
     1. .btn sets its own surface context, so a ghost button reads
        --on-surface as the on-accent colour. With a dark accent that is
        white, and a ghost button on a white section disappears.
     2. The CTA band button hovers to --accent-bright as TEXT on ink. Red on
        the night ink is 3.6:1.
     3. Focus rings are the accent. Red on the night ink is 3.2:1, legal for
        a ring but weak, so dark surfaces ring in the bulb gold.
--------------------------------------------------------------------------- */
.btn-ghost {
  border-color: var(--text-dark);
  color: var(--text-dark);
}

.hero .btn-ghost,
.section-dark .btn-ghost,
.footer .btn-ghost,
.vet-band .btn-ghost {
  border-color: rgb(var(--paper-rgb) / .78);
  color: var(--text-light);
}

.cta-band .btn:hover { color: var(--text-light); }

.header,
.nav-drawer,
.hero,
.section-dark,
.quote-band,
.footer,
.vet-band { --ring: var(--glow); }

/* A lit bulb throws light. So does the button you are about to press. */
.btn:hover,
.nav-cta:hover {
  box-shadow: 0 0 0 1px rgb(255 255 255 / .1), 0 8px 26px rgb(var(--accent-rgb) / .55);
}

.btn-ghost:hover { box-shadow: 0 8px 26px rgb(var(--accent-rgb) / .4); }

/* Pages with no hero (privacy, 404) put the see-through header over the
   white page, where it reads as a grey bar. Make it solid there. */
body:not(:has(.hero)) .header::before { background: var(--ink); }

/* The scrolled header drops the stencil's deep shadow, which is drawn for a
   dark page. Over the white sections here it reads as a grey smear. */
.header.is-scrolled { box-shadow: 0 6px 22px rgb(0 0 0 / .26); }

/* The veteran and discount chips repeat the patch that sits just above them
   in the hero on a phone, so the strip keeps to the other two there. */
@media (max-width: 559.98px) {
  .credential-strip .chip-row > li.is-wide { display: none; }
}

/* Rush Webworks house rule: the studio credit is always the studio blue,
   never the client's accent. This is the lighter blue for dark footers. */
.footer-credit a {
  color: #5b82ff;
  text-decoration-color: rgb(91 130 255 / .45);
}

.footer-credit a:hover {
  color: #9db4ff;
  text-decoration-color: #9db4ff;
}


/* --- 24.2  The logo lockup --------------------------------------------------
   Live type, not an image, so it stays sharp and follows the palette. It is
   drawn after the flier: MIKE in white brush, LIGHTS in red block capitals
   with a white keyline, HOLIDAY LIGHTING SERVICES tracked out underneath.
   When Mike sends a logo file, swap this for the file and keep the sizes.
--------------------------------------------------------------------------- */
.badge-mark .bm-glass { fill: var(--accent-bright); }
.badge-mark .bm-sock  { fill: var(--wire); }
.badge-mark .bm-shine { fill: #fff; opacity: .6; }
.badge-mark .bm-halo  { fill: var(--accent-bright); opacity: .28; }
.badge-mark .bm-ray   { stroke: var(--glow); stroke-width: 2.4; stroke-linecap: round; fill: none; }

.ml-logo {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  /* The name is slanted, so its top right corner leans a few pixels past
     its own box. .brand clips its overflow (stencil, section 10), and this
     is the room that keeps the last letter whole. */
  padding: 2px 6px 0 0;
  line-height: 1;
  white-space: nowrap;
}

.ml-name {
  display: flex;
  align-items: baseline;
  gap: .32em;
  font-size: clamp(1.32rem, 1.16rem + .7vw, 1.72rem);
  transform: skewX(var(--skew));
  transform-origin: left bottom;
}

.ml-mike {
  position: relative;
  font-family: var(--font-brush);
  font-size: .96em;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--text-light);
}

.ml-lights {
  font-family: var(--font-display);
  letter-spacing: .035em;
  text-transform: uppercase;
  color: var(--accent-bright);
  /* the white keyline off the flier, as four hard shadows */
  text-shadow: 1px 0 0 #fff, -1px 0 0 #fff, 0 1px 0 #fff, 0 -1px 0 #fff,
               1px 1px 0 #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff;
}

.ml-tag {
  font-family: var(--font-body);
  font-size: .56rem;
  font-weight: 700;
  letter-spacing: .21em;
  text-transform: uppercase;
  color: var(--text-light-mid);
}

@media (max-width: 400px) {
  .ml-tag { letter-spacing: .1em; }
}

.footer .ml-name { font-size: 1.9rem; }
.footer .ml-tag  { font-size: .62rem; }
.footer .badge-mark { width: 52px; height: 52px; }


/* --- 24.3  Strands -----------------------------------------------------------
   Markup comes from _src/make_scene.py. Three SVGs stacked: two blurred glow
   layers (odd and even bulbs) under one layer of wire and glass. The glow
   layers twinkle by opacity alone, so nothing repaints.

   Lit is the DEFAULT, so the bulbs are on with JavaScript off. The inline
   script in the head adds .js to <html>, which is what dims them until
   lights.js switches each strand on as it scrolls into view.
--------------------------------------------------------------------------- */
.strand {
  position: relative;
  width: 100%;
  pointer-events: none;
}

.strand svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.strand .wire {
  fill: none;
  stroke: var(--wire);
  stroke-width: 2.6;
  stroke-linecap: round;
}

.strand .sock { fill: #173F2A; }

.k0 { --bulb: var(--bulb-red); }
.k1 { --bulb: var(--bulb-green); }
.k2 { --bulb: var(--bulb-gold); }
.k3 { --bulb: var(--bulb-blue); }
.k4 { --bulb: var(--bulb-orange); }
.kw { --bulb: var(--bulb-warm); }

.strand .glass {
  fill: var(--bulb);
  transition: fill .5s ease;
  transition-delay: calc(var(--n) * 55ms);
}

.strand .shine {
  fill: #fff;
  opacity: .62;
  transition: opacity .5s ease;
  transition-delay: calc(var(--n) * 55ms);
}

.strand .halo {
  fill: var(--bulb);
  opacity: .62;
  transition: opacity .7s ease;
  transition-delay: calc(var(--n) * 55ms);
}

.strand-glow {
  filter: blur(9px);
  will-change: opacity;
}

.strand.is-on .glow-a { animation: ml-twinkle 3.4s ease-in-out 2.2s infinite; }
.strand.is-on .glow-b { animation: ml-twinkle 4.6s ease-in-out 3.1s infinite; }

@keyframes ml-twinkle {
  0%, 100% { opacity: 1; }
  50%      { opacity: .5; }
}

/* The white-hot centre of a lit bulb, and the hinge it swings from. */
.strand .core {
  fill: #FFF6DA;
  opacity: 0;
  transition: opacity 1.3s ease;
}

.strand .bulb-body {
  transform-box: fill-box;
  transform-origin: 50% 0;
}

/* A bulb a cursor or a finger just touched (lights.js adds .is-lit to the
   bulb and to its halo, and takes it off three seconds later). */
.strand .bulb.is-lit .core  { opacity: .62; transition-duration: .12s; transition-delay: 0s; }
.strand .bulb.is-lit .shine { opacity: .9;  transition-duration: .12s; transition-delay: 0s; }
.strand .bulb.is-lit .glass { fill: var(--bulb); transition-duration: .12s; transition-delay: 0s; }
.strand .halo.is-lit        { opacity: 1;   transition-duration: .16s; transition-delay: 0s; }

/* --- Hover lighting (computers with a mouse) ---------------------------------
   The strand at the top of each page rests at a soft glow, and each bulb
   flares when the cursor passes it, holds for three seconds, then eases back
   down. lights.js adds .is-hover only where a mouse can hover and motion is
   allowed. Phones keep the always-on twinkle above. */
.strand.is-hover .glass,
.strand.is-hover .shine,
.strand.is-hover .halo { transition-delay: 0s; }

.strand.is-hover .glow-a,
.strand.is-hover .glow-b { animation: none; }

.strand.is-hover .glass { fill: color-mix(in srgb, var(--bulb) 64%, #0c1626); transition: fill 1.3s ease; }
.strand.is-hover .shine { opacity: .4; transition: opacity 1.3s ease; }
.strand.is-hover .halo  { opacity: .2; transition: opacity 1.3s ease; }

.strand.is-hover .bulb.is-lit .glass { fill: var(--bulb); transition-duration: .12s; }
.strand.is-hover .bulb.is-lit .shine { opacity: .9; transition-duration: .12s; }
.strand.is-hover .halo.is-lit        { opacity: 1; transition-duration: .16s; }

/* off: dark glass, no glow */
.js .strand:not(.is-on) .glass { fill: color-mix(in srgb, var(--bulb) 30%, #0c1626); }
.js .strand:not(.is-on) .shine { opacity: .18; }
.js .strand:not(.is-on) .halo  { opacity: 0; }

/* If lights.js never arrives, the head script turns everything on anyway. */
.lights-failsafe .strand .glass { fill: var(--bulb) !important; }
.lights-failsafe .strand .shine { opacity: .62 !important; }
.lights-failsafe .strand .halo  { opacity: .62 !important; }

.strand-divider { height: clamp(52px, 6vw, 86px); }
.strand-page    { height: clamp(62px, 7vw, 100px); }

/* A strand that caps a section hangs from the section's top edge. */
.has-strand { position: relative; }

.has-strand > .strand {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
}

.section.has-strand { padding-top: calc(var(--section-y) + clamp(30px, 3.5vw, 52px)); }


/* --- 24.4  Home hero --------------------------------------------------------
   No photograph. A night sky, a little snow, a street of lit rooftops along
   the bottom and one strand of big bulbs under the header. When Mike sends a
   real night shot this becomes a .hero-slides photo hero in ten minutes.
--------------------------------------------------------------------------- */
.hero.hero-night {
  align-items: stretch;
  min-height: min(96vh, 900px);
  min-height: min(96svh, 900px);
  padding-top: var(--header-h);
  padding-bottom: 0;
  background: linear-gradient(180deg, var(--night-1) 0%, #06173A 42%, var(--night-2) 68%, var(--night-3) 100%);
}

.hero-sky {
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
}

.hero-sky svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  will-change: opacity, transform;
}

.hero-stars circle { fill: #fff; }
.hero-stars.star-a { animation: ml-star 5.2s ease-in-out infinite; }
.hero-stars.star-b { animation: ml-star 7.4s ease-in-out -3s infinite; }

@keyframes ml-star {
  0%, 100% { opacity: 1; }
  50%      { opacity: .35; }
}

.moon-disc { fill: #FFF2CC; }

/* Snow: three tiled layers of dots. Each layer is exactly one tile taller
   than the hero and slides down by exactly one tile, so the loop is seamless
   and only ever moves a transform. */
.hero-snow,
.scene-snow {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  pointer-events: none;
}

.hero-snow::before,
.hero-snow::after,
.hero-snow > i,
.scene-snow::before,
.scene-snow::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  will-change: transform;
}

.hero-snow::before,
.scene-snow::before {
  top: -260px;
  background-image:
    radial-gradient(circle 1.8px at 22px 34px, rgb(255 255 255 / .95) 0 99%, transparent 100%),
    radial-gradient(circle 1.5px at 138px 92px, rgb(255 255 255 / .8) 0 99%, transparent 100%),
    radial-gradient(circle 2.1px at 214px 178px, rgb(255 255 255 / .9) 0 99%, transparent 100%),
    radial-gradient(circle 1.4px at 78px 226px, rgb(255 255 255 / .75) 0 99%, transparent 100%);
  background-size: 260px 260px;
  animation: ml-snow-260 15s linear infinite;
}

.hero-snow::after,
.scene-snow::after {
  top: -180px;
  background-image:
    radial-gradient(circle 1.1px at 40px 20px, rgb(255 255 255 / .6) 0 99%, transparent 100%),
    radial-gradient(circle 1.2px at 122px 74px, rgb(255 255 255 / .55) 0 99%, transparent 100%),
    radial-gradient(circle 1px at 86px 148px, rgb(255 255 255 / .5) 0 99%, transparent 100%);
  background-size: 180px 180px;
  animation: ml-snow-180 24s linear infinite;
}

.hero-snow > i {
  top: -340px;
  background-image:
    radial-gradient(circle 2.8px at 60px 80px, rgb(255 255 255 / .5) 0 60%, transparent 100%),
    radial-gradient(circle 3.2px at 250px 240px, rgb(255 255 255 / .42) 0 60%, transparent 100%),
    radial-gradient(circle 2.6px at 170px 300px, rgb(255 255 255 / .45) 0 60%, transparent 100%);
  background-size: 340px 340px;
  animation: ml-snow-340 10s linear infinite;
}

@keyframes ml-snow-260 { to { transform: translate3d(0, 260px, 0); } }
@keyframes ml-snow-180 { to { transform: translate3d(0, 180px, 0); } }
@keyframes ml-snow-340 { to { transform: translate3d(0, 340px, 0); } }

/* The street of rooftops. */
.skyline {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  z-index: -1;
  height: clamp(118px, 20vw, 290px);
  pointer-events: none;
}

.skyline svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.sk-mtn    { fill: #0C2553; }
.sk-mtn-2  { fill: #0A2049; }
.sk-snow   { fill: #C9DAF5; opacity: .5; }
.sk-ridge  { fill: #061633; }
.sk-wall   { fill: #091A36; }
.sk-wall-2 { fill: #0B1F3F; }
.sk-roof   { fill: #050F22; }
.sk-fir    { fill: #06182B; }
.sk-win    { fill: #FFCF73; opacity: .92; }
.sk-ground { fill: var(--ink); }

.sd { fill: var(--bulb); }

.skyline-lights {
  filter: drop-shadow(0 0 3px rgb(255 211 110 / .85));
  will-change: opacity;
}

.skyline-lights.la { animation: ml-twinkle 3.1s ease-in-out infinite; }
.skyline-lights.lb { animation: ml-twinkle 4.3s ease-in-out -1.4s infinite; }
.skyline-lights.lc { animation: ml-twinkle 5.5s ease-in-out -2.7s infinite; }

.strand-hero {
  position: absolute;
  top: var(--header-h);
  left: 0;
  height: clamp(86px, 9.4vw, 138px);
  z-index: 0;
}

.hero-grid {
  position: relative;
  display: grid;
  gap: var(--sp-6);
  align-content: center;
  align-items: center;
  grid-template-columns: minmax(0, 1fr);
  padding-top: clamp(92px, 10.2vw, 150px);
  padding-bottom: calc(clamp(118px, 20vw, 290px) + var(--sp-4));
}

/* On a wide screen the whole hero fits one window: the words take the left,
   the patch takes the right, and the rooftops fade in from the middle so
   they sit under the patch instead of behind the buttons. */
@media (min-width: 1000px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--sp-7);
    padding-bottom: clamp(2.25rem, 4.4vw, 4rem);
  }

  .hero-night .skyline {
    -webkit-mask-image: linear-gradient(90deg, rgb(0 0 0 / 0) 41%, #000 64%);
    mask-image: linear-gradient(90deg, rgb(0 0 0 / 0) 41%, #000 64%);
  }
}

.hero.hero-night .hero-inner { max-width: 760px; }

.hero.hero-night .hero-inner h1 {
  font-size: clamp(2.85rem, 1.2rem + 7.3vw, 6.1rem);
  line-height: .93;
  margin-bottom: .16em;
  text-shadow: 0 4px 34px rgb(0 0 0 / .55);
}

/* The h1 is skewed, so the kicker inside it is set upright again. */
.hero-kicker {
  display: block;
  margin-bottom: 1.15em;
  font-family: var(--font-body);
  font-size: clamp(.7rem, .66rem + .26vw, .88rem);
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: var(--label-tracking);
  color: var(--glow);
  transform: skewX(var(--unskew));
  transform-origin: left bottom;
  text-shadow: none;
}

.hero-line { display: block; }

/* THE LIGHTS, in the brush hand, on a red brush stroke. Straight off the
   flier's banner. The stroke is the stencil's own short brush polygon. */
.hero-brush {
  position: relative;
  z-index: 0;
  display: inline-block;
  margin-top: .1em;
  margin-left: -.16em;
  padding: .12em .34em .04em .24em;
  font-family: var(--font-brush);
  letter-spacing: .005em;
  text-shadow: 0 2px 0 rgb(0 0 0 / .28);
}

/* On the narrowest phones the brush line comes down a size so THE LIGHTS
   stays on one line, the way the flier sets it. */
.hero-brush { white-space: nowrap; }

@media (max-width: 399.98px) {
  .hero-brush { font-size: .88em; }
}

.hero-brush::before {
  content: "";
  position: absolute;
  inset: 4% -1% 0 -2%;
  z-index: -1;
  background:
    repeating-linear-gradient(1.2deg,
      rgb(0 0 0 / 0) 0 13px, rgb(0 0 0 / .13) 13px 14.5px,
      rgb(0 0 0 / 0) 14.5px 27px, rgb(255 255 255 / .09) 27px 28px),
    linear-gradient(180deg, #EE1C22 0%, var(--accent) 55%, #A6060B 100%);
  /* a rough stroke with frayed ends, drawn for this banner */
  clip-path: polygon(
    2.5% 22%, .6% 30%, 3.2% 36%, 0% 46%, 2.6% 53%, .4% 62%, 3% 70%, 1% 80%, 4% 88%,
    8% 93%, 14% 97%, 23% 93%, 32% 98%, 41% 95%, 50% 99%, 60% 94%, 70% 98%, 79% 93%, 88% 97%, 95% 94%,
    98.4% 88%, 96% 79%, 99% 70%, 96.6% 60%, 99.6% 52%, 97.4% 43%, 100% 34%, 96.8% 26%, 99.4% 18%,
    97.5% 10%, 94% 5%, 88% 8%, 79% 4%, 70% 7%, 61% 3%, 52% 8%, 43% 4%, 33% 9%, 24% 5%, 16% 11%, 9% 8%, 4% 14%
  );
}

.hero-sub {
  margin: .9em 0 0;
  font-size: clamp(.74rem, .56rem + .9vw, 1.22rem);
  font-weight: 700;
  letter-spacing: clamp(.16em, .06em + .5vw, .3em);
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--text-light);
}

.hero.hero-night .lede { margin-top: var(--sp-4); max-width: 54ch; }

.hero-facts {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.4rem;
  margin: var(--sp-5) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--text-light);
}

.hero-facts li {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
}

.hero-facts li::before {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--dot, var(--bulb-gold));
  box-shadow: 0 0 10px 2px var(--dot, var(--bulb-gold));
}

.hero-facts li:nth-child(1) { --dot: var(--bulb-red); }
.hero-facts li:nth-child(2) { --dot: var(--bulb-green); }
.hero-facts li:nth-child(3) { --dot: var(--bulb-gold); }
.hero-facts li:nth-child(4) { --dot: var(--bulb-blue); }

/* The Christmas countdown, on its own line under the hero buttons (Mike
   asked for it about twice the first size). lights.js fills the numbers and
   ticks them once a second; screen readers get a plain sentence instead of
   a number that changes every second. */
.xmas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .75rem;
  margin: var(--sp-5) 0 0;
  color: var(--text-light);
}

.xmas-tick {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .35rem;
}

.xt {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  padding: .3rem .55rem .25rem;
  border: 1px solid rgb(255 255 255 / .16);
  border-radius: 6px;
  background: rgb(255 255 255 / .07);
  line-height: 1;
}

.xt b {
  display: inline-block;
  min-width: 1.9ch;
  text-align: right;
  font-family: var(--font-display);
  font-size: clamp(1.55rem, 1.3rem + 1vw, 1.95rem);
  font-weight: 400;
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
  color: var(--glow);
}

.xt b[data-u="d"] { min-width: 0; }

.xt i {
  font-style: normal;
  font-size: .8rem;
  font-weight: 700;
  color: var(--text-light-mid);
}

.xmas-label {
  font-size: clamp(.95rem, .9rem + .3vw, 1.1rem);
  font-weight: 700;
  white-space: nowrap;
}

.xmas + .hero-facts { margin-top: var(--sp-4); }

.hero-aside { display: none; }

@media (min-width: 1000px) {
  .hero-aside { display: block; justify-self: end; align-self: start; margin-top: clamp(1rem, 5vh, 4rem); }
  .hero-patch-inline { display: none; }
}

.hero-patch-inline { margin-top: var(--sp-5); }


/* --- 24.5  Interior page hero ---------------------------------------------- */
.hero.hero-page {
  padding-top: calc(var(--header-h) + clamp(74px, 8.6vw, 118px));
  background: linear-gradient(180deg, var(--night-1) 0%, #06173A 60%, var(--night-2) 100%);
}

.hero.hero-page .strand-page {
  position: absolute;
  top: var(--header-h);
  left: 0;
}

.hero.hero-page .hero-inner { max-width: 820px; }

.hero.hero-page::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
  height: 46%;
  background: radial-gradient(120% 100% at 50% 100%, rgb(18 64 127 / .55) 0%, rgb(18 64 127 / 0) 70%);
  pointer-events: none;
}



/* --- 24.6  The veteran patch and band ---------------------------------------
   Stars and stripes as plain shapes, with live text. No flag photo, no seal,
   no branch insignia: a service emblem needs that branch's licence.
--------------------------------------------------------------------------- */
.vet-patch {
  position: relative;
  width: min(100%, 320px);
  border: 3px solid #fff;
  border-radius: var(--r-md);
  background: var(--flag-navy);
  color: #fff;
  overflow: hidden;
  box-shadow: 0 18px 46px rgb(0 0 0 / .5);
  text-align: center;
}

.hero-aside .vet-patch { transform: rotate(2.4deg); }

.vet-flag {
  display: grid;
  grid-template-columns: 38% 1fr;
  height: 62px;
  border-bottom: 3px solid #fff;
}

.vet-flag-stars {
  display: grid;
  place-items: center;
  background: #14306B;
}

.vet-flag-stars svg { width: 78%; height: auto; fill: #fff; }

.vet-flag-stripes {
  background: repeating-linear-gradient(180deg, var(--accent) 0 12.4px, #fff 12.4px 24.8px);
}

.vet-title {
  margin: 0;
  padding: .7rem .75rem .1rem;
  font-family: var(--font-display);
  font-size: 2.7rem;
  line-height: .95;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: #fff;
}

.vet-title span {
  display: block;
  margin-top: .2em;
  font-size: .43em;
  letter-spacing: .09em;
}

.vet-stars {
  display: flex;
  justify-content: center;
  gap: .5rem;
  margin: .55rem 0 .8rem;
}

.vet-stars svg { width: 17px; height: 17px; fill: var(--glow); }

.vet-off {
  margin: 0;
  padding: .7rem .75rem;
  background: var(--accent);
  border-top: 3px solid #fff;
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: .12em;
  line-height: 1.3;
  text-transform: uppercase;
  color: #fff;
}

.vet-off strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.9rem;
  font-weight: 400;
  letter-spacing: .03em;
  line-height: 1;
  margin-bottom: .18em;
}

/* The compact patch that sits under the hero buttons on phones. */
.vet-strip {
  display: inline-grid;
  grid-template-columns: auto auto;
  align-items: stretch;
  border: 2px solid #fff;
  border-radius: var(--r-sm);
  overflow: hidden;
  font-weight: 700;
  line-height: 1.2;
  text-transform: uppercase;
  color: #fff;
  box-shadow: 0 10px 30px rgb(0 0 0 / .45);
}

.vet-strip > span {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .55rem .8rem;
  font-size: .7rem;
  letter-spacing: .13em;
}

.vet-strip > span:first-child { background: var(--flag-navy); }
.vet-strip > span:last-child  { background: var(--accent); }
.vet-strip svg { width: 13px; height: 13px; flex: none; fill: var(--glow); }

.vet-band {
  --surface:        var(--flag-navy);
  --raised:         #183468;
  --on-surface:     var(--text-light);
  --on-surface-mid: #C6D2E8;
  --accent-text:    var(--glow);
  --hairline:       #2C4884;
  --ring-halo:      var(--ink);
  position: relative;
  padding-block: var(--section-y);
  background:
    radial-gradient(90% 120% at 100% 0%, rgb(30 80 160 / .5) 0%, rgb(30 80 160 / 0) 60%),
    var(--flag-navy);
  color: var(--text-light);
  overflow: hidden;
}

/* a red, white and red rule top and bottom */
.vet-band::before,
.vet-band::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 12px;
  background: linear-gradient(180deg, var(--accent) 0 4px, #fff 4px 8px, var(--accent) 8px 12px);
}

.vet-band::before { top: 0; }
.vet-band::after  { bottom: 0; }

:where(.vet-band) :where(p, li) { color: var(--on-surface-mid); }

.vet-inner {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 900px) {
  .vet-inner { grid-template-columns: minmax(0, 1.25fr) auto; }
}

.vet-copy > * + * { margin-top: var(--sp-4); }
.vet-copy .eyebrow { margin-bottom: 0; }

.vet-art { display: grid; justify-items: center; }
.vet-art .vet-patch { transform: rotate(-2.2deg); width: min(100%, 340px); }

.offer {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: fit-content;
  max-width: 100%;
  padding: .85rem 1.15rem;
  border: 2px dashed rgb(255 255 255 / .55);
  border-radius: var(--r-md);
  background: rgb(255 255 255 / .06);
}

.offer-num {
  flex: none;
  font-family: var(--font-display);
  font-size: clamp(2.3rem, 1.8rem + 2vw, 3.3rem);
  line-height: .9;
  letter-spacing: .02em;
  color: var(--glow);
}

.offer-text {
  margin: 0;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-light);
}

.offer-text strong { display: block; text-transform: uppercase; letter-spacing: .1em; font-size: var(--fs-small); }

/* the same callout on a light surface (contact page) */
.offer.is-light {
  border-color: var(--accent);
  background: rgb(var(--accent-rgb) / .05);
}

.offer.is-light .offer-num  { color: var(--accent); }
.offer.is-light .offer-text { color: var(--text-dark); }

/* The discount callout has no number on it (Mike's call, 2026-10-06), so a
   gold star stands where a figure would go. */
.offer-star {
  flex: none;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
}

.offer-star svg { width: 100%; height: 100%; fill: var(--glow); }
.offer.is-light .offer-star svg { fill: var(--accent); }

/* the patch's red band now carries words, not a number */
.vet-off strong { font-size: 1.55rem; }


/* --- 24.7  Service cards ----------------------------------------------------
   The four services off the flier, each under the same red medallion with a
   white ring that the flier uses. No stock photos standing in for his work.
--------------------------------------------------------------------------- */
.icon-card .card-body { gap: var(--sp-4); padding-top: clamp(1.4rem, 3vw, 2rem); }

.icon-medal {
  display: grid;
  place-items: center;
  flex: none;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 26%, #F4474B 0%, var(--accent) 58%, var(--accent-deep) 100%);
  color: #fff;
  box-shadow: 0 0 0 4px var(--raised), 0 0 0 6px var(--accent), 0 10px 22px rgb(var(--accent-rgb) / .28);
  transition: box-shadow var(--t), transform var(--t);
}

.icon-medal svg {
  width: 34px;
  height: 34px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-card:hover .icon-medal {
  transform: translateY(-2px) scale(1.04);
  box-shadow: 0 0 0 4px var(--raised), 0 0 0 6px var(--accent-bright), 0 0 30px 6px rgb(255 80 70 / .55);
}

/* long service titles in a condensed face still need to break politely */
.icon-card .card-title { font-size: clamp(1.3rem, 1.15rem + .6vw, 1.6rem); }


/* --- 24.8  The house --------------------------------------------------------
   See _src/make_scene.py for the drawing. A look is a set of custom property
   values on .scene, chosen by data-state. Every shape transitions its own
   fill, so moving between looks cross-fades the whole drawing.
--------------------------------------------------------------------------- */
.scene {
  /* night, lights on: the default, and what shows with JavaScript off */
  --wall: #1E2C4F;  --wall-front: #25365F;  --wall-shade: #17223F;  --gable: #2B3D6B;
  --roof: #0E1730;  --roof2: #131D38;  --trim: #C3CEE4;  --stone: #3A4764;
  --door: #A8121A;  --knob: #FFD36E;  --pane: #FFD27A;  --brick: #472C3A;
  --tree: #0E3A34;  --trunk: #20181A;  --shrub: #12453B;  --lawn: #0A1D24;
  --path: #283452;  --far: #071428;  --wreath: #1C6B45;  --bow: #E3141A;
  --detail: rgb(255 255 255 / .07);
  --line: rgb(169 200 255 / 0);  --lw: 0;
  position: relative;
  aspect-ratio: 1200 / 760;
  border-radius: var(--r-lg);
  background: #061230;
  overflow: hidden;
  isolation: isolate;
  box-shadow: 0 0 0 1px rgb(255 255 255 / .08), var(--sh-dark);
}

.scene[data-state="install"] {
  --wall: #3A4A78;  --wall-front: #44568A;  --wall-shade: #2C3961;  --gable: #4D5F96;
  --roof: #1D2644;  --roof2: #232D4F;  --trim: #E6ECF7;  --stone: #5A6787;
  --door: #C0161F;  --pane: #EFBF73;  --brick: #6A4250;
  --tree: #17503F;  --trunk: #33262A;  --shrub: #1C5B48;  --lawn: #173A34;
  --path: #46557A;  --far: #1A2550;  --detail: rgb(255 255 255 / .09);
}

.scene[data-state="pickup"],
.scene[data-state="day"] {
  --wall: #6E87B5;  --wall-front: #7C95C2;  --wall-shade: #5C749F;  --gable: #8AA2CC;
  --roof: #2A3350;  --roof2: #313B5B;  --trim: #FFFFFF;  --stone: #9AA6BC;
  --door: #C8161F;  --pane: #B7D3EE;  --brick: #8A5560;
  --tree: #1F6A4C;  --trunk: #4A372F;  --shrub: #2A7A56;  --lawn: #7FA487;
  --path: #B9C4D6;  --far: #7F9ABD;  --wreath: #23784E;  --detail: rgb(7 17 31 / .09);
}

.scene[data-state="plan"] {
  /* Every fill is the blueprint paper itself, so a shape still hides what
     is behind it and the drawing reads as clean line work. */
  --wall: #0B2A5C;  --wall-front: #0B2A5C;  --wall-shade: #0B2A5C;  --gable: #0B2A5C;
  --roof: #0B2A5C;  --roof2: #0B2A5C;  --trim: #0B2A5C;  --stone: #0B2A5C;
  --door: #0B2A5C;  --knob: #0B2A5C;  --pane: #12366F;  --brick: #0B2A5C;
  --tree: #0B2A5C;  --trunk: #0B2A5C;  --shrub: #0B2A5C;  --lawn: #0B2A5C;
  --path: #0B2A5C;  --far: #0B2A5C;  --wreath: #A9C8FF;  --bow: #0B2A5C;
  --detail: rgb(169 200 255 / .2);
  --line: #A9C8FF;  --lw: 2;
}

.scene[data-state="plan"] .p-far { stroke: rgb(169 200 255 / .3); }

.scene .plan-grid { opacity: 0; transition: opacity 1s ease; pointer-events: none; }
.scene[data-state="plan"] .plan-grid { opacity: 1; }

.scene-layers,
.scene-layers > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.scene .s,
.scene .d,
.scene .p-wreath,
.scene .p-rake {
  transition: fill .9s ease, stroke .9s ease, stroke-width .9s ease;
}

.scene .s { stroke: var(--line); stroke-width: var(--lw); stroke-linejoin: round; }

.p-wall       { fill: var(--wall); }
.p-wall-front { fill: var(--wall-front); }
.p-wall-shade { fill: var(--wall-shade); }
.p-gable      { fill: var(--gable); }
.p-roof       { fill: var(--roof); }
.p-roof2      { fill: var(--roof2); }
.p-trim       { fill: var(--trim); }
.p-stone      { fill: var(--stone); }
.p-door       { fill: var(--door); }
.p-knob       { fill: var(--knob); }
.p-pane       { fill: var(--pane); }
.p-brick      { fill: var(--brick); }
.p-tree       { fill: var(--tree); }
.p-trunk      { fill: var(--trunk); }
.p-shrub      { fill: var(--shrub); }
.p-lawn       { fill: var(--lawn); }
.p-path       { fill: var(--path); }
.p-far        { fill: var(--far); }
.p-bow        { fill: var(--bow); }

.scene .p-rake {
  fill: none;
  stroke: var(--trim);
  stroke-width: 11;
  stroke-linejoin: miter;
}

.scene .p-wreath { fill: none; stroke: var(--wreath); }

.scene .d { fill: none; stroke: var(--detail); stroke-width: 1.5; }
.scene .d-muntin { stroke: var(--trim); stroke-width: 3.2; }

.scene[data-state="plan"] .p-rake   { stroke: var(--line); stroke-width: 2.2; }
.scene[data-state="plan"] .p-wreath { stroke-width: 2.2; }
.scene[data-state="plan"] .d-muntin { stroke: var(--line); stroke-width: 1.6; }

.scene .sky { opacity: 0; transition: opacity 1s ease; }
.scene[data-state="enjoy"]   .sky-night,
.scene[data-state="install"] .sky-dusk,
.scene[data-state="pickup"]  .sky-day,
.scene[data-state="day"]     .sky-day,
.scene[data-state="plan"]    .sky-plan { opacity: 1; }

.scene .stars { fill: #fff; opacity: 0; transition: opacity 1s ease; }
.scene .scene-moon { opacity: 0; transition: opacity 1s ease; }
.scene[data-state="enjoy"] .stars,
.scene[data-state="enjoy"] .scene-moon { opacity: 1; }
.scene[data-state="install"] .stars { opacity: .22; }

.scene .spill { opacity: 0; transition: opacity 1s ease; }
.scene[data-lit] .spill { opacity: .55; }

.ladder,
.tote {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .5s ease, transform .6s cubic-bezier(.16, .84, .44, 1);
}

.scene[data-state="install"] .ladder,
.scene[data-state="pickup"] .tote {
  opacity: 1;
  transform: none;
}

.ladder-rail { fill: none; stroke: #DCE3EF; stroke-width: 5.5; stroke-linecap: round; }
.ladder-rung { fill: none; stroke: #AEB9CC; stroke-width: 3.6; stroke-linecap: round; }

.tote-body { fill: #1B2640; }
.tote-lid  { fill: var(--accent-bright); }
.tote-tag  { fill: #fff; }
.tote-ink  { fill: none; stroke: #1B2640; stroke-width: 3; stroke-linecap: round; }
.tote-coil { fill: none; stroke: #1F6B40; stroke-width: 4; }
.tote-b1 { fill: var(--bulb-red); }
.tote-b2 { fill: var(--bulb-gold); }
.tote-b3 { fill: var(--bulb-blue); }
.tote-b4 { fill: var(--bulb-green); }

.guide {
  fill: none;
  stroke: var(--glow);
  stroke-width: 2.6;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1.1s ease;
}

.guide-tick { fill: none; stroke: var(--glow); stroke-width: 2.6; }
.guide-node { fill: var(--glow); }

.guide-tick,
.guide-node { opacity: 0; transition: opacity .4s ease; }

.scene[data-state="plan"] .guide { stroke-dashoffset: 0; }
.scene[data-state="plan"] .guide-tick,
.scene[data-state="plan"] .guide-node { opacity: 1; transition-delay: .6s; }

/* --- the bulbs ---
   --n is the bulb's place in the hanging order. --hung is how many are up;
   lights.js moves it with the scroll. clamp() turns the difference into an
   opacity, so one number on the wrapper hangs or strips the whole house and
   no bulb is ever touched individually. With no --hung set, all are up. */
.scene .b {
  fill: var(--bulb, var(--bulb-warm));
  stroke: var(--bulb, var(--bulb-warm));
  stroke-width: 9;
  stroke-opacity: .26;
  paint-order: stroke;
  opacity: clamp(0, calc(var(--hung, 99999) - var(--n)), 1);
  transition: fill .45s ease, stroke-opacity .45s ease;
  transition-delay: calc(var(--n) * 3ms);
}

.scene .b.mini  { stroke-width: 6.5; }
.scene .b.stake { stroke-width: 12; }

/* hung but not switched on: the glass is there, the light is not */
.scene:not([data-lit]) .b {
  fill: color-mix(in srgb, var(--bulb, var(--bulb-warm)) 46%, #25304A);
  stroke-opacity: 0;
}

.scene[data-state="pickup"]:not([data-lit]) .b,
.scene[data-state="day"]:not([data-lit]) .b {
  fill: color-mix(in srgb, var(--bulb, var(--bulb-warm)) 62%, #5A6F92);
}

.scene-lights { will-change: opacity; }

.scene[data-lit] .scene-lights {
  filter: drop-shadow(0 0 4px rgb(255 211 110 / .75)) drop-shadow(0 0 13px rgb(255 190 90 / .32));
}

.scene[data-lit].is-inview .scene-lights.la { animation: ml-twinkle-soft 3.3s ease-in-out infinite; }
.scene[data-lit].is-inview .scene-lights.lb { animation: ml-twinkle-soft 4.5s ease-in-out -1.5s infinite; }
.scene[data-lit].is-inview .scene-lights.lc { animation: ml-twinkle-soft 5.7s ease-in-out -2.8s infinite; }

@keyframes ml-twinkle-soft {
  0%, 100% { opacity: 1; }
  50%      { opacity: .66; }
}

/* colour schemes. k0-k4 is the five colour rotation, e0/e1 is every other. */
.scene[data-scheme="warm"] .b     { --bulb: var(--bulb-warm); }
.scene[data-scheme="cool"] .b     { --bulb: var(--bulb-cool); }
.scene[data-scheme="candy"] .e0   { --bulb: var(--bulb-red); }
.scene[data-scheme="candy"] .e1   { --bulb: #FFFFFF; }
.scene[data-scheme="redgreen"] .e0 { --bulb: var(--bulb-red); }
.scene[data-scheme="redgreen"] .e1 { --bulb: var(--bulb-green); }
.scene[data-scheme="blue"] .e0    { --bulb: #3AA4FF; }
.scene[data-scheme="blue"] .e1    { --bulb: #FFFFFF; }

/* zones the visitor has switched off */
.scene:not([data-zones~="roof"])  .zr,
.scene:not([data-zones~="win"])   .zw,
.scene:not([data-zones~="porch"]) .zp,
.scene:not([data-zones~="green"]) .zg,
.scene:not([data-zones~="walk"])  .za { display: none; }

.scene-snow { z-index: 3; opacity: 0; transition: opacity 1.2s ease; }
.scene[data-state="enjoy"][data-lit] .scene-snow { opacity: .9; }

.scene-note {
  margin-top: var(--sp-3);
  font-size: var(--fs-micro);
  letter-spacing: .04em;
  color: var(--on-surface-mid);
}


/* --- 24.9  The scroll story --------------------------------------------------
   The house stays put while four steps scroll past it, and the drawing
   follows the step you are reading. Nothing here is sized in vh: the step
   heights are svh with a pixel fallback, so a phone's address bar sliding in
   and out cannot change the page length mid-scroll.
--------------------------------------------------------------------------- */
.story-grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

.story-stage {
  position: sticky;
  top: var(--header-h);
  z-index: 2;
  margin-inline: calc(var(--gutter) * -1);
  padding: 10px var(--gutter) 12px;
  background: var(--ink);
  border-bottom: 1px solid var(--ink-3);
}

.story-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.story-step {
  counter-increment: step;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding-block: var(--sp-5);
}

.story-step > * { margin: 0; }

.js-story .story-step {
  min-height: 290px;
  min-height: 36svh;
  opacity: .34;
  transition: opacity .4s ease;
}

.js-story .story-step.is-active { opacity: 1; }
.js-story .story-step:last-child { min-height: 330px; min-height: 42svh; }

.story-num {
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--glow);
}

.story-num::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-display);
  font-size: 2.4rem;
  font-weight: 400;
  letter-spacing: .02em;
  line-height: 1;
  color: var(--text-light);
}

.story-step h3 { font-size: clamp(1.5rem, 1.25rem + 1.2vw, 2.15rem); }
.story-step p { color: var(--text-light-mid); max-width: 44ch; }

.story-meter {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .4rem;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

.story-meter li {
  display: flex;
  align-items: center;
  gap: .45rem;
  min-width: 0;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-light-mid);
  transition: color .3s ease;
}

.story-meter li::before {
  content: "";
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--dot) 34%, #101A2C);
  transition: background-color .3s ease, box-shadow .3s ease;
}

.story-meter li:nth-child(1) { --dot: var(--bulb-blue); }
.story-meter li:nth-child(2) { --dot: var(--bulb-gold); }
.story-meter li:nth-child(3) { --dot: var(--bulb-red); }
.story-meter li:nth-child(4) { --dot: var(--bulb-green); }

.story-meter li.is-on { color: var(--text-light); }
.story-meter li.is-on::before { background: var(--dot); box-shadow: 0 0 10px 2px var(--dot); }

@media (min-width: 900px) {
  .story-grid {
    grid-template-columns: minmax(0, 1.28fr) minmax(0, .72fr);
    gap: clamp(2rem, 4.5vw, 4.5rem);
  }

  .story-stage {
    top: calc(var(--header-h) + 26px);
    margin-inline: 0;
    padding: 0;
    background: none;
    border-bottom: 0;
  }

  .story-meter { margin-top: var(--sp-4); }
  .story-meter li { font-size: .72rem; }

  .js-story .story-step {
    min-height: 460px;
    min-height: 70svh;
    justify-content: center;
  }

  .js-story .story-step:last-child { min-height: 460px; min-height: 70svh; }
}


/* --- 24.10  The display designer -------------------------------------------- */
.designer {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

@media (min-width: 1000px) {
  .designer { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: var(--sp-6); }
}

/* The house stays put while the controls are used, so every tap shows.
   On a phone it pins under the header, the same way the story does. */
.designer-stage {
  position: sticky;
  top: var(--header-h);
  z-index: 2;
  margin-inline: calc(var(--gutter) * -1);
  padding: 10px var(--gutter) 6px;
  background: var(--ink);
  border-bottom: 1px solid var(--ink-3);
}

@media (min-width: 1000px) {
  .designer-stage {
    top: calc(var(--header-h) + 24px);
    margin-inline: 0;
    padding: 0;
    background: none;
    border-bottom: 0;
  }
}

.designer-panel {
  display: grid;
  gap: var(--sp-5);
  padding: clamp(1.1rem, 3vw, 1.75rem);
  border: 1px solid var(--ink-3);
  border-top: var(--accent-bar-lg) solid var(--accent);
  border-radius: var(--r-md);
  background: var(--ink-2);
}

.designer-group {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.designer-group legend,
.designer-label {
  display: block;
  margin-bottom: .6rem;
  padding: 0;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--glow);
}

.pick-grid {
  display: grid;
  gap: .5rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.pick {
  position: relative;
  display: flex;
  align-items: center;
  gap: .6rem;
  min-height: 48px;
  padding: .5rem .7rem;
  border: 2px solid var(--ink-3);
  border-radius: var(--r-sm);
  background: var(--ink);
  font-size: var(--fs-small);
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-light);
  cursor: pointer;
  transition: border-color var(--t-fast), background-color var(--t-fast);
}

.pick:hover { border-color: var(--text-light-mid); }

.pick input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.pick:has(input:checked) {
  border-color: var(--glow);
  background: #13233D;
}

.pick:has(input:focus-visible) {
  outline: 3px solid var(--glow);
  outline-offset: 2px;
}

.pick-dots { display: inline-flex; gap: 3px; flex: none; }

.pick-dots i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c);
  box-shadow: 0 0 6px var(--c);
}

.pd-multi i:nth-child(1) { --c: var(--bulb-red); }
.pd-multi i:nth-child(2) { --c: var(--bulb-green); }
.pd-multi i:nth-child(3) { --c: var(--bulb-gold); }
.pd-multi i:nth-child(4) { --c: var(--bulb-blue); }
.pd-warm i { --c: var(--bulb-warm); }
.pd-cool i { --c: var(--bulb-cool); }
.pd-candy i:nth-child(odd)     { --c: var(--bulb-red); }
.pd-candy i:nth-child(even)    { --c: #FFFFFF; }
.pd-redgreen i:nth-child(odd)  { --c: var(--bulb-red); }
.pd-redgreen i:nth-child(even) { --c: var(--bulb-green); }
.pd-blue i:nth-child(odd)      { --c: #3AA4FF; }
.pd-blue i:nth-child(even)     { --c: #FFFFFF; }

.pick-box {
  display: grid;
  place-items: center;
  flex: none;
  width: 20px;
  height: 20px;
  border: 2px solid var(--text-light-mid);
  border-radius: 4px;
}

.pick:has(input:checked) .pick-box {
  border-color: var(--glow);
  background: var(--glow);
}

.pick:has(input:checked) .pick-box::after {
  content: "";
  width: 10px;
  height: 6px;
  margin-top: -2px;
  border-left: 2.5px solid var(--ink);
  border-bottom: 2.5px solid var(--ink);
  transform: rotate(-45deg);
}

.switch {
  display: inline-flex;
  align-items: center;
  gap: .8rem;
  min-height: 48px;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--text-light);
  cursor: pointer;
}

.switch-track {
  position: relative;
  flex: none;
  width: 58px;
  height: 32px;
  border-radius: 999px;
  background: #33435F;
  transition: background-color var(--t);
}

.switch-track::after {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #fff;
  transition: transform var(--t), box-shadow var(--t), background-color var(--t);
}

.switch[aria-checked="true"] .switch-track { background: #6B4E0C; }

.switch[aria-checked="true"] .switch-track::after {
  transform: translateX(26px);
  background: var(--glow);
  box-shadow: 0 0 14px 3px rgb(255 211 110 / .8);
}

.designer-summary {
  margin: 0;
  padding: .75rem .9rem;
  border-left: var(--accent-bar) solid var(--glow);
  background: var(--ink);
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--text-light);
}


/* --- 24.11  Photo frames, before and after ----------------------------------
   The flier shows three white-framed photos, each a little off square. Same
   frames here, empty and labelled until Mike's own photos arrive. An empty
   frame says what goes in it. It never shows a picture that is not his work.
--------------------------------------------------------------------------- */
.shots {
  display: grid;
  gap: clamp(1.25rem, 3.4vw, 2.25rem);
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 620px)  { .shots { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .shots { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.shot { min-width: 0; }

.shot-frame {
  position: relative;
  aspect-ratio: 4 / 3;
  border: 7px solid #fff;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--night-1) 0%, var(--night-2) 68%, var(--night-3) 100%);
  box-shadow: var(--sh-3);
  overflow: hidden;
  transition: transform var(--t);
}

.shot:nth-child(3n+1) .shot-frame { transform: rotate(-1.6deg); }
.shot:nth-child(3n+2) .shot-frame { transform: rotate(1.1deg); }
.shot:nth-child(3n)   .shot-frame { transform: rotate(-.7deg); }
.shot:hover .shot-frame { transform: rotate(0deg) scale(1.02); }

.shot-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.shot-empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: .6rem;
  padding: 1rem;
  text-align: center;
  color: var(--text-light);
}

.shot-empty::before {
  content: "";
  position: absolute;
  inset: 12px;
  border: 2px dashed rgb(255 255 255 / .34);
  border-radius: 3px;
}

.shot-empty svg {
  width: 38px;
  height: 38px;
  fill: none;
  stroke: var(--glow);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.shot-empty strong {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 400;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.shot-empty span {
  max-width: 24ch;
  font-size: var(--fs-small);
  line-height: 1.4;
  color: var(--text-light-mid);
}

.shot-cap {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--on-surface-mid);
  text-align: center;
}

/* a photo eases in once it has arrived, so the frame never pops */
.js .shot-frame img { opacity: 0; transition: opacity .6s ease; }
.js .shot-frame img.is-loaded,
.lights-failsafe .shot-frame img { opacity: 1; }

/* Mike's avatar on the About page, where the flier was and framed the same
   way (Drew, 2026-10-06). The art is an illustration on a transparent
   background, but the transparency was made from its dark parts, his black
   hoodie and hair included, so on white he washes out to a ghost. The frame
   carries the hero's night sky behind him. */
.mike-card {
  width: min(100%, 360px);
  margin: 0 auto;
}

.mike-card-frame {
  overflow: hidden;
  border: 7px solid #fff;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--night-1) 0%, #06173A 60%, var(--night-2) 100%);
  box-shadow: var(--sh-3);
  transform: rotate(-1.4deg);
}

.mike-card-frame img { display: block; width: 100%; height: auto; }

.mike-card-cap {
  margin-top: var(--sp-4);
  font-size: var(--fs-small);
  font-weight: 600;
  text-align: center;
  color: var(--on-surface-mid);
}

/* the three "How Mike works" cards, stacked beside Mike on the About page */
.pillar-grid.is-stack { grid-template-columns: minmax(0, 1fr); }

/* the line under the before and after slider */
.ba-wrap { margin: 0; min-width: 0; }

.ba-cap {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--on-surface-mid);
}

/* snowfall outside a hero (the 404 band) */
.has-snow {
  isolation: isolate;
  overflow: hidden;
}

/* before and after: two pictures, one handle */
.ba {
  --pos: 50%;
  position: relative;
  aspect-ratio: 1200 / 760;
  border: 7px solid #fff;
  border-radius: 3px;
  background: var(--ink);
  box-shadow: var(--sh-3);
  overflow: hidden;
  touch-action: pan-y;
}

.ba img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
}

.ba-after { clip-path: inset(0 0 0 var(--pos)); }

.ba-line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 4px;
  margin-left: -2px;
  background: #fff;
  box-shadow: 0 0 0 1px rgb(0 0 0 / .25);
  pointer-events: none;
}

.ba-line::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border: 3px solid #fff;
  border-radius: 50%;
  background:
    url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 7 4.5 12l5 5M14.5 7l5 5-5 5'/%3E%3C/svg%3E") center / 26px 26px no-repeat,
    var(--accent);
  box-shadow: 0 6px 18px rgb(0 0 0 / .45);
}

.ba-tag {
  position: absolute;
  top: 12px;
  padding: .28rem .7rem;
  border-radius: var(--r-sm);
  background: rgb(var(--ink-rgb) / .82);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #fff;
  pointer-events: none;
}

.ba-tag-before { left: 12px; }
.ba-tag-after  { right: 12px; }

.ba-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
}

.ba:has(.ba-range:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}


/* --- 24.12  Service area, FAQ, small parts ---------------------------------- */
.area-list {
  display: flex;
  flex-wrap: wrap;
  gap: .55rem .6rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.area-list li {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: 40px;
  padding: .35rem .95rem;
  border: 2px solid var(--hairline);
  border-radius: 999px;
  background: var(--raised);
  font-weight: 600;
  color: var(--on-surface);
}

.area-list li::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.area-list li.is-home {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

.area-list li.is-home::before { background: #fff; }

.faq {
  display: grid;
  gap: .7rem;
  max-width: 880px;
}

.faq-item {
  border: 1px solid var(--hairline);
  border-left: var(--accent-bar-lg) solid var(--accent);
  border-radius: var(--r-sm);
  background: var(--raised);
}

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 60px;
  padding: .85rem 1.1rem;
  cursor: pointer;
  list-style: none;
}

.faq-item summary::-webkit-details-marker { display: none; }

.faq-item summary::after {
  content: "";
  flex: none;
  width: 12px;
  height: 12px;
  margin-right: .25rem;
  border-right: 3px solid var(--accent);
  border-bottom: 3px solid var(--accent);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform var(--t);
}

.faq-item[open] summary::after { transform: rotate(-135deg) translate(-3px, -3px); }

.faq-q {
  font-size: clamp(1.08rem, 1rem + .4vw, 1.3rem);
  letter-spacing: .025em;
  line-height: 1.2;
}

.faq-a {
  padding: 0 1.1rem 1.1rem;
  color: var(--on-surface-mid);
  max-width: 70ch;
}

.faq-a a { color: var(--accent-text); font-weight: 700; }

/* a short row of the three words off the bottom of the flier */
.motto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .5rem 1.1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--text-light);
}

.motto li {
  display: inline-flex;
  align-items: center;
  gap: 1.1rem;
}

.motto li + li::before {
  content: "";
  width: 17px;
  height: 17px;
  background: var(--accent-bright);
  -webkit-mask: var(--flake) center / contain no-repeat;
  mask: var(--flake) center / contain no-repeat;
}

/* On a phone the three lines stack, centered, without the flakes: the
   last one is too long to share a row, and a flake stranded at the start of
   a wrapped line reads as a stray mark. */
@media (max-width: 559.98px) {
  .motto { flex-direction: column; gap: .45rem; letter-spacing: .15em; text-align: center; }
  .motto li { gap: 0; }
  .motto li + li::before { content: none; }
}

:root {
  --flake: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M12 2v20M3.3 7l17.4 10M20.7 7 3.3 17M12 5.5 9.6 3.6M12 5.5l2.4-1.9M12 18.5l-2.4 1.9M12 18.5l2.4 1.9M6.4 8.8 3.5 9.9M6.4 8.8 5.9 5.7M17.6 15.2l2.9-1.1M17.6 15.2l.5 3.1M17.6 8.8l2.9 1.1M17.6 8.8l.5-3.1M6.4 15.2l-2.9-1.1M6.4 15.2l-.5 3.1'/%3E%3C/svg%3E");
}

.text-link {
  color: var(--accent-text);
  font-weight: 700;
  text-decoration-color: var(--accent);
}

.sms-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1.5rem;
  margin-top: var(--sp-4);
  font-weight: 700;
}

/* two links standing on their own, so each one is a full 44px target */
.sms-row a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--accent-text);
  text-decoration-color: var(--accent);
}

/* Stencil correction. .field:last-child drops its bottom margin, which is
   right for the last field in a form and wrong for the last field in a
   .field-row: once the row stacks on a phone, the next row sits flush
   against it. Measured 12px between Phone and Address before this. */
.field-row > .field:last-child { margin-bottom: var(--sp-5); }

/* the radio pair on the quote form sits side by side even on a phone */
.checkgroup.is-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.form-ok {
  margin-top: var(--sp-4);
  padding: .9rem 1rem;
  border-left: var(--accent-bar-lg) solid #16834A;
  border-radius: var(--r-sm);
  background: #E9F6EE;
  font-weight: 600;
  color: #0F4D2C;
}


/* --- 24.12b  Call or text ----------------------------------------------------
   Mike would rather take calls and texts than forms (2026-10-06), so the
   quote page is a big phone number and two buttons, and the designer ends
   in a text message with the picks already typed in. */
.big-phone {
  display: inline-block;
  margin-top: var(--sp-2);
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 1.8rem + 4vw, 4.4rem);
  line-height: 1;
  letter-spacing: .02em;
  color: var(--on-surface);
  text-decoration: none;
  border-bottom: 4px solid var(--accent);
}

.big-phone:hover { color: var(--accent-text); }

.call-tip {
  margin-top: var(--sp-5);
  padding: .9rem 1.1rem;
  border-left: var(--accent-bar-lg) solid var(--accent);
  border-radius: var(--r-sm);
  background: var(--paper-2);
  color: var(--text-mid);
}

.call-tip strong { color: var(--text-dark); }

.designer-call {
  margin: 0;
  font-size: var(--fs-small);
  text-align: center;
  color: var(--text-light-mid);
}

.designer-call a { color: var(--glow); font-weight: 700; }


/* --- 24.12c  Service area map -------------------------------------------------
   Drawn by _src/make_scene.py from real coordinates (OpenStreetMap). Each
   town is pinned with a Christmas bulb whose tip sits on the town; the bulbs
   come on one at a time when the map scrolls into view. */
.area-grid {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

@media (min-width: 1000px) {
  .area-grid { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); }
}

.area-group + .area-group { margin-top: var(--sp-5); }

.area-group h3 {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-micro);
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: var(--label-tracking);
  color: var(--accent-text);
}

.section-dark .area-list li {
  border-color: var(--ink-3);
  background: var(--ink-2);
  color: var(--text-light);
}

.section-dark .area-list li.is-home {
  border-color: var(--accent);
  background: var(--accent);
}

.area-map { margin: 0; min-width: 0; }

.area-map-svg {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--ink-3);
  border-radius: var(--r-lg);
  background: #081a35;
}

.map-land { fill: #0A1C38; }

.map-rivers path {
  fill: none;
  stroke: #2F6DB5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.map-rivers .river-big   { stroke-width: 9; }
.map-rivers .river-mid   { stroke-width: 5; }
.map-rivers .river-small { stroke-width: 3; stroke: #285E9E; }

.map-state {
  fill: rgb(255 255 255 / .16);
  font-family: var(--font-body);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: .4em;
}

.map-scale path  { fill: none; stroke: rgb(255 255 255 / .55); stroke-width: 2; }
.map-scale text,
.map-north text  { fill: rgb(255 255 255 / .6); font-family: var(--font-body); font-size: 15px; font-weight: 700; }
.map-north path  { fill: rgb(255 255 255 / .6); }

.pin .pin-glass { fill: var(--bulb); transition: fill .5s ease; transition-delay: calc(var(--n) * 90ms); }
.pin .pin-sock  { fill: #173F2A; }
.pin .pin-shine { fill: #fff; opacity: .6; }
.pin .pin-halo  {
  fill: var(--bulb);
  opacity: .55;
  transition: opacity .7s ease;
  transition-delay: calc(var(--n) * 90ms);
}

/* dark until the map scrolls into view (lights.js adds .is-on) */
.js .area-map:not(.is-on) .pin-glass { fill: color-mix(in srgb, var(--bulb) 35%, #0c1626); }
.js .area-map:not(.is-on) .pin-halo  { opacity: 0; }
.lights-failsafe .area-map .pin-glass { fill: var(--bulb) !important; }

.map-label {
  fill: #fff;
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 700;
  paint-order: stroke;
  stroke: #0A1C38;
  stroke-width: 5px;
  stroke-linejoin: round;
}

.map-label.is-home { fill: var(--glow); font-size: 22px; }

/* on a phone the map is a third of its drawn size, so the labels grow
   inside it to stay readable (their spots were picked for this size) */
@media (max-width: 719.98px) {
  .map-label { font-size: 26px; stroke-width: 6px; }
  .map-label.is-home { font-size: 28px; }
  .map-state { font-size: 26px; }
  .map-scale text, .map-north text { font-size: 22px; }
}

.area-map figcaption {
  margin-top: var(--sp-3);
  font-size: var(--fs-micro);
  color: var(--text-light-mid);
}

.area-map figcaption a { color: inherit; }


/* --- 24.13  Footer ------------------------------------------------------------ */
/* Two columns since the link lists came out (2026-10-06, a quieter site). */
@media (min-width: 1000px) {
  .footer-grid.is-short { grid-template-columns: 1.5fr 1fr; }
}

.footer.has-strand {
  padding-top: calc(clamp(2.5rem, 6vw, 4rem) * var(--section-rhythm) + clamp(34px, 4vw, 56px));
  border-top: 0;   /* the strand is the top edge here */
}

.footer .motto li { color: var(--text-light); }

.footer-motto {
  margin-top: clamp(2rem, 5vw, 3rem);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--ink-3);
}

.footer-motto + .footer-bottom { margin-top: var(--sp-5); }


/* --- 24.14  Preview markers ---------------------------------------------------
   Anything Mike has not confirmed yet carries .ph: a dotted gold underline.
   The corner note explains it. Both vanish when PREVIEW is False in _build.py,
   and python _check.py counts what is left.
--------------------------------------------------------------------------- */
.ph {
  text-decoration: underline dotted #B8860B;
  text-decoration-thickness: 2px;
  text-underline-offset: .2em;
}

.hero .ph,
.section-dark .ph,
.vet-band .ph,
.footer .ph { text-decoration-color: var(--glow); }

.preview-note {
  position: fixed;
  left: 8px;
  bottom: calc(var(--call-bar-h) + 8px + env(safe-area-inset-bottom, 0px));
  z-index: 90;
  display: flex;
  align-items: center;
  gap: .5rem;
  max-width: calc(100vw - 16px);
  padding: .3rem .3rem .3rem .7rem;
  border: 2px solid var(--glow);
  border-radius: var(--r-md);
  background: rgb(var(--ink-rgb) / .96);
  box-shadow: 0 10px 28px rgb(0 0 0 / .45);
  font-size: .74rem;
  line-height: 1.35;
  color: var(--text-light);
}

.preview-note p { margin: 0; }
.preview-note b { color: var(--glow); }
.preview-note .preview-more { display: none; }

.preview-note button {
  flex: none;
  width: 44px;
  height: 44px;
  margin: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: var(--ink-3);
  font-size: 1.1rem;
  line-height: 1;
  color: #fff;
  cursor: pointer;
}

.preview-note[hidden] { display: none; }

@media (min-width: 721px) {
  .preview-note {
    left: auto;
    right: 12px;
    bottom: 12px;
    max-width: 350px;
    padding: .55rem .5rem .55rem .9rem;
    font-size: .8rem;
    line-height: 1.45;
  }

  .preview-note .preview-more { display: inline; }
}


/* --- 24.15  Reduced motion and print ------------------------------------------
   The stencil's section 22 already stops every animation and transition.
   These are the pieces that would otherwise be left in a half state: the
   snow, the twinkle layers, and the dimmed story steps.
--------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .hero-snow,
  .scene-snow { display: none; }

  .strand-glow,
  .skyline-lights,
  .scene-lights,
  .hero-stars { animation: none !important; opacity: 1; }

  .js .strand:not(.is-on) .glass { fill: var(--bulb); }
  .js .strand:not(.is-on) .shine { opacity: .62; }
  .js .strand:not(.is-on) .halo  { opacity: .62; }

  .js-story .story-step { opacity: 1; }
}

@media print {
  .strand,
  .skyline,
  .hero-sky,
  .hero-snow,
  .preview-note,
  .scene-snow { display: none !important; }
}
