/* ============================================================================
   NORTHLINE — STORM THEME
   Additive layer. Loads AFTER css/style.css and overrides nothing structurally.
   Delete the one <link> line and the site returns to the light theme exactly.

   Brand ink is unchanged: navy #1F3864 · green #2E7D32 · ice #4FA3E3.
   What changes is the GROUND. The three dark surfaces below are new and are
   not among canon's twelve theme colours. They need a canon amendment.

   Contrast note: .hero, .page-hero, .sec--navy, .cta-band, .site-header,
   .site-footer and .mobile-cta were ALREADY dark navy surfaces with measured
   contrast. This file does not touch their text colours, so those
   measurements survive. Only the surfaces that were white are inverted, and
   those are the ones that need re-measuring. The list is in STORM_README.md.
   ========================================================================= */

:root{
  /* ---- inverted ground ---- */
  --bg:#0B1729;            /* was #FFFFFF */
  --bg-alt:#0E1E36;        /* was #F7F9FC */

  /* ---- text on dark ---- */
  --char:#FFFFFF;          /* was #222222 */
  --body:#C3D0E2;          /* was #4A4A4A */
  --mut:#95A8C3;           /* was #6B7280. Measured: 7.42:1 on --bg and 4.79:1 on the
                              navy band. #8FA2BE was the first pick and it misses by a
                              hair, 4.47:1 on navy, so it is retired. */

  --line:rgba(124,160,206,.22);   /* was #E4E8ED */

  /* ---- pale tints become deep tints ---- */
  --green-p:#0F2417;       /* was #F3F8F3 */
  --ice-p:#0C2135;         /* was #F1F8FE */
  --navy-p:#101F38;        /* was #F4F6FA */
  --gold-p:#241C06;        /* was #FFF9EC */

  /* ---- gold has to lift off a dark ground ---- */
  --gold:#D9A521;          /* was #957000 (that value is for white grounds) */

  /* ---- shadows go from soft grey to real depth ---- */
  --shadow:0 2px 14px rgba(0,0,0,.45);
  --shadow-lg:0 26px 60px -26px rgba(0,0,0,.8);

  /* ---- SEASONAL ----
     The GROUND never changes between seasons. That is deliberate: contrast is
     measured once, not twice a year, and the brand stays one brand. What
     changes is the accent, the ambient light and the weather layer. ---- */
  --accent:#4FA3E3;                       /* winter: canon ice */
  --accent-soft:#9AC9EE;
  --accent-ink:#08131f;
  --accent-rgb:79,163,227;

  /* ---- storm-only tokens ---- */
  --surface:#14294A;
  --surface-2:#1A3157;
  --line-strong:rgba(124,160,206,.4);
  --ice-soft:#9AC9EE;
  --glow-ice:0 0 26px rgba(var(--accent-rgb),.45);
  --glow-green:0 0 28px rgba(46,125,50,.55);
}

:root[data-season="summer"]{
  --accent:#6FBF73;                       /* summer: canon green, lifted for a dark ground */
  --accent-soft:#A8DCAB;
  --accent-ink:#06170A;
  --accent-rgb:111,191,115;
  --glow-ice:0 0 26px rgba(111,191,115,.45);
}
/* seasonal content. Mark a block .nl-w for winter or .nl-s for summer. */
:root[data-season="winter"] .nl-s{ display:none !important; }
:root[data-season="summer"] .nl-w{ display:none !important; }

/* ---------------------------------------------------------------------------
   0. THE TYPE SYSTEM
   The site shipped on a system stack. The dark theme needs a display face with
   real weight or the headings go soft against the ground. Archivo for display,
   IBM Plex Sans for reading, IBM Plex Mono for every measured value: 3 cm,
   7 AM, $129, 26 cuts. The mono is not decoration — this business runs on
   thresholds, and the numbers should look like readings.
   ------------------------------------------------------------------------ */
@import url("https://fonts.googleapis.com/css2?family=Archivo:wght@500;600;700;800&family=IBM+Plex+Mono:wght@500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap");

:root{
  --font:"IBM Plex Sans","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --font-display:"Archivo","Helvetica Neue",Arial,sans-serif;
  --font-mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,Consolas,monospace;
}
body{ font-family:var(--font); }
h1,h2,h3,.plan h3,.tier,.offer-tier,.logo-text .name{
  font-family:var(--font-display); letter-spacing:-.015em;
}
h1{ font-weight:800; line-height:1.05; }
h2{ font-weight:800; line-height:1.12; }
h3{ font-weight:700; }
.eyebrow,.crumb,.plan-flag,.offer-flag,.btn,.nav-cta,.header-cta .btn{
  font-family:var(--font-display); font-weight:700;
}
.price,.offer-price,.price-cell,.num,.step .num,.yr,.per{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
}

/* ---------------------------------------------------------------------------
   1. ATMOSPHERE
   Two fixed light pools behind everything, and the snow canvas storm.js
   injects. Both sit at z-index 0 and never intercept a click.
   ------------------------------------------------------------------------ */
body{
  background:var(--bg);
  color:var(--body);
}
body::before{
  content:"";
  position:fixed; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(900px 540px at 82% -8%, rgba(var(--accent-rgb),.18), transparent 62%),
    radial-gradient(780px 480px at 4% 28%, rgba(46,125,50,.14), transparent 66%),
    radial-gradient(700px 700px at 50% 108%, rgba(31,56,100,.5), transparent 70%);
  transform:translate3d(0,var(--nl-par,0px),0);
}
#nl-snow{
  position:fixed; inset:0; width:100%; height:100%;
  z-index:0; pointer-events:none;
}
main, .site-footer, .mobile-cta, .skip-link{ position:relative; z-index:1; }

/* THE HEADER IS NOT IN THAT LIST, AND LEAVING IT THERE BROKE TWO THINGS.
   13 September 2026. `position:relative` overrode style.css's `position:sticky`,
   so the header stopped following the page down, and `z-index:1` overrode its
   `z-index:100`, which put it on the same layer as `main`. Same layer, later in
   the document, so MAIN PAINTED OVER THE HEADER: on a phone the open menu was
   drawn underneath the hero and read as transparent text lying across the
   headline. The header keeps its own sticky and sits above the page. */
.site-header{ z-index:200; }

/* AND THE OPEN MENU CARRIES ITS OWN OPAQUE GROUND.
   The header itself is glass on purpose, and glass is the wrong material for a
   panel with eleven links on it. The dropdown gets a solid ground and a shadow
   so it reads as a layer above the page rather than a watermark on it. */
@media (max-width:1180px){
  .nav{ background:#0E1E36; }
  .nav.open{
    background:#0E1E36;
    box-shadow:0 26px 44px -20px rgba(0,0,0,.9);
    border-bottom:1px solid var(--line-strong);
  }
}

/* ---------------------------------------------------------------------------
   2. THE SURFACES THAT WERE WHITE
   Every one of these is a literal #fff in style.css. They are the entire
   re-measurement surface.
   ------------------------------------------------------------------------ */
.card,
.plan,
.hero-card,
.offer,
.tbl,
.tbl tr{
  background:linear-gradient(168deg, var(--surface), var(--bg-alt));
  border:1px solid var(--line);
  box-shadow:var(--shadow-lg);
}
.card{ color:var(--body); }
.card h2, .card h3, .card h4, .plan h3, .offer h3{ color:#fff; }

/* a card sitting inside an already-navy band needs a rim or it disappears */
.sec--navy .card,
.page-hero .offer,
.hero .hero-card{
  border-color:var(--line-strong);
  background:linear-gradient(168deg, var(--surface-2), var(--surface));
}

.plan-body li{ border-bottom:1px solid var(--line); color:var(--body); }
.plan-body li:last-child{ border-bottom:0; }
.tbl th{ background:var(--navy); }
.tbl td{ border-color:var(--line); color:var(--body); }
.tbl tbody tr:nth-child(even){ background:rgba(26,49,87,.45); }

.photo{ border:2px dashed var(--line-strong); background:rgba(11,23,41,.5); }
.photo em{ color:var(--mut); }
.artmeta span+span::before{ background:var(--line-strong); }

.trust{
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
  background:rgba(11,23,41,.55);
}

/* form controls */
input, select, textarea{
  background:rgba(8,17,31,.75);
  border:1px solid var(--line-strong);
  color:#fff;
}
input::placeholder, textarea::placeholder{ color:#7A8DA9; }
input:focus, select:focus, textarea:focus{
  border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb),.25);
  outline:none;
}

/* the one button that assumed a white page behind it */
.btn--outline{
  background:transparent;
  color:#fff;
  border:1px solid var(--line-strong);
}
.btn--outline:hover{ background:rgba(31,56,100,.6); border-color:var(--accent); }

/* .btn--call stays white: canon calls white-on-navy the highest contrast
   pair the palette allows, and that is still true here. */
.offer .btn--call{ border-color:var(--line-strong); }

/* ---------------------------------------------------------------------------
   3. THE GLOW
   Spent in exactly two places: the primary action, and the header CTA.
   Everything else stays quiet or it stops reading as emphasis.
   ------------------------------------------------------------------------ */
.btn--primary{
  box-shadow:0 0 0 1px rgba(111,191,115,.35), var(--glow-green);
  transition:box-shadow .3s ease, transform .2s ease;
}
.btn--primary:hover{
  box-shadow:0 0 0 1px rgba(111,191,115,.7), 0 0 42px rgba(46,125,50,.8);
  transform:translateY(-1px);
}
/* The action colour is canon GREEN in both seasons, never the seasonal accent.
   It is what .btn--primary already is on the live site, white on #2E7D32 at
   5.13:1, and keeping it green is what stops the CTA reading as somebody
   else's cyan. The accent is atmosphere: eyebrows, glow, focus, the map. */
.header-cta .btn, .nav-cta{
  box-shadow:0 0 0 1px rgba(111,191,115,.45), var(--glow-green);
  animation:nl-breathe 4.6s ease-in-out infinite;
}
.header-cta .btn:hover, .nav-cta:hover{
  animation:none;
  box-shadow:0 0 0 1px rgba(111,191,115,.85), 0 0 40px rgba(46,125,50,.9);
}
@keyframes nl-breathe{
  0%,100%{ box-shadow:0 0 0 1px rgba(111,191,115,.4), 0 0 18px rgba(46,125,50,.45); }
  50%    { box-shadow:0 0 0 1px rgba(111,191,115,.75), 0 0 34px rgba(46,125,50,.8); }
}

/* The accent measures 4.25:1 on the navy band, under the 4.5:1 body-text floor.
   It clears everywhere else. On a navy surface, accent-coloured text steps up to
   the soft tint (6.62:1 winter, 7.46:1 summer) rather than the accent itself. */
.sec--navy .eyebrow, .sec--navy .nl-kv .k, .sec--navy .nl-sub,
.hero .eyebrow, .page-hero .eyebrow, .cta-band .eyebrow{ color:var(--accent-soft); }

/* headings pick up a faint halo, never an outline */
.hero h1, .page-hero h1{ text-shadow:0 0 46px rgba(var(--accent-rgb),.3); }
.sec-head h2, .cta-band h2{ text-shadow:0 0 34px rgba(var(--accent-rgb),.18); }

/* the header earns a glass treatment now that there is something behind it */
.site-header{
  background:rgba(11,23,41,.82);
  backdrop-filter:blur(14px) saturate(140%);
  -webkit-backdrop-filter:blur(14px) saturate(140%);
  border-bottom:1px solid var(--line);
}
.mobile-cta{
  background:rgba(11,23,41,.95);
  backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  border-top:1px solid var(--line-strong);
}

/* cards lift on hover — the cheapest depth cue there is */
.card, .plan, .offer{ transition:transform .26s ease, border-color .26s ease, box-shadow .26s ease; }
.card:hover, .plan:hover, .offer:hover{
  transform:translateY(-5px);
  border-color:var(--line-strong);
  box-shadow:0 34px 70px -30px rgba(0,0,0,.9);
}
.plan--featured, .plan--gold{
  border-color:rgba(217,165,33,.75);
  box-shadow:0 0 48px -16px rgba(217,165,33,.5), var(--shadow-lg);
}
.plan--featured:hover, .plan--gold:hover{ border-color:var(--gold); }

/* steps and notes keep their meaning, on deep tints instead of pale ones */
.step--ice .num{ background:var(--accent); color:var(--accent-ink); }
.step--navy .num{ background:var(--surface-2); color:#fff; }
.step--gold .num{ background:var(--gold); color:#1A1403; }
.note{ border-color:var(--line-strong); }
.note--ice{ background:var(--ice-p); }
.note--green{ background:var(--green-p); }
.note--gold{ background:var(--gold-p); }
.note--navy{ background:var(--navy-p); }

/* FAQ */
.faq-item{ border-color:var(--line); background:rgba(20,41,74,.4); }
.faq-item summary{ color:#fff; }
.faq-item[open]{ border-color:var(--line-strong); }

/* ---------------------------------------------------------------------------
   4. SECTION REVEAL
   From a VISIBLE resting state. Nothing is parked at opacity 0 waiting on an
   observer, so the page reads correctly with JS off and in a screenshot.
   ------------------------------------------------------------------------ */
.nl-reveal{ opacity:1; transform:none; }
html.nl-js .nl-reveal{ opacity:0; transform:translateY(18px); transition:opacity .7s ease, transform .7s cubic-bezier(.22,.9,.28,1); }
html.nl-js .nl-reveal.nl-in{ opacity:1; transform:none; }

/* ===========================================================================
   5. TIER-4 COMPONENTS
   Each renders into a <div data-nl="..."> anchor. If storm.js does not run,
   the anchor's own fallback text is what a reader gets, and it is real text.
   ======================================================================== */

.nl-card{
  background:linear-gradient(168deg, var(--surface), var(--bg-alt));
  border:1px solid var(--line-strong);
  border-radius:14px;
  padding:24px;
  box-shadow:var(--shadow-lg);
}
.nl-h{
  font-size:.95rem; font-weight:700; color:#fff; margin:0 0 4px;
  letter-spacing:.01em;
}
.nl-sub{
  font-size:.76rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--mut); margin:0 0 18px;
}
.nl-kv-k, .nl-tl-key{ color:var(--accent); }
.nl-mono{ font-variant-numeric:tabular-nums; font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace; }

/* ---- 5a. the 3 cm gauge ---------------------------------------------- */
.nl-gauge-body{ display:flex; gap:20px; align-items:stretch; }
.nl-tube{
  position:relative; width:64px; flex:0 0 64px; height:180px;
  border-radius:9px; background:rgba(6,14,26,.9);
  border:1px solid var(--line); overflow:hidden;
}
.nl-fill{
  position:absolute; left:0; right:0; bottom:0; height:0;
  background:linear-gradient(180deg,#EAF4FD,#9AC9EE 55%,#4FA3E3);
  box-shadow:0 0 26px rgba(154,201,238,.6);
  transition:height 1.8s cubic-bezier(.22,.9,.28,1);
}
.nl-tick{ position:absolute; left:0; right:0; border-top:1px dashed rgba(154,201,238,.25); }
.nl-tick b{ position:absolute; right:5px; top:-8px; font-size:.6rem; color:var(--mut); font-weight:500; }
.nl-tick.nl-trip{ border-top:1.5px solid var(--green-l); }
.nl-tick.nl-trip b{ color:var(--green-l); font-weight:700; }
.nl-read{ flex:1; display:flex; flex-direction:column; justify-content:space-between; }
.nl-num{ font-size:2.7rem; line-height:1; color:#fff; font-weight:600; text-shadow:0 0 30px rgba(154,201,238,.45); }
.nl-num small{ font-size:1rem; color:var(--mut); margin-left:3px; }
.nl-status{ margin-top:14px; border-radius:10px; padding:12px 14px; border:1px solid var(--line); background:rgba(8,17,31,.6); transition:.5s; }
.nl-status .nl-st{ font-size:.78rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--mut); }
.nl-status p{ margin:6px 0 0; font-size:.8rem; line-height:1.5; color:var(--mut); }
.nl-status.nl-live{ border-color:rgba(111,191,115,.6); background:rgba(46,125,50,.16); box-shadow:0 0 30px -6px rgba(111,191,115,.4); }
.nl-status.nl-live .nl-st{ color:var(--green-l); }
.nl-status.nl-live p{ color:var(--body); }
.nl-dot{ display:inline-block; width:7px; height:7px; border-radius:50%; background:var(--mut); margin-right:8px; vertical-align:middle; }
.nl-live .nl-dot{ background:var(--green-l); animation:nl-ping 2s ease-out infinite; }
@keyframes nl-ping{ 0%{box-shadow:0 0 0 0 rgba(111,191,115,.6)} 70%{box-shadow:0 0 0 9px rgba(111,191,115,0)} 100%{box-shadow:0 0 0 0 rgba(111,191,115,0)} }

/* ---- 5b. the windrow scene (real 3D transforms) -----------------------
   Two vehicles. The CITY plow creates the windrow; a NORTHLINE truck is what
   removes it. The driveway starts clear, because that is the true order.    */
.nl-scene{ perspective:1100px; padding:14px 0 4px; }
.nl-stage{
  position:relative; width:100%; aspect-ratio:16/9; max-width:100%;
  transform-style:preserve-3d;
  transform:rotateX(56deg) rotateZ(-24deg) scale(.84);
  transition:transform .9s cubic-bezier(.22,.9,.28,1);
  margin-inline:auto;
}
.nl-stage.nl-flat{ transform:rotateX(0deg) rotateZ(0deg) scale(1); }
.nl-plane{ position:absolute; inset:0; border-radius:8px; transform-style:preserve-3d; }
.nl-lawn{ background:linear-gradient(160deg,#17351F,#0F2417); border:1px solid rgba(111,191,115,.28); }
/* the drive runs from the garage door down to the road, not from thin air */
.nl-drive{
  position:absolute; left:41%; top:30%; width:17%; height:54%;
  background:linear-gradient(180deg,#1B2C46,#16233A);
  border:1px solid rgba(124,160,206,.45); transform:translateZ(3px);
}
/* ---- the house ------------------------------------------------------
   It was one flat rectangle with three yellow squares on it, floating on
   the lawn with a gap between it and the driveway, which is the one thing
   a homeowner looks at first: nobody's driveway starts in the middle of
   the grass. It is now read the way you actually see a house from this
   angle, which is AS A ROOF: two slopes meeting at a ridge, a chimney, an
   eave shadow standing in for the walls, and warm light spilling out of
   the front windows onto the lawn.

   The garage is the part that matters. It is a second roof mass with its
   own ridge running the other way, it sits at the head of the driveway,
   and its door is the lit band where the two meet, so the driveway
   visibly RUNS OUT OF THE HOUSE instead of lying beside it. */
.nl-house{
  position:absolute; left:13%; top:3%; width:45%; height:31%;
  transform-style:preserve-3d; transform:translateZ(0);
}
/* the walls, seen only as the shadow the roof overhangs */
.nl-h-walls{
  position:absolute; left:2%; top:6%; width:56%; height:92%;
  background:#0D1729; border-radius:2px;
  transform:translateZ(16px);
  box-shadow:0 22px 30px -16px rgba(0,0,0,.95);
}
/* the main roof: two slopes, lit from the south west, meeting at a ridge */
.nl-h-roof{
  position:absolute; left:0; top:0; width:60%; height:100%;
  background:linear-gradient(180deg,#43608E 0%,#3A5480 49.5%,#1A2841 50.5%,#141F36 100%);
  border:1px solid rgba(140,170,210,.5); border-radius:2px;
  transform:translateZ(30px);
  box-shadow:0 26px 34px -18px rgba(0,0,0,.9);
}
.nl-h-ridge{
  position:absolute; left:-1%; right:-1%; top:50%; height:2px; margin-top:-1px;
  background:rgba(206,224,246,.85); box-shadow:0 0 6px rgba(206,224,246,.35);
}
/* the garage: its ridge runs toward the driveway, not along the house */
.nl-h-gar{
  position:absolute; left:55%; top:18%; width:45%; height:94%;
  background:linear-gradient(90deg,#3C5885 0%,#354E76 49.5%,#18253C 50.5%,#131E31 100%);
  border:1px solid rgba(140,170,210,.5); border-radius:2px;
  transform:translateZ(25px);
  box-shadow:0 22px 30px -16px rgba(0,0,0,.9);
}
.nl-h-gridge{
  position:absolute; top:-1%; bottom:-1%; left:50%; width:2px; margin-left:-1px;
  background:rgba(206,224,246,.8); box-shadow:0 0 6px rgba(206,224,246,.3);
}
/* the garage door, the lit band where the house meets the driveway */
.nl-h-gdoor{
  position:absolute; left:9%; right:9%; bottom:-4.5%; height:15%;
  background:linear-gradient(180deg,#8FB0D8,#5C7FB0);
  border:1px solid rgba(200,220,245,.8); border-radius:1.5px;
  box-shadow:0 0 20px rgba(143,176,216,.55), 0 6px 12px -4px rgba(0,0,0,.8);
}
.nl-h-chim{
  position:absolute; left:20%; top:36%; width:6%; height:11%;
  background:#2C3F5C; border:1px solid rgba(160,185,220,.6); border-radius:1px;
  transform:translateZ(44px);
  box-shadow:0 10px 14px -6px rgba(0,0,0,.9);
}
/* three lit windows along the front of the house */
.nl-house i{
  position:absolute; bottom:2%; width:8%; height:6.5%;
  background:#F4C86A; border-radius:1px;
  transform:translateZ(20px);
  box-shadow:0 0 13px rgba(244,200,106,.85);
}
.nl-house i:nth-of-type(1){ left:8%; }
.nl-house i:nth-of-type(2){ left:24%; }
.nl-house i:nth-of-type(3){ left:40%; }
/* and the light those windows throw onto the grass */
.nl-h-spill{
  position:absolute; left:-5%; bottom:-24%; width:70%; height:32%;
  background:radial-gradient(ellipse at 50% 20%, rgba(244,200,106,.22), rgba(244,200,106,.07) 45%, transparent 72%);
  transform:translateZ(1px); pointer-events:none;
}
.nl-road{ position:absolute; left:0; right:0; bottom:0; height:16%; background:#070E1A;
  border-top:1px solid rgba(124,160,206,.3); transform:translateZ(1px); }

/* the ridge does not exist until the city plow has been past */
.nl-ridge{
  position:absolute; left:41%; bottom:13.5%; width:19%; height:6%;
  background:linear-gradient(180deg,#FFF,#9AC9EE); border-radius:42% 42% 12% 12%;
  transform:translateZ(9px) scaleY(0); transform-origin:bottom; opacity:0;
  box-shadow:0 0 22px rgba(154,201,238,.7); transition:opacity .45s, transform .45s;
}
.nl-stage.has-ridge .nl-ridge{ opacity:1; transform:translateZ(9px) scaleY(1); }

.nl-veh{ position:absolute; transform:translateZ(14px); opacity:0; pointer-events:none; }
/* THE TRUCK FACES THE WAY IT IS DRIVING.
   Both vehicles are drawn facing LEFT: the blade is at the left end, the cab
   sits behind it and the windshield slopes toward the rear on the right. The
   animation runs them left to right. So the plow was being pushed backwards
   down the road, blade trailing, which is the one detail an operator notices
   in a second and a designer never does. The artwork is mirrored rather than
   redrawn, because the blade, the cab and the box are the same shapes either
   way and only the door panel bars change hand, which reads identically. */
.nl-veh svg{ width:100%; height:auto; display:block; transform:scaleX(-1);
             filter:drop-shadow(0 6px 10px rgba(0,0,0,.6)); }
/* SEATED ON THE ROAD, NOT ON THE VERGE. The road band is the bottom 16% of the
   plane and both trucks were parked at 12.5% and 16.5%, which after the stage
   rotation put their wheels on the grass beside it. A municipal plow driving
   down somebody's lawn undercuts the whole point of the diagram. */
.nl-v-city{ bottom:4%; left:-24%; width:21%; }
.nl-v-nl{ bottom:6.5%; left:-24%; width:19%; }
.nl-stage.run-city .nl-v-city{ opacity:1; animation:nl-drive 3.4s linear forwards; }
.nl-stage.run-nl   .nl-v-nl  { opacity:1; animation:nl-drive 3.4s linear forwards; }
@keyframes nl-drive{ from{ left:-24%; } to{ left:108%; } }

.nl-cap{ margin:16px 0 0; font-size:.86rem; line-height:1.5; color:var(--body); min-height:42px; }
.nl-cap b{ color:#fff; }
.nl-legend{ display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; }
.nl-legend button{
  font:inherit; font-size:.78rem; font-weight:600; cursor:pointer;
  color:var(--body); background:rgba(20,41,74,.7);
  border:1px solid var(--line-strong); border-radius:999px; padding:7px 15px;
}
.nl-legend button:hover{ color:#fff; border-color:var(--accent); }
.nl-legend button[disabled]{ opacity:.4; cursor:not-allowed; }
.nl-legend button[aria-pressed="true"]{ background:var(--green); color:#fff; border-color:var(--green-l); }

/* ---- 5c. service area map -------------------------------------------- */
.nl-map{ position:relative; width:100%; }
.nl-map svg{ width:100%; height:auto; display:block; overflow:visible; }
.nl-town{ cursor:pointer; }
a.nl-town{ text-decoration:none; outline:none; }
a.nl-town:focus-visible circle.nl-halo{ r:26; opacity:.55; }
a.nl-town:focus-visible text{ fill:#fff; }
.nl-town--here{ cursor:default; }
.nl-town circle{ transition:r .25s ease, opacity .25s ease; }
.nl-town:hover circle.nl-halo, .nl-town:focus-visible circle.nl-halo{ r:26; opacity:.55; }
.nl-town text{ font-size:12px; font-weight:600; fill:var(--body); }
.nl-kv .k{ color:var(--accent); }
.nl-town:hover text{ fill:#fff; }
.nl-base{ fill:var(--green-l); }

/* ---- 5d. address checker --------------------------------------------- */
.nl-check form{ display:flex; gap:10px; flex-wrap:wrap; }
.nl-check input{ flex:1 1 220px; border-radius:8px; padding:12px 14px; font:inherit; }
.nl-check button{ font:inherit; }
.nl-result{ margin-top:16px; border-radius:10px; padding:14px 16px; border:1px solid var(--line); background:rgba(8,17,31,.6); font-size:.88rem; line-height:1.55; color:var(--body); }
.nl-result.nl-yes{ border-color:rgba(111,191,115,.6); background:rgba(46,125,50,.16); }
.nl-result.nl-maybe{ border-color:rgba(217,165,33,.55); background:rgba(217,165,33,.12); }
.nl-result.nl-no{ border-color:var(--line-strong); }
.nl-result b{ color:#fff; }

/* ---------------------------------------------------------------------------
   6. MOTION AND ACCESS
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce){
  #nl-snow{ display:none; }
  *, *::before, *::after{ animation:none !important; transition:none !important; }
  html.nl-js .nl-reveal{ opacity:1 !important; transform:none !important; }
  .nl-stage{ transform:rotateX(0) rotateZ(0) scale(1) !important; }
}
@media (max-width:760px){
  .nl-gauge-body{ flex-direction:column; }
  .nl-tube{ width:100%; flex:0 0 84px; height:84px; }
  .nl-stage{ transform:rotateX(40deg) rotateZ(-14deg) scale(.92); }
}

/* ---------------------------------------------------------------------------
   7. DEPTH
   The 3D on this site is CSS perspective, not a scene and not a library.
   Angles stay under 7 degrees: six reads as a physical object catching light,
   twenty reads as a toy. storm.js drives the pointer tracking and switches
   itself off on touch, under 940px, and under reduced motion.
   ------------------------------------------------------------------------ */
.hero-grid, .plans{ perspective:1400px; }
.hero .hero-card{
  transform:rotateY(-7deg) rotateX(2deg);
  transition:transform .5s cubic-bezier(.22,.9,.28,1), box-shadow .5s ease;
  box-shadow:-24px 34px 70px -34px rgba(0,0,0,.95), inset 0 0 0 1px rgba(124,160,206,.16);
}
.hero .hero-card:hover{ transform:rotateY(-2deg) rotateX(0deg) translateZ(14px); }
.nl-tilt{ transform-style:preserve-3d; will-change:transform; }

/* ---------------------------------------------------------------------------
   8. STORM-NIGHT TIMELINE
   <ol class="nl-tl"> with <li><span class="nl-tl-t">…</span><div class="nl-tl-c">…
   Numbered because the ORDER is the product. Add class nl-tl-hit to the row
   that carries the 3 cm trigger.
   ------------------------------------------------------------------------ */
.nl-tl{ list-style:none; margin:0; padding:0; position:relative; max-width:820px; }
.nl-tl::before{ content:""; position:absolute; left:112px; top:10px; bottom:10px; width:1px;
  background:linear-gradient(180deg,transparent,var(--line-strong) 12%,var(--line-strong) 88%,transparent); }
.nl-tl li{ display:grid; grid-template-columns:112px 1fr; gap:0 34px; position:relative; padding-block:16px; }
.nl-tl li::after{ content:""; position:absolute; left:108px; top:24px; width:9px; height:9px;
  border-radius:50%; background:var(--bg); border:1.5px solid var(--line-strong); }
.nl-tl-t{ font-family:var(--mono,ui-monospace,Menlo,monospace); font-variant-numeric:tabular-nums;
  font-size:.82rem; font-weight:600; color:var(--mut); text-align:right; padding-top:2px; }
.nl-tl-c h3{ font-size:1rem; margin:0 0 5px; color:#fff; }
.nl-tl-c p{ margin:0; font-size:.9rem; color:var(--body); line-height:1.6; max-width:58ch; }
.nl-tl-hit .nl-tl-t{ color:var(--green-l); font-weight:700; }
.nl-tl-hit::after{ background:var(--green-l); border-color:var(--green-l);
  box-shadow:0 0 0 4px rgba(111,191,115,.18), 0 0 18px rgba(111,191,115,.75); }
.nl-tl-hit .nl-tl-c h3{ color:var(--green-l); }

@media (max-width:940px){
  .hero .hero-card{ transform:none; }
  .nl-tl::before{ left:8px; }
  .nl-tl li{ grid-template-columns:1fr; gap:4px; padding-left:34px; padding-block:14px; }
  .nl-tl li::after{ left:4px; top:20px; }
  .nl-tl-t{ text-align:left; }
}
@media (prefers-reduced-motion: reduce){
  .hero .hero-card{ transform:none !important; }
  .nl-tilt{ transform:none !important; }
}

/* ---------------------------------------------------------------------------
   9. THE SUMMER WEEK GRID  —  <div class="nl-weeks"></div>
   ------------------------------------------------------------------------ */
.nl-weeks{ width:64px; flex:0 0 64px; height:180px; display:grid;
  grid-template-columns:repeat(4,1fr); grid-auto-rows:1fr; gap:4px; }
.nl-weeks i{ display:block; border-radius:2px; background:rgba(111,191,115,.2);
  border:1px solid rgba(111,191,115,.3); }
.nl-weeks i.on{ background:var(--green-l); box-shadow:0 0 8px rgba(111,191,115,.55); }
@media (max-width:760px){ .nl-weeks{ width:100%; flex:0 0 84px; height:84px; grid-template-columns:repeat(13,1fr); } }

/* ---------------------------------------------------------------------------
   10. THE RENDERED SWEEP, AND WHAT IT FOUND
   Sections 2 and 3 inverted the SURFACES. They did not touch the INK, and the
   site sets most of its ink from --navy and --green, both of which are fills
   chosen for a white page. Measured across all 44 pages: 960 failing elements.
   The three big families were headings at 1.55:1, body links at 3.50:1 and the
   gold button at 2.24:1. Everything below is measured, not guessed.
   ------------------------------------------------------------------------ */

/* Headings. The site sets h2/h3 to navy #1F3864, which is 1.55:1 on the ground.
   --navy is NOT changed: it is still the brand fill for bands and table heads.
   Only the ink moves. */
h1, h2, h3, h4, h5, h6,
.sec-head h2, .card h3, .plan h3, .offer h3, .faq-item summary{
  color:var(--char);                       /* #FFFFFF, 17.97:1 on --bg */
}
.tier, .plan-head .tier, .offer-tier{ color:#fff; }

/* Body links. Canon green is 3.50:1 on the ground and fails the 4.5 floor.
   Same hue, lifted for a dark page, exactly as --gold was: 8.02:1. */
main a, .card a, .faq-body a, .note a, li a, p a, td a, dd a{
  color:var(--green-l);
}
main a:hover, .card a:hover, p a:hover, li a:hover{ color:#9BD79E; }

/* The gold button. Lifting --gold to #D9A521 for text legibility broke the one
   place gold is a FILL: white on it measured 2.24:1. Dark ink on gold is
   7.52:1 and is the only pairing that works on a gold pill. */
.btn--gold, .plan-flag, .offer-flag{
  background:var(--gold);
  color:#241C06 !important;
}
.btn--gold:hover{ background:#C79418; color:#241C06 !important; }

/* Prices, table price cells and inline emphasis all read from --green or --mut */
.price-cell, .price, .offer-price, .plan .price{ color:var(--green-l); }
em, .photo em, .form-note, .offer-fine, .small, .muted{ color:var(--mut); }

/* Form labels were navy on the ground */
label, legend, .field label, .form-grid label{ color:var(--body); }
label b, label strong{ color:#fff; }

/* A checkbox consent line and its helper text */
.field span, .form-note span{ color:var(--body); }

/* Steps, notes and callouts set their heading from --navy too */
.step h3, .note h2, .note h3, .note--band strong, .card-body h3{ color:#fff; }
.note--ice h2, .note--ice h3, .note--green h2, .note--green h3,
.note--gold h2, .note--gold h3, .note--navy h2, .note--navy h3{ color:#fff; }
.note, .note p, .note li{ color:var(--body); }

/* The last 21, each pinned to the rule that declared them ------------------ */

/* .tbl .price-cell beat a bare .price-cell on specificity */
.tbl .price-cell, .tbl td.price-cell{ color:var(--green-l); }

/* .trust li is set to navy, 1.55:1 on the ground */
.trust li{ color:var(--body); }
.trust li strong, .trust li b{ color:#fff; }

/* the consent helper em declares its own --mut, which is a light-page grey */
.field em, .form-note em, form em{ color:var(--mut); }

/* A white card inside a navy band deliberately keeps navy ink, because the card
   used to be white. It is not any more, so the rule inverts with the surface. */
.sec--navy .card h3, .sec--navy .card h4{ color:#fff; }
.sec--navy .card, .sec--navy .card p, .sec--navy .card li{ color:var(--body); }

/* An INLINE style="color:#5A6B80" on the consent note beat every stylesheet
   rule, because inline wins on specificity. The inline colour was removed from
   the two pages that carried it and replaced with this class. The rule below is
   the belt to that braces, in case the generator re-emits the inline form. */
.consent-note{ color:var(--mut); }
[style*="#5A6B80"], [style*="#5a6b80"]{ color:var(--mut) !important; }

/* ---------------------------------------------------------------------------
   11. THE HERO GROUND
   The site's hero paints its own lighter navy gradient, which is why a themed
   page and the design preview did not look like the same site. The hero now
   sits on the SAME ground as the page, with the light pool behind it, and the
   type treatment carries the weight instead of a background change.
   ------------------------------------------------------------------------ */
.hero, .page-hero{
  background:transparent !important;
  position:relative;
}
/* THE POOL IS DRAWN INSIDE THE BOX, NEVER WIDER THAN IT.
   This was `width:130%` centred with a translate, which put 15 per cent of the
   ellipse outside the viewport on every page whose hero is `.page-hero`, and
   `.page-hero` has no overflow rule of its own the way `.hero` does. The
   result was 216px of horizontal scroll at 1440 and 59px at 390 on roughly
   forty pages, which on a phone is a page that slides sideways under the
   thumb. The ellipse is now shaped by the GRADIENT rather than by an
   oversized box, so it reads the same and cannot overflow anything. */
.hero::before, .page-hero::before{
  content:""; position:absolute; left:0; right:0; top:-18%; height:96%;
  z-index:0; pointer-events:none; border-radius:50%;
  background:radial-gradient(ellipse 66% 100% at 50% 50%,
    rgba(var(--accent-rgb),.17), rgba(31,56,100,.11) 42%, transparent 68%);
}
.hero::after, .page-hero::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px; z-index:0;
  background:linear-gradient(90deg,transparent,rgba(124,160,206,.45),transparent);
}
.hero > *, .page-hero > *{ position:relative; z-index:1; }
.hero h1, .page-hero h1{ text-shadow:0 2px 40px rgba(0,0,0,.7), 0 0 50px rgba(var(--accent-rgb),.3); }

/* the hero's own card sits on its plane, as it does in the design */
.hero-grid, .page-hero .wrap{ perspective:1400px; }
.hero-card, .page-hero .offer{
  transform:rotateY(-6deg) rotateX(1.5deg);
  transition:transform .5s cubic-bezier(.22,.9,.28,1), box-shadow .5s ease;
  box-shadow:-24px 34px 70px -34px rgba(0,0,0,.95), inset 0 0 0 1px rgba(124,160,206,.16);
}
.hero-card:hover, .page-hero .offer:hover{ transform:rotateY(-2deg) rotateX(0) translateZ(14px); }
@media (max-width:940px){ .hero-card, .page-hero .offer{ transform:none; } }

/* the block storm.js injects, so it sits like a section of the page */
.nl-block{ padding-block:var(--nl-gap,64px); }
.nl-block > .wrap{ max-width:var(--wrap); margin-inline:auto; padding-inline:24px; }
.nl-two{ display:grid; grid-template-columns:1.1fr .9fr; gap:20px; align-items:start; }
.nl-two > *{ height:100%; }
@media (max-width:940px){ .nl-two{ grid-template-columns:1fr; } }
.nl-lead{ max-width:60ch; margin:0 0 28px; }
.nl-lead h2{ margin:0 0 10px; }
.nl-lead p{ margin:0; color:var(--body); }

/* ---------------------------------------------------------------------------
   12. THE HERO PROOF CHIPS
   The site renders the proof line as green-ticked text and the reassurance line
   as pills. In the design both are pills, which reads as a row of credentials
   rather than a bulleted list. Same markup, restyled.
   ------------------------------------------------------------------------ */
.hero-proof{ gap:8px; margin-top:24px; }
.hero-proof li{
  padding:6px 14px;
  border:1px solid var(--line-strong);
  border-radius:999px;
  background:rgba(11,23,41,.62);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  font-size:.8rem; font-weight:500; color:var(--body);
}
.hero-proof li::before{ display:none; }        /* the tick belongs on a list, not a chip */
.hero-proof li strong, .hero-proof li b{ color:#fff; font-weight:600; }

.hero-badges li{
  background:rgba(11,23,41,.62);
  border:1px solid var(--line-strong);
  color:var(--body); font-size:.8rem;
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}

/* the eyebrow carries the season, as it does in the design */
.eyebrow, .eyebrow--light{
  font-family:var(--font-mono); font-weight:600; font-size:.72rem;
  letter-spacing:.2em; text-transform:uppercase; color:var(--accent);
}

/* ---------------------------------------------------------------------------
   13. THE SEASONAL CARD IN THE HERO
   Inner pages ship a single-column hero. storm.js splits it into the two
   columns the design uses and puts the seasonal card on the right: the 3 cm
   gauge in winter, the 26-week season card in summer. The home page keeps its
   Gold offer card instead, because a diagram should not displace the price.
   ------------------------------------------------------------------------ */
.nl-herogrid{
  display:grid; grid-template-columns:1.05fr .95fr; gap:52px; align-items:center;
  perspective:1400px; perspective-origin:60% 50%;
}
.nl-heroright{ transform-style:preserve-3d; }
.nl-heroright > .nl-card{
  transform:rotateY(-6deg) rotateX(1.5deg);
  transition:transform .5s cubic-bezier(.22,.9,.28,1), box-shadow .5s ease;
  box-shadow:-24px 34px 70px -34px rgba(0,0,0,.95), inset 0 0 0 1px rgba(124,160,206,.16);
}
.nl-heroright > .nl-card:hover{ transform:rotateY(-2deg) rotateX(0) translateZ(14px); }
@media (max-width:940px){
  .nl-herogrid{ grid-template-columns:1fr; gap:34px; }
  .nl-heroright > .nl-card{ transform:none; }
}

/* ---------------------------------------------------------------------------
   14. THE PHONE. THE ACTION COMES BEFORE THE EXPLANATION.
   Measured on a 390px viewport, 13 September 2026: the first button sat 629px
   down, behind a four line headline, a six line paragraph and four proof chips,
   and a phone shows about 500px of page under the browser chrome. The most
   motivated visitor this business gets, somebody searching snow removal on a
   phone, had to scroll before they could act.

   NOTHING IS HIDDEN AND NO WORD IS CUT. The order is changed, which is a mobile
   pattern rather than a compromise: headline, action, then the explanation for
   the people who want it. A visitor who already knows what they want stops
   reading at the button, and a visitor who does not carries on down the page.
   ------------------------------------------------------------------------ */
@media (max-width:760px){
  /* THREE SHAPES, ONE RULE. The home page hero puts its copy in .hero-left; an
     inner page puts it straight in the .wrap; and once the season card is added
     that .wrap holds a .nl-heroleft instead. All three are the same box doing
     the same job, so all three are ordered the same way. */
  .hero .hero-left,
  .page-hero > .wrap,
  .page-hero .nl-heroleft{ display:flex; flex-direction:column; }
  .hero .hero-left > *,
  .page-hero > .wrap > *,
  .page-hero .nl-heroleft > *{ order:8; }
  .hero .hero-left > .eyebrow,
  .page-hero > .wrap > .eyebrow,
  .page-hero .nl-heroleft > .eyebrow{ order:1; }
  .hero .hero-left > h1,
  .page-hero > .wrap > h1,
  .page-hero .nl-heroleft > h1{ order:2; line-height:1.1; margin-bottom:8px; }
  .hero .hero-left > .btn-row,
  .page-hero > .wrap > .btn-row,
  .page-hero .nl-heroleft > .btn-row{ order:3; margin:0 0 10px; }
  /* THE SECOND ACTION SITS WITH THE FIRST, NOT BELOW THE READING.
     `.hero-link` is the alternative way to act: the quote form on /contact, the
     memberships page everywhere else. It was landing after the proof chips,
     which on /contact put the form link off the bottom of a phone screen while
     the page's whole job is collecting that form. Two ways to act, both above
     the fold, then the explanation. */
  /* THE SECOND ACTION SITS WITH THE FIRST ONLY WHERE IT IS AN ACTION.
     `.hero-link` is two different things wearing one class. On /contact it
     points at the quote form ON THIS PAGE, which is the page's entire job, and
     it belongs beside the buttons. Everywhere else it points at another page,
     which is navigation, and putting navigation above the opening paragraph
     costs two lines of the only paragraph anybody reads. Told apart by where
     the link GOES, not by a list of pages: an in page anchor is an action, a
     link to somewhere else is not. A browser without `:has()` gets the safe
     half of this, which is the link after the paragraph. */
  .hero .hero-left > .hero-link,
  .page-hero > .wrap > .hero-link,
  .page-hero .nl-heroleft > .hero-link{ order:6; margin:0 0 12px; }
  .hero .hero-left > .hero-link:has(a[href^="#"]),
  .page-hero > .wrap > .hero-link:has(a[href^="#"]),
  .page-hero .nl-heroleft > .hero-link:has(a[href^="#"]){ order:4; }
  .hero .hero-left > .lead,
  .page-hero > .wrap > .lead,
  .page-hero .nl-heroleft > .lead{ order:5; margin-bottom:12px; line-height:1.5; }
  .hero .hero-left > .hero-proof,
  .page-hero > .wrap > .hero-proof,
  .page-hero .nl-heroleft > .hero-proof{ order:7; }
  /* the grid itself must stay first in the wrap, or the card outruns the copy */
  .page-hero > .wrap > .nl-herogrid{ order:1; }

  /* THE BUTTONS ARE FULL WIDTH AND STACKED, AND THEY ARE NOT OVERSIZED.
     Measured at 375px: two buttons at 60px each, with an 18px gap under them,
     pushed the opening paragraph to exactly 500px, which is the fold. The
     paragraph was cut in half by the pinned bar on a real phone. 48px is a
     comfortable target and it is still four pixels above the 44px
     accessibility floor, so this buys 24px of readable page and costs nothing
     anybody can feel with a thumb. */
  .hero .hero-left > .btn-row .btn,
  .page-hero > .wrap > .btn-row .btn,
  .page-hero .nl-heroleft > .btn-row .btn{
    width:100%; justify-content:center;
    min-height:48px; padding:11px 18px; font-size:.97rem;
  }
  .hero .hero-left > .btn-row,
  .page-hero > .wrap > .btn-row,
  .page-hero .nl-heroleft > .btn-row{ gap:10px; }

  /* AND THE ACTION NEVER LEAVES THE SCREEN.
     The page already carries a mobile call to action block and it was sitting
     at the very bottom, 23,910px down, where it is not a call to action, it is
     a footer. Pinned to the bottom of the viewport it is reachable from every
     word of every page, which on a one truck local business is the difference
     between an inquiry and a back button. */
  .mobile-cta{
    position:fixed; left:0; right:0; bottom:0; z-index:60;
    margin:0; border-radius:0;
    border-top:1px solid var(--line-strong);
    background:rgba(8,17,31,.94);
    backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
    box-shadow:0 -10px 30px -12px rgba(0,0,0,.8);
    padding:7px 12px calc(7px + env(safe-area-inset-bottom));
  }
  /* AND THE PINNED BAR IS A BAR, NOT A PANEL. It was 77px tall with 56px
     buttons, which is a seventh of a phone screen spent on two words, and it
     was the thing cutting the opening paragraph in half. 46px buttons in a
     60px bar are still a comfortable thumb target and give the page back
     seventeen pixels where they matter most, at the bottom of the fold. */
  .mobile-cta .btn{ min-height:46px; padding:10px 14px; font-size:.95rem; }
  body{ padding-bottom:72px; }
}

/* ---------------------------------------------------------------------------
   15. THE TOUCH TILT SURFACE
   The script sets an inline transform every frame, so nothing here may set a
   competing one. What this rule does is give the card a real 3D context and a
   transition for the frames BETWEEN animation bursts, so a card that has
   settled and is then touched does not jump.
   ------------------------------------------------------------------------ */
.nl-tilt3d{
  transform-style:preserve-3d;
  backface-visibility:hidden;
  transition:box-shadow .3s ease;
}
@media (hover:none){
  /* SQUARE FROM FIRST PAINT, NOT FROM WHEN THE SCRIPT RUNS.
     The stylesheet gives these cards a resting angle, which is right on a
     pointer device and wrong on a phone: with no hover there is nothing for a
     resting angle to resolve into, so it just reads as a card that is not
     straight. The script sets an inline transform of its own, but only once it
     has run, and a tilt on the first frame is still a tilt somebody sees. */
  .hero-card, .page-hero .offer, .nl-heroright > .nl-card,
  .hero-grid .hero-card, .plan, .nl-card{ transform:none; transition:none; }
  /* a pressed card earns a little more shadow, which is what makes a tap feel
     like it touched something rather than just changed colour */
  .nl-tilt3d:active{ box-shadow:var(--shadow-lg); }
}
@media (prefers-reduced-motion: reduce){
  .nl-tilt3d{ transform:none !important; }
}
