/* ==========================================================================
   Penobscot Cleaning — design system
   Palette, type scale, button shape, section themes, and the fluid grid
   model are measured from the previous site (archive/css/site.css +
   per-section style tags + computed styles), not chosen.
   ========================================================================== */

/* Montserrat, self-hosted to match the original site's typography. These are
   variable-weight woff2 files, so one file per subset covers 400–700. Latin is
   declared last so it wins for the overlapping ranges most visitors hit. */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/montserrat-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/montserrat-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Live palette: hsl(0,88%,50%) accent, hsl(0,0%,98%) ground, teal hardcoded
     into the site's "dark" theme as hsl(185,59%,31%). */
  /* The live brand red is #F00F0F, which is 4.39:1 against white — just under
     the 4.5:1 WCAG AA floor for normal text, and the site sets white body copy
     on red bands. Two steps darker clears the threshold (4.73:1) at a
     difference no eye picks up side by side. */
  --c-red: #E60F0F;
  --c-teal: #20767E;
  --c-black: #000000;
  --c-offwhite: #FAFAFA;
  --c-white: #FFFFFF;
  --c-ink: #000000;
  --c-line: rgba(0, 0, 0, .12);
  /* One step up from --c-black — marks an open nav dropdown (button + panel)
     as a distinct surface instead of blending into the header. */
  --c-nav-open: #262626;

  --font: "Montserrat", "Segoe UI", system-ui, -apple-system, sans-serif;

  /* Live button: red, white text, weight 400, asymmetric corner. */
  --btn-radius: 6.4px 0 6.4px 6.4px;

  /* Fluid-engine constants, measured from the per-section style tags:
     page gutter 6vw mobile / 3vw desktop; an 11px gap between cells (a few
     sections use 0 — emitted per section as --fe-gap); grid cells cap so
     content tops out at maxPageWidth 3000px; rows are minmax(24px, auto) on
     mobile and minmax(container-width × 0.0215, auto) on desktop. */
  --gutter: 6vw;
  --max-w: 3000px;
  --row-h: 24px;
  --content-w: min(var(--max-w), 100vw - 12vw);
}
@media (min-width: 768px) {
  :root {
    --gutter: 3vw;
    --row-h: calc(min(var(--max-w), 100vw - 6vw) * 0.0215);
    --content-w: min(var(--max-w), 100vw - 6vw);
  }
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  color: var(--c-ink);
  background: var(--c-offwhite);
  /* Live body copy is a flat 16px: html/body are set to --base-font-size 16px
     and the site's --normal-text-size-value is 1, so paragraphs never scale
     with the viewport. Body text size drives the fluid-engine row heights
     (rows are minmax(row-h, auto)), so oversizing it here stretches every
     grid section that shares a row band with a text block. */
  font-size: 1rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; height: auto; }
a { color: inherit; text-underline-offset: 2px; }

/* Sitewide link affordance: prose links get a small arrow after the text
   instead of an underline. Scoped to where body copy actually lives (fluid-
   engine paragraphs/lists/quotes, directory descriptions) — buttons, cards,
   and the nav-style link lists (related pages, directory headings, blog
   "Read More", which already spells its own arrow into the label) keep
   their existing affordance untouched. */
:is(.fe-b p, .fe-b li, .fe-b blockquote, .dir-intro, .dir-item dd) a {
  text-decoration: none;
}
:is(.fe-b p, .fe-b li, .fe-b blockquote, .dir-intro, .dir-item dd) a::after {
  content: "\2192";
  display: inline-block;
  margin-left: .3em;
  font-size: .85em;
  transition: transform .15s ease;
}
:is(.fe-b p, .fe-b li, .fe-b blockquote, .dir-intro, .dir-item dd) a:is(:hover, :focus-visible)::after {
  transform: translateX(3px);
}
/* The live type scale, replayed from its own formula rather than approximated.
   The old site sized every heading and the large/small rich-text classes from
   one multiplier (--v, its *-size-value tweak) against a 3000px
   --max-w: below 768px portrait it scales with viewport HEIGHT, everywhere
   else with viewport width, capped so it stops growing past the max page
   width. Live values: h1 2.4, h2 2, h3 1.6, h4 1.4, large 1.2, small .8. */
h1 { --v: 2.4; }
h2 { --v: 2; }
h3 { --v: 1.6; }
h4 { --v: 1.4; }
.txt-lg { --v: 1.2; }
/* Size-preserving overrides. The build promotes headings whose archived level
   skipped a rank (the old editor picked h4 for "small bold line", not for
   depth); these carry the original level's scale so only the outline moves.
   No .hs-2: an h2 can never be promoted, only demoted to itself. */
.hs-1 { --v: 2.4; }
.hs-3 { --v: 1.6; }
.hs-4 { --v: 1.4; }
h1, h2, h3, h4, .txt-lg, .fe-b--btn .btn {
  font-size: calc((var(--v) - 1) * (.012 * min(100vh, 900px)) + 1rem);
}
@media (min-width: 768px), (orientation: landscape) {
  h1, h2, h3, h4, .txt-lg, .fe-b--btn .btn {
    font-size: min(
      calc((var(--v) - 1) * 1.2vw + 1rem),
      max(calc((var(--v) - 1) * .012 * var(--max-w) + 1rem), calc(var(--v) * 1rem))
    );
  }
}
h1, h2, h3, h4 {
  font-family: var(--font); font-weight: 500; letter-spacing: -.05em;
  /* Live tightens leading as the heading gets bigger. */
  line-height: calc(1.2em * (1 + (1 - var(--v)) / 25));
  margin: 0 0 .5em; text-wrap: balance;
}
p { margin: 0 0 1em; }
ul, ol { margin: 0 0 1em; padding-left: 1.25em; }
/* Live blockquote: 1.2× body, no indent (the site uses bare blockquotes as
   pull-quote paragraphs). */
blockquote { margin: 0 0 1em; font-size: 1.2em; }
blockquote p { font-size: inherit; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}
.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 1000;
  background: var(--c-black); color: #fff; padding: .75em 1em;
}
.skip-link:focus { left: 0; }

/* Keyboard focus. One ring for every control on the site — the old site shipped
   none, so tabbing through its nav and forms was invisible. :focus-visible
   (not :focus) keeps it off mouse clicks. The ring color is a token so the
   dark and red bands, where a red ring would disappear, can flip it to white. */
:focus-visible {
  outline: 3px solid var(--c-focus, var(--c-red));
  outline-offset: 2px;
  border-radius: 2px;
}
.site-header,
.site-footer,
.mobile-nav,
.band--bright,
.band--black,
.band--black-bold,
.band--dark-bold { --c-focus: #fff; }

/* Rich-text utilities — mapped 1:1 from the live sqsrte-* classes the archive
   actually uses (no right-aligned or small text anywhere on the site). */
.ta-c { text-align: center; }
.tc-lightAccent { color: #fff; }
.tc-black { color: #000; }

/* Buttons — the live shape everywhere. The colors are NOT fixed: live resolves
   a primary button against the section theme it sits in, which is why the
   service-hub buttons on a white band are near-black while the header's
   standalone CTA is red. Per-theme values are set with the band themes below;
   red is the chrome default for buttons that sit outside a band. */
.btn {
  display: inline-block;
  font-family: var(--font); font-weight: 400; font-size: 1rem;
  padding: 1em 1.7em; border: 0; border-radius: var(--btn-radius);
  background: var(--btn-bg, var(--c-red)); color: var(--btn-ink, #fff);
  text-decoration: none; text-align: center;
  cursor: pointer; transition: opacity .15s ease;
}
.btn:hover { opacity: .88; }

/* ==========================================================================
   Header — black, logo centered, nav row beneath, red call button at right
   (mirrors the live site's custom.css header layout).
   ========================================================================== */
.site-header { background: var(--c-black); color: #fff; position: relative; z-index: 500; }
.header-inner { display: flex; flex-direction: column; align-items: center; padding: .9rem var(--gutter) 0; position: relative; }
.brand { display: block; }
.brand img { height: 56px; width: auto; }
.header-cta { display: none; position: absolute; right: var(--gutter); top: 1.1rem; }
.header-cta .btn { font-size: .9rem; padding: .8em 1.4em; }

/* Sticky mobile call bar. .header-cta has nowhere to sit below 1025px — the
   header is already full with the centered logo and nav toggle — so phones
   got no persistent call-to-action at all. This pins one to the viewport
   bottom instead; body reserves the same height so it never covers page
   content, and the safe-area inset keeps it clear of the iOS home indicator. */
.mobile-call-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 400;
  background: var(--c-black);
  padding: .6rem var(--gutter) calc(.6rem + env(safe-area-inset-bottom));
  box-shadow: 0 -2px 10px rgba(0, 0, 0, .25);
}
.mobile-call-bar .btn { display: block; width: 100%; }
body { padding-bottom: calc(4.6rem + env(safe-area-inset-bottom)); }

.main-nav { display: none; width: 100%; }
.main-nav ul { list-style: none; margin: 0; padding: .9rem 0 .7rem; display: flex; justify-content: center; flex-wrap: nowrap; gap: 1.4vw; }
.main-nav > ul > li { position: relative; }
.main-nav > ul > li > a, .main-nav > ul > li > .nav-folder-title {
  color: #fff; text-decoration: none; font-size: 1rem; padding: .3rem .2rem; display: inline-block; white-space: nowrap;
}
.main-nav > ul > li > .nav-folder-title {
  background: none; border: 0; border-radius: 4px; font-family: inherit; line-height: inherit; cursor: pointer;
  display: inline-flex; align-items: center; gap: .45em;
  transition: background-color .2s ease;
}
/* Small triangle, rotates to point up when the panel is open. currentColor
   keeps it in step with the label's own hover/open color change below. */
.main-nav > ul > li > .nav-folder-title::after {
  content: ""; flex-shrink: 0;
  width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform .2s ease;
}
.main-nav > ul > li > .nav-folder-title[aria-expanded="true"]::after { transform: rotate(180deg); }
.main-nav > ul > li > a:hover,
.main-nav > ul > li > .nav-folder-title:hover,
.main-nav > ul > li.is-open > .nav-folder-title { color: var(--c-red); }
/* Open state gets its own surface — button and panel share one gray, flush
   edges and matched corner-rounding, so the pair reads as one connected shape
   ("tab" on top, panel hanging below it) instead of two separate boxes. */
.main-nav > ul > li.is-open > .nav-folder-title {
  background: var(--c-nav-open);
  /* Square off just the bottom, where the button sits flush against the
     panel's top edge — rounding there would put a visible notch in the seam. */
  border-radius: 6px 6px 0 0;
}
/* The live dropdown has no height cap and never scrolls — it opens to its full
   length. Capping it with overflow-y also forces overflow-x to compute to auto
   (CSS spec: a non-visible value on one axis makes the other non-visible),
   which is where the second, horizontal scrollbar came from. */
.main-nav .dropdown {
  display: none; position: absolute; top: 100%; left: 0;
  background: var(--c-nav-open); box-shadow: 0 14px 28px rgba(0,0,0,.28);
  border: 1px solid rgba(255,255,255,.08);
  /* Left edge is square — it's the one flush against the button above (see
     .is-open above). The other three corners are true outer corners. */
  border-radius: 0 8px 8px 8px;
  padding: .4rem 0; min-width: 240px; z-index: 50;
  /* Live inherits nowrap into the panel from `.header-nav-list > div`, which is
     what lets it size to its longest label instead of wrapping. */
  white-space: nowrap;
}
/* Contact sits last and its labels are the longest, so a left-anchored panel
   runs past the right edge on narrower desktops and pushes a horizontal page
   scrollbar. It opens right-anchored instead — the same flip live ships for its
   right-aligned header layout. Corners mirror: square on the right, where it's
   now flush against the button, round everywhere else. */
.main-nav > ul > li:last-child .dropdown { left: auto; right: 0; border-radius: 8px 0 8px 8px; }
.main-nav li.is-open .dropdown { display: block; }
.main-nav .dropdown li a {
  /* 44px minimum pointer target (WCAG 2.2) — the live panel used 12px padding
     on a .92rem line, which lands at 41px. Same 1.2rem inset left and right,
     rather than the button row's cramped .2rem — the button stays tight to fit
     ten items in one row, but the panel is free-floating and reads better with
     real breathing room around its longest label. */
  display: flex; align-items: center; min-height: 44px; padding: 8px 1.2rem; color: #fff; text-decoration: none; font-size: .92rem;
  transition: transform .3s ease, background-color .3s ease;
}
.main-nav .dropdown li a:hover { background: rgba(255,255,255,.06); transform: translateX(8px); }

.nav-toggle {
  background: none; border: none; border-radius: 6px;
  width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; position: absolute; right: var(--gutter); top: 1.2rem;
}
.nav-toggle span, .nav-toggle::before, .nav-toggle::after {
  content: ""; display: block; width: 20px; height: 2px; background: #fff;
  position: relative; transition: transform .2s ease;
}
.nav-toggle::before { position: absolute; transform: translateY(-6px); }
.nav-toggle::after { position: absolute; transform: translateY(6px); }
.nav-toggle[aria-expanded="true"] span { background: transparent; }
.nav-toggle[aria-expanded="true"]::before { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"]::after { transform: rotate(-45deg); }

.mobile-nav { display: none; background: var(--c-black); border-top: 1px solid rgba(255,255,255,.15); }
.mobile-nav.is-open { display: block; }
.mobile-nav ul { list-style: none; margin: 0; padding: .5rem var(--gutter) 1rem; }
.mobile-nav a { color: #fff; text-decoration: none; }
.mobile-nav > ul > li > a, .mobile-nav > ul > li > .nav-group-title {
  display: flex; align-items: center; min-height: 44px; padding: .5rem 0; border-bottom: 1px solid rgba(255,255,255,.15);
}
.mobile-nav > ul > li > .nav-group-title { color: #fff; }
.mobile-nav .dropdown { list-style: none; margin: 0; padding: 0 0 .5rem 1rem; }
.mobile-nav .dropdown a { min-height: 44px; padding: .35rem 0; font-size: .92rem; display: flex; align-items: center; opacity: .85; }

@media (min-width: 1025px) {
  .main-nav { display: block; }
  .header-cta { display: block; }
  /* `.mobile-nav.is-open` outranks a bare `.mobile-nav` selector, so a panel
     left open on a phone would stay on screen after a rotate or resize into
     the desktop layout. Match its specificity here to close it. */
  .nav-toggle, .mobile-nav, .mobile-nav.is-open { display: none; }
  .mobile-call-bar { display: none; }
  body { padding-bottom: 0; }
}

/* ==========================================================================
   Section bands — one class per theme carried over from the old site, colors
   resolved from its site.css. Text, headings, links and buttons all take the
   band's ink.
   ========================================================================== */
.band {
  position: relative;
  display: flex; flex-direction: column; justify-content: center;
}
/* Live section heights: small = 3.3vmax padding + 33vh floor, medium = 6.6vmax
   + 66vh, custom = content-driven. List/gallery sections get neither the
   padding nor the floor — verified against live (testimonial carousel,
   statement banner and before/after reel all measure 0px there); their
   height comes entirely from the carousel/reel's own min-height instead.
   The site uses only these three — no section is set to "large". */
.band--h-small:not(.band--list)  { padding: 3.3vmax 0; }
.band--h-medium:not(.band--list) { padding: 6.6vmax 0; }
/* Live custom-height sections have NO vertical padding (every content-wrapper
   padding rule in the live CSS carries :not(.section-height--custom)) — their
   height is purely the grid rows. This is what lets hero panels that span all
   rows touch the very top/bottom edges of their section. */
.band--h-custom { padding: 0; }
.band--h-small:not(.band--list)  { min-height: 33vh; }
.band--h-medium:not(.band--list) { min-height: 66vh; }

/* Best-practice departure from strict live-fidelity (requested explicitly):
   live really does stack full padding between two consecutive bands even
   when they share a background (verified on the online-form pages), so the
   seam disappears and the combined gap reads as one oversized dead zone.
   Collapse it to one band's worth wherever two bands in a row resolve to the
   same background — white/white-bold/dark-bold are all --c-offwhite,
   black/black-bold are both --c-black — by dropping the second band's top
   padding. The first band in the pair is excluded when it's a background
   photo (its own visual break already does the job) or a list/gallery band
   (it already carries zero padding via the rule above, so there's no real
   double-padding to collapse — the second band's padding is legitimate on
   its own and must be left alone). */
.band--white:not(.band--bg):not(.band--list) + .band--white,
.band--white:not(.band--bg):not(.band--list) + .band--white-bold,
.band--white:not(.band--bg):not(.band--list) + .band--dark-bold,
.band--white-bold:not(.band--bg):not(.band--list) + .band--white,
.band--white-bold:not(.band--bg):not(.band--list) + .band--white-bold,
.band--white-bold:not(.band--bg):not(.band--list) + .band--dark-bold,
.band--dark-bold:not(.band--bg):not(.band--list) + .band--white,
.band--dark-bold:not(.band--bg):not(.band--list) + .band--white-bold,
.band--dark-bold:not(.band--bg):not(.band--list) + .band--dark-bold,
.band--black:not(.band--bg):not(.band--list) + .band--black,
.band--black:not(.band--bg):not(.band--list) + .band--black-bold,
.band--black-bold:not(.band--bg):not(.band--list) + .band--black,
.band--black-bold:not(.band--bg):not(.band--list) + .band--black-bold,
.band--bright:not(.band--bg):not(.band--list) + .band--bright,
.band--bright-inverse:not(.band--bg):not(.band--list) + .band--bright-inverse,
.band--dark:not(.band--bg):not(.band--list) + .band--dark {
  padding-top: 0;
}

/* The eight themes the site's sections actually resolve to. The old theme set
   also defined "light"/"light-bold"; no section on this site uses either. */
.band--white       { background: var(--c-offwhite); color: var(--c-ink); }
.band--white-bold  { background: var(--c-offwhite); color: var(--c-ink); }
.band--white-bold :is(h1,h2,h3,h4) { color: var(--c-red); }
.band--bright      { background: var(--c-red); color: #fff; }
.band--bright-inverse { background: var(--c-white); color: var(--c-red); }
.band--dark        { background: var(--c-teal); color: var(--c-offwhite); }
.band--dark-bold   { background: var(--c-offwhite); color: var(--c-ink); }
.band--black, .band--black-bold { background: var(--c-black); color: var(--c-offwhite); }
.band--black-bold :is(h1,h2,h3,h4) { color: #fff; }

/* Band-aware buttons, resolved from each theme's own
   --primaryButtonBackgroundColor / --primaryButtonTextColor in the live CSS.
   The white theme is the one that isn't red: it draws the near-black button
   behind the service-hub links (darkAccent), which is what makes the hub's
   service buttons read as a dark band above their photos. */
.band--white          { --btn-bg: #1C1C1C; --btn-ink: var(--c-offwhite); }
.band--white-bold     { --btn-bg: var(--c-red); --btn-ink: #fff; }
.band--dark,
.band--dark-bold,
.band--black          { --btn-bg: #fff; --btn-ink: #000; }
.band--black-bold,
.band--bright-inverse { --btn-bg: var(--c-red); --btn-ink: #fff; }
.band--bright         { --btn-bg: #fff; --btn-ink: var(--c-red); }

/* Full-bleed section background photo + optional black overlay, straight from
   the section's data-current-styles (focal point → --bg-y, overlay → --ov). */
.band--bg {
  /* A background image can't carry srcset, so the responsive choice happens
     here: the build emits --bg-sm (≤900px) and --bg-lg (≤1800px) per section
     and the 768px breakpoint below picks one. Only the matching media query's
     declaration resolves, so a phone never fetches the desktop file. */
  background-image: var(--bg-sm);
  background-size: cover;
  background-position: center var(--bg-y, 50%);
  background-repeat: no-repeat;
  isolation: isolate;
}
@media (min-width: 768px) {
  .band--bg { background-image: var(--bg-lg); }
}
.band--bg::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: #000; opacity: var(--ov, 0);
}
.band--bg > * { position: relative; z-index: 1; }

/* ==========================================================================
   Fluid grid replay. Every block carries the grid coordinates measured from
   the old site: --gm (mobile, 8-column) and --gd (desktop, 24-column). The
   track model is the live one: gutter tracks either side (shrunk by the gap,
   since the gap re-adds it), an 11px gap between cells (0 on sections that
   emit --fe-gap:0), cells capped so content tops out at maxPageWidth 3000px,
   and fixed-minimum rows (--rows-m/--rows-d per section) so whitespace is
   reproduced even where no block sits.
   ========================================================================== */
.fe {
  --fe-gap: 11px;
  --gut: minmax(calc(var(--gutter) - var(--fe-gap)), 1fr);
  --cell-max: calc((var(--max-w) - var(--fe-gap) * 7) / 8);
  display: grid;
  grid-template-columns: var(--gut) repeat(8, minmax(0, var(--cell-max))) var(--gut);
  grid-template-rows: var(--row-tpl-m, repeat(var(--rows-m, 1), minmax(var(--row-h), auto)));
  grid-auto-rows: minmax(var(--row-h), auto);
  gap: var(--fe-gap);
  width: 100%;
}
/* Blocks are flex columns with no padding of their own (the live fluid engine
   is the same); panels below carry any inset. --vj-* replays each block's
   vertical alignment inside its area, --z-* its stacking over shape blocks. */
.fe > .fe-b {
  grid-area: var(--gm);
  min-width: 0;
  display: flex; flex-direction: column;
  justify-content: var(--vj-m, flex-start);
  z-index: var(--z-m, auto);
  position: relative;
}
@media (min-width: 768px) {
  .fe {
    --cell-max: calc((var(--max-w) - var(--fe-gap) * 23) / 24);
    grid-template-columns: var(--gut) repeat(24, minmax(0, var(--cell-max))) var(--gut);
    grid-template-rows: var(--row-tpl-d, repeat(var(--rows-d, 1), minmax(var(--row-h), auto)));
  }
  .fe > .fe-b {
    grid-area: var(--gd);
    justify-content: var(--vj-d, var(--vj-m, flex-start));
    z-index: var(--z-d, var(--z-m, auto));
  }
}
.fe-b > :first-child { margin-top: 0; }
.fe-b > :last-child { margin-bottom: 0; }

/* Text-block panel: the card the live site paints behind a text block (the
   hero's near-black glass card, the red call-now card, service-page panels).
   Color/padding/radius come from the block; padding is CSS %, so it scales
   with the block's width exactly as the original did. */
.fe-b--panel {
  background: var(--panel-bg);
  padding: var(--panel-pad, 3%);
  border-radius: var(--panel-radius, 0);
  color: var(--panel-ink, inherit);
}
/* Text on a panel takes the theme's "on background" ink (one color per theme
   in the live CSS); explicit text-color spans (.tc-*) still win, since they
   color the element itself rather than relying on inheritance. */
.band--white .fe-b--panel,
.band--dark .fe-b--panel, .band--black .fe-b--panel { --panel-ink: #000; }
.band--white-bold .fe-b--panel,
.band--dark-bold .fe-b--panel, .band--black-bold .fe-b--panel,
.band--bright .fe-b--panel { --panel-ink: var(--c-red); }
.band--bright-inverse .fe-b--panel { --panel-ink: #fff; }
.fe-b--blur { -webkit-backdrop-filter: blur(15px); backdrop-filter: blur(15px); }
@media (min-width: 768px) {
  .fe > .fe-b.fe-b--sticky { position: sticky; top: 0; }
}

/* Shape blocks: decorative rectangles layered behind content (FAQ cards, the
   grey backdrop behind the homepage's middle service column). The shadow
   approximates the live 145°/2px/3px drop shadow. */
.fe-b--shape { background: var(--shape-bg); border-radius: var(--shape-radius, 0); }
.fe-b--shadow { box-shadow: 1px 2px 3px rgba(0, 0, 0, .22); }

/* Image blocks. Most of the site's photos are NOT stretched: they keep their
   natural aspect ratio at the block's width, never cropping. Stretched ones
   (fe-b--img-fill) fill the grid area edge-to-edge, cropping around the
   stored focal point — that's how the photo collages work. */
.fe-b--img img { width: 100%; height: auto; border-radius: var(--img-radius, 0); }
.fe-b--img-fill { display: block; }
.fe-b--img-fill img { height: 100%; object-fit: cover; object-position: var(--img-pos, 50% 50%); }
/* Desktop: an image shares its row tracks with the text beside it, and the
   track is sized by whichever of the two is taller. In flow the image's
   intrinsic height wins, pushing the tracks well past the copy and leaving
   dead band background above and below it. Live avoids that by taking every
   fluid-engine image out of flow (absolute, inside a zero-height content-fit
   wrapper) so the text alone sizes the track and the image fits itself into
   the result — stretched ones cropping to fill, the rest letterboxing at
   their own aspect ratio. Mobile keeps images in flow: there they stack into
   rows nothing else occupies, so their own height is all the row has to go on. */
@media (min-width: 768px) {
  .fe-b--img { min-height: 0; }
  .fe-b--img img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain; object-position: var(--img-pos, 50% 50%);
  }
  .fe-b--img-fill { overflow: hidden; }
  .fe-b--img-fill img { object-fit: cover; }
}

/* Button blocks: every one of the site's 11 in-page buttons is a stretched,
   centered container on live (sqs-stretched / --center), so the button fills
   its whole grid area rather than sitting at natural height inside it, and the
   label centers in the box. Label size follows the live button tweak (1.2,
   the same multiplier the large rich-text class uses). */
.fe-b--btn { --vj-m: center; }
.fe-b--btn .btn {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  --v: 1.2; line-height: 1.2em;
  /* Trimmed from the standalone button's 1em so a two-line label's natural
     height stays inside its grid area — the label is centred in a stretched
     box, so this padding never shows, it only decides whether the row band
     grows past the live site's. */
  padding: .6em 1.7em;
}

/* Blocks that arrive without coordinates just flow full-width. */
.fe > .fe-b--flow { grid-column: 2 / -2; grid-row: auto; }

/* Classic (ungridded) sections — blog articles — flow as a reading column. */
/* Reading measure. A full-width rich-text block inherits the page's 3000px
   max width, which is ~190 characters a line on a wide monitor — well past the
   point where the eye loses the start of the next line. Capping the paragraph
   (not the block) keeps every grid placement exactly where the live site put
   it and only shortens the lines inside. Narrow blocks are unaffected: their
   column span is already tighter than the cap. */
@media (min-width: 768px) {
  .fe-b p, .fe-b li, .fe-b blockquote { max-width: 68ch; }
  .fe-b p.ta-c, .fe-b blockquote.ta-c { margin-left: auto; margin-right: auto; }

  /* The red CALL TODAY/CALL NOW banner: a short single-line phone CTA, not
     reading prose, so the cap above only forces it into an unwanted second
     line. Its grid area is already nearly full-bleed, so sizing to content
     keeps it on one line wherever there's room and wraps normally (never
     overflows) if there isn't. */
  .band--bright .txt-lg { max-width: none; }
}

.article-flow { width: 100%; max-width: 740px; margin: 0 auto; padding: 0 var(--gutter); }
.article-flow img { margin: 0 0 1em; }

/* ==========================================================================
   Items-list bands — the live site's banner slideshows: the testimonial
   carousel (quote on a white card, round arrows below) and the statement
   banner (bare text on the band, arrows at the sides). One slide fills the
   track; arrows scroll it, and it also scrolls/snaps by hand with no JS.
   Arrow and card colors are the live per-theme list variables.
   ========================================================================== */
.bslide {
  --arrow-bg: hsla(0, 88.24%, 50%, 1);
  --arrow-ink: #fff;
  --card-bg: #fff;
  --card-ink: #000;
  position: relative; width: 100%;
}
.band--bright .bslide { --arrow-bg: #fff; --arrow-ink: var(--c-red); }
.band--dark .bslide, .band--black .bslide, .band--black-bold .bslide { --arrow-bg: #fff; --arrow-ink: #000; }
.bslide-track {
  display: grid; grid-auto-flow: column; grid-auto-columns: 100%;
  overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.bslide-track::-webkit-scrollbar { display: none; }
.bslide-slide {
  scroll-snap-align: center;
  min-height: var(--slide-min-h, 50vh);
  display: flex; align-items: center; justify-content: center;
  padding: 2vmax var(--gutter);
}
.bslide-content { width: var(--slide-w, 90%); max-width: var(--content-w); text-align: center; }
.bslide--card .bslide-content { background: var(--card-bg); color: var(--card-ink); padding: clamp(1.4rem, 4vw, 3rem) 4%; }
.bslide-quote { margin: 0; font-size: inherit; }
/* Live slideshow title size: 1.6rem, regular weight. */
.bslide-title { font-size: clamp(1.15rem, 1rem + .9vw, 1.6rem); line-height: 1.5; font-weight: 400; margin: 0; }
.bslide-attrib { font-size: .95rem; margin: 1.3rem 0 0; }
.bslide-nav { display: flex; gap: 12px; justify-content: center; padding: .5rem 0 1.4rem; }
.bslide-arrow {
  width: 48px; height: 48px; flex: none;
  border: 0; border-radius: 50%; background: none; padding: 0; cursor: pointer;
  position: relative; color: var(--arrow-ink);
  display: grid; place-items: center;
}
/* The live arrow renders its circle at 70% opacity, full on hover. */
.bslide-arrow::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: var(--arrow-bg); opacity: .7; transition: opacity .3s ease;
}
.bslide-arrow:hover::before, .bslide-arrow:focus-visible::before { opacity: 1; }
.bslide-arrow svg { position: relative; width: 20px; height: 20px; }
@media (min-width: 768px) {
  .bslide-arrow { width: 60px; height: 60px; }
  .bslide-arrow svg { width: 24px; height: 24px; }
}
/* Side-arrow variant (the statement banner): arrows overlay the slide edges,
   vertically centered. */
.bslide--nav-side .bslide-nav {
  position: absolute; inset: 0; padding: 0 var(--gutter);
  justify-content: space-between; align-items: center;
  pointer-events: none;
}
.bslide--nav-side .bslide-arrow { pointer-events: auto; }
.bslide-track:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }

/* ==========================================================================
   Gallery band — the live "reel": uncropped natural-aspect images in a strip
   of fixed height, square white arrows at the strip's edges.
   ========================================================================== */
.reel { position: relative; width: 100%; }
.reel-track {
  display: flex; height: var(--reel-h, 50vh);
  overflow-x: auto; scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.reel-track::-webkit-scrollbar { display: none; }
.reel-track img { height: 100%; width: auto; max-width: none; flex: none; scroll-snap-align: start; }
.reel-track:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
.reel-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 48px; height: 48px; border: 0; padding: 0; cursor: pointer;
  background: #fff; color: #111;
  display: grid; place-items: center;
  opacity: .92; transition: opacity .15s ease;
}
.reel-arrow:hover, .reel-arrow:focus-visible { opacity: 1; }
.reel-arrow svg { width: 20px; height: 20px; }
.reel-arrow[data-dir="-1"] { left: 0; }
.reel-arrow[data-dir="1"] { right: 0; }


/* ==========================================================================
   Accordion (FAQ) — details/summary, crawlable with no JS.
   ========================================================================== */
.faq-list { border-top: 1px solid currentColor; width: 100%; }
.faq-item { border-bottom: 1px solid currentColor; }
.faq-item summary {
  cursor: pointer; padding: 1.05rem 0; font-weight: 700; list-style: none;
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: "+"; font-size: 1.4rem; flex-shrink: 0; }
.faq-item[open] summary::after { content: "\2212"; }
.faq-item p { padding-bottom: 1.05rem; }

/* Map link card (replaces the embedded Google map). */
.map-card {
  display: block; border: 2px solid currentColor; border-radius: var(--btn-radius);
  padding: 1.1rem 1.3rem; text-decoration: none; font-weight: 700;
  height: 100%;
}
.map-card:hover { background: rgba(0,0,0,.05); }
.map-card span { display: block; font-weight: 400; font-size: .9rem; margin-top: .25rem; }

/* ==========================================================================
   Blog index (composed — the one page the old platform generated itself) and
   the post header the templates add above each article.
   ========================================================================== */
.post-list { width: 100%; max-width: 1100px; margin: 0 auto; padding: 0 var(--gutter); display: grid; gap: 2.75rem; }
.post-row { display: grid; gap: 1.25rem; align-items: center; }
.post-row-img img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.post-row-body h2 { margin-bottom: .4em; }
.post-row-body h2 a { text-decoration: none; }
.post-row-body h2 a:hover, .read-more:hover { color: var(--c-red); }
/* Standalone call-to-action link, not a link inside a sentence — so it gets a
   real 44px target rather than the exemption inline links get. */
.read-more { font-weight: 700; text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; }
@media (min-width: 768px) {
  .post-row { grid-template-columns: 1fr 1fr; gap: 2rem; }
  .post-row--flip .post-row-img { order: 2; }
}
.post-meta { font-size: .85rem; opacity: .7; margin-bottom: .4em; }
.post-hero { margin-top: 1.25rem; }

/* ==========================================================================
   Forms (shared by the 4 rebuilt forms; unchanged behavior)
   ========================================================================== */
.form-grid { display: grid; gap: 1rem; }
.form-field { display: flex; flex-direction: column; gap: .35rem; }
.form-field label { font-weight: 700; font-size: .9rem; }
/* Form controls carry an intrinsic width — a text input defaults to 20
   characters, a <select> to its longest option, a file input to its button plus
   filename — and a grid/flex track will not shrink below an item's min-content.
   On a 375px screen the careers position picker (396px) and the contact file
   upload (392px) were forcing the whole form wider than the viewport. Zeroing
   the automatic minimum lets the tracks shrink; width:100% keeps the controls
   filling the row. */
.form-grid, .form-group, .form-row, .form-field { min-width: 0; }
.form-field input, .form-field select, .form-field textarea {
  padding: .75em .9em; border-radius: 4px; border: 1px solid var(--c-line);
  font: inherit; color: var(--c-ink); background: #fff;
  width: 100%; min-width: 0; max-width: 100%;
}
.form-field input:focus, .form-field select:focus, .form-field textarea:focus { outline: 2px solid var(--c-red); outline-offset: 1px; }
.form-group { border: 1px solid var(--c-line); border-radius: 4px; padding: 1rem 1.1rem 1.15rem; margin: 0; display: grid; gap: .75rem; background: #fff; }
.form-group > legend { font-weight: 700; font-size: .9rem; padding: 0 .35rem; }
.form-group .form-field label { font-weight: 500; }
.form-row { display: grid; gap: .75rem; grid-template-columns: 1fr 1fr; }
/* Section dividers within a form (the careers application's 8 groups). */
.form-section { margin-top: 1.1rem; }
.form-section-title { border-bottom: 2px solid var(--c-red); padding-bottom: .4rem; margin: 0; font-size: 1.15rem; }
.form-section-desc { font-size: .88rem; opacity: .85; margin: .5rem 0 0; }
.opt-list { display: flex; flex-wrap: wrap; gap: .25rem .9rem; }
/* A 17px radio is a 17px target. The row is the target instead: the label is
   bound to the input with `for`, so making the whole 44px-tall row clickable
   (WCAG 2.2 target size) costs no markup and no layout — the options were
   already on their own lines. */
.opt { display: flex; align-items: center; gap: .5rem; min-height: 44px; }
.opt input { width: 1.25rem; height: 1.25rem; accent-color: var(--c-red); flex: none; cursor: pointer; }
/* The label is bound to the input and fills the rest of the row, so the real
   activation target is the full row height even though the native radio glyph
   itself is only 20px. */
.opt label { font-weight: 500; font-size: .92rem; margin: 0; cursor: pointer; padding: .55rem 0; flex: 1 1 auto; }
.form-check { flex-direction: row; align-items: center; gap: .5rem; min-height: 44px; }
.form-check input { width: 1.25rem; height: 1.25rem; accent-color: var(--c-red); flex: none; cursor: pointer; }
.form-check label { font-weight: 500; cursor: pointer; padding: .55rem 0; }
.req { color: var(--c-red); }
.field-hint { font-size: .8rem; opacity: .75; margin: .1rem 0 0; }
/* Spam trap: must stay reachable to bots but never to real users or AT. */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
[data-show-when][hidden] { display: none; }
.form-note { font-size: .82rem; opacity: .75; }
.form-status { padding: .9rem 1.1rem; border-radius: 4px; font-weight: 600; display: none; }
.form-status.is-visible { display: block; }
.form-status.is-success { background: #EAF7EE; color: #17663A; }
.form-status.is-error { background: #FDECEC; color: #99231C; }

/* Related-pages strip — the last thing in <main>, above the footer. Deliberately
   quiet: page furniture at .9rem on the site ground, not another band. */
.related-links {
  padding: 1.6rem var(--gutter) 2rem;
  border-top: 1px solid var(--c-line);
  background: var(--c-offwhite);
}
.related-links ul {
  list-style: none; margin: 0 auto; padding: 0;
  max-width: var(--content-w);
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: .25rem 1.75rem;
}
.related-links a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: .9rem; font-weight: 600; text-decoration: none;
  border-bottom: 2px solid transparent;
}
.related-links a:hover { color: var(--c-red); border-bottom-color: var(--c-red); }

/* End-of-post next steps: the services the article is really about, the
   adjacent articles, and (only where the post is place-specific) the local
   area page. Grouped and labelled rather than one undifferentiated row, so a
   reader who wants to book and a reader who wants to keep reading each have an
   obvious next click. Columns collapse to a single stack on phones. */
.post-next { border-top: 1px solid var(--c-line); }
.post-next__inner {
  display: grid; gap: 1.75rem 3rem;
}
@media (min-width: 700px) {
  .post-next__inner { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
}
.post-next__group h3 {
  font-size: .8rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; opacity: .7;
  margin: 0 0 .6rem;
}
.post-next__group ul { list-style: none; margin: 0; padding: 0; }
.post-next__group li + li { margin-top: .15rem; }
.post-next__group a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: .95rem; font-weight: 600; text-decoration: none;
  border-bottom: 2px solid transparent;
}
.post-next__group a:hover { color: var(--c-red); border-bottom-color: var(--c-red); }

/* Described directory — the hub pages' child list, below the replayed grid.
   The live hubs show each service as a button over a photo with no words of
   its own; this is the same list with each page's description attached, so a
   visitor (or a retriever) can tell the services apart without opening all
   thirteen. Two columns from tablet up; the term and its description stay in
   one .dir-item so a column break never splits them. */
.directory { border-top: 1px solid var(--c-line); }
.dir-wrap { width: 100%; max-width: var(--content-w); margin: 0 auto; padding: 0 var(--gutter); }
.dir-intro { max-width: 68ch; }
.dir-list { margin: 1.5rem 0 0; display: grid; gap: 1.5rem 3rem; }
@media (min-width: 768px) {
  .dir-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.dir-item dt { margin: 0 0 .3rem; }
.dir-item dt a {
  display: inline-flex; align-items: center; min-height: 44px;
  font-weight: 700; text-decoration: none;
  border-bottom: 2px solid transparent;
}
.dir-item dt a:hover { color: var(--c-red); border-bottom-color: var(--c-red); }
.dir-item dd { margin: 0; font-size: .95rem; max-width: 68ch; }

/* Extended coverage — the towns we serve that don't have their own page, on
   the service areas hub only. Denser than .dir-list: each county is one term
   with a comma-run of towns, so 133 names stay scannable without becoming 133
   list items. Three columns on wide screens, since the runs are short. */
.coverage { border-top: 1px solid var(--c-line); }
.cov-list { margin: 1.5rem 0 0; display: grid; gap: 1.5rem 3rem; }
@media (min-width: 768px) {
  .cov-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1200px) {
  .cov-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.cov-group dt {
  margin: 0 0 .4rem;
  font-weight: 700;
  border-bottom: 2px solid var(--c-red);
  padding-bottom: .3rem;
}
.cov-group dd { margin: 0; font-size: .9rem; line-height: 1.7; }
/* The ZIP rides along with its town for anyone scanning for their own, but it
   is reference data, not the name — so it recedes rather than competes. #595959
   on the --c-offwhite band is 7.0:1, comfortably past AA at this size. */
.cov-zip { color: #595959; font-variant-numeric: tabular-nums; }

/* "Often booked together" — cross-links between sibling service pages, on the
   25 service detail pages. Two columns at most: each row is a link plus a
   clause explaining the pairing, so the line needs width to stay readable —
   the three-column treatment .cov-list uses would break the clauses up. */
.svc-related { border-top: 1px solid var(--c-line); }
.svc-related-list {
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 1rem 3rem;
}
@media (min-width: 900px) {
  .svc-related-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* The link carries the weight and the reason recedes, so the eye lands on the
   service names when scanning and only reads the clauses when one matches. */
.svc-related-list li {
  font-size: .95rem;
  line-height: 1.6;
  color: #595959;
  border-left: 2px solid var(--c-line);
  padding-left: 1rem;
}
.svc-related-list a {
  font-weight: 700;
  color: var(--c-ink);
  border-bottom: 1px solid transparent;
}
.svc-related-list a:hover,
.svc-related-list a:focus-visible { color: var(--c-red); border-bottom-color: var(--c-red); }

/* Trust-badge strip — certifications/memberships, repeated on service and
   contact pages so About is no longer the only place a visitor sees them. */
.badge-band { border-top: 1px solid var(--c-line); }
.badge-strip {
  list-style: none; margin: 1.5rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 2rem 2.5rem;
}
.badge-strip img { max-height: 64px; width: auto; }

/* ==========================================================================
   Gallery — before/after cards. No aspect-ratio crop: these are split
   before/after composites, and cropping could cut off half the comparison.
   ========================================================================== */
.gallery-grid {
  display: grid; gap: 1.75rem; margin: 1.5rem 0 0;
  grid-template-columns: 1fr;
}
@media (min-width: 700px) {
  .gallery-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1100px) {
  .gallery-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.gallery-card {
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--c-line); border-radius: 8px; overflow: hidden;
  transition: box-shadow .15s ease, transform .15s ease;
}
.gallery-card:hover { box-shadow: 0 6px 18px rgba(0, 0, 0, .12); transform: translateY(-2px); }
.gallery-card img { width: 100%; height: auto; display: block; }
.gallery-cap { display: block; padding: .8rem 1rem; font-weight: 600; font-size: .92rem; }
.gallery-card:hover .gallery-cap { color: var(--c-red); }

/* ==========================================================================
   Footer — rendered from the live site's own footer section (black-bold);
   the band brings its own padding, the wrapper only hosts the social row.
   ========================================================================== */
.site-footer { background: var(--c-black); color: var(--c-offwhite); }
.site-footer a { color: var(--c-offwhite); }
.footer-social { text-align: center; padding: 0 var(--gutter) 2rem; background: var(--c-black); }
.footer-social a { font-weight: 700; text-decoration: none; border: 1px solid rgba(255,255,255,.4); padding: .5em 1.1em; border-radius: var(--btn-radius); display: inline-flex; align-items: center; min-height: 44px; }
.footer-social a:hover { border-color: #fff; }

/* ==========================================================================
   Utility
   ========================================================================== */
address { font-style: normal; margin: 0 0 .75rem; }
@media (max-width: 640px) {
  .form-row { grid-template-columns: 1fr; }
  .brand img { height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition: none !important; }
}
