/* =====================================================================================
   BORING BUSINESS TEMPLATE — stylesheet
   -------------------------------------------------------------------------------------
   THE PALETTE BELOW IS THE ONLY THING YOU MUST CHANGE IN THIS FILE.
   Everything under it is structural and works for any trade.

   How to repalette, in order:
   1. Open the client's real logo file and sample from it. Do not guess brand colours and
      do not reuse the values below — they were sampled from a navy/stone logo and will
      look wrong on a green or red brand.
   2. --ink / --ink-800 / --stone / --stone-mid / --sand come off the logo.
   3. --mark is the CTA colour. It must clear 4.5:1 against BOTH white and --paper,
      because it is used as button fill AND as eyebrow text on a pale background.
      --mark-deep needs 7:1 with white (hover). --mark-light needs 4.5:1 on --ink
      (it is the only accent tone used as text on dark).
   4. Verify every one of those with a contrast checker before you ship. The three
      values here are verified; anything you replace them with is not until you check.

   TRAP: these tokens also appear in inline style attributes in the HTML
   (style="color:var(--mark-light)"). Renaming a token in this file alone will
   silently break those. grep the HTML too.

   The coursed-masonry divider near the top is a generated SVG data URI. If you
   re-generate it, keep the varied block LENGTHS — uniform widths are what made an
   earlier version read as bathroom tile instead of stone.
   ===================================================================================== */

:root{
    /* PALETTE — MONOCHROME, taken directly from their logo. The logo was measured rather
       than eyeballed: 62,500 sampled pixels, MAXIMUM SATURATION 0.000. It is a pure
       greyscale serif T|X monogram, #000000 on a #D8D8D8 ground, with no chromatic value
       anywhere in the file. So there is no brand hue to extend, and this palette is the
       logo's own ramp rather than a colour invented alongside it.

       CONSEQUENCE YOU MUST KNOW ABOUT: --mark and --ink are both near-black, so a default
       dark button is invisible on a dark band. Buttons on ink grounds are inverted further
       down this file (search "INVERTED BUTTON"). If you add a dark section, check its CTA.

       Tokens are named for what they hold. They were --navy/--slate/--accent in the
       template and --clay here before the palette went monochrome; a token called --clay
       holding a near-black is the same trap. Re-run check-palette.py after any change. */
    --ink:#121212;         /* the logo mark — dark sections and headings (17.95:1 on paper) */
    --ink-800:#262626;     /* one step up from ink — layered dark panels */
    --stone:#5E5E5E;       /* mid grey (6.71:1 on paper) */
    --stone-600:#6E6E6E;   /* tint — secondary text on paper (5.13:1) */
    --stone-mid:#9A9A9A;   /* rules and markers */
    --sand:#D8D8D8;        /* THE LOGO GROUND ITSELF — body text on ink (13.14:1) */
    --paper:#FAFAFA;       /* near-white */
    --paper-warm:#F0F0F0;  /* one step down — alternating bands break the flatness */
    --body:#595959;        /* muted body copy (6.71:1 on paper) */

    /* The mark. With no brand colour to fall back on, the call-to-action carries the
       logo's own black and earns attention through weight and contrast instead of hue.
       White button text sits on it at 18.73:1. */
    --mark:#121212;        /* THE call-to-action colour (18.73:1 white, 17.95:1 on paper) */
    --mark-deep:#000000;   /* hover — the mark's true value (21:1 with white) */
    --mark-light:#D8D8D8;  /* the only mark tone safe as text on ink (13.14:1) */

    --line:rgba(18,18,18,.16);
    --ease:cubic-bezier(.22,.61,.36,1);
  }
  *{box-sizing:border-box;margin:0;padding:0;}
  html{scroll-behavior:smooth;}
  /* Bottom padding clears the fixed mobile CTA bar, plus the iPhone home indicator.
     The bar is hidden from 1150px up, so the reserve is released there. */
  body{font-family:'Inter',system-ui,sans-serif;background:var(--paper);color:var(--ink);line-height:1.6;
       padding-bottom:calc(64px + env(safe-area-inset-bottom));}
  @media(min-width:1150px){body{padding-bottom:0;}}
  h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-weight:700;letter-spacing:-.02em;line-height:1.05;}
  a{color:inherit;text-decoration:none;}
  img{max-width:100%;display:block;}
  .wrap{max-width:1180px;margin:0 auto;padding:0 24px;}

  .btn{
    display:inline-block;background:var(--mark);color:#fff;font-weight:700;
    font-family:'Inter',system-ui,sans-serif;padding:15px 30px;border:none;font-size:1rem;cursor:pointer;
    letter-spacing:.01em;
    box-shadow:0 2px 0 0 var(--mark-deep);
    transition:transform .2s var(--ease), background-color .2s var(--ease), box-shadow .2s var(--ease);
  }
  .btn:hover,.btn:focus-visible{background:var(--mark-deep);transform:translateY(-2px);box-shadow:0 4px 0 0 #000;}
  .btn:active{transform:translateY(0);box-shadow:0 1px 0 0 var(--mark-deep);}

  .eyebrow{display:block;font-size:.76rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--mark);margin-bottom:14px;}
  .eyebrow::after{content:"";display:block;width:34px;height:3px;background:var(--mark);margin-top:8px;}
  /* The template centred this dash, which suits a centred hero. This hero's copy is
     left-aligned, so a centred 34px dash floated out on its own to the right of the
     eyebrow and read as a stray mark. Pinned back under the text. */
  .hero-copy .eyebrow::after{margin-left:0;margin-right:auto;}

  /* ===== SCROLL FADE — the default reveal for any block. Visible by default so a
     blocked CDN or stalled ticker never hides content; GSAP hides then reveals. ===== */
  .fade-up{will-change:transform,opacity;}

  /* ===== NAV — this is now a multi-page site, so the masthead carries real links.
     The template's "no nav" rule applies to single-page ads landing pages only. ===== */
  .mast-nav{display:none;gap:20px;align-items:center;}
  @media(min-width:1150px){.mast-nav{display:flex;}}
  .mast-nav a{
    color:var(--ink);font-size:.82rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase;white-space:nowrap;
    padding-bottom:3px;border-bottom:2px solid transparent;transition:border-color .2s var(--ease);
  }
  /* Active tab. This was --mark-light, picked for 8.0:1 on the old navy bar; on white
     it drops to about 1.9:1 and is unreadable. Back to --mark, which is 5.13:1 on white
     and is already the CTA colour, so the current tab and the CTA agree. */
  .mast-nav a:hover,.mast-nav a:focus-visible{border-bottom-color:var(--mark);}
  .mast-nav a[aria-current=page]{
    color:var(--mark);
    border-bottom-color:var(--mark);
  }

  /* ===== SIGNATURE: flagstone band =================================================
     Generated SVG tile, embedded as a data URI so it costs no request and scales. Random
     irregular flagstone — a Voronoi cellation of jittered seed points, each cell inset to
     leave a white joint. Replaces an earlier coursed running-bond brick
     band, on a client's reference image of exactly this crazy-paving look.

     SEEDED, so it is reproducible. The stone SIZES are deliberately mixed: the seeds come
     from a Poisson-style sample where each point carries its OWN minimum spacing, drawn
     from 9-28px. A plain jittered grid was tried first and every stone came out much the
     same size, which reads as mosaic rather than as laid flagstone.

     THE TILE MUST STAY SEAMLESS, AND THE WAY IT DOES IS SUBTLE. Seeds are duplicated at
     x-640 and x+640 so the pattern is periodic. Critically, each cell is bounded by a
     GENEROUS box rather than by the 640x44 tile rect, and the SVG viewport does the
     trimming. Clip cells to the tile rect instead and any stone crossing the edge becomes
     two half-cells, each inset toward its own centre — which opens a white vertical line
     every 640px. That was built and caught; do not reintroduce it. Verified seamless by
     scanning the rendered strip for full-height white columns at x=640 and x=1280.

     Contrast here is higher than the brick version it replaced, and that is intended: the
     old warning about keeping contrast low was about coursed brick reading as cartoon.
     Scattered dark stones among mid and pale ones is what the reference shows. */
  .course-band{
    height:34px;
    background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='640' height='44' viewBox='0 0 640 44'><rect width='640' height='44' fill='%23ECECEC'/><rect x='386.0' y='0.0' width='40.4' height='6.4' fill='%239E9E9E'/><rect x='428.0' y='0.0' width='19.4' height='6.4' fill='%238A8A8A'/><rect x='449.0' y='0.0' width='46.4' height='6.4' fill='%23D2D2D2'/><rect x='497.0' y='0.0' width='48.4' height='6.4' fill='%23C2C2C2'/><rect x='547.0' y='0.0' width='47.4' height='6.4' fill='%237A7A7A'/><rect x='596.0' y='0.0' width='47.4' height='6.4' fill='%23DCDCDC'/><rect x='-44.0' y='0.0' width='47.4' height='6.4' fill='%23DCDCDC'/><rect x='5.0' y='0.0' width='37.4' height='6.4' fill='%23D2D2D2'/><rect x='44.0' y='0.0' width='30.4' height='6.4' fill='%23C9C9C9'/><rect x='76.0' y='0.0' width='48.4' height='6.4' fill='%23C9C9C9'/><rect x='126.0' y='0.0' width='28.4' height='6.4' fill='%23C9C9C9'/><rect x='156.0' y='0.0' width='24.4' height='6.4' fill='%239E9E9E'/><rect x='182.0' y='0.0' width='22.4' height='6.4' fill='%234A4A4A'/><rect x='206.0' y='0.0' width='20.4' height='6.4' fill='%23A6A6A6'/><rect x='228.0' y='0.0' width='30.4' height='6.4' fill='%23DCDCDC'/><rect x='260.0' y='0.0' width='42.4' height='6.4' fill='%238A8A8A'/><rect x='304.0' y='0.0' width='39.4' height='6.4' fill='%23D2D2D2'/><rect x='345.0' y='0.0' width='39.4' height='6.4' fill='%234A4A4A'/><rect x='420.0' y='8.0' width='23.4' height='6.4' fill='%23C9C9C9'/><rect x='445.0' y='8.0' width='32.4' height='6.4' fill='%23C2C2C2'/><rect x='479.0' y='8.0' width='37.4' height='6.4' fill='%23A6A6A6'/><rect x='518.0' y='8.0' width='18.4' height='6.4' fill='%235E5E5E'/><rect x='538.0' y='8.0' width='22.4' height='6.4' fill='%235E5E5E'/><rect x='562.0' y='8.0' width='35.4' height='6.4' fill='%23A6A6A6'/><rect x='599.0' y='8.0' width='49.4' height='6.4' fill='%235E5E5E'/><rect x='-41.0' y='8.0' width='49.4' height='6.4' fill='%235E5E5E'/><rect x='10.0' y='8.0' width='41.4' height='6.4' fill='%23A6A6A6'/><rect x='53.0' y='8.0' width='50.4' height='6.4' fill='%239E9E9E'/><rect x='105.0' y='8.0' width='47.4' height='6.4' fill='%23A6A6A6'/><rect x='154.0' y='8.0' width='38.4' height='6.4' fill='%234A4A4A'/><rect x='194.0' y='8.0' width='47.4' height='6.4' fill='%237A7A7A'/><rect x='243.0' y='8.0' width='32.4' height='6.4' fill='%23DCDCDC'/><rect x='277.0' y='8.0' width='18.4' height='6.4' fill='%234A4A4A'/><rect x='297.0' y='8.0' width='31.4' height='6.4' fill='%237A7A7A'/><rect x='330.0' y='8.0' width='18.4' height='6.4' fill='%238A8A8A'/><rect x='350.0' y='8.0' width='49.4' height='6.4' fill='%23DCDCDC'/><rect x='401.0' y='8.0' width='17.4' height='6.4' fill='%23C2C2C2'/><rect x='382.0' y='16.0' width='35.4' height='7.4' fill='%239E9E9E'/><rect x='419.0' y='16.0' width='26.4' height='7.4' fill='%23D2D2D2'/><rect x='447.0' y='16.0' width='34.4' height='7.4' fill='%238A8A8A'/><rect x='483.0' y='16.0' width='36.4' height='7.4' fill='%23C2C2C2'/><rect x='521.0' y='16.0' width='40.4' height='7.4' fill='%23C9C9C9'/><rect x='563.0' y='16.0' width='22.4' height='7.4' fill='%23D2D2D2'/><rect x='587.0' y='16.0' width='31.4' height='7.4' fill='%23DCDCDC'/><rect x='620.0' y='16.0' width='29.4' height='7.4' fill='%23DCDCDC'/><rect x='-20.0' y='16.0' width='29.4' height='7.4' fill='%23DCDCDC'/><rect x='11.0' y='16.0' width='42.4' height='7.4' fill='%23DCDCDC'/><rect x='55.0' y='16.0' width='38.4' height='7.4' fill='%237A7A7A'/><rect x='95.0' y='16.0' width='23.4' height='7.4' fill='%23B4B4B4'/><rect x='120.0' y='16.0' width='47.4' height='7.4' fill='%23D2D2D2'/><rect x='169.0' y='16.0' width='42.4' height='7.4' fill='%23D2D2D2'/><rect x='213.0' y='16.0' width='21.4' height='7.4' fill='%235E5E5E'/><rect x='236.0' y='16.0' width='38.4' height='7.4' fill='%23C9C9C9'/><rect x='276.0' y='16.0' width='39.4' height='7.4' fill='%23C9C9C9'/><rect x='317.0' y='16.0' width='32.4' height='7.4' fill='%23DCDCDC'/><rect x='351.0' y='16.0' width='29.4' height='7.4' fill='%237A7A7A'/><rect x='513.0' y='25.0' width='48.4' height='7.4' fill='%23A6A6A6'/><rect x='563.0' y='25.0' width='50.4' height='7.4' fill='%237A7A7A'/><rect x='615.0' y='25.0' width='20.4' height='7.4' fill='%23A6A6A6'/><rect x='637.0' y='25.0' width='27.4' height='7.4' fill='%23D2D2D2'/><rect x='-3.0' y='25.0' width='27.4' height='7.4' fill='%23D2D2D2'/><rect x='26.0' y='25.0' width='36.4' height='7.4' fill='%23B4B4B4'/><rect x='64.0' y='25.0' width='44.4' height='7.4' fill='%23DCDCDC'/><rect x='110.0' y='25.0' width='42.4' height='7.4' fill='%235E5E5E'/><rect x='154.0' y='25.0' width='29.4' height='7.4' fill='%238A8A8A'/><rect x='185.0' y='25.0' width='20.4' height='7.4' fill='%23B4B4B4'/><rect x='207.0' y='25.0' width='35.4' height='7.4' fill='%23D2D2D2'/><rect x='244.0' y='25.0' width='32.4' height='7.4' fill='%23C9C9C9'/><rect x='278.0' y='25.0' width='27.4' height='7.4' fill='%234A4A4A'/><rect x='307.0' y='25.0' width='50.4' height='7.4' fill='%23D2D2D2'/><rect x='359.0' y='25.0' width='38.4' height='7.4' fill='%23B4B4B4'/><rect x='399.0' y='25.0' width='44.4' height='7.4' fill='%23A6A6A6'/><rect x='445.0' y='25.0' width='41.4' height='7.4' fill='%23DCDCDC'/><rect x='488.0' y='25.0' width='23.4' height='7.4' fill='%238A8A8A'/><rect x='288.0' y='34.0' width='49.4' height='8.4' fill='%23C9C9C9'/><rect x='339.0' y='34.0' width='32.4' height='8.4' fill='%23DCDCDC'/><rect x='373.0' y='34.0' width='33.4' height='8.4' fill='%239E9E9E'/><rect x='408.0' y='34.0' width='25.4' height='8.4' fill='%237A7A7A'/><rect x='435.0' y='34.0' width='28.4' height='8.4' fill='%23A6A6A6'/><rect x='465.0' y='34.0' width='36.4' height='8.4' fill='%23A6A6A6'/><rect x='503.0' y='34.0' width='32.4' height='8.4' fill='%239E9E9E'/><rect x='537.0' y='34.0' width='20.4' height='8.4' fill='%238A8A8A'/><rect x='559.0' y='34.0' width='41.4' height='8.4' fill='%238A8A8A'/><rect x='602.0' y='34.0' width='41.4' height='8.4' fill='%239E9E9E'/><rect x='-38.0' y='34.0' width='41.4' height='8.4' fill='%239E9E9E'/><rect x='5.0' y='34.0' width='41.4' height='8.4' fill='%23D2D2D2'/><rect x='48.0' y='34.0' width='30.4' height='8.4' fill='%234A4A4A'/><rect x='80.0' y='34.0' width='35.4' height='8.4' fill='%238A8A8A'/><rect x='117.0' y='34.0' width='24.4' height='8.4' fill='%237A7A7A'/><rect x='143.0' y='34.0' width='22.4' height='8.4' fill='%23C2C2C2'/><rect x='167.0' y='34.0' width='35.4' height='8.4' fill='%235E5E5E'/><rect x='204.0' y='34.0' width='24.4' height='8.4' fill='%23B4B4B4'/><rect x='230.0' y='34.0' width='19.4' height='8.4' fill='%23C9C9C9'/><rect x='251.0' y='34.0' width='35.4' height='8.4' fill='%238A8A8A'/></svg>");
    background-repeat:repeat-x;
    background-size:auto 34px;
    background-position:0 0;
    border-top:1px solid rgba(10,10,10,.07);
    border-bottom:1px solid rgba(10,10,10,.07);
  }
  @media(min-width:900px){.course-band{height:44px;background-size:auto 44px;}}
  /* Alternate placements start part-way along so two bands on a page never align. */
  .course-band.offset{background-position:-137px 0;}

  /* ===== MASTHEAD — SOLID white on every page. It is opaque, so the homepage hero photo
     begins below it rather than running underneath.

     EVERYTHING ON THIS BAR IS DARK BECAUSE THE BAR IS LIGHT. If you put it back on a dark
     ground, invert its contents too. --mark-light is readable on a dark bar and fails on
     a light one; --mark is the reverse.

     A translucent fading version was built and dropped. Light and dark scrims are NOT
     symmetric: a 55% white wash over a mid-dark hero photo measured 3.1:1 against dark
     labels and passed on only one of three slides. Solid white is 17.4:1 everywhere. ===== */
  .masthead{
    position:absolute;top:0;left:0;right:0;z-index:20;height:82px;
    display:flex;align-items:center;justify-content:space-between;
    padding:0 20px;
    background:#fff;
  }
  @media(min-width:900px){
    .masthead{height:90px;padding:0 32px;}
  }
  /* logo.png is dark art on a transparent ground, so it needed a white plate to be
     legible on the old navy bar. On a white bar the plate is redundant — dropped. */
  /* Logo sizing is bounded by the bar, not by taste: the masthead is 82px on phones and
     90px from 900px up, and the logo has to sit inside that with visible air above and
     below. 64/72px with 4px padding leaves ~5px clearance top and bottom. If you push it
     further you must raise the .masthead heights too, or the logo will look jammed against
     the edges. logo.png is 420x278, so height drives width at a 1.51 ratio — 72px tall is
     ~109px wide, which also eats into the nav headroom at the 1150px breakpoint. */
  .masthead-logo{
    height:64px;width:auto;background:transparent;
    padding:4px 0;border-radius:0;
  }
  @media(min-width:900px){.masthead-logo{height:72px;}}
  .masthead-actions{display:flex;align-items:center;gap:14px;}
  .masthead-tel{color:var(--ink);font-weight:600;font-size:.95rem;letter-spacing:.01em;}
  /* Header CTA. Fill and text were already solid --mark with #fff, so this only lifts
     the label from 14.4px to a full 16px. Height lands at ~48px, past the 44px minimum.
     Watch the 1150px breakpoint if you grow it further — the bar carries three service
     labels, the phone and this button, and 1150 exists because five labels wrapped below
     about 1085px. Re-check the nav at exactly 1150 after any size change here. */
  .masthead .btn{padding:11px 20px;font-size:1rem;}
  @media(max-width:559px){.masthead-tel{display:none;}}

  /* ===== HERO — full-bleed photo, no copy on the image. Aspect ratios match the
     reference exactly: 8:3 (2.667) on desktop, 0.842 on mobile, art-directed via
     <picture> so each device downloads only its own crop. ===== */
  /* ===== HERO — full-bleed photo slideshow, copy overlaid ==========================
     Replaces the earlier photo-on-top / copy-below stack. The masthead floats over the
     photo, the headline sits bottom-left rather than centred, and three slides cross-fade.
     Sized in svh so mobile browser chrome cannot crop the CTA out of view. */
  .hero{
    position:relative;isolation:isolate;
    min-height:88svh;display:flex;flex-direction:column;
    background:var(--ink);
  }
  @media(min-width:900px){.hero{min-height:82svh;}}

  /* ===== HERO -> SECTION BLEND =======================================================
     The services section was moved directly under the hero, so the hero's bottom became a
     hard horizontal line where the photograph simply stopped against solid ink. This fades
     the last slice of the hero to EXACTLY var(--ink) so the picture dissolves into the
     section below instead of butting against it.

     THE END STOP MUST BE THE SAME VALUE AS THE SECTION BELOW (#121212). Land on anything
     else and the join reappears as a faint band — which is harder to spot than the hard
     edge it replaced.

     z-index:1 puts it above .hero-slides (0) and after .hero-scrim in paint order, but
     below .hero-copy and .hero-foot (both 2), so the headline, dots and scroll cue stay
     on top of the fade rather than being washed out by it. */
  .hero::after{
    content:"";position:absolute;left:0;right:0;bottom:0;
    height:26%;z-index:1;pointer-events:none;
    background:linear-gradient(to bottom,
      rgba(18,18,18,0) 0%,
      rgba(18,18,18,.42) 44%,
      rgba(18,18,18,.84) 78%,
      #121212 100%);
  }

  .hero-slides{position:absolute;inset:0;z-index:0;overflow:hidden;}
  .hero-slide{position:absolute;inset:0;opacity:0;transition:opacity 1.2s var(--ease);}
  .hero-slide.is-on{opacity:1;}
  .hero-slide img{width:100%;height:100%;object-fit:cover;display:block;}

  /* Scrim. Strongest at the bottom-left where the copy sits, so the photo stays readable
     up top and the type never fights the image. */
  .hero-scrim{
    position:absolute;inset:0;z-index:1;pointer-events:none;
    background:
      /* 1. Focused pool behind the copy block only. An ellipse anchored bottom-left, so the
            lede stops sitting at the same tone as the paving without flattening the photo.
            This is the layer doing the contrast work; the two below stay light. */
      radial-gradient(120% 95% at 6% 100%, rgba(8,8,8,.86) 0%, rgba(8,8,8,.55) 34%, rgba(8,8,8,0) 66%),
      /* 2. Gentle floor so the dots and scroll cue read on any slide. */
      linear-gradient(to top, rgba(10,10,10,.52) 0%, rgba(10,10,10,.16) 26%, rgba(10,10,10,0) 52%, rgba(10,10,10,.24) 100%),
      /* 3. Slight lean to the text side. Clears well before the right edge so the
            stonework on that side keeps its contrast. */
      linear-gradient(to right, rgba(10,10,10,.34) 0%, rgba(10,10,10,.10) 38%, rgba(10,10,10,0) 64%);
  }

  /* On a narrow, tall viewport the copy sits mid-height, not bottom-left, so the
     desktop radial pool misses it and the lede washes out over pale stone. Below 900px
     the lower half gets a proper floor instead. The top 40% is left alone so the photo
     still opens the page. */
  @media(max-width:899px){
    .hero-scrim{
      background:linear-gradient(to top,
        rgba(8,8,8,.92) 0%,
        rgba(8,8,8,.78) 22%,
        rgba(8,8,8,.52) 42%,
        rgba(8,8,8,.18) 62%,
        rgba(8,8,8,.02) 78%,
        rgba(10,10,10,.34) 100%);
    }
  }

  /* z-index was 3 — above the hero's own layers (slides 0, scrim 1, copy 2) but BELOW the
     menu panel at 60, and this selector outranks the `.masthead{z-index:70}` rule further
     down by specificity. So on the homepage only, the open panel painted over the burger:
     no X, and on a phone no way to close the menu at all (Escape is the sole other exit and
     there is no click-outside handler). 70 matches the intent of that later rule and still
     sits under the lightbox at 100. Do not lower this back without checking the panel. */
  .hero .masthead{position:relative;z-index:70;}

  .hero-copy{
    position:relative;z-index:2;
    margin-top:auto;padding:0 24px 26px;text-align:left;
  }
  @media(min-width:900px){.hero-copy{padding:0 44px 42px;}}
  .hero-copy-inner{max-width:640px;margin:0;}
  .hero .eyebrow{color:var(--mark-light);}
  .hero .eyebrow::after{background:var(--mark-light);}
  .hero h1{
    color:#fff;font-size:clamp(2.1rem,6.4vw,4rem);line-height:1.02;
    text-shadow:0 2px 18px rgba(6,6,6,.42);
  }
  .hero h1 span{color:var(--mark-light);}
  .hero .lede{
    color:var(--sand);font-size:1.03rem;margin:16px 0 24px;max-width:46ch;
    text-shadow:0 1px 12px rgba(6,6,6,.5);
  }
  /* Hero CTA — same solid --mark fill as the header, deliberately larger: 18px label
     and 20px vertical padding, so the primary action outweighs the one in the bar.
     The 24px of air above it is already supplied by .lede's bottom margin — do not add a
     margin-top here as well or the gap doubles. */
  .hero-copy .btn{
    display:inline-block;width:auto;margin:0;
    font-size:1.125rem;padding:20px 34px;
  }

  /* Slide dots + scroll cue, mirroring the reference's bottom rail. */
  .hero-foot{
    position:relative;z-index:2;
    display:flex;align-items:center;justify-content:space-between;gap:16px;
    padding:0 24px 22px;
  }
  @media(min-width:900px){.hero-foot{padding:0 44px 26px;}}
  .hero-dots{display:flex;gap:9px;}
  /* 44px wide, not 34px. The visible dash stays 34px — the extra 10px is horizontal
     padding, so the tap target reaches the 44px minimum without the indicator changing
     size. box-sizing:border-box is global, so width:44px minus 5px each side leaves a
     34px content box and the ::after at width:100% draws exactly as before.
     Do not "tidy" this back to width:34px; that was the bug. */
  .hero-dots button{
    width:44px;height:44px;padding:0 5px;border:0;background:none;cursor:pointer;
    display:flex;align-items:center;
  }
  .hero-dots button::after{
    content:"";display:block;width:100%;height:3px;
    background:rgba(246,246,246,.42);transition:background-color .3s var(--ease);
  }
  .hero-dots button.is-on::after{background:var(--mark-light);}
  .hero-scroll{
    display:inline-flex;align-items:center;gap:8px;min-height:44px;
    color:var(--sand);font-size:.74rem;font-weight:700;
    letter-spacing:.16em;text-transform:uppercase;
  }
  .hero-scroll:hover,.hero-scroll:focus-visible{color:#fff;}

  /* Sits on the photo now, not on paper. --body is a mid grey tuned for a light
     background and disappears over the pale stamped concrete, so this reads in the
     pale stone tone with a shadow behind it instead. */
  .hero-facts{
    margin:24px 0 0;padding-top:14px;
    border-top:2px solid rgba(246,246,246,.32);
    font-size:.85rem;color:var(--sand);max-width:42ch;
    text-shadow:0 1px 10px rgba(6,6,6,.66);
  }




  /* ===== PINNED BRAND MARK (services) ================================================
     Their own logo held still under the viewport while the service cards scroll over it.

     HOW IT WORKS: .pin-bg is absolutely positioned to the section's bounds and the <img>
     inside it is position:sticky, top:0, one viewport tall, with the mark centred inside
     that box by object-fit:contain + padding. Sticky pins it while the section passes the
     viewport and releases at the section's end. No JavaScript, no scroll listener, no
     scroll maths — the compositor does all of it, which is why it stays smooth on a phone.
     The design system bans "parallax tied to raw scroll position"; this reads no scroll
     offset at all.

     THE TRAP: overflow:hidden on .pin-bg, .trades or any ancestor creates a scroll
     container and position:sticky silently stops working — no error, it just scrolls.
     Use overflow:clip if you ever need clipping; it does not create a scrollport.

     100svh not 100vh: on mobile the address bar collapsing changes vh and the mark jumps
     mid-scroll. svh is the stable one.

     THE MARK IS THEIR REAL LOGO FILE AND ITS NATIVE SIZE IS ONLY 290x226. Houzz holds no
     larger original — every requested size returns the same 498px of actual content padded
     out. So it is drawn at a deliberately restrained size and at 0.13 opacity, where the
     modest upscale cannot read. DO NOT push this bigger or more opaque without a real
     vector or a higher-resolution file from the owner; it will go soft immediately.
     filter:invert(1) turns the black mark white — the source is pure black on transparent. */
  /* `.trades.trades-pinned`, not `.trades-pinned`. The base `.trades{padding:64px 24px 56px}`
     rule sits LATER in this file, so at equal specificity its shorthand silently beat the
     padding below and the section stayed 1054px — the pin travel never changed. Doubling the
     class wins on specificity regardless of source order, so reordering this file later
     cannot bring the bug back. */
  .trades.trades-pinned{
    position:relative;isolation:isolate;
    background:var(--ink);           /* fail-safe: if the mark never loads the light heading is still readable */
  }
  /* overflow:CLIP, never hidden. clip does not create a scroll container, so position:sticky
     on the child still resolves against the viewport; hidden would create one and kill the
     pin silently. It is here so the oversized mark is trimmed to the section instead of
     causing horizontal overflow. */
  .trades-pinned .pin-bg{position:absolute;inset:0;z-index:0;overflow:clip;}
  /* The STICKY element is this wrapper, one viewport tall. The mark inside it is free to be
     larger than the wrapper — which it has to be, because at 420px it sat entirely behind an
     1180px card grid and could not be seen at all. */
  .trades-pinned .pin-mark{
    position:sticky;top:0;height:100svh;
    display:flex;align-items:center;justify-content:center;
  }
  .trades-pinned .pin-mark img{
    width:min(118vw,1500px);height:auto;max-width:none;
    filter:invert(1);opacity:.16;
  }
  .trades-pinned .pin-content{position:relative;z-index:1;}

  /* THE PIN ONLY LASTS (section height - viewport height). That is the whole mechanic, and
     it is why this needs deliberate vertical room. At 1440x900 the three-across card row
     made the section 1054px against a 900px viewport, so it held for 154px and the effect
     was invisible — measured, not guessed. This takes the section to roughly 1.6x the
     viewport, giving ~540px of travel, which reads as a proper hold.

     Only needed from 900px up. On a phone the cards stack and the section is already 2503px
     against an 844px viewport — 1659px of travel with no help at all. */
  @media(min-width:900px){
    /* Top and bottom are deliberately UNEQUAL now. The hero fades into this section, so
       the dissolve itself provides the space above the heading and 252px of flat ink on top
       of that left the heading stranded. The removed top padding is added to the bottom, so
       the TOTAL is identical and the pin travel does not change. */
    .trades.trades-pinned{padding-top:clamp(90px,14vh,160px);padding-bottom:clamp(190px,42vh,440px);}
  }

  /* The section head sits on the dark ground now, so it inverts. The CARDS do not — they
     keep their own white ground, which is why this section was the right one to pin: card
     text contrast is completely unaffected by what is behind them. */
  .trades-pinned .head .eyebrow{color:var(--mark-light);}
  .trades-pinned .head .eyebrow::after{background:var(--mark-light);}
  .trades-pinned .head h2{color:#fff;}

  /* Reduced motion: hold the mark against the section instead of the viewport, so there is
     no relative movement at all. The mark stays, the effect goes. */
  @media (prefers-reduced-motion: reduce){
    .trades-pinned .pin-mark{position:absolute;top:0;height:100%;width:100%;}
  }


  /* ===== INVERTED BUTTON ON DARK GROUNDS ==============================================
     Forced by the monochrome palette. --mark is #121212 and --ink is #121212, so the
     default dark-filled button is invisible on any ink band — the closing .cta-band, the
     About band and the footer. It is not a styling preference: measured, a #121212 fill on
     a #121212 ground is 1.00:1. On those grounds the button inverts to a paper fill with
     ink text, which is 17.95:1 and reads as the same button in negative.
     If you add a new dark section, add it here. */
  /* `footer .btn`, NOT `.site-foot .btn`. The footer element in this template carries NO
     class at all, so a class-based guess silently missed it and shipped a #121212 button
     on a #121212 footer — 1.00:1, invisible. Caught by measuring every button's computed
     background against its own section's, which is the only way this shows up. */
  /* `.hero-copy .btn`, NOT `.hero .btn`. The masthead is INSIDE the hero section and sits
     on a white bar, so `.hero .btn` would invert it to white-on-white. Measured on the
     rendered hero: the dark fill read 2.22:1 against the photograph behind it — under the
     3.0 an interactive edge needs — because a monochrome palette has no accent hue to
     separate the button from a dark image. Inverted it measures 8.4:1. */
  .hero-copy .btn,
  .trades-pinned .mid-cta .btn,
  .cta-band .btn,
  .craft .btn,
  footer .btn,
  .band-ink .btn{
    background:var(--paper);color:var(--ink);box-shadow:0 2px 0 0 var(--stone-mid);
  }
  .hero-copy .btn:hover,.hero-copy .btn:focus-visible,
  .trades-pinned .mid-cta .btn:hover,.trades-pinned .mid-cta .btn:focus-visible,
  .cta-band .btn:hover,.cta-band .btn:focus-visible,
  .craft .btn:hover,.craft .btn:focus-visible,
  footer .btn:hover,footer .btn:focus-visible,
  .band-ink .btn:hover,.band-ink .btn:focus-visible{
    background:#fff;color:#000;box-shadow:0 4px 0 0 var(--stone-mid);
  }

  .footer-logo{
    width:132px;height:auto;background:var(--paper);
    padding:9px 12px;border-radius:3px;flex-shrink:0;
  }

  /* ===== PROJECT GALLERY — the remaining real photos, moved out of the old
     split hero so none of them go unused. ===== */
  .gallery-band{padding:8px 0 40px;}
  .card-stack{display:flex;flex-direction:column;gap:14px;padding:0 24px 8px;max-width:1180px;margin:0 auto;}
  @media(min-width:820px){.card-stack{flex-direction:row;gap:16px;padding:0 24px 8px;}
    .card-stack .shot{flex:1 1 0;}}
  .shot{position:relative;overflow:hidden;}
  .shot img{width:100%;aspect-ratio:4/3;object-fit:cover;}
  .shot figcaption{
    position:absolute;left:0;right:0;bottom:0;padding:34px 18px 14px;
    background:linear-gradient(0deg, rgba(10,10,10,.88) 0%, rgba(10,10,10,0) 100%);
    color:var(--paper);
  }
  .shot figcaption b{display:block;font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:1rem;}
  .shot figcaption span{font-size:.8rem;color:var(--sand);}

  /* ===== SERVICES — grouped into three trades, not a flat grid ===== */
  .trades{padding:64px 24px 56px;}
  .trades .head{max-width:1180px;margin:0 auto 34px;}
  .trade-grid{max-width:1180px;margin:0 auto;display:grid;grid-template-columns:1fr;gap:0;}
  @media(min-width:820px){.trade-grid{grid-template-columns:repeat(3,1fr);gap:0;}}
  /* Trades are now links to their own pages, and each carries a photo. The warm rule
     on top replaces the flat hairline and ties them to the CTA colour. */
  .trade{
    display:block;padding:0 0 24px;background:#fff;
    border-top:3px solid var(--mark);
    box-shadow:0 1px 0 0 var(--line), 0 10px 24px -18px rgba(10,10,10,.5);
    transition:transform .25s var(--ease), box-shadow .25s var(--ease);
  }
  .trade:hover{transform:translateY(-4px);box-shadow:0 1px 0 0 var(--line), 0 18px 30px -18px rgba(10,10,10,.55);}
  .trade img{width:100%;aspect-ratio:4/3;object-fit:cover;margin-bottom:18px;}
  .trade h3{font-size:1.15rem;margin:0 22px 12px;}
  .trade ul{list-style:none;margin:0 22px;}
  .trade li{font-size:.93rem;color:var(--body);padding:5px 0;}
  .trade li::before{content:"▪";color:var(--mark);margin-right:9px;}
  .trade-more{
    display:block;margin:16px 22px 0;font-size:.85rem;font-weight:700;
    color:var(--mark);letter-spacing:.02em;
  }
  @media(min-width:820px){.trade-grid{gap:20px;}}

  .mid-cta{max-width:1180px;margin:34px auto 0;padding:0 24px;}

  /* ===== CRAFT — the crew mid-build, their strongest proof ===== */
  .craft{background:var(--ink);color:var(--paper);padding:0;}
  .craft-inner{max-width:1180px;margin:0 auto;display:grid;grid-template-columns:1fr;}
  @media(min-width:860px){.craft-inner{grid-template-columns:1.1fr .9fr;align-items:center;}}
  /* The About photo is natively 2.22:1 -- their site stores it pre-cropped wide. Forcing
     it to 4:3 threw away 40% of the frame, so the slot follows the photo instead. */
  /* height:100% was overriding aspect-ratio, so the grid row height decided the crop
     and this wide photo was being squeezed to nearly square. Let the ratio govern. */
  .craft img{width:100%;height:auto;object-fit:cover;aspect-ratio:16/9;}
  /* Desktop keeps the wide ratio too: forcing 4/3 here would re-crop the sides that
     the 16/9 export was made to preserve. The column centres, which is fine. */
  @media(min-width:860px){.craft img{aspect-ratio:16/9;}}
  .craft .txt{padding:48px 24px;}
  @media(min-width:860px){.craft .txt{padding:56px 40px;}}
  .craft h2{color:var(--paper);font-size:clamp(1.6rem,3.4vw,2.2rem);max-width:18ch;}
  .craft p{color:var(--sand);margin-top:16px;max-width:44ch;font-size:.98rem;}

  /* ===== CLOSING CTA BAND — navy panel so the azure button has maximum contrast ===== */
  .cta-band{background:var(--ink);padding:64px 24px 68px;text-align:center;}
  .cta-inner{max-width:640px;margin:0 auto;}
  .cta-band h2{color:#fff;font-size:clamp(1.7rem,3.6vw,2.4rem);}
  .cta-band p{color:var(--sand);margin:14px auto 26px;max-width:46ch;}
  .cta-band .eyebrow::after{margin-left:auto;margin-right:auto;background:var(--mark-light);}

  /* ===== QUOTE FORM (quote.html) ===== */
  .quote-band{padding:60px 24px 52px;}
  .quote-card{max-width:520px;margin:0 auto;background:#fff;border:1px solid var(--line);padding:30px 26px;}
  .quote-card h2{font-size:1.5rem;margin-bottom:6px;}
  .quote-card .sub{color:var(--body);font-size:.9rem;margin-bottom:20px;}
  .field{margin-bottom:15px;}
  .field label{display:block;font-size:.75rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;margin-bottom:6px;color:var(--body);}
  /* 16px, and this is NOT a style choice — it is the iOS zoom threshold.
     Safari on iPhone auto-zooms the whole page when you focus an input whose font-size is
     under 16px. These were .95rem = 15.2px, so tapping any field on the quote form zoomed
     the page and left the user pinching back out mid-form. On the one page whose entire job
     is capturing a lead, that is a conversion leak, not a cosmetic issue.
     DO NOT drop these below 1rem. If the fields look too large for the design, change the
     padding, never the font-size. */
  .field input,.field select{
    width:100%;padding:12px 13px;border:1px solid var(--line);border-radius:0;
    font-family:'Inter',system-ui,sans-serif;font-size:1rem;background:var(--paper);color:var(--ink);
  }
  .field input:focus,.field select:focus{outline:2px solid var(--stone);outline-offset:1px;}
  .quote-card .btn{width:100%;text-align:center;margin-top:4px;}
  .form-note{font-size:.75rem;color:var(--body);margin-top:12px;text-align:center;}
  .confirm{display:none;text-align:center;padding:10px;}
  .confirm.show{display:block;}
  .confirm h3{color:var(--stone);margin-bottom:6px;}

  footer{background:var(--ink);color:var(--sand);padding:28px 24px;}
  .footer-row{max-width:1180px;margin:0 auto;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:16px;font-size:.85rem;}
  footer a.tel{
    color:var(--paper);font-weight:600;
    display:inline-flex;align-items:center;min-height:44px;padding:4px 2px;
  }

  /* A floating button, not a bar. It used to span the full width and sit flush on the
     bottom edge at every width below 1150px. Now it is a pill in the bottom-right corner
     on every screen size, so it reads as an offer you can take rather than a permanent
     fixture bolted across the page.
     bottom: is offset by the iPhone home indicator rather than padded past it — a padded
     full-width bar could absorb that inset, a floating pill has to clear it. */
  /* CENTRED VIA AUTO MARGINS, NOT translateX. The show/hide slide below already owns
     `transform`; centring with translateX(-50%) would mean every one of those rules had to
     carry the -50% too, and the first one that forgot it would fling the button off-screen.
     left:0/right:0 + width:fit-content + margin-inline:auto centres it with no transform,
     so the slide stays a single-purpose property. */
  /* Frosted, not fully transparent. A clear fill was tried and failed on this page: the
     label had to compete with whatever sat behind it, and this page is mostly photography.
     Over a pool photo the words broke up against the loungers; over the navy About band
     azure-on-navy measured 3.5:1, under the 4.5:1 floor. The panel gives the label a
     ground to sit on while you can still see the page moving behind it.

     The white fill is the load-bearing part, NOT the blur. backdrop-filter is decoration
     and is allowed to fail — see the @supports below, which is written the safe way round:
     the base is the more opaque value, and only browsers that can actually blur get the
     thinner one. Never invert that, or a browser without backdrop-filter gets an 82% panel
     with no blur behind it and the label goes soft again. */
  .floating-cta{
    position:fixed;left:0;right:0;margin-inline:auto;width:fit-content;
    bottom:calc(16px + env(safe-area-inset-bottom));z-index:50;
    display:flex;border-radius:4px;
    background:rgba(255,255,255,.93);
    border:2px solid var(--mark);
    box-shadow:0 8px 22px -10px rgba(10,10,10,.5);
    -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  }
  @supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
    .floating-cta{background:rgba(255,255,255,.82);}
  }
  /* The <a> fills the whole bar. It used to be a 24px-tall link floating inside a
     padded div, so most of what looks like the button did nothing when tapped. */
  /* The <a> is the whole pill, so the entire visible shape is the tap target. It was once
     a 24px link floating inside a padded div, which meant most of what looked like the
     button did nothing when tapped. min-height keeps it past the 44px minimum. */
  /* Label is --mark-deep, not --mark, and this is a measured choice rather than taste.
     The panel is translucent, so what sits behind it changes how light it ends up: over a
     photo it composites to about #F5F5F4 and over the dark About band to about #DADADA.
     A mid-tone label on that darker composite fell under the 4.5:1 floor. --mark-deep
     clears it on both, so the panel can stay genuinely see-through instead of being made
     more opaque to rescue a lighter label. The BORDER stays --mark: it is a UI edge, not
     text, so it answers to the 3:1 threshold and passes. */
  .floating-cta a{
    display:flex;align-items:center;justify-content:center;
    min-height:52px;padding:14px 22px;white-space:nowrap;
    color:var(--mark-deep);font-weight:700;font-size:1rem;
  }
  /* ===== FLOATING CTA REVEAL ==========================================================
     The bar used to sit there from the first paint, on top of a hero that already has a
     large "Get a Free Estimate" button a few hundred pixels above it. Two identical CTAs
     on one screen is not twice the pull. It now stays out of the way until the hero has
     scrolled off, which is the point at which the page no longer offers one.

     FAIL-SAFE DIRECTION MATTERS. The auto-hide only engages once motion.js adds
     .cta-auto to <body>. With JS blocked, no observer support, or a stalled script, none
     of these rules apply and the bar simply stays visible exactly as it did before — the
     CTA can never end up permanently hidden by a script that failed to run. Never invert
     this by hiding the bar in the base rule and revealing it with JS.

     Only transform and opacity animate here. pointer-events is what actually makes the
     hidden bar untappable — a translated element still takes taps without it. */
  .floating-cta{
    transition:transform .28s var(--ease), opacity .22s var(--ease);
  }
  /* 140%, not 115%. The pill no longer sits flush on the bottom edge — it floats 16px
     above it — so a 115% slide left a few pixels of it peeking past the viewport edge.
     Invisible only because opacity also goes to 0, which is not something to rely on. */
  body.cta-auto .floating-cta{
    transform:translateY(140%);opacity:0;pointer-events:none;
  }
  body.cta-auto .floating-cta.is-on{
    transform:none;opacity:1;pointer-events:auto;
  }

  /* STAND DOWN WHENEVER A REAL CTA IS ON SCREEN. The rule is one sentence: a floating CTA
     is pointless while an actual one is visible, and actively bad when it lands on top of
     it. Two triggers, both watched in motion.js:

       .cta-band  the closing "Tell us what you're building" block. Centring the pill put
                  it on the same vertical line as that block's own button, so the two
                  stacked — identical text, one over the other, reading as a bug. This is
                  why the rule is not just about the footer.
       footer     the pill floats rather than reserving a strip, so at the end of the
                  document it settles on the footer's phone number and email.

     Consequence on short pages: a page barely taller than the viewport has one of these in
     view almost immediately, so the pill hardly appears there. Correct, not broken. */
  body.cta-auto.real-cta .floating-cta{
    transform:translateY(140%);opacity:0;pointer-events:none;
  }

  /* Desktop had NO floating CTA at all — it was display:none from 1150px up. Same pill,
     just further off the edge and slightly tighter, since there is more room to sit in. */
  @media(min-width:1150px){
    .floating-cta{bottom:26px;}
    .floating-cta a{min-height:0;padding:15px 26px;font-size:.95rem;}
  }

  /* Reduced motion: kill the hover transitions. Scroll reveals are gated in JS and
     never run at all, so there is no half-animated state to clean up here. */
  @media (prefers-reduced-motion: reduce){
    .btn,.shot,.trade{transition:none !important;}
    /* The CTA still appears and disappears at the same scroll points, it just cuts
       instead of sliding. Reduced motion means less movement, not a missing button. */
    .floating-cta{transition:none !important;}
  }

/* ===================================================================
   TRADE PAGES + QUOTE PAGE
   =================================================================== */

/* Inner pages have no hero photo behind the masthead. Since the homepage bar became
   opaque white too, .solid now differs only in taking part in flow rather than
   overlaying — the ground is the same white on every page. */
.masthead.solid{position:relative;background:#fff;border-bottom:1px solid var(--line);}
@media(min-width:1150px){.masthead.solid{background:#fff;}}

/* Page banner — a shallower version of the homepage hero */
.page-head{background:var(--ink);color:#fff;padding:44px 24px 52px;}
.page-head .inner{max-width:1180px;margin:0 auto;}
.page-head h1{color:#fff;font-size:clamp(1.9rem,4vw,2.8rem);max-width:18ch;}
.page-head p{color:var(--sand);margin-top:16px;max-width:56ch;font-size:1.02rem;}
.page-head .eyebrow{color:var(--mark-light);}
.page-head .eyebrow::after{background:var(--mark-light);}

/* Alternating section grounds so the page is not one flat field of white */
.band{padding:56px 24px;}
.band-paper{background:var(--paper);}
.band-warm{background:var(--paper-warm);}
.band-navy{background:var(--ink);color:#fff;}
.band .inner{max-width:1180px;margin:0 auto;}
.band h2{font-size:clamp(1.5rem,3.2vw,2rem);max-width:22ch;}
.band-navy h2{color:#fff;}
.band p{color:var(--body);margin-top:14px;max-width:62ch;}
.band-navy p{color:var(--sand);}

/* Service detail rows — image one side, copy the other, alternating */
.detail{display:grid;grid-template-columns:1fr;gap:24px;margin-top:36px;align-items:center;}
@media(min-width:820px){
  .detail{grid-template-columns:1fr 1fr;gap:48px;}
  .detail:nth-child(even) .detail-img{order:2;}
}
.detail + .detail{margin-top:52px;}
.detail-img{width:100%;aspect-ratio:4/3;object-fit:cover;
  box-shadow:0 18px 34px -22px rgba(10,10,10,.65);}
.detail h3{font-size:1.35rem;margin-bottom:10px;}
.detail h3::before{
  content:"";display:block;width:30px;height:3px;background:var(--mark);margin-bottom:12px;
}
.detail p{color:var(--body);font-size:.98rem;}
.detail ul{list-style:none;margin-top:14px;}
.detail li{font-size:.93rem;color:var(--body);padding:4px 0;}
.detail li::before{content:"▪";color:var(--mark);margin-right:9px;}

/* Related-trade links at the foot of each trade page */
.also{display:grid;grid-template-columns:1fr;gap:14px;margin-top:28px;}
@media(min-width:700px){.also{grid-template-columns:1fr 1fr;}}
.also a{
  display:block;padding:20px 22px;background:#fff;border-left:3px solid var(--mark);
  box-shadow:0 10px 24px -20px rgba(10,10,10,.6);
  transition:transform .2s var(--ease), box-shadow .2s var(--ease);
}
.also a:hover{transform:translateY(-3px);box-shadow:0 16px 28px -20px rgba(10,10,10,.6);}
.also b{display:block;font-family:'Fraunces',Georgia,serif;font-size:1.05rem;}
.also span{font-size:.88rem;color:var(--body);}

/* Quote page */
.quote-page{padding:48px 24px 60px;background:var(--paper-warm);}
.quote-layout{max-width:1080px;margin:0 auto;display:grid;grid-template-columns:1fr;gap:32px;}
@media(min-width:860px){.quote-layout{grid-template-columns:1fr 1.05fr;gap:56px;align-items:start;}}
.quote-aside h2{font-size:clamp(1.6rem,3.4vw,2.2rem);max-width:16ch;}
.quote-aside p{color:var(--body);margin-top:14px;max-width:44ch;}
.quote-points{list-style:none;margin-top:24px;}
.quote-points li{padding:10px 0;border-bottom:1px solid var(--line);font-size:.95rem;color:var(--body);}
.quote-points li::before{content:"▪";color:var(--mark);margin-right:10px;}
/* On the quote page this is the only alternative to filling in the form, so it has to
   be a comfortable tap target, not just large text. */
.quote-aside .tel-big{
  display:inline-flex;align-items:center;min-height:44px;
  margin-top:24px;font-family:'Fraunces',Georgia,serif;font-weight:700;
  font-size:1.3rem;color:var(--mark);
}

/* Video block. Capped at the source's native 640x360 so the clip is never upscaled —
   the only public copy of it is 360p and it falls apart if stretched. */
.video-wrap{margin-top:22px;max-width:640px;}
.video-wrap video{width:100%;height:auto;display:block;border-radius:4px;background:#000;}

/* ===== STAMP / COLOURS PAGE =========================================================
   Swatch grid is 2-up on a phone on purpose. One-up wastes the screen for something
   people scan comparatively, and 3-up makes the texture too small to actually judge. */
.band-lede{margin-top:14px;}
.swatch-grid{
  display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:26px;
}
@media(min-width:700px){.swatch-grid{grid-template-columns:repeat(3,1fr);gap:20px;}}
.swatch{background:#fff;border:1px solid var(--line);overflow:hidden;}
.swatch img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;}
.swatch figcaption{
  padding:11px 13px;font-weight:600;font-size:.92rem;color:var(--ink);
  border-top:3px solid var(--mark);
}

/* Colour charts are wide and detail-dense. Squeezing them to phone width makes the
   swatch names unreadable, so each one scrolls sideways in its own rail instead and
   the page itself never scrolls horizontally. */
.chart-stack{display:grid;gap:18px;margin-top:26px;}
.chart-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;background:#fff;border:1px solid var(--line);}
.chart-scroll img{min-width:680px;width:100%;height:auto;}
@media(min-width:760px){.chart-scroll img{min-width:0;}}

/* ===== FOOTER NAV ==================================================================
   The masthead nav is display:none below 900px and there is no hamburger, so without
   this the whole site is unnavigable on a phone — and stamp-colors.html, which is
   linked from nowhere else, was completely unreachable there. Every page links to
   every page from the footer. Wraps rather than scrolls, 44px tap targets. */
.footer-nav{
  max-width:1180px;margin:0 auto 22px;padding-bottom:20px;
  display:flex;flex-wrap:wrap;gap:4px 22px;align-items:center;
  border-bottom:1px solid rgba(198,198,198,.22);
}
/* min-WIDTH as well as min-height. min-height:44px alone left "Home" at 41x44 — short
   labels do not reach 44px on their own, and the footer nav is on every page, so it was
   the same near-miss seven times over. Only the shortest labels are affected; everything
   longer already exceeds it, so the row spacing is unchanged. */
.footer-nav a,.footer-nav span{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;min-width:44px;
  font-size:.86rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--sand);
  border-bottom:2px solid transparent;
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.footer-nav a:hover,.footer-nav a:focus-visible{color:#fff;border-bottom-color:var(--mark-light);}
.footer-nav [aria-current=page]{color:#fff;border-bottom-color:var(--mark-light);}

/* Email. Take the address from the client's OWN site, or from a listing you actually
   fetched and read. A search-engine summary is NOT a source for a contact detail — it will
   confidently assert whichever address you typed into the query. Fetch the page and look.
   word-break:break-all below matters — real addresses run long.
   If the client publishes no social accounts, do not invent profile links. */
footer a.mail{
  color:var(--sand);font-weight:600;word-break:break-all;
  display:inline-flex;align-items:center;min-height:44px;padding:4px 2px;
  border-bottom:1px solid transparent;transition:color .2s var(--ease),border-color .2s var(--ease);
}
footer a.mail:hover,footer a.mail:focus-visible{color:#fff;border-bottom-color:var(--mark-light);}

.quote-aside .mail-big{
  display:inline-flex;align-items:center;min-height:44px;margin-top:6px;
  font-weight:600;font-size:1rem;color:var(--mark);word-break:break-all;
}

/* ===== MOBILE NAV =====================================================================
   Below 900px the masthead nav is hidden and, until now, nothing replaced it — any page
   linked only from that nav was unreachable on a phone. This is the replacement.

   The masthead's "Get a Free Estimate" button is hidden on phones ON PURPOSE. The fixed
   bottom bar already carries that exact CTA, follows the visitor down the whole page and
   sits under their thumb, so repeating it up top only crowded a 375px bar and pushed the
   burger into the logo. Phones get: logo left, burger right. Nothing else. */

.mast-burger{
  display:flex;flex-direction:column;justify-content:center;gap:5px;
  width:48px;height:48px;padding:12px 10px;margin-left:auto;
  background:none;border:0;cursor:pointer;
}
.mast-burger span{
  display:block;height:2px;width:100%;background:var(--ink);
  transition:transform .28s var(--ease), opacity .18s var(--ease);
}
/* Burger becomes an X while the panel is open. */
.mast-burger[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg);}
.mast-burger[aria-expanded="true"] span:nth-child(2){opacity:0;}
.mast-burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg);}

@media(min-width:1150px){
  .mast-burger{display:none;}
}
@media(max-width:1149px){
  /* The sticky bottom bar is the CTA below this width. */
  .masthead-actions{display:none;}
}

.menu-panel{
  position:fixed;left:0;right:0;top:0;z-index:60;
  background:var(--ink-800);
  padding:84px 24px calc(28px + env(safe-area-inset-bottom));
  box-shadow:0 18px 40px rgba(6,6,6,.42);
  transform:translateY(-100%);transition:transform .34s var(--ease);
  max-height:100svh;overflow-y:auto;
}
.menu-panel[hidden]{display:none;}
.menu-panel.is-open{transform:translateY(0);}
.menu-panel nav{display:flex;flex-direction:column;}
.menu-panel nav a,
.menu-panel nav span{
  display:flex;align-items:center;min-height:56px;
  font-family:'Fraunces',Georgia,serif;font-weight:700;font-size:1.32rem;
  color:#fff;border-bottom:1px solid rgba(198,198,198,.16);
}
.menu-panel nav a[aria-current=page],
.menu-panel nav span[aria-current=page]{color:var(--mark-light);}
.menu-contact{display:flex;flex-direction:column;margin-top:22px;}
.menu-contact a{
  display:flex;align-items:center;min-height:48px;
  color:var(--sand);font-weight:600;font-size:.98rem;word-break:break-all;
}
.menu-contact a:first-child{font-size:1.16rem;color:var(--mark-light);font-weight:700;}

/* The burger has to sit above the panel so it can close it. */
.masthead{z-index:70;}
body.menu-open{overflow:hidden;}

@media (prefers-reduced-motion: reduce){
  .menu-panel{transition:none;}
  .mast-burger span{transition:none;}
}

/* Footer CTA on its own line. It was sharing a flex row with the address, phone and
   email, which squeezed all four. */
.footer-cta{
  max-width:1180px;margin:18px auto 0;
  padding-top:18px;border-top:1px solid rgba(198,198,198,.16);
}

/* Serving areas. Towns rather than counties, because towns are what people type into
   a search box. Deliberately quiet — this is SEO surface and reassurance, not a section
   that should compete with the CTA above it. */
.footer-areas{
  max-width:1180px;margin:16px auto 0;
  font-size:.82rem;line-height:1.7;color:var(--stone-mid);
}
.footer-areas span{
  color:var(--sand);font-weight:700;
  text-transform:uppercase;letter-spacing:.14em;font-size:.72rem;
  margin-right:6px;
}

/* ===== GALLERY =====================================================================
   Two up on a phone, three from tablet, four on a wide screen. Fixed 4:3 tiles so the
   grid stays even regardless of how the source photo was framed. Captions are quiet:
   the photo is the content, the caption is only there so a screen reader and a curious
   visitor both know what they are looking at. */
.gallery-grid{
  display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:26px;
}
@media(min-width:700px){.gallery-grid{grid-template-columns:repeat(3,1fr);gap:18px;}}
@media(min-width:1150px){.gallery-grid{grid-template-columns:repeat(4,1fr);}}
.gshot img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;display:block;}

/* "See the full gallery" under the three home-page cards. */
.gallery-more{max-width:1180px;margin:26px auto 0;padding:0 24px;}

/* Gallery tiles are buttons now, so they need the button defaults stripped. */
.gshot{
  display:block;width:100%;padding:0;text-align:left;cursor:pointer;
  background:#fff;border:1px solid var(--line);overflow:hidden;font:inherit;color:inherit;
  transition:transform .18s var(--ease), box-shadow .18s var(--ease);
}
.gshot:hover,.gshot:focus-visible{transform:translateY(-2px);box-shadow:0 6px 18px rgba(10,10,10,.14);}
.gshot img{transition:transform .3s var(--ease);}
.gshot:hover img{transform:scale(1.03);}
.gcap{display:block;padding:10px 12px;border-top:3px solid var(--mark);}
.gcap b{display:block;font-size:.92rem;font-weight:700;color:var(--ink);}
.gcap em{
  display:block;font-style:normal;margin-top:2px;
  font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;color:var(--stone-600,#6E6E6E);
}

/* ===== LIGHTBOX ==================================================================== */
.lightbox{
  position:fixed;inset:0;z-index:100;
  display:flex;align-items:center;justify-content:center;
  padding:16px;background:rgba(6,6,6,.94);
  opacity:0;transition:opacity .28s var(--ease);
}
.lightbox[hidden]{display:none;}
.lightbox.is-open{opacity:1;}
.lb-figure{margin:0;max-width:1100px;width:100%;display:flex;flex-direction:column;align-items:center;}
.lb-figure img{
  max-width:100%;max-height:66svh;width:auto;height:auto;display:block;
  background:var(--ink-800);
}
.lb-cap{
  width:100%;max-width:820px;margin-top:16px;text-align:center;color:var(--sand);
}
.lb-service{
  display:block;font-size:.72rem;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--mark-light);margin-bottom:8px;
}
.lb-cap p{margin:0;font-size:1rem;line-height:1.55;color:#fff;}
.lb-count{display:block;margin-top:12px;font-size:.8rem;color:var(--stone-mid);}

.lb-close,.lb-nav{
  position:absolute;background:rgba(10,10,10,.72);border:1px solid rgba(198,198,198,.28);
  color:#fff;cursor:pointer;line-height:1;
  display:flex;align-items:center;justify-content:center;
  transition:background-color .2s var(--ease);
}
.lb-close:hover,.lb-nav:hover,.lb-close:focus-visible,.lb-nav:focus-visible{background:var(--mark);}
.lb-close{top:14px;right:14px;width:48px;height:48px;font-size:1.9rem;}
.lb-nav{top:50%;transform:translateY(-50%);width:48px;height:64px;font-size:2.2rem;}
.lb-prev{left:10px;}
.lb-next{right:10px;}
@media(min-width:900px){
  .lb-close{top:22px;right:24px;}
  .lb-prev{left:24px;}
  .lb-next{right:24px;}
}
@media (prefers-reduced-motion: reduce){
  .lightbox{transition:none;}
  .gshot,.gshot img{transition:none;}
}

/* The About band carries two paragraphs now, where it used to carry one. */
.craft .txt p + p{margin-top:14px;}
