/* mws-site-kit v0.2.0 | layout.css
   ==========================================================================
   THE HOUSE STYLE. The page furniture every MWS client site has needed so
   far: top bar, masthead, nav, hero, section bands, card grids, fact strip,
   footer, and the hatched photo placeholders used during preview.

   WHY THIS FILE EXISTS. It was extracted, not designed. The same 126-line
   site.css was byte-identical across the Tom & Jerry's and Stellar Plumbing
   builds - copied rather than re-derived, which is the signal that it had
   stopped being per-client work and become a layer. Promoting it means the
   next site gets the shell for free and a fix lands in one place.

   THE LAYER BOUNDARY, so this file does not become a dumping ground:

     tokens.css    what the brand IS      - edited per client
     base.css      what HTML looks like   - never edited
     patterns.css  motion + torn edge     - never edited
     layout.css    what a PAGE looks like - never edited  <- you are here
     site.css      what THIS client needs - per client, usually short

   If you are editing this file for one client, the change belongs in that
   site's own site.css instead. If you are editing it for the third client
   in a row, it belongs here and the version gets bumped.

   Load order matters: tokens, base, patterns, layout, then site.
   ========================================================================== */

:root{
  /* The house style runs a 1120px container rather than base.css's 1240px
     default. 1120 is the OUTER width, gutters included: the hand-written
     site.css set only .wrap's width, so base.css's padding-inline survived
     underneath it and the real content measure is 1040px at desktop. */
  --wrap:1120px;

  /* Secondary paragraph grey. Previously written as var(--ink-soft,#555) in
     every copy of this file, with --ink-soft never defined anywhere - so it
     silently fell back to #555 on all four sites. Defined here at the value
     that was actually rendering, so nothing shifts. It should move onto the
     neutral ramp (--ink-500) when there is an appetite for the small colour
     change that entails. */
  --ink-soft:#555;
}

/* ==========================================================================
   KNOWN DEBT: this file does not use the fluid type scale.

   18 hard-coded pixel font sizes below (13.5px, 16.5px, 18.5px, 9.5px and
   so on) and zero var(--step-*) values. The two clamp() calls present are
   on .hero h1 and h2.sec and were written by hand, not taken from tokens.

   tokens.css ships a six-step fluid scale precisely so type grows with the
   viewport and then stops, and PLANNING.md section 4 names it as most of
   the gap between an MWS site and a cheap one. Every real build so far has
   bypassed it, because the house style was authored in pixels before it
   was a shared layer.

   NOT FIXED HERE ON PURPOSE. Converting 18 sizes to the scale changes how
   every page renders at every width, which is a redesign rather than a
   promotion, and Tom & Jerry's has approved the current look. It is also
   cheapest to do now, while this file has one consumer instead of four.

   Tracked as S8 in PLANNING.md. Do it as its own change, with the same
   element-by-element before/after comparison used to promote this file.
   ========================================================================== */

/* ── placeholder blocks ────────────────────────────────────────────────
   Deliberately obvious. A grey box that says what belongs in it reads as
   "not finished yet"; a stock photo of someone else's plumber reads as
   "this is what you're buying", which is a lie. */
.ph {
  position: relative;
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(45deg,
      var(--brand-100) 0 12px, var(--brand-050) 12px 24px);
  border: 2px dashed var(--brand-400);
  color: var(--brand-700);
  font-family: var(--font-util, ui-monospace, Menlo, Consolas, monospace);
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: center;
  padding: 18px;
  min-height: 190px;
  border-radius: 2px;
}
.ph span { max-width: 30ch; line-height: 1.5; }
.ph--hero { min-height: 340px; }
.ph--wide { min-height: 260px; }
.ph--sq   { aspect-ratio: 1; min-height: 0; }

/* ── shell ─────────────────────────────────────────────────────────── */
/* The container. base.css owns .wrap's max-width, auto margins and fluid
   padding; this narrows it and adds the house style's 20px outer gutter on
   each side, which sits OUTSIDE base.css's padding rather than replacing it.

   That doubled gutter is deliberate and was measured, not assumed: dropping
   it in favour of the token alone matched at desktop and silently widened
   every mobile section by 40px, pushing body copy to within 17.6px of the
   screen edge. Phones are most of the traffic on these sites.

   Written against --wrap rather than a literal so the token stays live and
   a per-client width change is still a one-line edit in tokens.css. */
.wrap { width: min(var(--wrap), 100% - 40px); }

.topbar {
  background: var(--brand-900);
  color: var(--brand-100);
  font-size: 13.5px;
  padding: 7px 0;
}
.topbar .wrap { display: flex; gap: 18px; flex-wrap: wrap; align-items: center; justify-content: space-between; }
.topbar a { color: #fff; font-weight: 700; text-decoration: none; }

.masthead { background: #fff; border-bottom: 1px solid var(--brand-200); }
.masthead .wrap { display: flex; align-items: center; gap: 22px; padding: 16px 0; flex-wrap: wrap; }
.brandmark { display: flex; align-items: center; gap: 14px; text-decoration: none; }
.brandmark .ph { width: 122px; min-height: 62px; padding: 8px; font-size: 9.5px; }
.brandmark b { display: block; font-size: 21px; color: var(--brand-800); line-height: 1.15; }
.brandmark small { display: block; font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--brand-500); margin-top: 3px; }

nav.main { margin-left: auto; display: flex; gap: 4px; flex-wrap: wrap; }
nav.main a {
  padding: 9px 13px; font-size: 14px; font-weight: 600;
  color: var(--brand-800); text-decoration: none; border-radius: 3px;
}
nav.main a:hover { background: var(--brand-050); }
nav.main a[aria-current="page"] { background: var(--brand-800); color: #fff; }

.cta-call {
  background: var(--accent); color: var(--brand-950);
  padding: 11px 20px; border-radius: 3px; font-weight: 800;
  text-decoration: none; white-space: nowrap;
  box-shadow: 0 2px 0 var(--accent-dk, #b8900f);
}

/* ── hero ──────────────────────────────────────────────────────────── */
.hero { background: var(--brand-800); color: #fff; padding: 54px 0 60px; }
.hero .wrap { display: grid; grid-template-columns: 1.05fr .95fr; gap: 44px; align-items: center; }
.hero h1 { font-size: clamp(30px, 4.4vw, 46px); line-height: 1.08; margin: 0 0 16px; text-wrap: balance; }
.hero p.lede { font-size: 18px; color: var(--brand-100); margin: 0 0 26px; max-width: 46ch; }
.est { display: inline-block; font-size: 12px; letter-spacing: .16em; text-transform: uppercase;
       color: var(--accent); border: 1px solid var(--accent); padding: 5px 12px; margin-bottom: 20px; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.btn-ghost { border: 2px solid var(--brand-300); color: #fff; padding: 10px 20px;
             border-radius: 3px; text-decoration: none; font-weight: 700; }

/* Torn edge, opt-in. build-site.mjs adds .hero--tear and the .tear element
   only when design.tear is true in content.js, so a page without it is
   untouched - which is every page built before v0.2.0.

   The kit's own .band--tear reserves room the same way; this exists because
   the house style's .hero is not a .band. --tear-fill is the colour of the
   section BELOW, which for the first band is the page background. */
.hero--tear { position: relative; padding-bottom: calc(60px + var(--tear-h)); }

/* ── sections ──────────────────────────────────────────────────────── */
section.band { padding: 56px 0; }
section.band.alt { background: var(--brand-050); }
h2.sec { font-size: clamp(23px, 3vw, 32px); margin: 0 0 10px; color: var(--brand-900); }
p.sub { color: var(--ink-soft); max-width: 62ch; margin: 0 0 30px; font-size: 16.5px; }

.grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 22px; }
.grid4 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; }

.card {
  background: #fff; border: 1px solid var(--brand-200);
  border-radius: 4px; padding: 22px; display: flex; flex-direction: column; gap: 9px;
}
.card h3 { margin: 0; font-size: 18.5px; color: var(--brand-800); }
.card p { margin: 0; color: #555; font-size: 15px; }
.card a.more { margin-top: auto; font-weight: 700; color: var(--brand-600); text-decoration: none; font-size: 14.5px; }

.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0;
         border: 1px solid var(--brand-200); background: #fff; border-radius: 4px; overflow: hidden; }
.facts div { padding: 20px 22px; border-right: 1px solid var(--brand-100); }
.facts div:last-child { border-right: none; }
.facts b { display: block; font-size: 28px; color: var(--brand-700); line-height: 1.1; }
.facts span { display: block; font-size: 12.5px; text-transform: uppercase; letter-spacing: .09em; color: #777; margin-top: 5px; }

.split { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: center; }

/* ── footer ────────────────────────────────────────────────────────── */
footer.site { background: var(--brand-950); color: var(--brand-200); padding: 46px 0 30px; margin-top: 10px; }
footer.site .wrap { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 32px; }
footer.site h4 { color: #fff; font-size: 13px; letter-spacing: .1em; text-transform: uppercase; margin: 0 0 12px; }
footer.site a { color: var(--brand-200); text-decoration: none; display: block; padding: 3px 0; font-size: 14.5px; }
footer.site a:hover { color: #fff; }
.legal { border-top: 1px solid var(--brand-800); margin-top: 30px; padding-top: 18px; font-size: 12.5px; color: var(--brand-400); }

/* ── preview banner — REMOVE BEFORE LAUNCH ─────────────────────────── */
.previewbar {
  background: #B3261E; color: #fff; text-align: center;
  padding: 9px 16px; font-size: 13.5px; line-height: 1.5;
}
.previewbar b { color: #fff; }

@media (max-width: 860px) {
  .hero .wrap, .split { grid-template-columns: 1fr; gap: 28px; }
  nav.main { width: 100%; margin-left: 0; }
  .facts div { border-right: none; border-bottom: 1px solid var(--brand-100); }
}

/* --- prose lists (v0.2.1) -------------------------------------------------
   For the `prose` section. A migrated service inventory can run to thirty
   items ("junk we take"), which in one column is a wall the visitor scrolls
   past rather than reads. auto-fit keeps it multi-column on a desktop and
   collapses it to one column on a phone with no media query of its own. */
.prose-body ul,
ul.list-cols { margin: 0 0 26px; padding-left: 22px; color: var(--ink-soft);
               font-size: 16.5px; line-height: 1.85; }
ul.list-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
               column-gap: 26px; row-gap: 2px; padding-left: 20px; }
ul.list-cols li { break-inside: avoid; }

/* --- real photos (v0.2.1) -------------------------------------------------
   `pic()` emits <picture class="ph-img"> into the same slots .ph occupies,
   carrying the same .ph--hero / .ph--wide / .ph--sq modifiers so a page does
   not reflow when a placeholder is swapped for the client's actual photo.
   The modifiers set min-height on .ph, which would stretch a real image, so
   they are neutralised here and the aspect ratio comes from the file's own
   width/height attributes instead. */
.ph-img { display: block; position: relative; overflow: hidden;
          border-radius: 2px; background: var(--brand-050); }
.ph-img > img { display: block; width: 100%; height: auto; }

.ph-img.ph--hero, .ph-img.ph--wide { min-height: 0; }
/* Square slots crop rather than letterbox - a gallery of mixed portrait and
   landscape shots reads as a grid only if the tiles agree. */
.ph-img.ph--sq { aspect-ratio: 1; }
.ph-img.ph--sq > img { height: 100%; object-fit: cover; }

/* The masthead logo is artwork, not a photo: never crop it, and cap it so a
   2400px square brand mark does not dominate the bar. */
.ph-img.ph--logo { background: none; max-width: 260px; }
.ph-img.ph--logo > img { object-fit: contain; }

/* The masthead is white, so a logo supplied as transparent artwork sits on
   it directly. Matches the width .brandmark .ph reserves, so swapping the
   placeholder for the real mark does not move the nav. */
.brandmark .ph-img { width: 122px; flex: none; }

/* ==========================================================================
   FULL-BLEED HERO (v0.2.2)
   `heroStyle: 'full'`. The photo fills the band and the copy sits on it,
   instead of the two-column band with an inset picture. Opt-in per page.
   ========================================================================== */
.hero--full { position: relative; overflow: hidden; padding: 0;
              background: var(--brand-950);
              min-height: clamp(480px, 72vh, 760px);
              display: flex; align-items: flex-end; }
.hero--full .hero-media { position: absolute; inset: 0; z-index: 0; }
.hero--full .hero-media .ph-img,
.hero--full .hero-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* THE SCRIM, in two layers rather than one. A single bottom gradient makes
   the headline readable and lets a bright sky blow out against the masthead;
   a single flat wash mutes the photo everywhere to fix one corner. Bottom-
   weighted for the copy, plus a left wash for the same reason, plus a light
   overall veil so the top edge never fights the white masthead above it. */
.hero--full .hero-media::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to top,
      rgba(18,19,12,.92) 0%, rgba(18,19,12,.70) 34%,
      rgba(18,19,12,.26) 64%, rgba(18,19,12,.10) 100%),
    linear-gradient(to right, rgba(18,19,12,.55) 0%, rgba(18,19,12,0) 62%);
}
.hero--full .wrap { position: relative; z-index: 1; display: block;
                    padding-top: 84px; padding-bottom: 54px; }
.hero--full h1 { font-size: clamp(32px, 5.6vw, 64px); max-width: 16ch;
                 text-shadow: 0 2px 30px rgba(0,0,0,.32); }
.hero--full p.lede { color: #E8EBDA; max-width: 52ch; }
.hero--full .est { background: rgba(0,0,0,.34); }

@media (max-width: 700px) {
  .hero--full { min-height: 74vh; }
  .hero--full .wrap { padding-top: 54px; padding-bottom: 40px; }
}

/* ==========================================================================
   BEFORE / AFTER SLIDER (v0.2.2)
   ========================================================================== */
.ba-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
           gap: 24px; }
.ba-fig { margin: 0; }
.ba-fig figcaption { margin-top: 10px; }
.ba-fig figcaption b { display: block; font-size: 15.5px; color: var(--brand-800); }
.ba-fig figcaption span { display: block; font-size: 14.5px; color: var(--ink-soft);
                          line-height: 1.6; margin-top: 2px; }

.ba { position: relative; overflow: hidden; border-radius: 3px;
      border: 1px solid var(--brand-200); background: var(--brand-100);
      touch-action: pan-y; }
.ba .ph-img, .ba img { display: block; width: 100%; height: auto; }

/* WITHOUT JS this is two images stacked, the AFTER one covering the BEFORE.
   Readable, no hole in the page. js/media.js adds .ba-ready, and only then
   does the crop, the handle and the labels appear. Nothing is hidden by CSS
   that only the script can reverse - that was the v0.1.0 card mistake. */
.ba .ba-after { position: absolute; inset: 0; }
.ba .ba-after .ph-img, .ba .ba-after img { height: 100%; object-fit: cover; }
.ba .ba-handle, .ba .ba-tag { display: none; }

.ba.ba-ready { cursor: ew-resize; user-select: none; }
.ba.ba-ready .ba-after { clip-path: inset(0 0 0 var(--ba-pos, 50%)); }
.ba.ba-ready .ba-handle { display: block; position: absolute; top: 0; bottom: 0;
  left: var(--ba-pos, 50%); width: 3px; background: var(--accent);
  transform: translateX(-1.5px); z-index: 3; box-shadow: 0 0 0 1px rgba(0,0,0,.22); }
.ba.ba-ready .ba-knob { position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%); width: 44px; height: 44px; border-radius: 50%;
  background: var(--accent); color: var(--brand-950); display: grid;
  place-items: center; font: 800 15px/1 var(--font-util, system-ui);
  box-shadow: 0 4px 16px rgba(0,0,0,.32); }
.ba.ba-ready .ba-tag { display: block; position: absolute; bottom: 12px; z-index: 2;
  font: 800 10.5px/1 var(--font-util, system-ui); letter-spacing: .12em;
  text-transform: uppercase; padding: 7px 11px; border-radius: 2px;
  background: rgba(18,19,12,.78); color: #fff; }
.ba.ba-ready .ba-tag--b { left: 12px; }
.ba.ba-ready .ba-tag--a { right: 12px; }
.ba:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

/* ==========================================================================
   VERTICAL REELS (v0.2.2)
   9:16 in the shape it was shot in. See the note in tools/sections.mjs for
   why these are not cropped into a wide background video.
   ========================================================================== */
/* Tiles are CAPPED, not stretched. `1fr` as the max lets auto-fit expand each
   tile to fill the row, which is fine at three across and absurd at two: a
   9:16 tile 570px wide is over 1000px tall and pushes everything else off the
   screen. Capping at 300px and centring the row means the section looks
   deliberate at two, three or four clips. */
.reels { display: grid; gap: 18px; justify-content: center;
         grid-template-columns: repeat(auto-fit, minmax(190px, 300px)); }
.reel { position: relative; margin: 0; aspect-ratio: 9 / 16; overflow: hidden;
        border-radius: 4px; background: var(--brand-950); }
.reel video { width: 100%; height: 100%; object-fit: cover; display: block; }
.reel figcaption { position: absolute; left: 0; right: 0; bottom: 0;
  padding: 30px 14px 12px; color: #fff; font: 700 13.5px/1.4 var(--font-body, system-ui);
  background: linear-gradient(to top, rgba(18,19,12,.90), rgba(18,19,12,0)); }
.reel-snd { position: absolute; top: 10px; right: 10px; z-index: 2; border: 0;
  cursor: pointer; width: 34px; height: 34px; border-radius: 50%;
  background: rgba(18,19,12,.70); color: #fff; font-size: 14px; line-height: 1; }
.reel-snd:hover { background: rgba(18,19,12,.9); }
.reel-snd.on { background: var(--accent); color: var(--brand-950); }
.reel-snd:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- social links + embedded form (v0.2.3) -------------------------------- */
.social { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 8px; }
.social a { font-size: 14.5px; font-weight: 700; }

/* A third-party booking form in an iframe. It has no idea how wide it is
   going to be, so it gets the full column and a generous default height -
   an embedded form that scrolls inside its own little box is the fastest way
   to lose a lead. */
.form-embed { display: block; width: 100%; min-height: 760px; border: 0;
              border-radius: 3px; background: #fff; }
.embed-fallback { margin: 12px 0 0; font-size: 13.5px; color: var(--ink-soft);
                  line-height: 1.6; }

/* The embedded form needs the room, so the card stops squeezing it. */
.card:has(.form-embed) { padding: 20px; }
@media (max-width: 900px) { .form-embed { min-height: 900px; } }

/* --- the quote section (v0.2.4) ------------------------------------------
   The booking form on a page that is not the contact page. Split band: the
   reason to fill it in on the left, the form itself on the right. */
.quote-form { background: #fff; border: 1px solid var(--brand-200);
              border-radius: 3px; padding: 14px; }
.quote-form .form-embed { min-height: 820px; }

/* Reassurance points next to the form. A tick list rather than bullets -
   these are answers to "what happens if I send this", and they are the
   difference between a form being looked at and a form being filled in. */
ul.tick { list-style: none; padding: 0; margin: 0 0 24px; }
ul.tick li { position: relative; padding-left: 26px; margin-bottom: 9px;
             color: var(--ink-soft); font-size: 16px; line-height: 1.6; }
ul.tick li::before { content: "\2713"; position: absolute; left: 0; top: 0;
  font-weight: 800; color: var(--brand-600); }

@media (max-width: 900px) {
  .quote-form { padding: 10px; }
  .quote-form .form-embed { min-height: 900px; }
}

/* --- masthead badge (v0.2.5) ---------------------------------------------
   Sponsor / accreditation mark on the right of the masthead. margin-left:auto
   pushes it to the far edge of the first flex row, which is the space the
   wrapped nav leaves empty. */
.masthead-badge { margin-left: auto; display: flex; flex-direction: column;
  align-items: center; gap: 4px; text-decoration: none; flex: none;
  padding: 2px 0 0; }
.masthead-badge .badge-label { font: 700 9.5px/1 var(--font-util, system-ui);
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-500); }
.masthead-badge .badge-mark { display: block; width: 104px; background: none; }
.masthead-badge .badge-mark > img { width: 100%; height: auto;
  object-fit: contain; }
/* A link version gets a hover, a plain div does not pretend to be clickable. */
a.masthead-badge:hover .badge-label { color: var(--brand-700); }

/* On a phone the masthead is already tight and the badge is the least
   important thing in it - it drops below the logo rather than squeezing the
   business name. */
@media (max-width: 700px) {
  .masthead-badge { margin: 6px 0 0; align-items: flex-start; }
  .masthead-badge .badge-mark { width: 86px; }
}

/* --- plain hero (v0.2.6) --------------------------------------------------
   No picture, copy centred. For pages that have no photograph and never will
   - terms, privacy, a legal notice. The split hero on one of those reserves
   half the band for a hatched box captioned "no photo on this page", which
   is the placeholder correctly reporting there is nothing to show while
   looking like a broken image. */
.hero--plain .wrap { display: block; max-width: 860px; text-align: center; }
.hero--plain .wrap > div { margin: 0 auto; }

/* Bigger, because there is nothing competing for the space now. */
.hero--plain h1 { font-size: clamp(32px, 5vw, 54px); margin-bottom: 20px;
                  text-wrap: balance; }
.hero--plain p.lede { font-size: clamp(17px, 1.7vw, 20px); max-width: 56ch;
                      margin-left: auto; margin-right: auto; }
.hero--plain .hero-actions { justify-content: center; }
.hero--plain { padding: 64px 0 68px; }

@media (max-width: 700px) {
  .hero--plain { padding: 46px 0 48px; }
}

/* --- hero video (v0.2.7) --------------------------------------------------
   Same slot as the hero photo. The clip is vertical 9:16 because that is how
   a phone shoots, so in a wide band it is centre-cropped: object-position
   holds the middle, which on plough footage is where the truck is. Not ideal
   - the honest fix is landscape footage - but it beats letterboxing a
   portrait video into a widescreen band. */
.hero--full .hero-media .hero-v { width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 45%; display: block; }
