/* styles.css -- the PUBLIC marketing site's own stylesheet (index.html +
   5 handyman-*.html landing pages), including the shared design tokens
   (:root) and universal resets (box-sizing, scrollbar, body defaults)
   that the tool suite also depends on.

   Split 2026-08-20 (structural item #43): this file used to ALSO contain
   the entire tool suite's CSS (roughly 1,363 more lines) appended after
   this content -- meaning every tool page loaded ~1,363 lines of public-
   site-irrelevant CSS on every visit, and the public site loaded nothing
   extra since it only ever needed this half anyway.

   Deliberately the SAFEST possible version of this split: the public
   site's own <link> tag referencing this file was never touched at all
   -- same filename, same URL, same content for everything the public
   site actually uses. Only tool pages changed, gaining one additional
   <link> to styles-tools.css (loaded AFTER this file, preserving the
   exact same cascade order the single combined file always had).
*/

  :root{
    /* ---- Type system (2026-09-06) ----
       Every font-family in this project now resolves through these three
       tokens. Before this, 188 declarations across 38 files each named a
       face literally, which made changing one a 38-file edit, and the
       four different Google Fonts URLs in circulation had already
       drifted apart from each other.

       Inter is deliberately gone. It had the body-copy job since
       2026-08-16 and did it competently -- it is also the most-used
       interface typeface on the web, which is why a page set in it reads
       as generically well-made rather than as ours. Newsreader replaces
       it: a text serif with a real optical-size axis, so 15px running
       copy and a 22px deck are drawn differently rather than one being
       a scaled copy of the other.

       Anton and Oswald both stay, in the roles they were already good
       at -- Anton for display, Oswald for small uppercase UI labels,
       where its condensed width is an asset and where its size and case
       keep it clear of Anton. The stack that read flat before (two
       condensed grotesques straight on top of each other) now has a
       serif between them doing the actual reading. */
    --font-display:'Anton', sans-serif;
    --font-body:'Newsreader', Georgia, 'Times New Roman', serif;
    --font-ui:'Oswald', sans-serif;
    --font-app:'Archivo', -apple-system, 'Segoe UI', sans-serif;
    --bg:#0a0a0a;
    --bg-panel:#141414;
    --bg-panel-2:#1a1a1a;
    --bg-panel-3:#1f1f1f;
    --border:#2a2a2a;
    --text:#e8e8e8;
    --text-dim:#9a9a9a;
    --white:#ffffff;
    --steel:#d8d8d8;
    --orange:#ff8000;
    --orange-dark:#c96400;
    --orange-light:#ffb347;
    --orange-tint-soft: rgba(255,128,0,0.08);
    --orange-tint-border: rgba(255,128,0,0.3);
    --orange-tint-glow: rgba(255,128,0,0.12);
    --blue:#3aa0ff;
    --blue-dark:#1f5f9e;
    --blue-light:#7ec4ff;
    /* Accent colours used as TEXT (2026-09-06). The light theme below
       redefines every neutral but never touched the accents, so brand
       orange and light blue were being painted straight onto a near-white
       ground: 2.31:1 for orange and 1.72:1 for blue, against the 4.5:1 that
       WCAG AA asks for normal text. Section eyebrows, service tags, process
       numbers and the blog kicker were all failing.
       --orange-light and --blue-light could not simply be flipped, because
       each is also a background or a border in about fourteen places (the
       motto rail, carousel dots, progress fills) where darkening would be
       wrong. So text gets its own tokens. In dark mode they resolve to
       exactly the values they replace, which makes this change invisible
       there and confined entirely to the light theme.
       Content locked to a dark surface regardless of theme -- the hero over
       its photo, the gallery caption overlay, the lightbox scrim -- keeps
       its hardcoded colours and is deliberately not routed through these. */
    --orange-text:var(--orange-light);
    --orange-text-vivid:var(--orange);
    --blue-text:var(--blue-light);
    /* F28 fix (2026-09-07): the post-submit success message was a
       hardcoded green (measured ~1.9:1 on the light background) --
       the one thing a customer must actually see after sending a
       lead. Same reasoning as --orange-text/--blue-text above: dark
       mode keeps the existing bright green unchanged, light mode gets
       its own darker value verified for real contrast. */
    --success-text:#3ad66b;
    /* F28 fix (2026-09-07): placeholder text was hardcoded, measuring
       only ~3.05:1 against the real input background (--bg-panel-3)
       in dark mode. */
    --placeholder-text:#8c8c8c;
    --radius:10px;
    --hex: polygon(25% 6%, 75% 6%, 97% 50%, 75% 94%, 25% 94%, 3% 50%);
    --maxw:1180px;
    /* The header spans wider than the reading column -- see the .nav
       rule for the measurements behind this number. */
    --maxw-nav:1400px;
    /* #2: a deliberate 3-tier shadow depth scale (resting / hover /
       active) instead of every elevated surface picking its own ad-hoc
       shadow values -- applied within the tool suite below. */
    --shadow-resting: 0 1px 2px rgba(0,0,0,.25), 0 8px 24px -18px rgba(0,0,0,.55);
    --shadow-hover: 0 4px 14px -4px rgba(0,0,0,.5), 0 14px 34px -14px rgba(0,0,0,.65);
    --shadow-active: 0 1px 3px rgba(0,0,0,.4);
  }

  /* Light theme (2026-08-20): the JS toggle has always correctly set
     this attribute and saved the preference -- this override block is
     what was actually missing the whole time, which is why toggling
     never visibly changed anything. Neutrals invert; brand accent
     colors (orange, blue) stay the same on purpose, since they're
     brand identity rather than theme-dependent, and already read fine
     against a light background. Shadow alpha values are reduced
     slightly, since the same rgba(0,0,0,.55) that reads as a subtle
     "resting" depth cue against near-black reads as noticeably heavier
     against a light panel. */
  [data-theme="light"]{
    /* Verified against all four light surfaces: #994a00 gives 5.78 / 6.30 /
       5.43 / 5.01, and #1a5a94 gives 6.57 / 7.16 / 6.17 / 5.69. */
    --orange-text:#994a00;
    --orange-text-vivid:#994a00;
    --blue-text:#1a5a94;
    /* Verified against white: 6.51:1. */
    --success-text:#146c34;
    /* Verified against the light-mode input background (--bg-panel-3,
       #e8e5df): 5.69:1. */
    --placeholder-text:#5c5750;
    --bg:#f7f5f1;
    --bg-panel:#ffffff;
    --bg-panel-2:#f0eee9;
    --bg-panel-3:#e8e5df;
    --border:#ddd9d1;
    --text:#201d18;
    --text-dim:#6b665e;
    --white:#161310;
    --steel:#4a4640;
    --shadow-resting: 0 1px 2px rgba(0,0,0,.10), 0 8px 24px -18px rgba(0,0,0,.20);
    --shadow-hover: 0 4px 14px -4px rgba(0,0,0,.18), 0 14px 34px -14px rgba(0,0,0,.28);
    --shadow-active: 0 1px 3px rgba(0,0,0,.16);
  }

  *{box-sizing:border-box;}

  /* dark themed scrollbar, so scrolling inside modals (FAQ, Terms, Gallery) matches the site instead of a default light scrollbar */
  html{scrollbar-color:var(--border) var(--bg); scrollbar-width:thin;}
  ::-webkit-scrollbar{width:10px; height:10px;}
  ::-webkit-scrollbar-track{background:var(--bg-panel);}
  ::-webkit-scrollbar-thumb{background:var(--border); border-radius:6px;}
  ::-webkit-scrollbar-thumb:hover{background:var(--blue-dark);}
  html{scroll-behavior:smooth;}
  @media (prefers-reduced-motion: reduce){
    html{scroll-behavior:auto;}
    *{animation-duration:0.001ms !important; animation-iteration-count:1 !important; transition-duration:0.001ms !important;}
    ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
      animation-duration: 0.001ms !important;
    }
  }

  body{
    margin:0;
    background:var(--bg);
    color:var(--text);
    /* 2026-08-16: body copy, form values, and table data now read in
       Inter -- a face built for long-form and dense-data legibility --
       instead of Oswald, a condensed DISPLAY face never meant to carry
       paragraph text at length. Oswald stays exactly where it already
       was doing real work: headings, labels, buttons, and every other
       selector that names it explicitly (all untouched by this change,
       since they declare their own font-family rather than inheriting
       this one). Anton is untouched too -- still only the giant hero
       title. */
    font-family:var(--font-body);
    font-weight:400;
    line-height:1.6;
    -webkit-font-smoothing:antialiased;
  }

  h1,h2,h3,h4{
    /* Balanced heading wrapping (2026-09-06). Anton is a poster face set at
       display sizes, so a heading that breaks badly is very visible: "SOMETIMES
       WE'LL TELL YOU NOT TO FIX / IT" was leaving a single two-letter word
       alone on its own line at 1440px. text-wrap:balance evens the lines out
       instead. Browsers that do not support it simply wrap as they do now. */
    text-wrap:balance;
  }
  h1,h2,h3{
    font-family:var(--font-display);
    font-weight:400;
    text-transform:uppercase;
    letter-spacing:0.5px;
    margin:0;
    color:var(--white);
  }
  /* Running prose gets `pretty` rather than `balance`: it leaves the body of
     the paragraph alone and only prevents the last line being a single
     orphaned word. */
  p,li,dd,blockquote{text-wrap:pretty;}

  a{color:inherit; text-decoration:none;}
  img{max-width:100%; display:block;}
  /* A button is a control, not prose. Before the type change this
     inherited Inter and happened to be right; inheriting now would
     put every button on the public site into Newsreader. */
  /* Form controls are UI, never prose. Before the type change these
     inherited Inter and happened to be right; inheriting now would put
     every button, field and dropdown on the public site into
     Newsreader. Element selectors, so anything that names its own face
     by class still wins. */
  button,input,select,textarea,label{font-family:var(--font-app);}

  a:focus-visible,
  button:focus-visible,
  input:focus-visible,
  select:focus-visible,
  textarea:focus-visible{
    outline:2px solid var(--blue-light);
    outline-offset:2px;
    box-shadow:0 0 0 5px rgba(58,160,255,0.16);
  }

  .skip-link{
    font-family:var(--font-ui);
    position:absolute; top:-60px; left:12px;
    background:var(--blue); color:#08131f;
    padding:10px 18px; border-radius:6px;
    font-weight:600; font-size:14px;
    z-index:1000; transition:top .15s ease;
  }
  .skip-link:focus{top:12px;}

  .wrap{
    max-width:var(--maxw);
    margin:0 auto;
    padding:0 24px;
  }

  .section-head{
    max-width:640px;
    margin:0 0 44px;
  }
  .section-head h2{
    font-size:clamp(28px,4vw,42px);
    line-height:1.05;
  }
  .section-head p{
    color:var(--text-dim);
    margin-top:14px;
    font-size:16px;
  }

  section{padding:88px 0;}
  section + section{border-top:1px solid var(--border);}

  /* buttons */
  .btn{
    display:inline-flex;
    align-items:center;
    gap:10px;
    padding:15px 26px;
    border-radius:6px;
    font-family:var(--font-ui);
    font-weight:600;
    letter-spacing:1.5px;
    text-transform:uppercase;
    font-size:14px;
    border:2px solid transparent;
    cursor:pointer;
    transition:transform .15s ease, box-shadow .15s ease, background .15s ease;
    white-space:nowrap;
  }
  .btn:hover{transform:translateY(-2px);}
  .btn:active{transform:translateY(0);}

  /* U01 fix (High-Impact Upgrades, 2026-09-07): flat fill + a solid
     offset shadow, replacing the glossy vertical gradient + colour-glow
     look -- "the clearest date stamp on a page" per the finding. Applies
     everywhere .btn.orange is used (it's the site's one consistent
     primary-action colour), not just the hero. */
  .btn.orange{
    background:var(--orange);
    color:#161005;
    box-shadow:4px 4px 0 0 var(--orange-dark);
  }
  .btn.orange:hover{box-shadow:6px 6px 0 0 var(--orange-dark);}
  .btn.orange:active{box-shadow:2px 2px 0 0 var(--orange-dark);}
  /* Regression-recovery fix (2026-09-08): missed by U01's flat-fill
     pass -- .btn.blue still carried the old glossy vertical gradient +
     colour-glow shadow it was describing as "the clearest date stamp on
     a page." Flattened to the exact same treatment .btn.orange already
     uses (solid fill + hard offset shadow), just in the secondary blue
     token -- these 5 usages are a genuine second CTA next to Send Email
     (Book Instantly), not a page-level primary action, so blue stays;
     only the dated rendering style is gone. */
  .btn.blue{
    background:var(--blue);
    color:#04101c;
    box-shadow:4px 4px 0 0 var(--blue-dark);
  }
  .btn.blue:hover{box-shadow:6px 6px 0 0 var(--blue-dark);}
  .btn.blue:active{box-shadow:2px 2px 0 0 var(--blue-dark);}
  .btn svg{width:16px; height:16px; flex-shrink:0;}

  /* U01: the quiet link that replaces a second, competing glossy button
     wherever Call and Schedule used to sit at equal weight (header,
     hero, triage result, service modal, and the landing pages' "Call or
     Text" vs "Book" pair). Call stays the one primary action; this is
     deliberately just underlined text, not a button. */
  .cta-quiet-link{
    display:inline-flex;
    align-items:center;
    gap:8px;
    background:none;
    border:none;
    padding:0;
    margin:0;
    color:var(--text-dim);
    font-family:var(--font-ui);
    font-size:14px;
    font-weight:500;
    letter-spacing:.3px;
    text-decoration:underline;
    text-underline-offset:3px;
    cursor:pointer;
    transition:color .15s ease;
  }
  .cta-quiet-link:hover{color:var(--white);}
  .cta-quiet-link svg{width:15px; height:15px; flex-shrink:0;}

  /* Regression-recovery fix (2026-09-08): U01 was right to demote the
     hero's second CTA to a quiet link -- Call should be the one loud
     action there. But the same pass also turned the persistent header
     nav's own "Schedule" into .cta-quiet-link, leaving both the hero's
     secondary path AND the nav's only booking link quiet at once.
     Restored here as a real, findable button -- flat and orange-tinted
     (not the old blue-gloss weight, and not competing with the header's
     own Call link either). Header-only; the hero and every other
     .cta-quiet-link usage is untouched. */
  .nav-schedule-btn{
    display:inline-flex;
    align-items:center;
    padding:9px 16px;
    border-radius:6px;
    border:1.5px solid var(--orange-tint-border);
    background:var(--orange-tint-soft);
    color:var(--orange-text-vivid);
    font-family:var(--font-ui);
    font-size:13.5px;
    font-weight:600;
    letter-spacing:.4px;
    text-decoration:none;
    white-space:nowrap;
    transition:background .15s ease, border-color .15s ease, transform .15s ease;
  }
  .nav-schedule-btn:hover{border-color:var(--orange-text-vivid); transform:translateY(-1px);}

  /* header */
  header{
    position:sticky;
    top:0;
    z-index:60;
    border-bottom:1px solid var(--border);
  }
  /* Bug fix (2026-08-20), found from a direct screenshot showing the
     logo overlapping with nav text: sticky elements that carry
     backdrop-filter directly on themselves can visually duplicate/
     ghost during scroll on iOS Safari (confirmed via research earlier
     this session to be a real, documented issue, especially on iOS
     26) -- that fix only covered the internal tools app's own
     stylesheet, missing this identical pattern on the public site's
     header. Moves the blur/background onto an absolutely-positioned
     child instead -- position: sticky already provides a valid
     containing block for this, no extra positioning context needed. */
  header::before{
    content:''; position:absolute; inset:0; z-index:-1;
    background:rgba(10,10,10,.88);
    -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  }
  /* Overflow fix (2026-09-06). Measured in a real browser: the row needs
     1405px of content -- brand 253 + links 805 + actions 299, plus two
     24px gaps -- while --maxw (1180px) minus 48px of padding gave it
     1132px. It had been overflowing its own container by 273px since the
     nav grew to nine links, and below roughly 1620px viewport that spilled
     out far enough to scroll the whole PAGE sideways: 405px of horizontal
     overflow at 1024px, 199px at 1280px, 119px at 1440px. Three separate
     changes, because no single one of them was enough:
       - the header gets its own, wider max-width. A navigation bar is not
         body copy and has no reason to sit inside the 1180px reading
         column;
       - the links tighten (gap and letter-spacing), which is where most of
         the 805px sat;
       - and an intermediate breakpoint below drops the two lowest-value
         items before the row can run out of room, rather than waiting for
         the 860px hamburger. */
  .nav{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:20px;
    padding:14px 24px;
    max-width:var(--maxw-nav);
    margin:0 auto;
  }
  .brand{
    display:flex;
    align-items:center;
    gap:12px;
    flex-shrink:0;
  }
  .brand img{height:44px; width:44px; object-fit:contain;}
  .brand-name{
    font-family:var(--font-display);
    font-size:17px;
    letter-spacing:1px;
    color:#ffffff;
    line-height:1.1;
    white-space:nowrap;
  }
  .brand-name span{
    display:block;
    font-family:var(--font-ui);
    font-size:10px;
    font-weight:500;
    letter-spacing:2px;
    color:var(--orange-text-vivid);
    margin-top:2px;
    white-space:nowrap;
  }

  .nav-links{
    display:flex;
    align-items:center;
    gap:18px;
    list-style:none;
    margin:0;
    padding:0;
  }
  .nav-links a{
    /* Uppercase, letter-spaced navigation labels are exactly what --font-ui
       is for. These carried no font-family at all and so inherited the body
       face -- which was fine while that was a sans and put them in a serif
       the moment it stopped being one. */
    font-family:var(--font-ui);
    font-size:14px;
    font-weight:500;
    letter-spacing:0.6px;
    white-space:nowrap;
    text-transform:uppercase;
    color:#d8d8d8;
    transition:color .15s ease;
  }
  .nav-links a:hover{color:#ffffff;}

  .nav-actions{
    display:flex;
    align-items:center;
    gap:14px;
  }
  .nav-phone{
    font-family:var(--font-ui);
    display:flex;
    align-items:center;
    gap:8px;
    font-size:14px;
    font-weight:600;
    color:#ffffff;
    /* It was being squeezed to 59px and wrapping a ten-digit number onto
       three lines. A phone number breaks as one thing or not at all. */
    white-space:nowrap;
  }
  .nav-phone svg{width:16px; height:16px; color:var(--orange-text-vivid);}

  /* This had no base rule at all, only a display:none inside a media
     query, so it had nothing but the inherited body face to go on. */
  .theme-toggle-label{font-family:var(--font-ui);}

  .nav-toggle{
    display:none;
    background:none;
    border:1px solid var(--border);
    border-radius:6px;
    width:42px;
    height:42px;
    align-items:center;
    justify-content:center;
    color:#ffffff;
    cursor:pointer;
  }
  .nav-toggle svg{width:20px; height:20px;}

  /* theme toggle (desktop button) -- previously had zero CSS at all,
     which is why it looked unstyled/"dumb": a plain, borderless button
     with both the sun and moon icon rendering simultaneously (nothing
     ever hid either one). Styled as a compact pill matching this nav's
     existing bordered-button conventions (.nav-toggle above uses the
     same border/radius approach). */
  .theme-toggle{
    display:flex; align-items:center; gap:7px;
    background:none;
    border:1px solid var(--border);
    border-radius:999px;
    padding:8px 14px;
    /* F23 fix (2026-09-07): this nav bar is always dark regardless of
       site theme (see .nav-links a above, and .nav-toggle's icon
       color) -- but this button's own text color used to follow the
       theme tokens, so in light mode it resolved to a near-black on
       this near-black bar (measured: ~3.4:1 resting, 1.61:1 hover,
       both failing or barely passing AA). Hardcoded to match
       .nav-links a's own resting/hover pair exactly (13.89:1 in both
       themes). */
    color:#d8d8d8;
    font-size:13px; font-weight:500;
    cursor:pointer;
    transition:color .15s, border-color .15s;
  }
  .theme-toggle:hover{color:#ffffff; border-color:var(--steel);}
  .theme-toggle svg{width:16px; height:16px; flex-shrink:0;}
  /* Icon show/hide -- mirrors how the text label already shows the
     CURRENT mode's name, not the destination: the moon shows while
     currently in dark mode, the sun while currently in light mode. */
  .theme-icon-light{display:none;}
  [data-theme="light"] .theme-icon-dark{display:none;}
  [data-theme="light"] .theme-icon-light{display:block;}

  .mobile-menu{
    display:none;
    flex-direction:column;
    gap:2px;
    padding:8px 24px 20px;
    border-top:1px solid var(--border);
  }
  .mobile-menu.is-open{display:flex;}

  /* mobile theme row + switch -- same "zero CSS at all" problem as the
     desktop button above. Built as a standard sliding toggle switch:
     an invisible checkbox input, a pill-shaped track, and a circular
     thumb that slides across it -- the checked state (dark mode, per
     the JS) uses the brand accent color to read as "on", matching how
     toggle switches conventionally show their active state regardless
     of what the underlying boolean means semantically. */
  .mobile-theme-row{
    /* The 'Dark Mode' text here is a bare <span> with no class, so it had
       only the inherited face. Set on the row so the label goes with it. */
    font-family:var(--font-ui);
    display:flex; align-items:center; justify-content:space-between;
    padding:14px 4px;
    font-size:15px; font-weight:500; letter-spacing:1px; text-transform:uppercase;
    color:#e8e8e8;
    border-bottom:1px solid var(--border);
  }
  .theme-switch{position:relative; display:inline-block; width:46px; height:26px; flex-shrink:0;}
  .theme-switch input{opacity:0; width:0; height:0; position:absolute;}
  .theme-switch-track{
    position:absolute; inset:0;
    background:var(--bg-panel-3);
    border:1px solid var(--border);
    border-radius:999px;
    cursor:pointer;
    transition:background .15s, border-color .15s;
  }
  .theme-switch-thumb{
    position:absolute; top:2px; left:2px;
    width:20px; height:20px;
    border-radius:50%;
    background:var(--text-dim);
    transition:transform .15s, background .15s;
  }
  .theme-switch input:checked + .theme-switch-track{background:var(--orange-tint-soft); border-color:var(--orange-text-vivid);}
  .theme-switch input:checked + .theme-switch-track .theme-switch-thumb{transform:translateX(20px); background:var(--orange);}

  /* The step between the full desktop row and the 860px hamburger. Without
     it the row went straight from fitting to spilling off the page, which
     is what produced the 119px of horizontal overflow measured at 1440px.
     Both items dropped here are still reachable: the number is in the hero,
     the footer and the mobile menu, and the theme toggle keeps its icon --
     only its text label goes. */
  @media (max-width:1400px){
    .nav-links{gap:12px;}
    .nav-links a{font-size:12.5px; letter-spacing:0.4px;}
    .nav-phone-desktop.js-phone-link{display:none;}
    .theme-toggle-label{display:none;}
    /* The tagline under the wordmark is the widest part of the brand block
       (it takes the lockup from 166px to 253px) and it is the one piece of
       the header that repeats what the page below already says. */
    .brand-name span{display:none;}
  }

  /* Raised from 860px to 960px (2026-09-06). Even fully tightened, the
     nine-link row still needed 918px of content at a 900px viewport, which
     left a 66px band of horizontal page overflow sitting between the
     desktop row and the hamburger. Nothing is lost by switching earlier:
     the mobile menu carries every link, the phone number and the CTA.
     Only nav rules live in this block, and .mobile-menu is shown by an
     .is-open class rather than by width, so moving the threshold cannot
     strand the menu open or closed. */
  @media (max-width:960px){
    .nav-links, .nav-phone-desktop{display:none;}
    .nav-toggle{display:flex;}
    .theme-toggle{display:none;} /* the mobile menu's own switch covers this on small screens */
    .mobile-menu a{
      font-family:var(--font-ui);
      padding:14px 4px;
      font-size:15px;
      font-weight:500;
      letter-spacing:1px;
      text-transform:uppercase;
      color:#e8e8e8;
      border-bottom:1px solid var(--border);
    }
    .mobile-menu .btn{margin-top:14px; justify-content:center;}
  }

  /* hero */
  .hero{
    padding:76px 0 90px;
    position:relative;
    overflow:hidden;
    background:
      linear-gradient(to bottom, rgba(8,8,8,0) 82%, rgba(8,8,8,1) 100%),
      linear-gradient(100deg, rgba(8,8,8,0.94) 0%, rgba(8,8,8,0.90) 45%, rgba(8,8,8,0.72) 70%, rgba(8,8,8,0.45) 100%),
      url('/images/hero-bg-canyon.webp?v=202608241900');
    background-size: cover;
    background-position: center 70%;
  }
  /* This section always sits on a dark photo now, regardless of the
     site's light/dark toggle -- h1 and p.lede normally use var(--white)
     and var(--text-dim), which flip to near-black/dark-gray in light
     mode and would be illegible here. Fixed light colors instead, same
     reasoning already applied to the gallery caption overlay and
     lightbox scrim elsewhere in this file: content sitting on top of a
     photo or dark backdrop stays dark/light on its own terms, not tied
     to the page's own theme. */
  .hero h1{ color:#ffffff; }

  .hero::before{
    content:"";
    position:absolute;
    top:-20%; right:-10%;
    width:60%; height:140%;
    background:radial-gradient(circle, rgba(255,128,0,.10), transparent 65%);
    pointer-events:none;
  }
  .hero::after{
    content:"";
    position:absolute;
    bottom:-30%; left:-10%;
    width:50%; height:120%;
    background:radial-gradient(circle, rgba(58,160,255,.08), transparent 65%);
    pointer-events:none;
  }
  .hero-grid{
    display:grid;
    grid-template-columns:1.15fr 0.85fr;
    gap:56px;
    align-items:center;
    position:relative;
    z-index:1;
  }
  .hero h1{
    font-size:clamp(34px, 5.4vw, 60px);
    line-height:1.04;
    margin-top:16px;
  }
  .hero p.lede{
    color:#e5e2dc;
    font-size:17px;
    max-width:520px;
    margin:22px 0 32px;
  }
  /* U01: the hero sits on a permanently-dark photo backdrop regardless
     of site theme, same reasoning as .hero h1/.hero p.lede above --
     var(--text-dim) would go near-invisible-dark in light mode here. */
  .hero .cta-quiet-link{color:#c9c5bc;}
  .hero .cta-quiet-link:hover{color:#ffffff;}
  .hero-ctas{
    display:flex;
    gap:16px;
    flex-wrap:wrap;
  }
  .hero-badge{
    display:flex;
    justify-content:center;
    padding-left:36px;
  }
  .hero-badge img{
    width:min(440px, 100%);
    filter:drop-shadow(0 30px 50px rgba(0,0,0,.55));
  }
  @media (max-width:860px){
    .hero-grid{grid-template-columns:1fr; text-align:center;}
    .hero::before, .hero::after{display:none;}
    .hero{
      background:
        linear-gradient(to bottom, rgba(8,8,8,0) 82%, rgba(8,8,8,1) 100%),
        linear-gradient(to bottom, rgba(8,8,8,0.42) 0%, rgba(8,8,8,0.58) 40%, rgba(8,8,8,0.78) 70%, rgba(8,8,8,0.85) 100%),
        url('/images/hero-bg-canyon.webp?v=202608241900');
      background-size: cover;
      background-position: center 70%;
    }
    .hero p.lede{margin-left:auto; margin-right:auto;}
    .hero-ctas{justify-content:center;}
    .hero-badge{order:-1; margin-bottom:8px; padding-left:0;}
    .hero-badge img{width:250px;}
  }

  /* stats strip -- real numbers only (rating/review count match the
     JSON-LD and the reviews carousel; community count matches the 6
     pages this site actually serves), not invented marketing stats. */
  .stats-bar{
    padding:52px 0;
    background:linear-gradient(135deg, var(--bg-panel-3), var(--bg-panel-2));
    border-top:1px solid var(--border);
    border-bottom:1px solid var(--border);
  }
  .stats-grid{
    display:grid;
    grid-template-columns:repeat(4,1fr);
    gap:28px;
    text-align:center;
  }
  .stat-number{
    font-family:var(--font-display);
    font-size:clamp(32px,5vw,50px);
    line-height:1;
    margin:0 0 6px;
    color:var(--orange-text-vivid);
    letter-spacing:0.5px;
  }
  .stat-item:nth-child(2) .stat-number,
  .stat-item:nth-child(4) .stat-number{color:var(--blue-text);}
  .stat-badge-text{font-size:clamp(20px,3.2vw,30px);}
  .stat-label{margin:0; color:var(--text-dim); font-size:12.5px; letter-spacing:0.5px; text-transform:uppercase;}
  @media (max-width:860px){.stats-grid{grid-template-columns:repeat(2,1fr); row-gap:32px;}}
  @media (max-width:480px){.stats-grid{grid-template-columns:1fr; row-gap:26px;}}

  /* trust strip */
  /* F29 fix (2026-09-07): .trust is a <section> immediately after
     .stats-bar (another <section>), so it also picked up the generic
     `section + section{border-top:1px solid var(--border);}` rule below
     -- landing directly on top of .stats-bar's own explicit
     border-bottom with zero gap between them (padding-top was 0), so the
     two 1px lines rendered as one slightly-doubled edge with the
     trust-item cards' top corners butting straight up against it. Removes
     the redundant auto border (stats-bar already frames itself) and
     restores a real gap before the cards. */
  .trust{padding:44px 0 88px; border-top:none;}
  .trust-grid{
    display:grid;
    /* 4 items as of the real Licensed & Insured fact added 2026-09-07
       (a registered LLC + active GL policy, both on file in Compliance)
       -- same responsive breakpoints as .stats-grid just above, since
       both are now 4-item fact strips. */
    grid-template-columns:repeat(4,1fr);
    gap:1px;
    background:var(--border);
    border:1px solid var(--border);
    border-radius:var(--radius);
    overflow:hidden;
  }
  .trust-item{background:var(--bg-panel); padding:28px 26px; border-radius:var(--radius); box-shadow:var(--shadow-resting);}
  .trust-item h3{font-size:16px; letter-spacing:1px; margin-bottom:8px;}
  .trust-item p{color:var(--text-dim); font-size:14px; margin:0;}
  @media (max-width:860px){.trust-grid{grid-template-columns:repeat(2,1fr);}}
  @media (max-width:480px){.trust-grid{grid-template-columns:1fr;}}

  /* services */
  .services-grid{
    display:grid;
    grid-template-columns:repeat(3,1fr);
    gap:20px;
  }
  .service-card{
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-top:3px solid var(--blue);
    border-radius:var(--radius);
    padding:26px 24px;
    transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease;
    text-align:left;
    width:100%;
    color:var(--text);
    cursor:pointer;
    box-shadow:var(--shadow-resting);
  }
  .service-card:hover{transform:translateY(-4px); border-color:var(--blue-text); box-shadow:var(--shadow-hover);}
  .service-card.is-urgent{border-top-color:var(--orange-text-vivid);}
  .service-card.is-urgent:hover{border-color:var(--orange-text);}
  .service-icon{
    width:42px; height:42px;
    border-radius:8px;
    display:flex; align-items:center; justify-content:center;
    background:rgba(58,160,255,0.28);
    color:var(--blue-text);
    margin-bottom:16px;
  }
  .service-card.is-urgent .service-icon{background:rgba(255,128,0,0.28); color:var(--orange-text);}
  .service-icon svg{width:22px; height:22px;}
  .service-card h3{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:17px;
    text-transform:none;
    letter-spacing:0;
    margin-bottom:8px;
  }
  .service-card p{color:var(--text-dim); font-size:14.5px; margin:0;}
  .service-card p + p{margin-top:10px;}
  .service-card p a{color:var(--blue-text); font-weight:600; text-decoration:none;}
  .service-card p a:hover{text-decoration:underline;}
  .service-tag{
    display:inline-flex;
    align-items:center;
    gap:6px;
    margin-top:14px;
    font-size:11px;
    font-weight:600;
    letter-spacing:1.5px;
    text-transform:uppercase;
    color:var(--blue-text);
  }
  .service-card.is-urgent .service-tag{color:var(--orange-text);}
  .service-tag svg{width:12px; height:12px;}
  @media (max-width:860px){.services-grid{grid-template-columns:repeat(2,1fr);}}
  @media (max-width:600px){.services-grid{grid-template-columns:1fr;}}

  /* blog teaser -- direct links to each post so the homepage itself
     signals which posts matter, not just a link to /blog/ as a whole */
  .blog-teaser-grid{
    display:grid;
    grid-template-columns:repeat(3,1fr);
    gap:20px;
  }
  .blog-teaser-card{
    display:block;
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-top:3px solid var(--blue);
    border-radius:var(--radius);
    padding:26px 24px;
    transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease;
    text-decoration:none;
    color:var(--text);
    box-shadow:var(--shadow-resting);
  }
  .blog-teaser-card:hover{transform:translateY(-4px); border-color:var(--blue-text); box-shadow:var(--shadow-hover);}
  .blog-teaser-card h3{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:17px;
    text-transform:none;
    letter-spacing:0;
    margin-bottom:8px;
  }
  .blog-teaser-card p{color:var(--text-dim); font-size:14.5px; margin:0;}
  .blog-teaser-more{text-align:center; margin-top:28px;}
  .blog-teaser-more a{color:var(--blue-text); font-weight:600; text-decoration:none;}
  .blog-teaser-more a:hover{text-decoration:underline;}
  @media (max-width:860px){.blog-teaser-grid{grid-template-columns:repeat(2,1fr);}}
  @media (max-width:600px){.blog-teaser-grid{grid-template-columns:1fr;}}

  /* about */
  .about-grid{
    display:grid;
    grid-template-columns:0.9fr 1.1fr;
    gap:56px;
    align-items:start;
  }
  .about-copy p{color:var(--text-dim); font-size:16px; margin:0 0 18px;}
  .values{display:flex; gap:14px; margin:28px 0 0; flex-wrap:wrap;}
  .value-pill{
    flex:1;
    min-width:140px;
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-top:3px solid var(--border);
    border-radius:var(--radius);
    padding:18px 16px;
    text-align:center;
    box-shadow:var(--shadow-resting);
  }
  .value-pill:nth-child(1){border-top-color:var(--orange-text); background:linear-gradient(180deg, rgba(255,128,0,0.06), var(--bg-panel) 60%);}
  .value-pill:nth-child(2){border-top-color:var(--steel); background:linear-gradient(180deg, rgba(150,150,150,0.08), var(--bg-panel) 60%);}
  .value-pill:nth-child(3){border-top-color:var(--blue-text); background:linear-gradient(180deg, rgba(58,160,255,0.07), var(--bg-panel) 60%);}
  .value-pill .word{font-family:var(--font-display); font-size:15px; letter-spacing:1px; color:var(--white);}
  .value-pill:nth-child(1) .word{color:var(--orange-text);}
  .value-pill:nth-child(2) .word{color:var(--steel);}
  .value-pill:nth-child(3) .word{color:var(--blue-text);}
  .value-pill p{color:var(--text-dim); font-size:12.5px; margin:8px 0 0;}

  .quote-block{
    position:relative;
    background:linear-gradient(160deg, var(--bg-panel-2), var(--bg-panel));
    border:1px solid var(--border);
    border-left:4px solid var(--orange);
    border-radius:var(--radius);
    padding:34px 30px;
  }
  .quote-block p{
    font-family:var(--font-display);
    font-size:clamp(20px,2.6vw,28px);
    line-height:1.25;
    color:var(--white);
    margin:0 0 14px;
  }
  .quote-block cite{
    font-family:var(--font-ui);
    font-size:13px;
    font-weight:500;
    letter-spacing:1px;
    color:var(--text-dim);
    font-style:normal;
  }
  @media (max-width:860px){.about-grid{grid-template-columns:1fr;}}

  /* schedule */
  .booking-grid{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:20px;
    max-width:940px;
  }
  .booking-cta{
    background:linear-gradient(135deg, var(--bg-panel-2), var(--bg-panel));
    border:1px solid var(--border);
    border-left:4px solid var(--blue);
    border-radius:var(--radius);
    padding:28px 26px;
    display:flex;
    flex-direction:column;
    justify-content:space-between;
    gap:20px;
  }
  .booking-grid .booking-cta:last-child{border-left-color:var(--orange-text-vivid);}
  .booking-cta-text h3{font-size:19px; margin-bottom:8px;}
  .booking-cta-text p{color:var(--text-dim); font-size:14px; margin:0; line-height:1.55;}
  .booking-cta .btn{align-self:flex-start;}
  @media (max-width:760px){
    .booking-grid{grid-template-columns:1fr;}
  }

  /* Regression-recovery fix (2026-09-08): the same audit that flagged
     #areas also measured #schedule (543px), #contact (597px) and
     #closing (646px) as three consecutive full sections all asking for
     the same thing -- 1,786px, 15.5% of the page. #contact's four cards
     also just repeated the phone/email the header, sticky-call button,
     closing screen and footer already carry. Folded into #schedule as
     one compact strip carrying only what's genuinely nowhere else on
     the page: the hours table and the map link. #contact stays a real,
     working anchor (the nav/footer links to it are unchanged) by living
     on this strip instead of its own section. */
  .schedule-info-strip{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:28px;
    max-width:940px;
    margin-top:20px;
    padding-top:18px;
    border-top:1px solid var(--border);
  }
  .schedule-info-strip h4{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:13px;
    text-transform:uppercase;
    letter-spacing:1px;
    color:var(--text-dim);
    margin:0 0 12px;
  }
  @media (max-width:760px){
    .schedule-info-strip{grid-template-columns:1fr; gap:20px;}
  }
  .modal-email{max-width:640px;}
  .email-form .form-row{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:18px;
    margin-bottom:18px;
  }
  @media (max-width:600px){
    .email-form .form-row{grid-template-columns:1fr;}
  }
  .form-row{display:grid; grid-template-columns:1fr 1fr; gap:18px; margin-bottom:18px;}
  .field{display:flex; flex-direction:column; gap:8px;}
  .field.full{grid-column:1 / -1;}
  label{
    font-size:12.5px;
    font-weight:600;
    letter-spacing:1px;
    text-transform:uppercase;
    color:var(--text-dim);
  }
  input, select, textarea{
    background:var(--bg-panel-3);
    border:1px solid var(--border);
    border-radius:6px;
    padding:12px 14px;
    color:var(--white);
    font-family:var(--font-ui);
    font-size:14.5px;
  }
  input::placeholder, textarea::placeholder{color:var(--placeholder-text);}
  textarea{resize:vertical; min-height:96px;}
  select{appearance:none; background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="%239a9a9a"><path d="M5 8l5 5 5-5z"/></svg>'); background-repeat:no-repeat; background-position:right 12px center; padding-right:36px;}
  /* F28 fix (2026-09-07): a custom property can't be substituted
     inside a data: URI, so the light-mode chevron needs its own copy
     rather than a var() -- the dark-mode #9a9a9a fill measured
     ~2.2:1 in light mode (losing the dropdown's own affordance);
     #545454 verified at 7.57:1 on white. */
  [data-theme="light"] select{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="%23545454"><path d="M5 8l5 5 5-5z"/></svg>');}

  .form-note{font-size:12.5px; color:var(--text-dim); margin-top:14px; line-height:1.5;}
  .form-note strong{color:var(--steel);}
  .form-footer{display:flex; align-items:center; gap:18px; flex-wrap:wrap; margin-top:22px;}
  .form-status{font-size:13.5px; color:var(--blue-text); display:none;}
  .form-status.is-visible{display:inline;}
  .form-status.is-success{color:var(--success-text);}
  .form-status.is-error{color:var(--orange-text);}

  @media (max-width:600px){
    .form-row{grid-template-columns:1fr;}
  }

  /* contact */
  .contact-grid{display:grid; grid-template-columns:repeat(4,1fr); gap:20px;}
  .contact-card{
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-radius:var(--radius);
    padding:26px 24px;
    box-shadow:var(--shadow-resting);
  }
  .contact-card .service-icon{color:var(--orange-text); cursor:default;}
  .contact-card h3{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:15px;
    text-transform:uppercase;
    letter-spacing:1px;
    margin-bottom:10px;
  }
  /* Contact details are data a customer copies or dials, not prose --
     phone number, email address, city. The app face keeps the digits and
     the address legible and unambiguous. */
  .contact-card a.value, .contact-card p.value{font-family:var(--font-app); color:var(--white); font-size:16px; font-weight:500;}
  .contact-card a.value:hover{color:var(--orange-text);}
  .contact-card p.sub{color:var(--text-dim); font-size:13.5px; margin-top:6px;}
  /* Bug fix (2026-08-20): had zero CSS at all, anywhere -- with none,
     the day and time spans defaulted to plain inline display with no
     separation, rendering as "Mon7:00 AM" with zero space between
     them. Fixed-width day column so all 7 time values line up
     vertically; colors match this card's existing .value/.sub
     language above. */
  .hours-grid{display:flex; flex-direction:column; gap:5px;}
  /* Seven rows of opening times read down as a column, so they need the
     app face and tabular figures -- a serif's proportional digits made
     '2:00 PM' and '11:00 AM' different widths and left the column ragged. */
  .hours-row{display:flex; gap:10px; font-size:14px; font-family:var(--font-app);}
  .hours-day{color:var(--white); font-weight:600; width:34px; flex-shrink:0;}
  .hours-value{color:var(--text-dim); font-variant-numeric:tabular-nums;}
  .map-link{
    font-family:var(--font-ui);
    display:inline-flex;
    align-items:center;
    gap:6px;
    margin-top:12px;
    font-size:13px;
    font-weight:600;
    color:var(--blue-text);
    letter-spacing:.5px;
  }
  .map-link svg{width:14px; height:14px;}
  @media (max-width:860px){.contact-grid{grid-template-columns:repeat(2,1fr);}}
  @media (max-width:560px){.contact-grid{grid-template-columns:1fr;}}

  /* W20/M04 fix (Master Audit, 2026-09-08): the page's real close --
     fixed dark background regardless of site theme, same reasoning as
     .hero (this is a photo-free bookend to it, not a themed content
     section), one lit ambient glow, the real mission statement at
     hero-scale type, one Call button, and the live status/next-opening
     lines the hero already computes (see triage.js). */
  .closing{
    position:relative;
    overflow:hidden;
    padding:120px 0;
    min-height:60vh;
    display:flex;
    align-items:center;
    background:#0a0a0a;
  }
  .closing::before{
    content:"";
    position:absolute;
    top:-30%; left:50%;
    width:70%; height:160%;
    transform:translateX(-50%);
    background:radial-gradient(circle, rgba(255,128,0,.14), transparent 65%);
    pointer-events:none;
  }
  .closing-inner{
    position:relative;
    z-index:1;
    max-width:720px;
    margin:0 auto;
    text-align:center;
  }
  .closing-eyebrow{
    font-family:var(--font-ui);
    font-size:13px;
    font-weight:600;
    letter-spacing:3px;
    text-transform:uppercase;
    color:var(--orange-text-vivid);
    margin:0 0 18px;
  }
  .closing-promise{
    font-family:var(--font-display);
    font-weight:400;
    font-size:clamp(30px,4.8vw,48px);
    line-height:1.15;
    color:#ffffff;
    margin:0 0 40px;
  }
  .closing-cta{margin:0 auto 30px; justify-content:center;}
  /* Dedicated classes rather than a compound descendant selector on the
     shared pill/next-opening classes on purpose -- an existing test in
     frontend-design-tells-removed.test.js locates that shared pill rule
     by searching the raw stylesheet text for its exact selector immediately
     followed by an opening brace, and a compound selector ending the
     same way would satisfy that same search first if it appeared earlier
     in the file, grabbing this override instead of the real base rule. */
  .closing-open-status{
    display:inline-flex;
    margin-top:0;
  }
  .closing-next-opening{
    margin-top:12px;
    font-size:14px;
  }
  @media (max-width:620px){
    .closing{padding:80px 0; min-height:auto;}
  }

  /* footer */
  footer{background:var(--bg-panel); border-top:1px solid var(--border); padding:48px 0 28px;}
  /* Widened from 4 to 5 columns (2026-09-02) when the Client Portal
     column was added -- with the old 4-column rule that fifth column
     wrapped onto a second row and sat alone under the brand, which
     looked broken rather than intentional. The brand column keeps a
     slightly wider share (1.4fr) since it holds the logo, tagline and
     social row. An intermediate breakpoint stops 5 columns from
     getting crushed on tablet widths before the existing mobile
     single-column rule takes over. */
  .footer-grid{display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr 1fr; gap:32px; margin-bottom:36px;}
  .footer-brand{display:flex; align-items:center; gap:12px; margin-bottom:14px;}
  /* Bug fix (2026-08-20), found from a direct screenshot pair: white-
     on-white in light mode. .brand-name is shared with the header,
     where the background is always hardcoded dark regardless of
     theme (so hardcoded white text there is correct and safe) -- but
     the footer's own background correctly flips via var(--bg-panel),
     and .brand-name's hardcoded #ffffff never flips to match. Scoped
     here rather than changing the shared rule, which would break the
     always-dark header instead. */
  .footer-brand .brand-name{color:var(--white);}
  .footer-brand img{height:38px; width:38px; object-fit:contain;}
  .footer-tagline{font-family:var(--font-ui); color:var(--text); font-size:13.5px; letter-spacing:1px; text-transform:uppercase;}
  .social-links{display:flex; gap:10px; margin-top:16px;}
  .social-links a{
    width:34px; height:34px;
    border-radius:8px;
    border:1px solid rgba(255,128,0,0.35);
    background:rgba(255,128,0,0.14);
    color:var(--orange-text);
    display:flex; align-items:center; justify-content:center;
    transition:color .15s ease, border-color .15s ease, background .15s ease;
  }
  .social-links a:hover{color:#1a0d02; background:var(--orange-light); border-color:var(--orange-text);}
  .social-links svg{width:17px; height:17px;}
  .footer-col h4{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:13px;
    letter-spacing:1.5px;
    text-transform:uppercase;
    color:var(--steel);
    margin:0 0 14px;
  }
  .footer-col ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:10px;}
  /* Navigation, like the header's -- and like the h4 above it, which was
     already on --font-ui. Only the links had no face of their own. */
  .footer-col a{font-family:var(--font-ui); color:var(--text); font-size:14px;}
  .footer-col a:hover{color:var(--white);}
  .footer-bottom{
    border-top:1px solid var(--border);
    padding-top:20px;
    display:flex;
    justify-content:space-between;
    flex-wrap:wrap;
    gap:10px;
    font-size:12.5px;
    color:var(--text);
  }
  @media (max-width:1100px){.footer-grid{grid-template-columns:1.4fr 1fr 1fr; gap:28px;}}
  @media (max-width:760px){.footer-grid{grid-template-columns:1fr; gap:28px;}}

  /* service detail modal */
  .modal-overlay{
    position:fixed;
    inset:0;
    background:rgba(4,4,4,.72);
    backdrop-filter:blur(3px);
    display:none;
    align-items:center;
    justify-content:center;
    padding:24px;
    z-index:100;
    opacity:0;
    transition:opacity .18s ease, display .18s ease allow-discrete;
  }
  .modal-overlay.is-open{display:flex; opacity:1;}
  @starting-style{
    .modal-overlay.is-open{opacity:0;}
  }
  .modal{
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-radius:14px;
    max-width:520px;
    width:100%;
    max-height:88vh;
    overflow-y:auto;
    padding:32px;
    position:relative;
    box-shadow:var(--shadow-hover);
    transform:scale(.96) translateY(8px);
    transition:transform .18s ease;
  }
  .modal-overlay.is-open .modal{transform:scale(1) translateY(0);}
  @starting-style{
    .modal-overlay.is-open .modal{transform:scale(.96) translateY(8px);}
  }
  .modal-close{
    position:absolute;
    top:16px; right:16px;
    width:34px; height:34px;
    border-radius:8px;
    border:1px solid var(--border);
    background:var(--bg-panel-3);
    color:var(--text-dim);
    font-size:18px;
    line-height:1;
    cursor:pointer;
    display:flex; align-items:center; justify-content:center;
  }
  .modal-close:hover{color:var(--white);}
  .modal .service-icon{margin-bottom:18px;}
  .modal h3{font-size:24px; margin-bottom:12px; padding-right:30px;}
  .modal .modal-intro{color:var(--text-dim); font-size:15px; margin:0 0 18px;}
  .modal ul{list-style:none; margin:0 0 26px; padding:0; display:flex; flex-direction:column; gap:10px;}
  .modal ul li{
    display:flex;
    gap:10px;
    align-items:flex-start;
    font-size:14.5px;
    color:var(--text);
  }
  .modal ul li svg{width:16px; height:16px; margin-top:2px; color:var(--blue-text); flex-shrink:0;}
  .modal.is-urgent ul li svg{color:var(--orange-text);}
  .modal-actions{display:flex; gap:12px; flex-wrap:wrap;}
  .modal-gallery{max-width:900px;}
  .modal-gallery .gallery-grid{margin-top:22px;}
  @media (max-width:760px){.modal-gallery{max-width:94vw;}}

  .terms-body{margin-top:18px;}
  /* Measured at 104ch before this -- the widest running text on the site,
     in the one document people actually have to read carefully. ch units so
     the cap follows the typeface instead of a pixel guess that stops being
     right the next time the body face changes. */
  .terms-body p, .terms-body li{max-width:68ch;}
  .terms-body h4{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:15px;
    color:var(--steel);
    margin:22px 0 8px;
  }
  .terms-body h4:first-child{margin-top:0;}
  .terms-body p{color:var(--text-dim); font-size:14px; line-height:1.6; margin:0 0 4px;}

  /* FAQ */
  .faq-list{max-width:760px;}
  .faq-item{
    border:1px solid var(--border);
    border-radius:var(--radius);
    background:var(--bg-panel);
    margin-bottom:12px;
    overflow:hidden;
    box-shadow:var(--shadow-resting);
    transition:box-shadow .18s ease;
  }
  .faq-item.is-open{box-shadow:var(--shadow-hover);}
  .faq-question{
    width:100%;
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:16px;
    padding:20px 22px;
    background:none;
    border:none;
    color:var(--white);
    font-family:var(--font-ui);
    font-weight:600;
    font-size:15.5px;
    text-align:left;
    cursor:pointer;
  }
  .faq-question svg{
    width:18px; height:18px;
    flex-shrink:0;
    color:var(--blue-text);
    transition:transform .2s ease;
  }
  .faq-item.is-open .faq-question svg{transform:rotate(180deg);}
  /* F33 fix (2026-09-07): this used to be a max-height:0 -> max-height:
     <scrollHeight>px transition, with the JS-measured px value stored as
     an inline style at the moment the answer opened. Rotating the device
     (or any resize) while an answer was open reflowed its text to a
     different number of lines, but the stored max-height stayed frozen
     at the old value -- clipping the answer's last line(s) until it was
     closed and reopened. grid-template-rows:0fr -> 1fr needs no measured
     height at all, so it always tracks the answer's real content height,
     resize or rotate included. */
  .faq-answer{
    display:grid;
    grid-template-rows:0fr;
    overflow:hidden;
    transition:grid-template-rows .25s ease;
  }
  .faq-item.is-open .faq-answer{grid-template-rows:1fr;}
  .faq-answer p{
    min-height:0;
    overflow:hidden;
    padding:0 22px 20px;
    margin:0;
    color:var(--text-dim);
    font-size:14.5px;
    line-height:1.6;
  }
  .faq-answer p em{color:var(--orange-text); font-style:normal;}

  /* sticky mobile call button */
  .sticky-call{
    display:none;
    position:fixed;
    bottom:0; left:0; right:0;
    z-index:70;
    padding:12px 16px;
    background:rgba(10,10,10,.95);
    backdrop-filter:blur(8px);
    border-top:1px solid var(--border);
  }
  .sticky-call a{
    display:flex;
    align-items:center;
    justify-content:center;
    gap:10px;
    width:100%;
  }
  @media (max-width:760px){
    .sticky-call{display:block;}
    body{padding-bottom:70px;}
  }

  /* gallery */
  .gallery-grid{
    display:grid;
    grid-template-columns:repeat(3,1fr);
    gap:20px;
  }
  .gallery-tile{
    aspect-ratio:4/3;
    border-radius:var(--radius);
    border:1px dashed var(--border);
    background:
      repeating-linear-gradient(135deg, var(--bg-panel-2) 0 12px, var(--bg-panel) 12px 24px);
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:10px;
    color:var(--text-dim);
    text-align:center;
    padding:20px;
  }
  .gallery-tile svg{width:30px; height:30px; opacity:.6;}
  .gallery-tile span{font-size:12.5px; letter-spacing:.5px;}
  .gallery-tile.has-photo{
    padding:0;
    border-style:solid;
    position:relative;
    overflow:hidden;
  }
  .gallery-tile.has-photo img{
    width:100%;
    height:100%;
    object-fit:cover;
    display:block;
  }
  .gallery-category{
    font-family:var(--font-ui);
    font-size:13px; letter-spacing:.08em; text-transform:uppercase;
    /* F24 fix (2026-09-07): --blue-light is a fixed brand blue, not a
       theme-aware token -- it measured 1.87:1 in light mode (on
       white). --blue-text exists for exactly this (dark: the same
       --blue-light; light: a darker #1a5a94 with real contrast) and
       was simply missed here. */
    color:var(--blue-text); margin:26px 0 10px; font-weight:600;
  }
  .gallery-category:first-of-type{margin-top:6px;}
  .gallery-row{display:contents;}
  .gallery-tile{cursor:pointer; transition:transform .18s ease, box-shadow .18s ease;}
  .gallery-tile:hover{transform:translateY(-3px); box-shadow:var(--shadow-hover);}
  .gallery-tile.has-photo img{transition:filter .18s ease;}
  .gallery-tile.has-photo:hover img{filter:brightness(1.05);}
  .lightbox-overlay{
    display:none; position:fixed; inset:0; background:rgba(0,0,0,.88);
    z-index:9999; align-items:center; justify-content:center; flex-direction:column;
    opacity:0;
    transition:opacity .18s ease, display .18s ease allow-discrete;
  }
  .lightbox-overlay.is-visible{display:flex; opacity:1;}
  @starting-style{
    .lightbox-overlay.is-visible{opacity:0;}
  }
  .lightbox-img-wrap{
    max-width:90vw; max-height:78vh; display:flex; align-items:center; justify-content:center;
    transform:scale(.96);
    transition:transform .18s ease;
  }
  .lightbox-overlay.is-visible .lightbox-img-wrap{transform:scale(1);}
  @starting-style{
    .lightbox-overlay.is-visible .lightbox-img-wrap{transform:scale(.96);}
  }
  .lightbox-overlay img{max-width:90vw; max-height:78vh; object-fit:contain; border-radius:6px;}
  .lightbox-caption{color:#eee; margin-top:14px; font-size:14px; text-align:center; max-width:80vw;}
  .lightbox-close{
    position:absolute; top:18px; right:24px; background:none; border:none;
    color:#fff; font-size:32px; cursor:pointer; line-height:1;
  }
  .lightbox-nav{
    position:absolute; top:50%; transform:translateY(-50%); background:rgba(255,255,255,.1);
    border:none; color:#fff; font-size:28px; cursor:pointer; padding:10px 16px; border-radius:4px;
  }
  .lightbox-nav:hover{background:rgba(255,255,255,.2);}
  .lightbox-prev{left:16px;}
  .lightbox-next{right:16px;}
  @media (max-width:760px){
    .lightbox-nav{font-size:22px; padding:8px 12px;}
    .lightbox-close{top:10px; right:14px;}
  }

  /* back to top */
  .back-to-top{
    position:fixed;
    bottom:24px; right:24px;
    z-index:79;
    width:48px; height:48px;
    border-radius:50%;
    background:var(--bg-panel-2);
    border:1px solid var(--border);
    color:var(--text-dim);
    display:flex; align-items:center; justify-content:center;
    cursor:pointer;
    opacity:0; visibility:hidden; transform:translateY(10px);
    transition:opacity .2s ease, transform .2s ease, border-color .15s ease, color .15s ease;
  }
  .back-to-top.is-visible{opacity:1; visibility:visible; transform:translateY(0);}
  .back-to-top:hover{border-color:var(--blue-text); color:var(--blue-text);}
  .back-to-top svg{width:22px; height:22px;}
  @media (max-width:760px){
    .back-to-top{bottom:90px; right:16px; width:44px; height:44px;}
  }

  /* text-us chat bubble */
  @media (pointer: fine){
    .chat-bubble-btn, .chat-panel{display:none !important;}
  }
  .chat-bubble-btn{
    position:fixed;
    bottom:24px; left:24px;
    z-index:80;
    width:60px; height:60px;
    border-radius:50%;
    background:linear-gradient(135deg, #7ec4ff, #3aa0ff 60%, #1f5f9e);
    border:none;
    box-shadow:var(--shadow-hover);
    display:flex; align-items:center; justify-content:center;
    cursor:pointer;
    color:#0a0a0a;
    transition:transform .18s ease;
  }
  .chat-bubble-btn:hover{transform:scale(1.06);}
  .chat-bubble-btn svg{width:28px; height:28px;}
  .chat-bubble-btn .chat-dot{
    position:absolute; top:2px; right:2px;
    width:14px; height:14px; border-radius:50%;
    background:#ff8000;
    border:2px solid #0a0a0a;
  }
  @media (max-width:760px){
    .chat-bubble-btn{bottom:90px; left:16px; width:54px; height:54px;}
  }

  .chat-panel{
    position:fixed;
    bottom:96px; left:24px;
    z-index:81;
    width:300px;
    max-width:calc(100vw - 48px);
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-radius:14px;
    box-shadow:var(--shadow-hover);
    display:none;
    flex-direction:column;
    overflow:hidden;
  }
  .chat-panel.is-open{display:flex;}
  @media (max-width:760px){
    .chat-panel{bottom:156px; left:16px;}
  }
  .chat-panel-header{
    background:linear-gradient(135deg, var(--blue-dark), var(--bg-panel-2));
    padding:14px 16px;
    display:flex; align-items:center; justify-content:space-between;
  }
  .chat-panel-header-title{display:flex; align-items:center; gap:10px;}
  .chat-panel-header-title .dot{width:9px; height:9px; border-radius:50%; background:#3ad66b; flex-shrink:0;}
  .chat-panel-header h4{margin:0; font-size:14.5px; color:var(--text);}
  .chat-panel-header p{margin:0; font-size:11.5px; color:var(--text-dim);}
  .chat-panel-close{background:none; border:none; color:var(--text-dim); font-size:20px; cursor:pointer; line-height:1; padding:0 4px;}
  .chat-panel-close:hover{color:var(--text);}
  .chat-panel-body{padding:16px; display:flex; flex-direction:column; gap:12px;}
  .chat-bubble-msg{
    background:var(--bg-panel-3);
    border-radius:12px 12px 12px 4px;
    padding:10px 13px;
    font-size:13.5px;
    line-height:1.5;
    color:var(--text);
    max-width:92%;
  }
  .chat-panel-actions{display:flex; flex-direction:column; gap:8px;}
  .chat-action-btn{
    font-family:var(--font-ui); /* matches .btn, which these are a variant of */
    display:flex; align-items:center; justify-content:center; gap:8px;
    padding:11px 14px; border-radius:8px; border:none;
    font-size:14px; font-weight:600; cursor:pointer;
    text-decoration:none;
  }
  .chat-action-btn svg{width:16px; height:16px; flex-shrink:0;}
  .chat-action-btn.primary{background:var(--blue); color:#08131f;}
  .chat-action-btn.primary:hover{background:var(--blue-light);}
  .chat-action-btn.secondary{background:transparent; border:1px solid var(--border); color:var(--text-dim);}
  .chat-action-btn.secondary:hover{border-color:var(--blue-text); color:var(--blue-text);}
  .chat-panel-note{font-size:11px; color:var(--text-dim); text-align:center; margin:0;}

  .gallery-caption{
    position:absolute;
    bottom:0; left:0; right:0;
    background:linear-gradient(to top, rgba(0,0,0,.8), transparent);
    /* Bug fix, found during the light-theme audit: this sits on a dark
       photo overlay just like .hero h1 above, which already has this
       same hardcoded-color treatment for the same reason -- var(--white)
       would flip to a dark color in light mode and become illegible
       against its own overlay. */
    color:#ffffff;
    font-size:12.5px;
    font-weight:500;
    letter-spacing:.3px;
    padding:20px 14px 10px;
    text-align:left;
  }
  @media (max-width:860px){.gallery-grid{grid-template-columns:repeat(2,1fr);}}
  @media (max-width:600px){.gallery-grid{grid-template-columns:1fr;}}

  /* reviews (U03 fix, High-Impact Upgrades, 2026-09-07): a carousel
     showed one review at a time behind 9px dots -- reviews are this
     business's strongest asset and were rationed one at a time. Now a
     static two-column wall, newest first (already the source order),
     with the real Google mark kept on each card and the existing
     "see more / leave your own" link doing the "read all on Google"
     job this finding also asked for. */
  .reviews-wall{
    max-width:900px;
    margin:0 auto;
    display:grid;
    grid-template-columns:repeat(2,1fr);
    align-items:start;
    gap:20px;
  }
  @media (max-width:760px){.reviews-wall{grid-template-columns:1fr;}}
  /* Regression-recovery fix (2026-09-08): align-items:start above stops
     a short quote from stretching to match the tallest card in its
     grid row -- real, measured height this section didn't need. The
     last 3 of 7 cards live in this collapsed-by-default disclosure,
     same zero-JS-needed <details> pattern used for the triage rows. */
  .reviews-toggle{max-width:900px; margin:18px auto 0;}
  .reviews-toggle summary{
    font-family:var(--font-ui);
    font-size:13.5px;
    color:var(--blue-text);
    cursor:pointer;
    padding:4px 0;
    list-style:none;
    text-align:center;
  }
  .reviews-toggle summary::-webkit-details-marker{display:none;}
  .reviews-toggle summary::before{content:'+ '; color:var(--text-dim);}
  .reviews-toggle[open] summary::before{content:'\2212 ';}
  .reviews-toggle summary:hover{color:var(--white);}
  .reviews-toggle .reviews-wall-more{margin-top:20px;}
  /* Design feedback (2026-09-08): native <details> used to snap this
     open/closed with no transition, unlike the FAQ accordion's own real
     animation elsewhere on the page. ::details-content is the browser's
     own animatable box for everything inside a <details> except its
     <summary> -- real <details>/<summary> stays exactly as before
     (no-JS fallback, native keyboard/screen-reader semantics), this
     just gives the content height something to transition between
     instead of an instant swap. */
  .reviews-toggle{interpolate-size:allow-keywords;}
  .reviews-toggle::details-content{
    height:0;
    overflow:hidden;
    transition:height .25s ease, content-visibility .25s ease allow-discrete;
  }
  .reviews-toggle[open]::details-content{height:auto;}
  .review-card{
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-radius:var(--radius);
    padding:28px 30px;
    text-align:left;
    box-shadow:var(--shadow-resting);
  }
  .review-stars{
    display:flex;
    justify-content:flex-start;
    gap:4px;
    margin-bottom:18px;
    color:var(--orange-text);
  }
  .review-stars svg{width:18px; height:18px;}
  /* Design feedback (2026-09-08): quote length here ranges from a
     4-word one-liner to a 60-word paragraph -- with each card sized to
     its own content, the wall read as inconsistent/"bouncy" rather than
     a matched set. A fixed height + line-clamp gives every card the
     same footprint: short quotes leave harmless whitespace, the one
     long quote truncates with an ellipsis -- exactly what the existing
     "read all on Google" link in .reviews-footer is already there for. */
  .review-quote{
    font-size:17px;
    color:var(--white);
    line-height:1.6;
    margin:0 0 18px;
    height:110px;
    display:-webkit-box;
    -webkit-line-clamp:4;
    line-clamp:4;
    -webkit-box-orient:vertical;
    overflow:hidden;
  }
  .review-meta{
    font-size:13px;
    color:var(--text-dim);
    letter-spacing:.5px;
  }
  .review-meta strong{color:var(--steel); font-weight:600;}
  /* Real-source badge (2026-09-07), not a per-reviewer avatar: every
     review on this page is deliberately anonymized to "Verified
     Customer" (no name/photo was ever collected for public display),
     so a fabricated initial or headshot per review would invent an
     identity that doesn't exist. The real Google "G" mark instead
     visually reinforces the one true, verifiable fact each card
     already states in text -- this review came from Google. */
  .review-attribution{display:flex; align-items:center; justify-content:flex-start; gap:10px;}
  .review-avatar{
    width:30px; height:30px; border-radius:50%; flex-shrink:0;
    background:#fff; display:flex; align-items:center; justify-content:center;
    box-shadow:0 0 0 1px var(--border);
  }
  .review-avatar svg{width:16px; height:16px;}
  .reviews-footer{
    text-align:center;
    margin-top:26px;
    font-size:13.5px;
    color:var(--text-dim);
  }
  .reviews-footer a{color:var(--blue-text); font-weight:600;}
  @media (max-width:760px){
    .review-card{padding:24px 22px;}
  }


  /* =====================================================================
     scroll-craft pass (2026-09-05): the public site's scroll language.

     Presentation layer only. Every selector below is new or additive --
     nothing here redefines an existing rule, and nothing here is targeted
     by the lead form, booking pages, the Supabase site-content/FAQ/terms
     fetch scripts, the service-detail modals, or any other live hook.

     Three ideas, in order of weight:
       1. A blueprint world behind every page, so six separately-themed
          sections read as one continuous place.
       2. A scroll-driven exploded appliance -- the clearest statement of
          what this business actually is: the people who know what's
          inside. Drawn as real inline SVG, so it needs no photography
          and stays sharp at any size.
       3. Draft-line annotation: the page marks itself up like a technical
          drawing as you scroll, which is the connective tissue between
          the two above.

     SAFETY MODEL, since this ships to a live lead-generating site:
     every scroll-driven element's DEFAULT (no JS, JS error, old browser,
     reduced motion) is its FINISHED state -- assembled machine, full
     opacity, no offset. Motion is an enhancement layered on top of a
     composition that is already correct and readable without it.
  ===================================================================== */

  /* ---------- 1. the blueprint world ---------- */
  /* Drafting-paper linework behind every public page, so separately
     themed sections read as one continuous place.

     F31 fix (2026-09-07): this used to be painted as body's own
     background-image with background-attachment:fixed directly. That
     meant every scroll frame had to repaint all 6 gradient layers on the
     main thread instead of being handled by the compositor -- a real
     cost on a long page. Moved to a real position:fixed element instead
     (never repaints on scroll, no background-attachment needed), but
     that alone isn't enough: confirmed by direct rendering that a
     negative-z-index child still gets fully painted OVER by its own
     parent's background, because the parent's own background paints in
     a later step than a negative-z-index sibling within the same
     stacking context (this is why the original code kept the gradient on
     body's own background rather than a child element). So body itself
     goes transparent on these 6 pages specifically (the .has-blueprint-bg
     class added to <body> alongside the div) -- .bg-blueprint alone now
     supplies both the solid backdrop color and the gradients, with
     nothing left to occlude it. */
  body.has-blueprint-bg{background:transparent;}
  .bg-blueprint{
    position:fixed; inset:0; z-index:-1;
    background-color:var(--bg);
    background-image:
      radial-gradient(ellipse 62% 46% at 86% 4%, rgba(255,128,0,.085), transparent 62%),
      radial-gradient(ellipse 58% 42% at 6% 72%, rgba(58,160,255,.070), transparent 62%),
      repeating-linear-gradient(0deg, rgba(255,255,255,.026) 0 1px, transparent 1px 96px),
      repeating-linear-gradient(90deg, rgba(255,255,255,.026) 0 1px, transparent 1px 96px),
      repeating-linear-gradient(0deg, rgba(255,255,255,.011) 0 1px, transparent 1px 24px),
      repeating-linear-gradient(90deg, rgba(255,255,255,.011) 0 1px, transparent 1px 24px);
  }
  [data-theme="light"] .bg-blueprint{
    background-image:
      radial-gradient(ellipse 62% 46% at 86% 4%, rgba(255,128,0,.070), transparent 62%),
      radial-gradient(ellipse 58% 42% at 6% 72%, rgba(58,160,255,.060), transparent 62%),
      repeating-linear-gradient(0deg, rgba(20,40,70,.042) 0 1px, transparent 1px 96px),
      repeating-linear-gradient(90deg, rgba(20,40,70,.042) 0 1px, transparent 1px 96px),
      repeating-linear-gradient(0deg, rgba(20,40,70,.018) 0 1px, transparent 1px 24px),
      repeating-linear-gradient(90deg, rgba(20,40,70,.018) 0 1px, transparent 1px 24px);
  }

  /* ---------- 2. real scroll-reveal ---------- */
  /* [data-reveal] was already wired in JS (IntersectionObserver adds
     .is-visible, plus .reveal-ready on <html>) but nothing ever hid the
     elements first, so the mechanism was a site-wide no-op. This is the
     missing half. Reduced-motion visitors are covered by the global
     prefers-reduced-motion rule near the top of this file, which zeroes
     every transition-duration. */
  html.reveal-ready [data-reveal]{
    opacity:0;
    transform:translateY(24px);
    transition:opacity .7s cubic-bezier(.22,.61,.36,1), transform .7s cubic-bezier(.22,.61,.36,1);
  }
  html.reveal-ready [data-reveal].is-visible{
    opacity:1;
    transform:translateY(0);
  }
  /* Cards in a grid arrive in sequence rather than as one block. Small
     stagger -- large enough to feel deliberate, short enough that nobody
     waiting on a broken dryer is kept waiting on an animation. */
  html.reveal-ready .services-grid [data-reveal]:nth-child(2){transition-delay:.06s;}
  html.reveal-ready .services-grid [data-reveal]:nth-child(3){transition-delay:.12s;}
  html.reveal-ready .services-grid [data-reveal]:nth-child(4){transition-delay:.18s;}
  html.reveal-ready .services-grid [data-reveal]:nth-child(5){transition-delay:.24s;}
  html.reveal-ready .services-grid [data-reveal]:nth-child(6){transition-delay:.30s;}

  /* ---------- 3. draft-line annotation ---------- */
  /* Every section heading gets a measurement line that draws itself in as
     the heading arrives, with a tick at each end -- the page annotating
     itself the way a shop drawing does. Pure pseudo-elements on an
     existing class, so no page markup changes and it applies across all
     6 pages at once. scaleX from a left origin, so it costs one composited
     transform and never touches layout. */
  .section-head{position:relative;}
  .section-head::before{
    content:"";
    position:absolute;
    top:-18px; left:0;
    width:clamp(120px, 22vw, 260px);
    height:1px;
    background:linear-gradient(90deg, var(--orange), rgba(255,128,0,0));
    transform:scaleX(0);
    transform-origin:left center;
    transition:transform .9s cubic-bezier(.22,.61,.36,1) .1s;
  }
  .section-head.is-visible::before,
  html:not(.reveal-ready) .section-head::before{transform:scaleX(1);}
  .section-head::after{
    content:"";
    position:absolute;
    top:-22px; left:0;
    width:1px; height:9px;
    background:var(--orange);
    opacity:0;
    transition:opacity .5s ease .5s;
  }
  .section-head.is-visible::after,
  html:not(.reveal-ready) .section-head::after{opacity:.85;}

  /* W23 fix (Master Audit, 2026-09-08): the generic opacity+translateY
     entrance fade above was applied identically to every section
     heading sitewide -- the same motion repeated 10 times dilutes the
     handful of places where scroll-triggered movement is a deliberate,
     designed moment (the hero, the teardown spotlight, the process
     step-line, the service-area diagram's own draw-in). Headings now
     render immediately; [data-reveal]/.is-visible stay fully wired
     (more specific than the base [data-reveal] rule above, so this
     wins regardless of source order) since #process's connecting line
     and the draft-line tick marks just above both key off the exact
     same .section-head.is-visible state. */
  html.reveal-ready .section-head[data-reveal]{
    opacity:1;
    transform:none;
    transition:none;
  }

  /* The blog uses bare <h2>s rather than .section-head, so it gets the
     same annotation through its own selector -- one visual language
     across marketing pages and articles. */
  .blog-article h2{position:relative; padding-top:16px;}
  .blog-article h2::before{
    content:"";
    position:absolute;
    top:0; left:0;
    width:64px; height:1px;
    background:linear-gradient(90deg, var(--orange), rgba(255,128,0,0));
    transform:scaleX(0);
    transform-origin:left center;
    transition:transform .8s cubic-bezier(.22,.61,.36,1);
  }
  .blog-article h2.is-visible::before,
  html:not(.reveal-ready) .blog-article h2::before{transform:scaleX(1);}

  /* ---------- 4. the motto rail (signature) ---------- */
  /* The tagline becomes the page's spine: three segments, one per word,
     each lighting as its matching proof -- the About section's value
     pills -- actually scrolls into view. Decorative (aria-hidden); the
     real content stays in the pills themselves. Sits under the header's
     stacking (55 vs 60) so it never covers nav. */
  .motto-rail{
    position:fixed;
    left:0; top:0; bottom:0;
    width:3px;
    z-index:55;
    display:flex;
    flex-direction:column;
    gap:3px;
    pointer-events:none;
  }
  .motto-rail .rail-seg{
    flex:1;
    background:var(--border);
    opacity:.5;
    transition:background .7s ease, opacity .7s ease, box-shadow .7s ease;
  }
  .motto-rail .rail-seg.is-lit{opacity:1;}
  .motto-rail .rail-honesty.is-lit{background:var(--orange-light); box-shadow:2px 0 14px rgba(255,128,0,.4);}
  .motto-rail .rail-hustle.is-lit{background:var(--steel); box-shadow:2px 0 14px rgba(216,216,216,.28);}
  .motto-rail .rail-helpfulness.is-lit{background:var(--blue-light); box-shadow:2px 0 14px rgba(58,160,255,.4);}
  /* The 5 location pages have no value pills to sync against, so they
     show the rail already lit -- a brand signature, not a mechanism with
     nothing to drive it. */
  .motto-rail.is-static .rail-seg{opacity:1;}
  .motto-rail.is-static .rail-honesty{background:var(--orange-light);}
  .motto-rail.is-static .rail-hustle{background:var(--steel);}
  .motto-rail.is-static .rail-helpfulness{background:var(--blue-light);}

  /* ---------- 5. hero depth ---------- */
  /* The canyon photo is the deep plane; this adds a schematic mid-plane
     between it and the type, so the hero has real separation instead of
     one flat backdrop. Parallax factors are driven from JS via --shift,
     defaulting to 0 so the composition is correct with no JS at all. */
  .hero-plane{
    position:absolute;
    inset:0;
    pointer-events:none;
    z-index:0;
    opacity:.5;
    transform:translate3d(0, calc(var(--shift, 0) * 1px), 0);
    background-image:
      repeating-linear-gradient(90deg, rgba(255,255,255,.05) 0 1px, transparent 1px 140px);
    -webkit-mask-image:radial-gradient(ellipse 70% 60% at 70% 45%, #000 10%, transparent 72%);
    mask-image:radial-gradient(ellipse 70% 60% at 70% 45%, #000 10%, transparent 72%);
  }
  .hero-badge{
    transform:translate3d(0, calc(var(--shift, 0) * 1px), 0);
  }
  /* A third depth plane, added between the canyon photo and the
     schematic grid: a real finished-job photo, not another graphic. The
     canyon is the deep background, this is a midground with genuine
     texture, and it moves at its own rate (--shift2, set from JS at a
     rate between the grid's and the badge's) so the hero reads as actual
     depth rather than a flat photo with two decorative layers pasted on
     top of it. Masked to the right side only, under the badge, so it
     never competes with the headline on the left. Desaturated and
     blurred so it stays atmosphere, not a second subject. */
  .hero-subject{
    position:absolute;
    inset:0;
    z-index:0;
    pointer-events:none;
    overflow:hidden;
    opacity:.32;
    transform:translate3d(0, calc(var(--shift2, 0) * 1px), 0);
    -webkit-mask-image:linear-gradient(100deg, transparent 0%, transparent 40%, #000 66%, #000 100%);
    mask-image:linear-gradient(100deg, transparent 0%, transparent 40%, #000 66%, #000 100%);
  }
  .hero-subject img{
    width:100%;
    height:100%;
    object-fit:cover;
    object-position:60% 45%;
    filter:saturate(.55) brightness(.8) blur(1.5px);
  }
  @media (max-width:860px){
    .hero-subject{display:none;}
  }
  @media (prefers-reduced-motion: reduce){
    .hero-subject{transform:none;}
  }
  /* The motto, finally on the homepage itself rather than only in the
     footer and the value pills. Three words, arriving in sequence. */
  .hero-motto{
    display:flex;
    flex-wrap:wrap;
    align-items:center;
    gap:10px 18px;
    margin:30px 0 0;
    font-family:var(--font-ui);
    font-weight:600;
    font-size:13px;
    letter-spacing:3px;
    text-transform:uppercase;
  }
  .hero-motto span{
    opacity:0;
    transform:translateY(8px);
    transition:opacity .6s ease, transform .6s ease;
  }
  .hero-motto span:nth-child(1){color:#ffb347; transition-delay:.35s;}
  .hero-motto span:nth-child(2){color:#d8d8d8; transition-delay:.55s;}
  .hero-motto span:nth-child(3){color:#7ec4ff; transition-delay:.75s;}
  .hero-motto.is-visible span,
  html:not(.reveal-ready) .hero-motto span{opacity:1; transform:translateY(0);}
  .hero-motto b{
    display:inline-block;
    width:4px; height:4px;
    border-radius:50%;
    background:currentColor;
    opacity:.6;
    margin-left:14px;
    vertical-align:middle;
  }
  @media (max-width:860px){
    .hero-motto{justify-content:center;}
    .hero-plane{display:none;}
  }

  /* ---------- 6. the exploded appliance ---------- */
  /* A washer drawn as a shop diagram that the visitor rebuilds themselves
     by dragging, rather than by being forced to scroll through a long
     pinned section. Progress (--p) runs 0 (apart, labelled) to 1 (whole).
     It DEFAULTS to 1, so with no JS, a JS error, an old browser or reduced
     motion, a visitor sees a clean finished machine and a slider already
     at the end -- never scattered parts with no way to fix them. */
  .teardown{
    padding:96px 0;
    position:relative;
    --p:1;
  }
  /* W19/M02 fix (Master Audit, 2026-09-08): a before/after audit measured
     this section as one of the page's flattest -- brightness 17, barely
     above the darkest spots, with nothing marking it as a second real
     moment alongside the finished-work reveal. Same ambient-glow
     convention already used on .hero (radial-gradient, no overflow risk
     since it sits fully inside the section's own box via inset:0),
     biased toward the figure side of the grid. */
  .teardown::before{
    content:"";
    position:absolute;
    inset:0;
    background:radial-gradient(ellipse 60% 70% at 78% 50%, rgba(255,128,0,.11), transparent 68%);
    pointer-events:none;
    z-index:0;
  }
  .teardown-grid{
    display:grid;
    grid-template-columns:0.9fr 1.1fr;
    gap:52px;
    align-items:center;
    position:relative;
    z-index:1;
  }
  .teardown-copy{position:relative;}
  .teardown-copy h2{
    position:relative;
    padding-top:18px;
    font-size:clamp(30px,4.4vw,50px);
    line-height:1.02;
  }
  .teardown-copy h2::before{
    content:"";
    position:absolute;
    top:0; left:0;
    width:clamp(120px, 18vw, 220px);
    height:1px;
    background:linear-gradient(90deg, var(--orange), rgba(255,128,0,0));
  }
  .teardown-copy p{
    color:var(--text-dim);
    font-size:16.5px;
    margin:18px 0 0;
    max-width:44ch;
  }
  .teardown-figure{
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:20px;
    position:relative;
  }
  /* The spotlight brightens as the machine comes back together (--p runs
     0 "Apart" -> 1 "Whole", the same live custom property already
     driving every exploded part) -- real, interaction-tied light, not
     just a static backdrop, so dragging the slider is rewarded twice:
     once by the parts assembling, once by the room getting brighter. */
  .teardown-figure::before{
    content:"";
    position:absolute;
    top:50%; left:50%;
    width:560px;
    max-width:100%;
    aspect-ratio:1;
    transform:translate(-50%,-50%);
    border-radius:50%;
    background:radial-gradient(circle, rgba(255,128,0, calc(.10 + var(--p, 1) * .22)) 0%, rgba(255,128,0,0) 68%);
    pointer-events:none;
    z-index:0;
  }
  .teardown-figure svg{
    width:100%;
    max-width:600px;
    height:auto;
    overflow:visible;
    position:relative;
    z-index:1;
  }
  .teardown-figure .td-scrub{position:relative; z-index:1;}
  /* Shared transform for every exploded part. Each supplies its own
     --dx/--dy/--rot; all resolve to zero at --p:1. transform-box keeps SVG
     rotation about the part's own centre, not the viewBox origin. */
  .td-part{
    transform-box:fill-box;
    transform-origin:center;
    transform:
      translate(
        calc(var(--dx, 0) * (1 - var(--p, 1)) * 1px),
        calc(var(--dy, 0) * (1 - var(--p, 1)) * 1px)
      )
      rotate(calc(var(--rot, 0) * (1 - var(--p, 1)) * 1deg));
  }
  .td-inner{opacity:calc(.35 + (1 - var(--p, 1)) * .65);}
  .td-line{stroke:var(--steel); stroke-width:2; fill:none; vector-effect:non-scaling-stroke;}
  .td-line-soft{stroke:var(--text-dim); stroke-width:1.4; fill:none; vector-effect:non-scaling-stroke;}
  .td-accent{stroke:var(--orange); stroke-width:2.4; fill:none; vector-effect:non-scaling-stroke;}
  .td-fill{fill:var(--bg-panel);}
  .td-fill-2{fill:var(--bg-panel-2);}
  .td-belt{stroke:var(--steel); stroke-width:6; fill:none; opacity:.45; vector-effect:non-scaling-stroke;}
  .td-callout{opacity:calc(1 - var(--p, 1));}
  .td-callout text{
    font-family:var(--font-ui);
    font-size:16px;
    letter-spacing:1.6px;
    text-transform:uppercase;
    fill:var(--text-dim);
  }
  .td-callout line{stroke:var(--orange); stroke-width:1; opacity:.65; vector-effect:non-scaling-stroke;}
  .td-callout circle{fill:var(--orange);}

  /* The control. Deliberately the most tactile thing on the page: a real
     <input type="range">, so it is draggable by finger, mouse, and arrow
     keys, and screen readers announce it, with none of that hand-built. */
  .td-scrub{
    width:100%;
    max-width:600px;
    display:flex;
    flex-direction:column;
    gap:10px;
  }
  .td-scrub-labels{
    display:flex;
    align-items:baseline;
    justify-content:space-between;
    gap:12px;
    font-family:var(--font-ui);
    font-size:12.5px;
    letter-spacing:2.2px;
    text-transform:uppercase;
    color:var(--text-dim);
  }
  .td-scrub-state{color:var(--orange-text-vivid); font-weight:600;}
  .td-scrub input[type="range"]{
    -webkit-appearance:none;
    appearance:none;
    width:100%;
    height:44px;              /* full-height touch target on phones */
    background:transparent;
    padding:0;
    margin:0;
    border:none;
    cursor:grab;
  }
  .td-scrub input[type="range"]:active{cursor:grabbing;}
  .td-scrub input[type="range"]::-webkit-slider-runnable-track{
    height:6px;
    border-radius:999px;
    background:
      linear-gradient(90deg, var(--orange-dark), var(--orange-light)) 0/calc(var(--p, 1) * 100%) 100% no-repeat,
      var(--bg-panel-3);
    border:1px solid var(--border);
  }
  .td-scrub input[type="range"]::-moz-range-track{
    height:6px;
    border-radius:999px;
    background:var(--bg-panel-3);
    border:1px solid var(--border);
  }
  .td-scrub input[type="range"]::-moz-range-progress{
    height:6px;
    border-radius:999px;
    background:linear-gradient(90deg, var(--orange-dark), var(--orange-light));
  }
  .td-scrub input[type="range"]::-webkit-slider-thumb{
    -webkit-appearance:none;
    appearance:none;
    width:30px; height:30px;
    margin-top:-13px;
    border-radius:50%;
    background:linear-gradient(160deg, #ffb347, #ff8000 62%, #c96400);
    border:2px solid #1a1208;
    box-shadow:0 4px 14px rgba(255,128,0,.45);
    cursor:grab;
  }
  .td-scrub input[type="range"]::-moz-range-thumb{
    width:30px; height:30px;
    border-radius:50%;
    background:linear-gradient(160deg, #ffb347, #ff8000 62%, #c96400);
    border:2px solid #1a1208;
    box-shadow:0 4px 14px rgba(255,128,0,.45);
    cursor:grab;
  }
  .td-scrub input[type="range"]:focus-visible{outline:none;}
  .td-scrub input[type="range"]:focus-visible::-webkit-slider-thumb{
    box-shadow:0 0 0 5px rgba(58,160,255,.35), 0 4px 14px rgba(255,128,0,.45);
  }
  .td-scrub input[type="range"]:focus-visible::-moz-range-thumb{
    box-shadow:0 0 0 5px rgba(58,160,255,.35), 0 4px 14px rgba(255,128,0,.45);
  }
  .td-hint{
    font-size:13.5px;
    color:var(--text-dim);
    margin:22px 0 0;
  }
  .td-hint b{color:var(--orange-text); font-weight:600;}

  /* ---------- before/after reveal: a second bespoke device, deliberately
     not a copy of the teardown above it. Real photos instead of a
     diagram, a horizontal wipe instead of an exploded assembly. Same
     control language (a real range input, not pointer-drag hijacking)
     so the two devices read as one site's way of doing this, not two
     unrelated widgets. --p is a 0-1 fraction, set on the section itself
     so both the frame's clip-path and the input's own track fill (which
     reuses the teardown's exact track/thumb rules below) read the same
     value. */
  .reveal-job{padding:96px 0;}
  .reveal-frame{
    position:relative;
    width:100%;
    aspect-ratio:1400/787;
    border-radius:14px;
    overflow:hidden;
    margin:0 0 22px;
    box-shadow:var(--shadow-hover);
  }
  .reveal-img{position:absolute; inset:0;}
  .reveal-img img{width:100%; height:100%; object-fit:cover; display:block;}
  /* Both source photos are portrait shots cropped into this wide frame.
     Each gets its own vertical anchor tuned to that specific photo, so the
     crop favors clean floor over whatever's cluttering that particular
     shot's cabinets-up-top or counter-in-the-foreground. */
  .reveal-before img{object-position:center 85%;}
  .reveal-after img{object-position:center 100%;}
  .reveal-before{z-index:1;}
  .reveal-after{
    z-index:2;
    clip-path:inset(0 0 0 calc((1 - var(--p, .5)) * 100%));
  }
  .reveal-divider{
    position:absolute;
    top:0; bottom:0;
    left:calc(var(--p, .5) * 100%);
    width:3px;
    margin-left:-1.5px;
    background:#fff;
    box-shadow:0 0 0 1px rgba(0,0,0,.35), 0 0 24px rgba(0,0,0,.4);
    z-index:3;
    pointer-events:none;
  }
  .reveal-tag{
    position:absolute;
    top:16px;
    z-index:3;
    font-family:var(--font-ui);
    font-weight:600;
    font-size:12px;
    letter-spacing:1.5px;
    text-transform:uppercase;
    color:#fff;
    background:rgba(10,10,10,.62);
    padding:6px 13px;
    border-radius:999px;
    pointer-events:none;
  }
  .reveal-tag-before{left:16px;}
  .reveal-tag-after{right:16px;}
  .reveal-scrub{
    -webkit-appearance:none;
    appearance:none;
    display:block;
    width:100%;
    max-width:560px;
    margin:0 auto;
    height:44px;
    background:transparent;
    padding:0;
    border:none;
    cursor:grab;
  }
  .reveal-scrub:active{cursor:grabbing;}
  .reveal-scrub::-webkit-slider-runnable-track{
    height:6px;
    border-radius:999px;
    background:
      linear-gradient(90deg, var(--blue-dark), var(--blue-light)) 0/calc(var(--p, .5) * 100%) 100% no-repeat,
      var(--bg-panel-3);
    border:1px solid var(--border);
  }
  .reveal-scrub::-moz-range-track{
    height:6px;
    border-radius:999px;
    background:var(--bg-panel-3);
    border:1px solid var(--border);
  }
  .reveal-scrub::-moz-range-progress{
    height:6px;
    border-radius:999px;
    background:linear-gradient(90deg, var(--blue-dark), var(--blue-light));
  }
  .reveal-scrub::-webkit-slider-thumb{
    -webkit-appearance:none;
    appearance:none;
    width:30px; height:30px;
    margin-top:-13px;
    border-radius:50%;
    background:linear-gradient(160deg, var(--blue-light), var(--blue) 62%, var(--blue-dark));
    border:2px solid #0a1420;
    box-shadow:0 4px 14px rgba(58,160,255,.45);
    cursor:grab;
  }
  .reveal-scrub::-moz-range-thumb{
    width:30px; height:30px;
    border-radius:50%;
    background:linear-gradient(160deg, var(--blue-light), var(--blue) 62%, var(--blue-dark));
    border:2px solid #0a1420;
    box-shadow:0 4px 14px rgba(58,160,255,.45);
    cursor:grab;
  }
  .reveal-scrub:focus-visible{outline:none;}
  .reveal-scrub:focus-visible::-webkit-slider-thumb{
    box-shadow:0 0 0 5px rgba(255,128,0,.35), 0 4px 14px rgba(58,160,255,.45);
  }
  .reveal-scrub:focus-visible::-moz-range-thumb{
    box-shadow:0 0 0 5px rgba(255,128,0,.35), 0 4px 14px rgba(58,160,255,.45);
  }
  @media (max-width:600px){
    .reveal-job{padding:64px 0;}
    .reveal-tag{font-size:10.5px; padding:5px 10px;}
  }

  /* Phone-first: the diagram and its control are the main event on a
     small screen, so they lead and get the full width. */
  @media (max-width:860px){
    .teardown{padding:72px 0;}
    .teardown-grid{
      grid-template-columns:1fr;
      gap:26px;
      text-align:center;
    }
    .teardown-copy p{margin-left:auto; margin-right:auto;}
    .teardown-copy h2::before{left:50%; transform:translateX(-50%);}
    .teardown-figure{order:-1;}
    .teardown-figure svg{max-width:100%;}
    .td-callout text{font-size:19px;}
    .td-scrub input[type="range"]::-webkit-slider-thumb{width:34px; height:34px; margin-top:-15px;}
    .td-scrub input[type="range"]::-moz-range-thumb{width:34px; height:34px;}
  }

  /* ---------- 7. service cards as diagnostic plates ---------- */
  .service-card{position:relative; overflow:hidden;}
  /* Corner registration ticks, the way a drawing plate is cornered. */
  .service-card::before{
    content:"";
    position:absolute;
    top:10px; right:10px;
    width:12px; height:12px;
    border-top:1px solid var(--border);
    border-right:1px solid var(--border);
    transition:border-color .18s ease;
    pointer-events:none;
  }
  .service-card:hover::before,
  .service-card:focus-visible::before{border-color:var(--blue-text);}
  .service-card.is-urgent:hover::before,
  .service-card.is-urgent:focus-visible::before{border-color:var(--orange-text);}
  /* A single scan pass on hover/focus. Transform-only, so it composites. */
  .service-card::after{
    content:"";
    position:absolute;
    left:0; right:0; top:0;
    height:100%;
    pointer-events:none;
    background:linear-gradient(180deg, transparent 0%, rgba(58,160,255,.10) 48%, transparent 60%);
    transform:translateY(-100%);
    transition:transform .7s cubic-bezier(.22,.61,.36,1);
  }
  .service-card:hover::after,
  .service-card:focus-visible::after{transform:translateY(100%);}
  .service-card.is-urgent::after{
    background:linear-gradient(180deg, transparent 0%, rgba(255,128,0,.10) 48%, transparent 60%);
  }

  /* ---------- 8. the peak: Steven's section resolves ---------- */
  /* The engineered peak of the page. It gets more room than the site's
     normal section rhythm, and the blueprint world warms up behind it --
     the drawing becoming a person. Quietest motion on the page, by
     design: the words carry this one. */
  #about{
    padding-top:172px;
    position:relative;
    isolation:isolate;
  }
  #about::before{
    content:"";
    position:absolute;
    inset:0;
    z-index:-1;
    pointer-events:none;
    background:
      radial-gradient(ellipse 70% 52% at 22% 30%, rgba(255,128,0,.10), transparent 68%),
      radial-gradient(ellipse 56% 44% at 84% 74%, rgba(255,179,71,.055), transparent 70%);
  }
  /* F34 fix (2026-09-07): position:relative removed here -- redundant,
     .quote-block already sets it in its own base rule above. */
  .quote-block{overflow:hidden;}
  .quote-block::before{
    content:"";
    position:absolute;
    top:-40px; right:-40px;
    width:150px; height:150px;
    border-radius:50%;
    border:1px solid var(--orange-tint-border);
    opacity:.5;
    pointer-events:none;
  }
  @media (max-width:760px){
    #about{padding-top:108px;}
  }

  /* ---------- 9. reduced motion ---------- */
  /* The global rule near the top of this file already zeroes every
     transition and animation. These are the extras it cannot reach:
     scroll-driven custom properties. Pinning them to their finished
     values guarantees a correct, complete, static composition. */
  @media (prefers-reduced-motion: reduce){
    .teardown{--p:1 !important;}
    .hero-plane, .hero-badge{--shift:0 !important;}
    html.reveal-ready [data-reveal]{opacity:1 !important; transform:none !important;}
    .section-head::before{transform:scaleX(1) !important;}
    .section-head::after{opacity:.85 !important;}
    .hero-motto span{opacity:1 !important; transform:none !important;}
  }

  /* =====================================================================
     Feature pass 2 (2026-09-06): symptom triage, service-radius diagram,
     and a live open/closed indicator. Additive; no existing selector is
     redefined and no live hook is targeted.
  ===================================================================== */

  /* ---------- triage: "is it worth fixing?" ---------- */
  .triage-shell{
    background:var(--bg-panel);
    border:1px solid var(--border);
    border-radius:var(--radius);
    padding:30px 28px;
    box-shadow:var(--shadow-resting);
    max-width:900px;
  }
  .triage-step{margin-bottom:22px;}
  .triage-step:last-child{margin-bottom:0;}
  .triage-label{
    font-family:var(--font-ui);
    font-size:12px;
    font-weight:600;
    letter-spacing:2.2px;
    text-transform:uppercase;
    color:var(--text-dim);
    margin:0 0 12px;
    display:flex; align-items:center; gap:10px;
  }
  .triage-label i{
    font-style:normal;
    width:20px; height:20px;
    border-radius:50%;
    border:1px solid var(--orange-tint-border);
    background:var(--orange-tint-soft);
    color:var(--orange-text);
    display:inline-flex; align-items:center; justify-content:center;
    font-size:11px;
  }
  /* Style pass (2026-09-09), direct feedback on a screenshot: the
     bordered-card/plus-icon accordion look (below, since removed) read
     as less interactive and less clean than the pill-button style this
     tool used to have elsewhere on the page. Rebuilt on that pill
     language instead -- one row of appliance pills, then a stacked list
     of symptom pills for whichever appliance is selected. */
  .triage-appliance-pills{display:flex; flex-wrap:wrap; gap:10px;}
  .triage-appliance-pill{
    display:inline-flex; align-items:center; gap:9px;
    font-family:var(--font-ui);
    font-size:14px;
    font-weight:500;
    letter-spacing:.4px;
    padding:12px 18px;
    min-height:44px;                 /* real touch target */
    border-radius:999px;
    border:1px solid var(--border);
    background:var(--bg-panel-3);
    color:var(--text);
    cursor:pointer;
    transition:border-color .15s ease, background .15s ease, color .15s ease, transform .15s ease;
  }
  .triage-appliance-pill:hover{border-color:var(--blue-text); color:var(--white); transform:translateY(-1px);}
  .triage-appliance-pill[aria-pressed="true"]{
    background:linear-gradient(135deg, #ffb347, #ff8000 60%, #c96400);
    border-color:transparent;
    color:#161005;
    font-weight:600;
  }
  .triage-appliance-pill-icon{display:flex; flex-shrink:0; width:18px; height:18px; align-items:center; justify-content:center;}
  .triage-appliance-pill-icon svg{width:18px; height:18px;}

  .triage-symptom-pills{display:flex; flex-direction:column; gap:8px; margin-top:16px;}
  .triage-symptom-pill{
    font-family:var(--font-ui);
    text-align:left;
    font-size:14.5px;
    font-weight:500;
    padding:13px 18px;
    min-height:44px;                 /* real touch target */
    border-radius:999px;
    border:1px solid var(--border);
    background:var(--bg-panel-3);
    color:var(--text);
    cursor:pointer;
    transition:border-color .15s ease, background .15s ease, color .15s ease, transform .15s ease;
  }
  .triage-symptom-pill:hover{border-color:var(--blue-text); color:var(--white); transform:translateY(-1px);}
  .triage-symptom-pill[aria-pressed="true"]{
    background:linear-gradient(135deg, #ffb347, #ff8000 60%, #c96400);
    border-color:transparent;
    color:#161005;
    font-weight:600;
  }

  .triage-result{
    margin-top:22px;
    border-top:1px solid var(--border);
    padding-top:22px;
  }
  .triage-result[hidden]{display:none;}
  .triage-verdict{
    font-family:var(--font-display);
    font-size:clamp(19px,2.4vw,25px);
    line-height:1.2;
    color:var(--white);
    margin:0 0 12px;
    text-transform:uppercase;
    letter-spacing:.5px;
  }
  .triage-body{color:var(--text-dim); font-size:15.5px; margin:0 0 16px; max-width:62ch;}
  .triage-caveat{
    font-size:13px;
    color:var(--text-dim);
    border-left:3px solid var(--orange);
    background:var(--orange-tint-soft);
    padding:11px 14px;
    border-radius:0 6px 6px 0;
    margin:0 0 18px;
    max-width:62ch;
  }
  .triage-actions{display:flex; gap:12px; flex-wrap:wrap;}
  @media (max-width:600px){
    .triage-shell{padding:22px 18px;}
    .triage-actions .btn{flex:1 1 100%; justify-content:center;}
  }

  /* ---------- service radius diagram ---------- */
  .radius-figure{margin:0 0 40px; display:flex; justify-content:center;}
  .radius-figure svg{width:100%; max-width:720px; height:auto;}
  .radius-spoke{stroke:var(--blue-dark); stroke-width:1.5;}
  .radius-spoke.is-request{stroke:var(--orange-dark); stroke-dasharray:5 5;}
  .radius-node{fill:var(--bg-panel-2); stroke:var(--blue-light); stroke-width:2;}
  .radius-node.is-request{stroke:var(--orange);}
  .radius-hub{fill:var(--orange); stroke:none;}
  .radius-figure text{font-family:var(--font-ui); letter-spacing:1px;}
  .radius-name{font-size:15px; font-weight:600; fill:var(--text);}
  /* F24 fix (2026-09-07): same miss as .gallery-category above --
     --orange-light is a fixed brand orange, not theme-aware, and reads
     too light in light mode. --orange-text exists for exactly this. */
  .radius-name.is-request{fill:var(--orange-text);}
  .radius-note{font-size:12.5px; fill:var(--text-dim); letter-spacing:.6px;}
  .radius-hub-name{font-size:17px; font-weight:700; fill:var(--white);}
  .radius-legend{
    display:flex; gap:22px; flex-wrap:wrap; justify-content:center;
    margin:-18px 0 40px;
    font-size:13px; color:var(--text-dim);
  }
  .radius-legend span{display:inline-flex; align-items:center; gap:8px;}
  .radius-legend b{width:22px; height:0; border-top:2px solid var(--blue-light);}
  .radius-legend b.is-request{border-top:2px dashed var(--orange);}
  @media (max-width:760px){
    .radius-figure text{font-size:19px;}
    .radius-note{font-size:16px;}
    .radius-hub-name{font-size:21px;}
  }

  /* Regression-recovery fix (2026-09-08): a second measured audit found
     the page still 1,296px longer than it started, and named #areas as
     the single largest block on it -- 1,240px, at less than half the
     page's average change intensity even after W21 wired the diagram up
     to hover. W21 was right to activate it rather than delete it, but
     activating it didn't earn its height: with the diagram now carrying
     the interaction, the 6 full description cards below it were a
     second copy of the same information. This replaces that card grid
     with one row of compact links -- each still points at its own
     landing page (the real, non-decorative part) and still shares
     data-city with its diagram group so the W21 hover-link keeps
     working, it just no longer repeats the diagram's own prose. Not
     .service-card/.services-grid -- that shared component is still used
     for "What we fix" here and the landing pages' own service lists,
     untouched by this. */
  .areas-links{
    display:flex;
    flex-wrap:wrap;
    gap:10px;
    margin:0 0 16px;
  }
  .areas-link{
    display:flex;
    flex-direction:column;
    gap:3px;
    padding:12px 18px;
    border:1px solid var(--border);
    border-radius:var(--radius);
    background:var(--bg-panel);
    color:var(--text);
    text-decoration:none;
    transition:border-color .15s ease, transform .15s ease;
  }
  .areas-link b{font-family:var(--font-ui); font-weight:700; font-size:15px;}
  .areas-link small{color:var(--text-dim); font-size:12.5px;}
  .areas-link:not(.is-hub):hover,
  .areas-link:not(.is-hub):focus-visible{border-color:var(--blue-text); transform:translateY(-2px);}
  .areas-link.is-request{border-color:var(--orange-tint-border);}
  .areas-link.is-request:hover,
  .areas-link.is-request:focus-visible{border-color:var(--orange-text);}
  .areas-link.is-hub{background:var(--bg-panel-2); cursor:default;}
  .areas-request-note{color:var(--text-dim); font-size:13.5px; max-width:90ch; margin:0;}
  /* Same tighter figure/legend spacing the landing pages' own
     .areas-map-section already uses (26px) -- the homepage's #areas
     never picked it up since it doesn't carry that class. Applied here
     as part of the same regression-recovery pass that shrank the cards
     below the diagram, not a separate change. */
  #areas .radius-figure{margin-bottom:26px;}
  #areas .radius-legend{margin-bottom:20px;}
  @media (max-width:600px){
    .areas-links{gap:8px;}
    .areas-link{padding:10px 14px;}
  }

  /* W21/M03 fix (Master Audit, 2026-09-08): hovering (or tab-focusing)
     one of the 6 service cards below re-lights that exact city's own
     spoke/node/label here -- a real, ongoing reason to keep looking at
     the diagram after its one-time entrance animation (below) has
     already played, rather than a static picture nobody revisits. */
  .radius-figure .radius-spoke,
  .radius-figure .radius-node,
  .radius-figure .radius-hub,
  .radius-figure .radius-name,
  .radius-figure .radius-hub-name{
    transition:stroke .2s ease, fill .2s ease, filter .2s ease;
  }
  .radius-figure g.is-linked .radius-spoke{stroke:var(--blue-light); stroke-width:2.5;}
  .radius-figure g.is-linked .radius-spoke.is-request{stroke:var(--orange);}
  .radius-figure g.is-linked .radius-node{filter:drop-shadow(0 0 6px var(--blue-light));}
  .radius-figure g.is-linked .radius-node.is-request{filter:drop-shadow(0 0 6px var(--orange));}
  .radius-figure g.is-linked .radius-name{fill:var(--white);}
  .radius-figure g.is-hub.is-linked .radius-hub{filter:drop-shadow(0 0 8px var(--orange));}

  /* Light-trail arrival (2026-09-07): reuses the [data-reveal]/.is-visible
     mechanism already driving every other reveal on this page (a single
     IntersectionObserver, once per element) rather than adding a second
     one just for this diagram, and the existing global
     prefers-reduced-motion rule (animation-duration/transition-duration
     both zeroed for every element) already collapses this to its end
     state instantly for reduced-motion visitors with no extra code here.
     The 3 solid spokes draw in stroke-first via pathLength="1" (set on
     those <line> elements only -- the dashed "by request" spokes keep
     their real stroke-dasharray:5 5 untouched, so pathLength stays off
     them to avoid reinterpreting that pattern in a 0-1 coordinate
     space); the 2 dashed spokes fade in instead, arriving last since
     they're already the visually secondary "by request" cities. Each
     node gets a brief arrival pulse timed to when its own spoke finishes. */
  .radius-figure .radius-spoke:not(.is-request){
    stroke-dasharray:1;
    stroke-dashoffset:1;
  }
  .radius-figure .radius-spoke.is-request{opacity:0;}
  /* F26 fix (2026-09-07): with JS disabled, reveal-ready and is-visible
     never get added (both are set by the same IntersectionObserver-driven
     script that runs every other reveal on this page), so the two rules
     above left every connector line permanently hidden -- the whole
     diagram rendered as five floating dots with no lines connecting them
     to the hub. Same fallback idiom already used elsewhere on this page
     (.section-head::before, .blog-article h2::before, .hero-motto span):
     show the fully-arrived end state directly for a no-JS visitor,
     skipping only the decorative draw-in animation. */
  html:not(.reveal-ready) .radius-figure .radius-spoke:not(.is-request){stroke-dashoffset:0;}
  html:not(.reveal-ready) .radius-figure .radius-spoke.is-request{opacity:1;}
  html.reveal-ready .radius-figure.is-visible .radius-spoke:not(.is-request){
    stroke-dashoffset:0;
    transition:stroke-dashoffset .5s ease-out;
  }
  html.reveal-ready .radius-figure.is-visible .radius-spoke.is-request{
    opacity:1;
    transition:opacity .45s ease-out;
  }
  /* Each spoke leaves the hub at a different moment -- nearer cities
     first -- rather than every line drawing at once. */
  html.reveal-ready .radius-figure.is-visible g[data-city="washington-city"] .radius-spoke{transition-delay:.1s;}
  html.reveal-ready .radius-figure.is-visible g[data-city="hurricane"] .radius-spoke{transition-delay:.25s;}
  html.reveal-ready .radius-figure.is-visible g[data-city="santa-clara-ivins"] .radius-spoke{transition-delay:.4s;}
  html.reveal-ready .radius-figure.is-visible g[data-city="cedar-city"] .radius-spoke{transition-delay:.55s;}
  html.reveal-ready .radius-figure.is-visible g[data-city="mesquite"] .radius-spoke{transition-delay:.7s;}

  .radius-figure .radius-node,
  .radius-figure .radius-hub{
    transform-box:fill-box;
    transform-origin:center;
  }
  html.reveal-ready .radius-figure.is-visible .radius-hub{animation:radiusPulse .5s ease-out;}
  /* Each node pulses right as its own spoke finishes arriving --
     transition-delay + transition-duration from above, matched here. */
  html.reveal-ready .radius-figure.is-visible g[data-city="washington-city"] .radius-node{animation:radiusPulse .5s ease-out .6s backwards;}
  html.reveal-ready .radius-figure.is-visible g[data-city="hurricane"] .radius-node{animation:radiusPulse .5s ease-out .75s backwards;}
  html.reveal-ready .radius-figure.is-visible g[data-city="santa-clara-ivins"] .radius-node{animation:radiusPulse .5s ease-out .9s backwards;}
  html.reveal-ready .radius-figure.is-visible g[data-city="cedar-city"] .radius-node{animation:radiusPulse .5s ease-out 1s backwards;}
  html.reveal-ready .radius-figure.is-visible g[data-city="mesquite"] .radius-node{animation:radiusPulse .5s ease-out 1.15s backwards;}
  /* W23 fix (Master Audit, 2026-09-08): scale(1.8) read as an oversized,
     cartoonish jump for a small 6px node -- toned down to a still-clear
     but proportionate pulse. */
  @keyframes radiusPulse{
    0%{transform:scale(1); filter:drop-shadow(0 0 0 transparent);}
    50%{transform:scale(1.25); filter:drop-shadow(0 0 5px var(--blue-light));}
    100%{transform:scale(1); filter:drop-shadow(0 0 0 transparent);}
  }

  /* ---------- live open / closed ---------- */
  .open-status{
    display:inline-flex;
    align-items:center;
    gap:9px;
    margin-top:22px;
    padding:8px 15px;
    border-radius:999px;
    border:1px solid var(--border);
    background:rgba(10,10,10,.55);
    -webkit-backdrop-filter:blur(6px);
    backdrop-filter:blur(6px);
    font-family:var(--font-ui);
    font-size:13px;
    letter-spacing:.8px;
    color:#e8e8e8;
  }
  .open-status[hidden]{display:none;}
  .open-status .dot{
    width:9px; height:9px; border-radius:50%;
    background:var(--text-dim);
    flex-shrink:0;
    position:relative;
  }
  .open-status.is-open .dot{background:#3ad66b; box-shadow:0 0 0 4px rgba(58,214,107,.18);}
  .open-status.is-closed .dot{background:var(--orange);}
  /* Design feedback (2026-09-08): shown in both the hero and the
     closing screen as a live "are we open right now" read, but it sat
     static -- a small pulse when open is the standard "this is live,
     not printed" cue, not decoration. Closed stays a plain dot; there's
     no live moment to signal there. A real ::after ring (scale+fade)
     rather than animating box-shadow, which is expensive to animate. */
  .open-status.is-open .dot::after{
    content:'';
    position:absolute; inset:0;
    border-radius:50%;
    background:#3ad66b;
    animation:openDotPulse 2.2s ease-out infinite;
  }
  @keyframes openDotPulse{
    0%{transform:scale(1); opacity:.55;}
    100%{transform:scale(2.6); opacity:0;}
  }
  .open-status b{font-weight:600; color:#ffffff;}

  /* U04 fix (2026-09-08): the real next opening, beside the pill above --
     plain text (no pill chrome of its own) so it doesn't compete with
     the pill as a second visual element, just a line of information. */
  .next-opening{
    margin:8px 0 0;
    font-family:var(--font-ui);
    font-size:13px;
    color:#c9c5bc;
  }
  .next-opening[hidden]{display:none;}
  .next-opening b{font-weight:600; color:#ffffff;}
  .next-opening a{color:var(--orange-light); text-decoration:underline;}
  .next-opening a:hover{color:#ffffff;}

  /* ---------- city-page coverage badge (2026-09-07) ---------- */
  /* City landing pages need one real fact above the fold: is this a
     standard coverage area or a by-request one. That used to be a
     generic ALL-CAPS .eyebrow label -- the frontend-design skill's own
     checklist names that treatment as one of the commonest AI-design
     tells. The fact itself is real and load-bearing (unlike the
     decorative eyebrows removed elsewhere this pass), so instead of
     deleting it, it gets the same inline dot+pill treatment this page
     already uses for the live open/closed status below the CTAs --
     reusing an existing, subject-grounded idiom rather than inventing
     a new one or reaching for the generic label. */
  .coverage-badge{
    display:inline-flex;
    align-items:center;
    gap:9px;
    margin-bottom:18px;
    padding:8px 15px;
    border-radius:999px;
    border:1px solid var(--border);
    background:rgba(10,10,10,.55);
    -webkit-backdrop-filter:blur(6px);
    backdrop-filter:blur(6px);
    font-family:var(--font-ui);
    font-size:13px;
    letter-spacing:.8px;
    color:#e8e8e8;
  }
  .coverage-badge .dot{
    width:9px; height:9px; border-radius:50%;
    flex-shrink:0;
  }
  .coverage-badge.is-standard .dot{background:var(--blue); box-shadow:0 0 0 4px rgba(58,160,255,.18);}
  .coverage-badge.is-by-request .dot{background:var(--orange); box-shadow:0 0 0 4px rgba(255,128,0,.18);}

  /* =====================================================================
     Feature pass 3 (2026-09-06): the honest repair-vs-replace split, a
     seasonal care note, an hours-aware booking nudge, and blog reading
     progress. Additive; no existing selector is redefined.
  ===================================================================== */

  /* ---------- repair vs replace: the honesty split ---------- */
  .verdict-split{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:20px;
    align-items:stretch;
  }
  .verdict-col{
    border:1px solid var(--border);
    border-radius:var(--radius);
    background:var(--bg-panel);
    padding:28px 26px;
    position:relative;
    overflow:hidden;
    box-shadow:var(--shadow-resting);
  }
  .verdict-col.is-fix{border-top:3px solid var(--blue);}
  .verdict-col.is-replace{border-top:3px solid var(--orange);}
  .verdict-col h3{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:17px;
    text-transform:none;
    letter-spacing:0;
    margin:0 0 6px;
  }
  .verdict-col .verdict-sub{
    font-size:13px;
    color:var(--text-dim);
    margin:0 0 18px;
  }
  .verdict-col ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:13px;}
  .verdict-col li{
    display:flex;
    gap:11px;
    align-items:flex-start;
    font-size:14.5px;
    color:var(--text-dim);
    line-height:1.55;
  }
  .verdict-col li svg{width:15px; height:15px; margin-top:4px; flex-shrink:0;}
  .verdict-col.is-fix li svg{color:var(--blue-text);}
  .verdict-col.is-replace li svg{color:var(--orange-text);}
  .verdict-note{
    margin:26px 0 0;
    padding:16px 18px;
    border-left:3px solid var(--orange);
    background:var(--orange-tint-soft);
    border-radius:0 6px 6px 0;
    color:var(--text);
    font-size:15px;
    max-width:70ch;
  }
  .verdict-note b{color:var(--orange-text);}
  .verdict-note a{color:var(--orange-text); font-weight:600; text-decoration:underline;}
  .verdict-note a:hover{text-decoration:none;}
  @media (max-width:760px){
    .verdict-split{grid-template-columns:1fr;}
  }

  /* ---------- seasonal care note ---------- */
  .care-card{
    display:flex;
    gap:20px;
    align-items:flex-start;
    background:linear-gradient(160deg, var(--bg-panel-2), var(--bg-panel));
    border:1px solid var(--border);
    border-left:4px solid var(--blue);
    border-radius:var(--radius);
    padding:24px 26px;
    max-width:820px;
  }
  .care-card[hidden]{display:none;}
  .care-icon{
    width:44px; height:44px;
    border-radius:10px;
    background:rgba(58,160,255,.18);
    color:var(--blue-text);
    display:flex; align-items:center; justify-content:center;
    flex-shrink:0;
  }
  .care-icon svg{width:22px; height:22px;}
  .care-month{
    font-family:var(--font-ui);
    font-size:12px;
    font-weight:600;
    letter-spacing:2.2px;
    text-transform:uppercase;
    color:var(--blue-text);
    margin:0 0 6px;
  }
  .care-body{margin:0; max-width:68ch; color:var(--text-dim); font-size:15px; line-height:1.6;}
  .care-body b{color:var(--text); font-weight:600;}
  @media (max-width:600px){
    .care-card{flex-direction:column; gap:14px; padding:20px;}
  }

  /* ---------- hours-aware booking nudge, inside the status pill ---------- */
  .open-status a{
    color:var(--blue-text);
    font-weight:600;
    text-decoration:underline;
    text-underline-offset:3px;
    white-space:nowrap;
  }
  .open-status a:hover{color:#ffffff;}

  /* ---------- blog reading progress ---------- */
  .read-progress{
    position:fixed;
    top:0; left:0; right:0;
    height:3px;
    z-index:59;
    background:transparent;
    pointer-events:none;
  }
  .read-progress i{
    display:block;
    height:100%;
    width:100%;
    transform:scaleX(var(--read, 0));
    transform-origin:left center;
    background:linear-gradient(90deg, var(--orange-dark), var(--orange-light));
  }
  .read-meta{
    font-family:var(--font-ui);
    font-size:12.5px;
    letter-spacing:1.6px;
    text-transform:uppercase;
    color:var(--text-dim);
    margin:0 0 6px;
  }

  /* ---------- how a visit goes: the process line ---------- */
  /* Removes the main unknown for someone who has never called a repair
     person: what actually happens next. Every claim here already exists
     elsewhere on the site (the trust strip's upfront pricing, the Terms'
     guarantee) -- nothing new is asserted. */
  .process{
    display:grid;
    grid-template-columns:repeat(4, 1fr);
    gap:24px;
    position:relative;
  }
  .process::before{
    content:"";
    position:absolute;
    top:21px; left:8%; right:8%;
    height:1px;
    background:linear-gradient(90deg, var(--orange-tint-border), var(--border) 55%, var(--orange-tint-border));
    transform-origin:left;
    transform:scaleX(0);
    transition:transform .9s cubic-bezier(.22,.61,.36,1);
  }
  /* Draws in once the section's own heading has revealed, the same
     [data-reveal]/.is-visible mechanism used everywhere else on the
     site -- gated off the section-head sibling rather than adding
     data-reveal to .process itself, since .process-step children
     already carry their own reveal independently and stacking a
     second transform on their shared parent would double the distance
     they travel. */
  html.reveal-ready .section-head.is-visible ~ .process::before{
    transform:scaleX(1);
  }
  /* Steps arrive in sequence rather than as one block, same small
     stagger already used by .services-grid. */
  html.reveal-ready .process [data-reveal]:nth-child(2){transition-delay:.08s;}
  html.reveal-ready .process [data-reveal]:nth-child(3){transition-delay:.16s;}
  html.reveal-ready .process [data-reveal]:nth-child(4){transition-delay:.24s;}
  .process-step{position:relative; padding-top:0;}
  .process-num{
    width:44px; height:44px;
    border-radius:50%;
    background:var(--bg-panel);
    border:1px solid var(--orange-tint-border);
    color:var(--orange-text);
    font-family:var(--font-display);
    font-size:17px;
    display:flex; align-items:center; justify-content:center;
    margin:0 0 18px;
    position:relative;
    z-index:1;
    box-shadow:0 0 0 6px var(--bg);
  }
  .process-step h3{
    font-family:var(--font-ui);
    font-weight:700;
    font-size:16px;
    text-transform:none;
    letter-spacing:0;
    margin:0 0 8px;
  }
  .process-step p{color:var(--text-dim); font-size:14.5px; margin:0; line-height:1.6;}
  @media (max-width:860px){
    .process{grid-template-columns:1fr; gap:0;}
    .process::before{
      top:8px; bottom:8px; left:21px; right:auto;
      width:1px; height:auto;
      background:linear-gradient(180deg, var(--orange-tint-border), var(--border) 55%, var(--orange-tint-border));
      transform-origin:top;
      transform:scaleY(0);
    }
    html.reveal-ready .section-head.is-visible ~ .process::before{
      transform:scaleY(1);
    }
    .process-step{padding:0 0 26px 62px;}
    .process-step:last-child{padding-bottom:0;}
    .process-num{position:absolute; left:0; top:0; margin:0;}
  }

  /* ---------- city-focused service radius ---------- */
  /* One diagram, six pages. Each landing page sets data-focus so its own
     city is the lit one and the rest recede -- the page reads as local
     without inventing any local content. The homepage sets no focus, so
     everything stays at full strength there. */
  .radius-figure[data-focus] g[data-city]{
    opacity:.34;
    transition:opacity .4s ease;
  }
  .radius-figure[data-focus] g[data-city].is-hub{opacity:.8;}
  .radius-figure[data-focus="washington-city"]  g[data-city="washington-city"],
  .radius-figure[data-focus="hurricane"]        g[data-city="hurricane"],
  .radius-figure[data-focus="santa-clara-ivins"] g[data-city="santa-clara-ivins"],
  .radius-figure[data-focus="cedar-city"]       g[data-city="cedar-city"],
  .radius-figure[data-focus="mesquite"]         g[data-city="mesquite"]{
    opacity:1;
  }
  /* The focused node reads as the destination, not just another dot. */
  .radius-figure[data-focus] g[data-city]:not(.is-hub) .radius-node{
    transition:r .4s ease, filter .4s ease;
  }
  .radius-figure[data-focus="washington-city"]  g[data-city="washington-city"]  .radius-node,
  .radius-figure[data-focus="hurricane"]        g[data-city="hurricane"]        .radius-node,
  .radius-figure[data-focus="santa-clara-ivins"] g[data-city="santa-clara-ivins"] .radius-node,
  .radius-figure[data-focus="cedar-city"]       g[data-city="cedar-city"]       .radius-node,
  .radius-figure[data-focus="mesquite"]         g[data-city="mesquite"]         .radius-node{
    r:9;
    filter:drop-shadow(0 0 10px rgba(58,160,255,.55));
  }
  .radius-figure[data-focus="cedar-city"] g[data-city="cedar-city"] .radius-node,
  .radius-figure[data-focus="mesquite"]   g[data-city="mesquite"]   .radius-node{
    filter:drop-shadow(0 0 10px rgba(255,128,0,.6));
  }
  .radius-figure[data-focus] .radius-name{font-size:15px;}

  /* ---------- landing page sections ---------- */
  .areas-map-section .radius-figure{margin-bottom:26px;}
  .trust-strip-section{padding-top:0;}
  .trust-strip-section + section{border-top:1px solid var(--border);}

  /* Landing hero lede. F32 fix (2026-09-07): this used to be a raw
     inline-styled paragraph (identical inline style string repeated on
     all 5 landing pages) selected here by mere presence of a `style`
     attribute, with the real fix (a max-width cap, since it had none and
     ran the full width of a desktop screen) forced in via !important to
     beat that same inline style's own max-width. Given a real class,
     replacing the inline style outright -- no attribute-presence
     selector and no !important needed anymore. */
  .hero-lede{
    max-width:64ch;
    color:var(--text-dim);
    font-size:18px;
    margin-top:16px;
    line-height:1.7;
  }
