/* ============================================================================
   COMPONENTS                    client-sites/_engine/assets/css/components.css

   Byte identical on every client. One block per component, in the order the
   components render. Every value is a custom property or a measured constant
   written once; no client specific colour appears anywhere in this file.

   The rating badge block at the bottom is generated from
   .skills/client-website/components/rbadge-css.js and must not be hand edited.
   ============================================================================ */

/* ---- mobile call bar, the BOTTOM of the phone screen ---------------------- */
.band--mobile-cta-bar{
  position:fixed;left:0;right:0;bottom:0;
  display:none;
  height:var(--cta-bar-h);
  padding:0;
  z-index:var(--z-callbar);
  background:var(--brand-dark);
}
@media (max-width:767px){.band--mobile-cta-bar{display:grid;grid-template-columns:1fr 1fr}}
.callbar__book,.callbar__call{
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-display);font-size:15px;font-weight:var(--fw-btn);
  text-transform:var(--tt-btn);line-height:1.1;text-align:center;padding:0 8px;
}
.callbar__book{background:var(--brand-action);color:var(--on-action)}
.callbar__call{background:var(--brand-dark);color:#FFFFFF;border-top:1px solid rgba(255,255,255,.18)}

/* ---- utility bar ---------------------------------------------------------- */
/* the small type is on the row's children, not on the row: the row is also the
   .shell, whose em based padding shrank with it and set the bar 9px off the
   page's gutter at 1280 and 1440 (found by gates/band-balance.js GUTTER beside
   the legal row's identical defect, 2026-10-01) */
.band--utility-bar{padding-block:0;background:var(--brand-dark);color:#FFFFFF}
.utility__row{display:flex;align-items:center;justify-content:space-between;gap:var(--gap);min-height:42px}
.utility__row > *{font-size:var(--fs-small)}
.utility__place{display:inline-flex;align-items:center;gap:8px}
.utility__place svg{width:16px;height:16px;flex:0 0 16px;color:var(--brand-action-on-dark)}
.utility__phone{font-weight:700}
@media (max-width:767px){.band--utility-bar{display:none}}

/* ---- nav ------------------------------------------------------------------ */
.band--nav{
  position:sticky;top:0;z-index:var(--z-nav);
  padding-block:0;background:var(--brand-dark);color:#FFFFFF;
}
/* THE LOGO NEVER TOUCHES THE EDGE OF THE BAR. The row had a minimum height and
   no padding, so a page that sized its logo to the height of the bar (72px in a
   74px row) put the artwork 1px from the top and bottom, and the 5px rule its
   presentation painted along the foot of the band (::after, bottom:0) covered
   the last 4px of it. Seen on the first page the cloud builder shipped. 2026-10-01.
   Now the row carries real padding: 6px of clear air (--nav-logo-air) plus 6px
   held back for a rule painted on the band's top or bottom edge (--nav-edge).
   The box sizing is border-box, so the 74px minimum still holds the stock 46px
   logo at exactly the height it always was; a taller logo grows the bar instead
   of reaching its edges. A presentation that paints an edge rule thicker than
   6px sets --nav-edge on .band--nav to that thickness. The logo is also lifted
   above the band's own ::before and ::after, so a rule can sit beside it but
   never paint over it. It stays below the menu (9997) and the toggle (9998),
   and the row itself is NOT made a stacking context, or the drawer would drop
   behind its scrim. gates/nav-logo.js measures the clear space at four widths
   and fails anything under 6px. */
.nav__row{display:flex;align-items:center;gap:var(--gap);min-height:74px;
  padding-block:calc(var(--nav-logo-air,6px) + var(--nav-edge,6px))}
.nav__logo{display:flex;align-items:center;gap:12px;flex:0 0 auto;position:relative;z-index:1}
/* a telephone number that wraps to two lines reads as two numbers */
.nav__actions .btn,.nav .btn--sm{white-space:nowrap}
/* A WORDMARK IS NOT A SQUARE. This forced every logo into 52 by 52, so a
   600 by 156 wordmark rendered as an illegible stamp beside the business name.
   The height is fixed and the width follows the artwork, which is right for a
   square mark and for a wide one. 2026-09-11. */
.nav__logo img{height:46px;width:auto;max-width:240px;object-fit:contain}
.nav__wordmark{font-family:var(--font-display);font-size:22px;font-weight:800;text-transform:var(--tt-heading)}
.nav__hamburger{
  margin-left:auto;display:flex;flex-direction:column;justify-content:center;gap:5px;
  width:48px;height:48px;padding:10px;background:none;border:0;cursor:pointer;
  position:relative;z-index:var(--z-menu-toggle);
}
.nav__hamburger span{display:block;height:3px;border-radius:2px;background:#FFFFFF}
.nav__close{display:none}
.nav__scrim{display:none}
.nav__link,.nav__dropdown-toggle{
  font-family:var(--font-display);font-size:var(--fs-nav);font-weight:var(--fw-nav);
  text-transform:var(--tt-heading);color:#FFFFFF;background:none;border:0;cursor:pointer;
  display:inline-flex;align-items:center;gap:6px;padding:8px 0;
}
.nav__dropdown-toggle svg{width:14px;height:14px}
.nav__dropdown{position:relative}
.nav__dropdown-menu{display:none}
.nav__actions{display:flex;gap:12px}

/* THE NAV SWITCHES TO THE DRAWER WHEN IT STOPS FITTING, not at a number
   inherited from the grid. Seven links, two buttons and a wordmark overflowed a
   1025px window by 105 pixels; the rest of the layout still turns at 1025.
   Measured 2026-09-11 at 1025, 1280 and 1440. */
@media (min-width:1180px){
  .nav__hamburger{display:none}
  .nav__links{display:flex;align-items:center;gap:18px;margin-left:auto}
  .nav__actions{margin-left:8px}
  .nav__dropdown:hover .nav__dropdown-menu,.nav__dropdown:focus-within .nav__dropdown-menu{
    display:block;position:absolute;top:100%;left:0;min-width:264px;
    background:var(--surface);color:var(--ink);border-radius:var(--radius);
    box-shadow:0 18px 44px rgba(0,0,0,.22);padding:10px;z-index:var(--z-nav);
  }
  .nav__dropdown-item{display:block;padding:9px 12px;border-radius:6px;font-size:15px}
  .nav__dropdown-item:hover{background:var(--surface-alt);color:var(--brand-action-ink)}
}
/* THE DRAWER BLOCK HAS TO END WHERE THE HAMBURGER ENDS. When the bar moved to
   1180 this block still stopped at 1024, so between 1025 and 1179 a hamburger
   was painted with no panel behind it and tapping it did nothing. The two
   numbers are one decision and they move together. 2026-09-11. */
@media (max-width:1179px){
  /* THE DRAWER IS .nav__links ITSELF, and it opens on the class "open". The gate
     probes that element by name at 375, 414 and 768, at the top of the page and
     scrolled, and asks the browser what a thumb landing on each row is handed. */
  .nav__links{
    position:fixed;inset:0 0 0 auto;width:min(88vw,380px);
    background:var(--brand-dark);z-index:var(--z-menu-panel);
    padding:84px 24px calc(var(--cta-bar-h) + 24px);
    overflow-y:auto;transform:translateX(100%);transition:transform .28s ease;
    display:flex;flex-direction:column;align-items:stretch;gap:2px;
  }
  .nav__links.open{transform:none}
  .nav__link,.nav__dropdown-toggle{padding:13px 0;width:100%;justify-content:space-between;
    border-bottom:1px solid rgba(255,255,255,.14)}
  .nav__dropdown.is-open .nav__dropdown-menu{display:block;padding:6px 0 12px}
  .nav__dropdown-item{display:block;padding:10px 14px;font-size:15px;opacity:.92}
  .nav__actions{flex-direction:column;margin:var(--gap) 0 0}
  .nav__actions .btn{width:100%}
  /* THE CLOSE CONTROL SITS ON THE LEFT OF THE DRAWER, NOT THE RIGHT.
     The drawer is anchored to the right edge and the menu button is at the right
     edge of the nav row, so a close control at top:18 right:18 landed underneath
     the menu button, which outranks the panel on the z ladder (9998 over 9997).
     A thumb aiming at the X was handed the hamburger on every phone width. Both
     controls have to stay tappable while the drawer is open, so they cannot
     share the same corner: the X moves to the opposite one. */
  .nav__close{
    display:block;position:absolute;top:18px;left:18px;right:auto;
    width:44px;height:44px;font-size:30px;line-height:1;
    background:none;border:0;color:#FFFFFF;cursor:pointer;z-index:var(--z-menu-toggle);
  }
  .nav__scrim{display:block;position:fixed;inset:0;background:rgba(0,0,0,.5);
    z-index:calc(var(--z-menu-panel) - 1)}
  .nav__scrim[hidden]{display:none}
}

/* ---- hero ----------------------------------------------------------------- */
.band--hero{
  padding-block:calc(var(--shell-y) * 1.5);
  min-height:var(--hero-min-h);
  display:flex;align-items:center;
  background:var(--brand-dark);color:#FFFFFF;overflow:hidden;
}
.hero__media{position:absolute;inset:0;z-index:0}
.hero__media picture,.hero__media img{width:100%;height:100%;object-fit:cover}
.band--hero::after{
  content:"";position:absolute;inset:0;z-index:1;
  background:var(--overlay);opacity:var(--overlay-opacity);
}
.hero__inner{position:relative;z-index:2;width:100%}
.hero__col{max-width:none}
.hero__title{margin:0 0 .35em}
.hero__count{
  font-family:var(--font-display);font-size:var(--fs-small);font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--brand-action-on-dark);margin:0 0 1em;
}
.hero__sub{font-size:calc(var(--fs-body) * 1.08);max-width:56ch;margin-bottom:1.4em}
@media (min-width:1025px){
  .hero--split .hero__col{width:var(--hero-text-col);min-width:560px}
  .hero--split .hero__media{left:auto;width:57%;clip-path:polygon(9% 0,100% 0,100% 100%,0 100%)}
  .hero--split.band--hero::after{background:linear-gradient(100deg,var(--overlay) 0 52%,transparent 78%);opacity:1}
}

/* ---- breadcrumbs ---------------------------------------------------------- */
.crumbs{margin:0 0 var(--gap);font-size:var(--fs-small)}
.crumbs ol{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.crumbs__item+.crumbs__item::before{content:"/";opacity:.55;margin-right:8px}
.crumbs a{text-decoration:underline;text-underline-offset:3px}

/* ---- trust checks --------------------------------------------------------- */
.checks{display:flex;flex-wrap:wrap;gap:12px 24px;margin:0 0 calc(var(--gap) * 1.4)}
.checks__item{display:inline-flex;align-items:flex-start;gap:10px;font-size:var(--fs-body);font-weight:400}
.checks__item svg{width:24px;height:24px;flex:0 0 24px;color:var(--brand-action-on-dark);margin-top:2px}
@media (max-width:767px){.checks__item{flex:1 1 100%}}

/* ---- cta pair ------------------------------------------------------------- */
.ctapair{display:flex;flex-wrap:wrap;gap:14px}
.band__foot{margin-top:calc(var(--gap) * 2);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:14px 26px}
.band__foot--left{justify-content:flex-start}
/* A HUB OR REVIEW LINK IS A TEXT LINK, NOT A THIRD COLOUR OF BUTTON. On a
   prospect page the filled button always means the estimate and the outlined
   one always means the call; "See All Services", "About Us", "Read More
   Reviews" and "Leave Us a Review" print beside the pair as plain links
   (pages/index.js, components/review-ctas.js, 2026-10-01). gates/cta-meaning.js
   holds the colours to their meaning. */
.band__links{display:inline-flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px 26px}
.band__more{display:inline-flex;align-items:center;min-height:44px;font-family:var(--font-display);font-size:var(--fs-btn);
  font-weight:var(--fw-btn);text-transform:var(--tt-btn);color:var(--brand-action-ink);
  text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:5px}
.band[data-ground="dark"] .band__more{color:var(--brand-action-on-dark)}
.band__more:hover{text-decoration-thickness:3px}

/* ---- trust strip ---------------------------------------------------------- */
.band--trust-strip{padding-block:18px;background:var(--surface-alt);color:var(--ink)}
.strip__row{display:grid;gap:16px var(--gap);grid-template-columns:1fr}
.strip__item{display:flex;align-items:center;gap:12px}
.strip__icon{display:flex;width:34px;height:34px;flex:0 0 34px;color:var(--brand-action-ink)}
.strip__icon svg{width:100%;height:100%}
.strip__text{display:flex;flex-direction:column;line-height:1.35}
.strip__text strong{font-family:var(--font-display);text-transform:var(--tt-heading);font-size:15px}
.strip__text span{font-size:14px;color:var(--ink-soft)}
@media (min-width:768px){.strip__row{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1025px){.strip__row{grid-template-columns:repeat(4,1fr)}}
/* THE STRIP IS LAID OUT BY ITS COUNT (components/trust-strip.js prints
   strip__row--N). Three cells sat in four columns with the last quarter of the
   band empty (Mendez request 6, 2026-10-01). Four cells keep the four columns. */
@media (min-width:768px){.strip__row--1{grid-template-columns:minmax(0,1fr);justify-items:center}}
@media (min-width:1025px){.strip__row--3{grid-template-columns:repeat(3,1fr)}
  .strip__row--2{grid-template-columns:repeat(2,1fr)}
  /* a licensed shop with four strip lines prints five cells (the licence
     plus four): in four columns the fifth sat alone on a second row (the
     10/1 verifier, round 2). No approved brief prints five. */
  .strip__row--5{grid-template-columns:repeat(5,1fr)}}

/* ---- service cards -------------------------------------------------------- */
.svc{
  display:flex;flex-direction:column;background:var(--surface);
  border:1px solid var(--border);border-radius:var(--radius-card);overflow:hidden;
  transition:transform .2s ease,box-shadow .2s ease;
  /* A CARD THAT PAINTS ITS OWN SURFACE SETS ITS OWN INK. Without this the card
     inherits the colour of the band it sits in, so the same card reads black on
     a light band and WHITE ON WHITE inside the dark localised-service band on
     every area page. That is a 1.00:1 title, invisible, and it shipped. */
  color:var(--ink);
}
.svc:hover{transform:translateY(-4px);box-shadow:0 14px 34px rgba(0,0,0,.13)}
/* 230px on a desktop and 200px from tablet up, both measured. On a PHONE the
   grid is one column, so a homepage with four service cards stacks four 200px
   photo bands and the services band busts the measured 2,200px ceiling for a
   single band. The ceiling wins: a visitor scrolling past a fifth screen of
   cards has already gone. 170px keeps the photograph readable and the band
   inside budget. */
.svc__photo{display:block;height:170px;overflow:hidden}
.svc__photo picture,.svc__photo img{width:100%;height:100%;object-fit:cover}
/* 20/18/22 from tablet up. On a PHONE the four cards stack, so four bottom pads
   are four times whatever this number is, and the services band was 4px over the
   measured 2,200px single band ceiling. 18px at the bottom below 768 buys back
   16px and reads no tighter. */
.svc__body{display:flex;flex-direction:column;padding:20px 18px 18px;flex:1}
@media (min-width:768px){.svc__body{padding:20px 18px 22px}}
.svc__title{
  font-family:var(--font-display);font-size:var(--fs-h3);font-weight:var(--fw-h3);
  text-transform:var(--tt-heading);line-height:var(--lh-heading);margin:0 0 15px;
}
.svc__text{flex:1;font-size:15.5px;color:var(--ink-soft)}
.svc__more{
  margin-top:16px;font-family:var(--font-display);font-weight:700;font-size:15px;
  text-transform:var(--tt-btn);color:var(--brand-action-ink);
}
@media (min-width:768px){.svc__photo{height:200px}}
@media (min-width:1025px){.svc__photo{height:230px}
  .svc__body{padding:24px 22px 26px}}

/* ---- form split ----------------------------------------------------------- */
.split--form .split__form{align-self:start}
@media (min-width:1025px){.split--form .split__form{position:sticky;top:96px}}
/* PANEL MODE (components/form-split.js prints split--panel when the brief
   names no form and no endpoint, which is every cold web preview). The sticky
   start alignment above was made for a tall form. A contact panel is a short
   list, so it sat thin and unframed at the top of a taller prose column with a
   dark pocket under it (Mendez request 4: prose 479px against a 287px panel;
   seven judge rounds over four builds, 2026-10-01). Here the column stretches
   to the row and the panel is a framed card with its rows centred in it.
   gates/band-balance.js fails a framed column more than 25% short of the
   column beside it, so a presentation.css cannot pin it back to the top. */
.split--panel .split__form{align-self:stretch;position:static}
.split--panel .contact-panel{height:100%;align-content:center}
/* the card's frame is the engine's default only: zero specificity, so a client
   layer that draws its own frame on .contact-panel keeps its colours */
:where(.split--panel) :where(.contact-panel){padding:28px 26px;border:1px solid var(--border);border-radius:var(--radius-lg)}
:where(.band[data-ground="dark"] .split--panel) :where(.contact-panel){border-color:rgba(255,255,255,.18)}
.leadform{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);
  padding:26px 24px 28px;box-shadow:0 12px 34px rgba(0,0,0,.09);
}
.band[data-ground="dark"] .leadform{color:var(--ink)}
.leadform__title{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:var(--fw-h3);
  text-transform:var(--tt-heading);margin-bottom:6px}
.leadform__sub{font-size:15px;color:var(--ink-soft);margin-bottom:18px}
.leadform__grid{display:grid;gap:14px;grid-template-columns:1fr 1fr}
.field{display:flex;flex-direction:column;gap:6px}
.field--full{grid-column:1 / -1}
.field__label{font-size:14px;font-weight:600}
.field__input,.field__area{
  width:100%;padding:12px 13px;font:inherit;font-size:16px;
  border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);color:var(--ink);
}
.field__area{resize:vertical;min-height:92px}
.field__radios{display:flex;gap:18px}
.radio,.consent{display:flex;align-items:flex-start;gap:9px;font-size:14px;line-height:1.5}
.leadform__submit{
  grid-column:1 / -1;min-height:54px;border:0;border-radius:var(--radius);cursor:pointer;
  background:var(--brand-action);color:var(--on-action);
  font-family:var(--font-display);font-size:var(--fs-btn);font-weight:var(--fw-btn);
  text-transform:var(--tt-btn);
}
.leadform__submit:hover{background:var(--brand-dark);color:#FFFFFF}
.leadform__result{grid-column:1 / -1;font-size:14px;margin:0}
.leadform__result.is-ok{color:var(--brand-action-ink)}
.leadform__note{grid-column:1 / -1;font-size:12px;line-height:1.55;color:var(--ink-soft);margin:0}
.leadform__note a{text-decoration:underline}
.ghlform{display:block;width:100%;border:0;border-radius:var(--radius-lg);background:var(--surface)}
@media (max-width:767px){.leadform__grid{grid-template-columns:1fr}}

/* ---- why cards ------------------------------------------------------------ */
.split--why .split__img picture,.split--why .split__img img{
  width:100%;border-radius:var(--radius-lg);object-fit:cover}
/* THE PHOTO IS CUT TO THE LIST, NOT PAINTED AT ITS OWN HEIGHT (a prospect's
   why band only: components/why-card.js prints split--why-fit there). A
   portrait phone photo ran far past the text column on a desktop, and on a
   phone the band ended on the photograph, after its buttons (Mendez, all four
   builds, 2026-10-01; requests 5 and 6 each hand wrote a 4 by 3 cap in
   presentation.css). Under 1025px the photo is a 4 by 3 crop stacked above
   the list, so the band ends on its pair. From 1025px a fixed 4 by 3 still
   left a pocket under the photo beside a long list (474px of photo against
   966px of prose on request 4), so there the photo fills the height of the
   list beside it, cut to cover, never under 320px: the two columns end
   together. gates/band-balance.js fails a photo column more than 25% taller,
   or a framed column more than 25% shorter, than the column beside it, and
   gates/mobile-shape.js a prospect band whose last painted box on a phone is
   a photo. Every approved page keeps its why band as it was (E&A's photo
   under its list, at its own height: the 10/1 verifier, round 2). */
.split--why-fit .split__img img{aspect-ratio:4/3;height:auto;object-fit:cover}
@media (max-width:1024px){.split--why-fit .split__img{order:-1}}
@media (min-width:1025px){
  .split--why-fit .split__img{align-self:stretch;position:relative;min-height:320px}
  .split--why-fit .split__img picture{position:absolute;inset:0;display:block}
  .split--why-fit .split__img img{position:absolute;inset:0;width:100%;height:100%;aspect-ratio:auto}
  .split--why-fit .split__img picture img{position:static}
}
.why{display:flex;flex-direction:column;gap:var(--gap);margin-top:var(--gap)}
/* the engine's brand device: each reason behind a 4px action coloured rule,
   20px in. Kept on every approved page (E&A, the 10/1 verifier, round 2). */
.why__item{
  display:flex;flex-direction:column;gap:5px;
  padding-left:20px;border-left:4px solid var(--brand-action);
}
/* on a prospect the list starts on the heading's left edge: the indent behind
   the rule sat it 24px in from the heading above it (Mendez requests 4 to 6).
   A brief-named icon medallion still prints before the headline. Zero
   specificity on the prospect class, so a client layer that draws its own rule
   on .why__item (Dany's, Icy Hot) still wins. */
:where(.split--why-fit) .why__item{padding-left:0;border-left:0}
.why__head{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:var(--fw-h3);
  text-transform:var(--tt-heading);line-height:var(--lh-heading)}
.why__fact{font-size:14px;font-weight:700;color:var(--brand-action-ink);text-transform:uppercase;letter-spacing:.06em}
.band[data-ground="dark"] .why__fact{color:var(--brand-action-on-dark)}
.why__body{font-size:var(--fs-body);color:var(--ink-soft)}
.band[data-ground="dark"] .why__body{color:#FFFFFF}

/* ---- offer tickets ---------------------------------------------------------
   LAYOUT ONLY. components/offer-ticket.js prints the tickets; the client layer
   draws them (the frame, the notches, the colours, the type sizes). Until
   2026-10-01 there was no block here at all, so every presentation invented
   its own specials layout, and the Mendez dress rehearsal (request 5) laid ONE
   ticket out 820px wide as a two column grid with its small print capped at
   36ch: the words ended 256px short of the right edge against 52px on the
   left, the creative director's first fix read "a stretched panel with the
   right third empty", and the page failed step 7b on a 7. A lone ticket is a
   coupon, not a banner, so it sits centred at a coupon's measure (600px, the
   judge's own number) with its words stacked and centred in it. Two tickets
   sit side by side from a tablet up and three in a row, each the same width,
   with the button dropped to the foot so the buttons line up. A presentation
   never sets a width, a max-width or a grid on .offers or .offer__ticket;
   gates/ticket-fill.js fails a framed box with one side left empty and a
   ticket that paints no frame, which is what keeps a presentation.css from
   undoing this. Icy Hot and Dany's specials bands paint pixel for pixel what
   they did before at 390, 1280 and 1440. */
.offers{list-style:none;margin:0 auto;padding:0;display:grid;gap:calc(var(--gap) * 1.5);
  grid-template-columns:minmax(0,1fr);max-width:1040px}
.offers--1{max-width:600px}
.offers--2{max-width:820px}
@media (min-width:768px){.offers--2{grid-template-columns:repeat(2,minmax(0,1fr))}
  .offers--3{grid-template-columns:repeat(3,minmax(0,1fr))}}
.offer{display:flex;min-width:0}
.offer__ticket{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:4px;text-align:center}
.offer__big,.offer__label,.offer__fine{display:block}
.offer__fine{max-width:36ch}
.offer__cta{margin-top:auto}

/* ---- review cards --------------------------------------------------------- */
.revs{display:grid;gap:var(--gap-cards);grid-template-columns:1fr}
.review-card{
  display:flex;flex-direction:column;margin:0;
  background:var(--surface);border-radius:var(--radius-card);
  padding:28px 26px 24px;box-shadow:0 6px 20px rgba(0,0,0,.09);color:var(--ink);
}
.review-card__stars{display:flex;gap:3px;margin-bottom:14px;color:var(--review-star)}
.review-card__stars svg{width:24px;height:24px}
.review-card__text{
  margin:0 0 18px;flex:1;font-size:15.5px;font-family:var(--font-body);font-weight:400;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;
}
/* A prospect card ends on a whole sentence (components/review-card.js trims the
   quote and marks the card); nothing is clamped mid word with an ellipsis. */
.review-card--whole .review-card__text{display:block;-webkit-line-clamp:unset;overflow:visible}
.review-card__meta{display:flex;flex-direction:column;gap:2px}
.review-card__author{font-family:var(--font-display);font-size:22px;font-weight:800;
  text-transform:uppercase;line-height:1.1}
.review-card__when{font-size:13px;color:var(--ink-soft)}
@media (min-width:768px){.revs{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1025px){.revs.cards--3{grid-template-columns:repeat(3,1fr)}
  .revs.cards--2{grid-template-columns:repeat(2,1fr)}
  .revs.cards--4{grid-template-columns:repeat(4,1fr)}}
/* one review on a prospect stands alone, as wide as a lone offer ticket */
@media (min-width:768px){.revs.cards--1{grid-template-columns:minmax(0,1fr);max-width:640px;margin-inline:auto}}

/* ---- area chips ----------------------------------------------------------- */
.chips{display:grid;gap:12px;grid-template-columns:1fr}
.chip{
  display:flex;align-items:center;gap:9px;padding:12px;
  border:1px solid var(--border);border-radius:var(--radius-chip);
  background:var(--surface);font-weight:600;color:var(--ink);
}
.chip svg{width:18px;height:18px;flex:0 0 18px;color:var(--brand-action-ink)}
.chip:hover{border-color:var(--brand-action);color:var(--brand-action-ink)}
@media (min-width:768px){.chips{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1025px){.chips{grid-template-columns:repeat(4,1fr)}}
/* a prospect's chips laid out by their count (components/area-chip.js prints
   chips--N, the column count): one or two towns sit centred at their own
   width, three columns take three, five, six or nine towns */
.chips--1,.chips--2{display:flex;flex-wrap:wrap;justify-content:center}
@media (min-width:768px){.chips--3{grid-template-columns:repeat(3,1fr)}}
/* 13 or 25 towns: neither four nor three columns leave a last row half full,
   five do (lib.countCols, the 10/1 verifier, round 2) */
@media (min-width:1025px){.chips--5{grid-template-columns:repeat(5,1fr)}}
/* THE STAKE CARD (pages/parts.js): a homepage with one or two towns carries
   one centred card, as wide as a lone offer ticket, holding the town chips,
   the office address and the opening hours, instead of a lone chip in a four
   column grid (Mendez request 6 attempt 2 failed on exactly that). */
.areas-base{max-width:600px;margin:0 auto;padding:26px 28px;display:grid;gap:18px;justify-items:center;
  background:var(--surface);color:var(--ink);border:1px solid var(--border);border-radius:var(--radius-lg);
  box-shadow:0 10px 28px rgba(0,0,0,.07)}
.chips--base{display:flex;flex-wrap:wrap;justify-content:center;gap:12px}
.areas-base__facts{display:grid;gap:10px;justify-items:start}
.areas-base__row{display:flex;align-items:flex-start;gap:10px;font-size:var(--fs-body);line-height:1.5;text-align:left}
.areas-base__row svg{width:20px;height:20px;flex:0 0 20px;margin-top:2px;color:var(--brand-action-ink)}
/* opening hours (components/lib.js hoursHtml): a day group per line, and a
   time range never wraps ("Friday 6 / AM to 6:30 PM" was the defect) */
.hours__group{display:block}
.hours__time{white-space:nowrap}

/* ---- faq ------------------------------------------------------------------ */
.faq{display:flex;flex-direction:column;gap:10px;max-width:960px;margin-inline:auto}
.faq__row{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);
  color:var(--ink);padding:0;overflow:hidden}
.faq__q{
  cursor:pointer;list-style:none;padding:18px 22px;
  font-family:var(--font-display);font-size:calc(var(--fs-h3) * .78);font-weight:var(--fw-h3);
  text-transform:var(--tt-heading);line-height:1.3;
}
.faq__q::-webkit-details-marker{display:none}
.faq__q::after{content:"+";float:right;margin-left:16px;font-weight:400}
/* a MINUS SIGN, U+2212, not an en dash. Nothing this site paints is a dash. */
.faq__row[open] .faq__q::after{content:"\2212"}
.faq__a{padding:0 22px 20px;font-size:var(--fs-body);color:var(--ink-soft)}

/* ---- steps ---------------------------------------------------------------- */
.steps{counter-reset:step;display:grid;gap:var(--gap-cards);grid-template-columns:1fr}
.step{position:relative;padding-top:8px}
.step__n{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:50%;margin-bottom:12px;
  background:var(--brand-action);color:var(--on-action);
  font-family:var(--font-display);font-weight:800;font-size:20px;
}
.step__head{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:var(--fw-h3);
  text-transform:var(--tt-heading);margin:0 0 8px}
.step__body{font-size:15.5px;color:var(--ink-soft)}
.band[data-ground="dark"] .step__body{color:#FFFFFF}
@media (min-width:768px){.steps{grid-template-columns:repeat(3,1fr)}}

/* ---- edu blocks ----------------------------------------------------------- */
.edu{display:flex;flex-direction:column;gap:calc(var(--gap) * 1.6);max-width:78ch}
.edu__head{font-family:var(--font-display);font-size:var(--fs-h3);font-weight:var(--fw-h3);
  text-transform:var(--tt-heading);margin:0 0 8px}
.edu__body{margin:0;color:var(--ink-soft)}

/* ---- maps ----------------------------------------------------------------- */
.foot__maps{margin-top:calc(var(--gap) * 2)}
.map{border-radius:var(--radius);overflow:hidden;background:var(--surface-alt);position:relative}
.map iframe{display:block;width:100%;border:0;position:relative;z-index:1}
/* the fallback under the frame (components/maps.js): seen whenever the frame
   has not painted, tappable until the frame says it has loaded */
.map iframe:not(.is-live){pointer-events:none}
.map__open{position:absolute;left:0;right:0;top:0;height:240px;z-index:0;padding:18px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;text-align:center;
  color:var(--ink);font-size:14px;line-height:1.4;
  /* its own ground: a client layer that paints .map dark (request 5's dotted
     #1E252D panel) left the address at 1.04 to 1; the loaded frame covers it */
  background:var(--surface-alt)}
.map__open svg{width:26px;height:26px;color:var(--brand-action-ink)}
.map__cta{font-family:var(--font-display);font-weight:700;font-size:16px;text-transform:var(--tt-btn);
  text-decoration:underline;text-underline-offset:4px;color:var(--brand-action-ink)}
.map__label{margin:0;padding:12px 14px;font-size:14px;background:var(--surface-alt);color:var(--ink)}
.band--directions-map .map{max-width:900px;margin-inline:auto}

/* ---- before and after ------------------------------------------------------ */
.bagrid{display:grid;gap:var(--gap-cards);grid-template-columns:1fr}
.ba__frame{display:grid;grid-template-columns:1fr 1fr;gap:8px;position:relative}
.ba__side{position:relative}
.ba__tag{position:absolute;left:10px;bottom:10px;padding:5px 10px;border-radius:4px;
  background:var(--brand-dark);color:#FFFFFF;font-size:12px;font-weight:700;text-transform:uppercase}
.ba__range{display:none}
.ba__caption{margin-top:10px;font-size:15px;color:var(--ink-soft)}
/* with the script running the two frames stack and the range reveals one over
   the other. With it off they stay side by side, which is why the fallback is
   the DEFAULT here rather than an add on. */
.js .ba__frame{grid-template-columns:1fr}
.js .ba__side{grid-area:1 / 1}
.js .ba__side--after{clip-path:inset(0 0 0 var(--ba-split,50%))}
.js .ba__range{display:block;width:100%;margin-top:10px}
@media (min-width:1025px){.bagrid{grid-template-columns:repeat(2,1fr)}}

/* ---- footer ---------------------------------------------------------------- */
.band--footer{background:var(--brand-dark);color:#FFFFFF;padding-block:68px}
.foot__grid{display:grid;gap:calc(var(--gap) * 1.7);grid-template-columns:1fr}
.foot__logo img{width:96px;height:96px;object-fit:contain}
.foot__blurb{font-size:15px;margin:14px 0 0;color:#FFFFFF}
.foot__social{margin-top:16px;display:flex;gap:12px}
.foot__social svg{width:26px;height:26px}
.foot__head{
  font-family:var(--font-display);font-size:18px;font-weight:800;
  text-transform:var(--tt-heading);margin:0 0 12px;color:#FFFFFF;
}
.foot__links{display:flex;flex-direction:column;gap:7px;font-size:15px}
.foot__links a:hover{color:var(--brand-action-on-dark)}
.foot__item{display:flex;align-items:flex-start;gap:10px;margin-bottom:12px;font-size:15px}
.foot__item svg{width:19px;height:19px;flex:0 0 19px;margin-top:3px;color:var(--brand-action-on-dark)}
/* an email address breaks only at the <wbr> a prospect page prints before the
   @ and the last dot (components/lib.js emailHtml), never mid word */
.foot__item a,.contact-panel__row{overflow-wrap:normal}
@media (min-width:1025px){.foot__grid{grid-template-columns:1.3fr 1fr 1fr 1fr}}

/* ---- legal strip ----------------------------------------------------------- */
.band--legal{padding-block:18px;background:var(--brand-dark);color:#FFFFFF;
  border-top:1px solid rgba(255,255,255,.16)}
/* THE TYPE SIZE IS ON THE CHILDREN, NOT THE ROW. .legal__row is also the
   .shell, and the shell's padding is em based (--shell-x), so a 13px row
   shrank its own gutter: 39px against 51px on every other band at 1440, the
   legal line sat 12px off the footer above it (Mendez request 6, 2026-10-01).
   gates/band-balance.js (check GUTTER) holds every shell to one left edge. */
.legal__row{display:flex;flex-wrap:wrap;gap:10px 20px;align-items:center;justify-content:space-between}
.legal__row > *{font-size:13px}
.legal__links{display:flex;flex-wrap:wrap;gap:16px}
.legal__links a:hover{color:var(--brand-action-on-dark)}

/* ---- dividers -------------------------------------------------------------- */
/* a divider overlaps the band above and the band below by a pixel, so no
   hairline of the page's own white shows at either edge of the shape */
.divider{line-height:0;margin:-1px 0;position:relative;z-index:1}
.divider svg{display:block;width:100%}
.band--owns-divider{padding-bottom:calc(var(--shell-y) * 2)}

/* ---- chat widget lift ------------------------------------------------------ */
/* the launcher never sits on top of the phone call bar */
@media (max-width:767px){
  chat-widget,[id*="chat-widget"]{bottom:calc(var(--cta-bar-h) + 8px)!important}
}

/* >>> rating-badge component: generated by .skills/client-website/components/rbadge-css.js. Do not edit inside the markers. <<< */
.rbadges {
  display: flex;
  flex-wrap: nowrap;              /* THE FIX. It physically cannot stack. */
  align-items: center;
  justify-content: flex-start;
  gap: 20px;
  width: 100%;
  max-width: 520px;
  margin: 0 0 20px;
  padding: 0;
  background: none;               /* no panel, no pill, no card behind the row */
  border: 0;
}
.rbadges[data-count="3"] { gap: 12px; }
.rbadges[data-count="4"] { gap: 10px; }
.rbadges__item {
  flex: 1 1 0;
  min-width: 0;                   /* REQUIRED. Without it the row breaks. */
  max-width: 193px;
  display: block;
  line-height: 0;
  /* The item is an <a>, so without this it inherits the site's link colour. Nothing inside the
     badge is painted with that colour, but a contrast sweep reading currentColor would measure a
     colour the badge never paints. Pin it to the same token the artwork uses so the two agree. */
  color: var(--rbadge-ink, #fff);
}
.rbadges__item:focus-visible { outline: 3px solid var(--rbadge-focus, currentColor); outline-offset: 4px; border-radius: 2px; }
.rbadge { display: block; width: 100%; height: auto; aspect-ratio: 193 / 60; color: var(--rbadge-ink, #fff); }
.rbadge text { fill: currentColor; font-family: var(--font-body, inherit); }
.rbadge path[fill="#F6AB26"] { fill: var(--rbadge-star, #F6AB26); }
@media (max-width: 768px) { .rbadges { max-width: none; gap: 12px; } }
@media (max-width: 400px)  { .rbadges { gap: 8px; } }
/* A light section flips ONLY the ink. The Google G keeps Google's colours on any ground. */
.rbadges--dark .rbadge { color: #0E1A2B; }
/* <<< end rating-badge component >>> */

/* ---- contact panel -------------------------------------------------------
   What stands where a lead form would, for a business whose brief names no
   form endpoint. It is not a form and does not pretend to be one: it is the
   real ways to reach them. Same in a preview and a launched site. 2026-09-11 */
.contact-panel { display: grid; gap: 14px; align-content: start; }
.contact-panel__lede { margin: 0; color: var(--on-dark-muted, inherit); }
.contact-panel__call {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-display); font-size: clamp(24px, 4vw, 34px);
  line-height: 1.1; text-decoration: none; color: inherit;
}
.contact-panel__call svg { width: 28px; height: 28px; flex: none; }
.contact-panel__row { margin: 0; text-decoration: none; color: inherit; }
.contact-panel .btn { justify-self: start; margin-top: 6px; }
/* the tap to save button (on by default on a prospect, components/form-split.js)
   sizes its own phone icon: unsized, the svg painted about 70px wide and pushed
   the label onto three lines (the 10/1 verifier, Mendez request 4 engine only) */
.contact-panel__save { display: inline-flex; align-items: center; gap: 8px; }
.contact-panel__save svg { width: 18px; height: 18px; flex: 0 0 18px; }

/* ---- gallery -------------------------------------------------------------
   A grid of the client's own job photographs. Two columns on a phone so a
   thumb still sees four frames without scrolling, three from a tablet up.
   The caption is visible on a phone and rides in on hover on a pointer device,
   because a phone has no hover and a caption that only exists on hover is a
   caption that a phone user never reads. */
.gal{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}
@media (min-width:768px){.gal{grid-template-columns:repeat(3,1fr);gap:16px}}
/* THE CAPTION SITS UNDER THE PHOTOGRAPH, NOT ON IT. It used to ride the foot
   of the photo over a dark gradient, which hid the bottom of every job photo
   (Mendez requests 4 and 6, 2026-10-01). The frame is now the photograph
   alone, cut 4 by 3, and the caption is a strip under it in the brand dark,
   white type, still clamped to two lines, on any band ground. A strip, not
   bare band type, because the client layers already drew white captions on
   their own dark strips (Dany's, Mendez request 6) and white type has to stay
   white for them. The photo no longer zooms on hover (it would spill over the
   caption); it brightens. The lightbox caption is separate and unchanged.
   gates/band-balance.js (check CAPTION) fails a caption that overlaps its
   photograph. The tile is a column (photo, then caption) and the caption
   strip fills whatever the row leaves: a tile is a button, and a button
   centres its content, so a one line caption beside a two line one floated
   its photo down and showed a dark bar over it (Dany's rebuilt, 2026-10-02). */
.gal__item{
  position:relative;display:flex;flex-direction:column;padding:0;border:0;margin:0;cursor:pointer;
  background:var(--brand-dark);border-radius:var(--radius);overflow:hidden;
  color:#FFFFFF;text-align:left;
}
.gal__img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;display:block;
  background:var(--surface-alt);transition:filter .35s ease}
.gal__item:hover .gal__img,.gal__item:focus-visible .gal__img{filter:brightness(1.08)}
.gal__zoom{
  position:absolute;top:10px;right:10px;width:34px;height:34px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.48);opacity:0;transition:opacity .2s ease;
}
.gal__zoom svg{width:18px;height:18px}
.gal__item:hover .gal__zoom,.gal__item:focus-visible .gal__zoom{opacity:1}
.gal__cap{
  flex:1 0 auto;
  padding:9px 12px 10px;font-size:13px;line-height:1.35;color:#FFFFFF;background:var(--brand-dark);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.gal__item:focus-visible{outline:3px solid var(--brand-action);outline-offset:3px}

/* the lightbox. Hidden until the script opens it. */
.gal__box{
  position:fixed;inset:0;z-index:var(--z-menu-panel);
  background:rgba(8,12,18,.94);
  display:flex;align-items:center;justify-content:center;gap:8px;padding:16px;
}
.gal__box[hidden]{display:none}
.gal__stage{margin:0;max-width:min(1100px,92vw);text-align:center}
.gal__stage img{max-width:100%;max-height:76vh;border-radius:var(--radius);display:block;margin:0 auto}
.gal__stage figcaption{color:#FFFFFF;font-size:14px;padding:14px 8px 0;line-height:1.4}
.gal__close{
  position:absolute;top:14px;right:16px;width:46px;height:46px;font-size:32px;
  line-height:1;background:none;border:0;color:#FFFFFF;cursor:pointer;
  z-index:var(--z-menu-toggle);
}
.gal__nav{
  flex:0 0 auto;width:48px;height:48px;font-size:34px;line-height:1;
  background:rgba(255,255,255,.12);border:0;border-radius:50%;color:#FFFFFF;cursor:pointer;
}
.gal__nav:hover{background:rgba(255,255,255,.24)}
@media (max-width:600px){
  .gal__nav{position:absolute;bottom:18px}
  .gal__nav--prev{left:22px}.gal__nav--next{right:22px}
  .gal__stage figcaption{padding-bottom:76px}
}

/* Brand-led variants. Opt-in through the same brief for previews and full sites. */
.nav__wordmark{font-family:var(--font-display);font-style:var(--display-style,normal);font-size:30px;font-weight:800;letter-spacing:-.025em;line-height:1.05;white-space:nowrap}
/* THE WHOLE NAME, BESIDE THE MENU BUTTON (a prospect's nav only: nav.js prints
   nav__logo--fit there). At 30px a two word name pushed the hamburger off a
   375px screen (Mendez requests 5 and 6, 2026-10-01), and an ellipsis then cut
   it to "MENDEZ PLUM..." (the 10/1 verifier). Under 1180px the logo link may
   shrink, never the button, and the name wraps in balanced lines at a size
   that follows the screen (18px at 375), never cut: a two word name takes two
   lines beside an emblem, a three word name ("Mendez Plumbing Service") may
   stack in three, about the emblem's own height. Every approved page keeps
   the 30px one line name above (the engine fixture, E&A). gates/nav-logo.js
   reads it at 375. */
@media (max-width:1179px){.nav__logo--fit{min-width:0;flex-shrink:1}
  .nav__logo--fit .nav__wordmark{min-width:0;font-size:clamp(16px,4.8vw,30px);white-space:normal;text-wrap:balance}}
/* A PHONE AND TABLET FIX ONLY. From 1180px the desktop row also holds the
   links and both buttons, and the 30px name beside an emblem that already
   spells it ran that row past the screen: the call button sat 1316 to 1502px
   on a 1280px screen, hidden by the page's overflow clip (the 10/2 verifier,
   Mendez requests 4, 5 and 6). There the emblem is 76px tall and its own
   lettering reads, so the name beside it drops, exactly as the approved pages
   print it. gates/nav-logo.js checks every nav link and button ends inside the
   screen at 1180, 1280 and 1440. */
@media (min-width:1180px){.nav__logo--fit.nav__logo--named .nav__wordmark{display:none}}
.nav--light{background:#fff;color:var(--brand-dark);border-bottom:3px solid var(--brand-detail)}
.nav--light .nav__logo{color:var(--brand-dark)}
.nav--light .nav__hamburger span{background:var(--brand-dark)}
.hero__eyebrow{font-family:var(--font-body);font-size:13px;line-height:1.4;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--brand-detail);margin:0 0 22px}
.hero--angular .hero__title{font-style:var(--display-style,normal);font-size:clamp(60px,5.3vw,80px);line-height:.99;letter-spacing:-.025em;white-space:pre-line;margin:25px 0 20px;text-transform:uppercase}
.hero--angular .hero__count{color:var(--brand-detail);margin-bottom:15px}
.hero--angular .hero__sub{max-width:41ch;font-size:17px;line-height:1.65;margin-bottom:28px}
.hero--angular .hero__col{position:relative}
.hero--angular .ctapair{gap:12px}
.hero--angular .btn{border-radius:2px}
.hero--angular .btn--ghost{border:1px solid #fff}
.hero--angular .rbadges{margin-bottom:10px}
@media(min-width:1180px){
 .nav--light .nav__link,.nav--light .nav__dropdown-toggle{color:var(--brand-dark)}
 .nav--light .nav__link:hover,.nav--light .nav__dropdown-toggle:hover{color:var(--brand-action)}
 .nav--light .nav__actions .btn--ghost{color:var(--brand-dark);border:1px solid var(--brand-dark)}
 .nav--light .nav__links{gap:20px}
 .nav--light .nav__row{min-height:86px}
}
@media(min-width:1025px){
 .hero--angular.hero--split{padding-block:58px;min-height:660px}
 .hero--angular.hero--split .hero__col{width:49%;min-width:0}
 .hero--angular.hero--split .hero__media{left:42%;width:58%;clip-path:none}
 .hero--angular.hero--split .hero__media img{object-position:66% center}
 .hero--angular.hero--split::after{width:56%;background:var(--brand-dark);opacity:1;clip-path:polygon(0 0,100% 0,89% 100%,0 100%)}
 .hero--angular.hero--split::before{content:"";position:absolute;inset:0 auto 0 0;width:56.8%;background:var(--brand-detail);z-index:1;clip-path:polygon(0 0,100% 0,89% 100%,0 100%)}
}
@media(max-width:1024px){
 .hero--angular.hero--split{display:flex;flex-direction:column;align-items:stretch;padding:0;min-height:0}
 .hero--angular.hero--split .hero__media{position:relative;inset:auto;order:2;height:320px;flex:none;border-top:4px solid var(--brand-detail)}
 .hero--angular.hero--split .hero__inner{order:1;padding-top:40px;padding-bottom:40px}
 .hero--angular.hero--split::after{display:none}
 .hero--angular .hero__title{font-size:clamp(50px,8vw,74px);max-width:620px}
 .hero--angular .hero__sub{max-width:48ch}
}
@media(max-width:520px){
 .nav__wordmark{font-size:29px}
 .nav__logo--fit .nav__wordmark{font-size:clamp(16px,4.8vw,29px)}
 .hero--angular .hero__title{font-size:51px;letter-spacing:-.035em}
 .hero--angular .hero__eyebrow{font-size:11px;margin-bottom:18px}
 .hero--angular .hero__sub{font-size:15px}
 .hero--angular.hero--split .hero__media{height:270px}
 .hero--angular .ctapair{display:grid;grid-template-columns:1fr}
 .hero--angular .ctapair .btn{width:100%;justify-content:center}
}
@media(min-width:768px){.gal{grid-template-columns:repeat(var(--gallery-cols,3),minmax(0,1fr))}}
@media(max-width:520px){
 .hero--angular.hero--split .hero__media{order:1;height:210px;border-top:0;border-bottom:3px solid var(--brand-detail)}
 .hero--angular.hero--split .hero__inner{order:2;padding-top:26px;padding-bottom:30px}
 .hero--angular .hero__title{font-size:44px;margin-top:18px;margin-bottom:16px}
 .hero--angular .hero__eyebrow{margin-bottom:15px}
 .hero--angular .hero__count{margin-bottom:12px}
 .hero--angular .hero__sub{font-size:14px;line-height:1.65;margin-bottom:20px}
 .hero--angular .ctapair{grid-template-columns:1fr 1fr;gap:10px}
 .hero--angular .ctapair .btn{min-height:46px;padding-inline:10px;font-size:14px}
}
.gal__item{font-family:var(--font-body);font-size:14px;line-height:1.45}
@media(max-width:1179px){.nav--light .nav__actions .btn--ghost{color:#fff;border-color:#fff}}
@media(max-width:1179px){
 .nav--light .nav__dropdown-item,.nav--light .nav__dropdown-toggle,.nav--light .nav__link{color:#fff}
 .nav--light .nav__hamburger[aria-expanded="true"] span{background:#fff}
}

/* ---- photo on top, on a phone: opt-in ---------------------------------------
   theme.layout.hero.phone = 'photo-top'. Below 1025px the hero stacks: the
   photograph in full colour on top, a short fade into the brand dark at its
   foot, and the badge row, headline and buttons below it on the solid dark.
   That is the approved Geaux 2 Guy phone hero. The flat wash over the
   whole band is off here, because the words no longer sit on the photograph.
   Only a brief that opts in carries the class, so every other hero is exactly
   what it was. The block never drops under 200px tall: the mobile shape gate
   only counts a hero photograph that paints at least 200 by 200. The focal
   point is the brief's theme.layout.hero.phone_focus, carried on the media
   block by hero.js. Placed after the angular block so a brief that sets both
   gets photo on top. 2026-09-29. */
@media(max-width:1024px){
 .band--hero.hero--photo-top{display:flex;flex-direction:column;align-items:stretch;padding:0;min-height:0}
 .band--hero.hero--photo-top::before,.band--hero.hero--photo-top::after{display:none}
 .band--hero.hero--photo-top .hero__media{position:relative;inset:auto;order:1;flex:none;width:100%;height:clamp(230px,62vw,420px);clip-path:none}
 .band--hero.hero--photo-top .hero__media img{object-position:var(--hero-phone-focus,50% 40%)}
 .band--hero.hero--photo-top .hero__media::after{content:"";position:absolute;left:0;right:0;bottom:0;height:34%;background:linear-gradient(to bottom,transparent,var(--overlay));pointer-events:none}
 .band--hero.hero--photo-top .hero__inner{order:2;padding-top:22px;padding-bottom:calc(var(--shell-y) * 1.2)}
}

/* ---- a logo that already spells the name: opt-in ----------------------------
   The logo's image manifest row says contains_name: true, so nav.js and
   footer.js print the logo alone, never the name again beside it. A lockup
   carries its own lettering under or beside the mark, so it gets more height
   than a bare mark, or that lettering is too small to read on a phone. The
   footer drops the 96px square for the same reason: a 1.5 to 1 lockup squeezed
   into a square prints its lettering at a few pixels. The rounded corner is for
   a logo that ships on its own white ground (a PNG cut from his site header):
   on the dark footer it reads as a deliberate logo card, not a pasted
   rectangle, and on a transparent logo it changes nothing. 2026-09-29. */
.nav__logo--named img{height:58px;max-width:200px}
@media(min-width:1180px){.nav__logo--named img{height:76px;max-width:280px}}
.foot__logo--named img{width:auto;height:auto;max-width:220px;max-height:120px;border-radius:var(--radius)}

/* Optional service carousel: native overflow remains usable without JavaScript. */
.cards--carousel{display:grid;grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:82%;overflow-x:auto;scroll-snap-type:x mandatory;gap:22px;padding:8px 2px 22px;scrollbar-width:thin;overscroll-behavior-x:contain}
.cards--carousel .svc{scroll-snap-align:start;min-width:0}
.service-slider__controls{display:flex;justify-content:space-between;align-items:center;gap:18px;margin-top:12px}
.service-slider__controls p{margin:0;font-size:14px}
.service-slider__controls>div{display:none;gap:10px}
.service-slider--ready .service-slider__controls>div{display:flex}
.service-slider__controls button{width:48px;height:48px;border:1px solid var(--brand-dark);border-radius:50%;background:var(--surface);color:var(--brand-dark);font-size:24px;cursor:pointer}
.service-slider__controls button:hover{background:var(--brand-dark);color:white}
.service-slider__controls button:disabled{opacity:.35;cursor:default}
@media(min-width:768px){.cards--carousel{grid-auto-columns:43%}}
