/* ==========================================================================
   Inside Out Flooring & Restoration. Brand system
   --------------------------------------------------------------------------
   The single source of truth for colour, type, spacing, shape and motion.

   Two layers live in this file:
     1. TOKENS:   custom properties under :root. Never hardcode a value that
                   a token already covers.
     2. COMPONENTS: reusable classes built entirely from those tokens.

   Load order: normalize.css -> brand.css -> nav.css. Tokens must be defined
   before anything consumes them.

   Section 2.0 carries the handful of element-level rules the pages rely on.
   Everything else is class-scoped: an element takes a brand class or it is
   left alone. The two Webflow stylesheets the site was exported with are gone,
   and nothing here exists to counter them any more.

   See BRAND.md for the written guideline and /brand for the live showcase.
   ========================================================================== */


/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {

  /* ------------------------------------------------------------------------
     1.1 Colour: brand teal
     A ten-step ramp on hue 187. Steps 400/500/700/900 are the colours the
     site already used; the rest fill in the gaps so tints and shades no
     longer have to be invented per component.
     ---------------------------------------------------------------------- */

  --teal-50:  #f3f6f6;
  --teal-100: #e2e8e9;
  --teal-200: #bfcdcf;
  --teal-300: #97acaf;
  --teal-400: #71898d;  /* primary surface green */
  --teal-500: #516b6f;  /* secondary surface green */
  --teal-600: #3d5457;
  --teal-700: #2c484d;  /* borders on teal surfaces */
  --teal-800: #1a393d;
  --teal-900: #06373c;  /* deepest, for form submit, high-contrast panels */

  /* ------------------------------------------------------------------------
     1.2 Colour: accent orange
     --orange-500 is the brand accent and is used for anything decorative:
     underlines, icons, rules, small marks.

     White text on --orange-500 measures 2.96:1, which fails WCAG AA. So any
     time orange carries white text, buttons above all, use --orange-600
     (4.51:1). Both are the brand orange; they just have different jobs.
     ---------------------------------------------------------------------- */

  --orange-100: #fdece3;
  --orange-300: #fba077;
  --orange-500: #f9692b;  /* decorative accent only */
  --orange-600: #d54406;  /* accessible: safe under white text */
  --orange-700: #ab3605;  /* pressed / active */

  /* ------------------------------------------------------------------------
     1.3 Colour: ink (text)
     ---------------------------------------------------------------------- */

  --ink-900: #181818;  /* headings, strongest text */
  --ink-700: #23262b;  /* body copy, nav links */
  --ink-500: #5d646e;  /* secondary and supporting text */
  --ink-300: #9aa1a9;  /* placeholders, disabled */
  --ink-100: #d5d8dc;  /* dividers on dark surfaces */

  /* ------------------------------------------------------------------------
     1.4 Colour: surfaces and lines
     The three near-identical creams and three near-identical borders that
     had accumulated are consolidated into one of each.
     ---------------------------------------------------------------------- */

  --surface-white: #ffffff;
  --surface-cream: #fbf6f2;  /* hover washes, soft panels */
  --surface-mist:  #e6e8e9;  /* page-level alternate background */
  /* teal-500, not teal-400: the band has to carry 16px white body copy, and
     teal-400 only reaches 3.71:1 against white. See §2.1. */
  --surface-teal:  var(--teal-500);
  --surface-ink:   var(--ink-900);

  --line:        #e8e3dd;  /* default hairline */
  --line-strong: var(--teal-700);

  /* ------------------------------------------------------------------------
     1.5 Colour: semantic aliases
     Components reference these, not the raw ramp. Changing a brand decision
     then means editing one line here.
     ---------------------------------------------------------------------- */

  --color-text:          var(--ink-700);
  --color-text-strong:   var(--ink-900);
  --color-text-muted:    var(--ink-500);
  --color-text-inverse:  var(--surface-white);
  --color-heading:       var(--ink-900);

  --color-accent:        var(--orange-500);
  --color-accent-strong: var(--orange-600);

  --color-link:          var(--ink-700);
  --color-link-hover:    var(--orange-600);
  --color-focus:         var(--orange-600);

  --color-border:        var(--line);
  --color-success:       #2f7d34;
  --color-error:         #b3261e;

  /* ------------------------------------------------------------------------
     1.6 Typography: families
     Vollkorn carries the whole site. Bradley Hand is a display accent for
     short headline phrases only, never for body copy or UI.
     ---------------------------------------------------------------------- */

  /* Marcellus SC, set on client instruction for both roles.
     It ships a single weight (400) and no italic, so --weight-bold and any
     <em> are synthesised by the browser rather than drawn. See BRAND.md §4. */
  --font-body:    "Marcellus SC", Georgia, "Times New Roman", serif;
  --font-display: "Bradley Hand", "Segoe Script", cursive;
  /* Headings resolve to the body face. Kept as its own token so the two roles
     can be split again without touching a single component. */
  --font-heading: var(--font-body);

  /* Marcellus SC has one real weight, 400. Everything bold below is therefore
     drawn by the browser smearing the regular, not by a designed bold. Kept at
     700 so emphasis is still visible; swap the body face to regain a real one. */
  --weight-regular: 400;
  --weight-bold:    700;

  /* ------------------------------------------------------------------------
     1.7 Typography: scale
     A single ladder in rem. The two largest steps are fluid so headlines
     shrink on small screens without a stack of media queries.
     ---------------------------------------------------------------------- */

  --text-2xs:  0.75rem;    /* 12px, legal small print */
  --text-xs:   0.8125rem;  /* 13px, button labels, eyebrows */
  --text-sm:   0.875rem;   /* 14px, captions, footer links */
  --text-base: 1rem;       /* 16px, body copy */
  --text-md:   1.125rem;   /* 18px, lead paragraphs, quotes */
  --text-lg:   1.25rem;    /* 20px, h6, pills */
  --text-xl:   1.5rem;     /* 24px, h5 */
  --text-2xl:  1.875rem;   /* 30px, h4 */
  --text-3xl:  2.25rem;    /* 36px, h3 */
  --text-4xl:  clamp(1.875rem, 4.5vw, 3rem);  /* 30–48px, h2 */
  /* The lower bound of each clamp is the phone size. At 40px and 44px a
     headline got only two or three words per line on a 375px screen, and
     "Gloucestershire" came within a few pixels of the viewport edge. */
  --text-5xl:  clamp(2rem, 5vw, 4rem);        /* 32–64px, section headline */
  --text-display: clamp(2.25rem, 7vw, 5rem);  /* 36–80px, hero h1 */

  /* ------------------------------------------------------------------------
     1.8 Typography: leading and tracking
     Tracking is in em so it scales with the type size. Fixed px tracking
     (the old 4px on both a 100px hero and a 32px heading) does not.
     ---------------------------------------------------------------------- */

  --leading-tight:   1.1;   /* display and hero */
  --leading-snug:    1.25;  /* headings */
  --leading-normal:  1.5;   /* UI text */
  --leading-relaxed: 1.7;   /* body copy and long-form prose */

  --tracking-tight:  -0.01em;
  --tracking-normal: 0;
  --tracking-wide:   0.04em;
  --tracking-wider:  0.08em;
  --tracking-widest: 0.14em;  /* uppercase eyebrows and button labels */

  /* Measure: long-form copy should not exceed roughly 70 characters.
     The number looks too small for the job, and it is not. `ch` is the width of
     the digit zero, which in Marcellus SC is 12.9px against an average
     lowercase letter of about 7.5px, so a `ch` here buys roughly 1.7
     characters. At the 68ch this was set to, the legal pages ran 116 characters
     to the line and the section leads 140, against a typographic maximum of
     about 75. Measured on the privacy policy, 42ch gives 71. Re-measure this if
     the typeface ever changes: the two are not independent. */
  --measure: 42ch;

  /* ------------------------------------------------------------------------
     1.9 Spacing: a strict 8pt grid (with a 4px half-step)
     Every margin, padding and gap comes from this scale. No arbitrary values.
     ---------------------------------------------------------------------- */

  --space-1:  0.25rem;  /*   4px */
  --space-2:  0.5rem;   /*   8px */
  --space-3:  0.75rem;  /*  12px */
  --space-4:  1rem;     /*  16px */
  --space-5:  1.5rem;   /*  24px */
  --space-6:  2rem;     /*  32px */
  --space-7:  2.5rem;   /*  40px */
  --space-8:  3rem;     /*  48px */
  --space-9:  4rem;     /*  64px */
  --space-10: 5rem;     /*  80px */
  --space-11: 6rem;     /*  96px */
  --space-12: 8rem;     /* 128px */

  /* Vertical rhythm for page sections, and the page's side gutter. */
  --section-y:       clamp(3rem, 8vw, 6rem);   /* 48–96px */
  --section-y-tight: clamp(2rem, 5vw, 3.5rem); /* 32–56px */
  --gutter:          clamp(1rem, 4vw, 1.5rem); /* 16–24px */
  /* How far a band rides up over the one before it, so a row of tiles can
     straddle the join. 144px, on the 8pt grid. */
  --overlap:         9rem;

  /* ------------------------------------------------------------------------
     1.10 Layout: container widths
     Four widths, down from the five that previously disagreed on one page.
     Everything on a page should share an edge with one of these.
     ---------------------------------------------------------------------- */

  --container-xl: 1240px;  /* header, hero, full-bleed feature rows */
  --container-lg: 1080px;  /* standard content */
  --container-md: 940px;   /* footer, narrow feature rows */
  --container-sm: 720px;   /* long-form prose, legal pages */

  /* ------------------------------------------------------------------------
     1.11 Shape
     ---------------------------------------------------------------------- */

  --radius-sm:     6px;
  --radius-md:     10px;   /* dropdown panels, popovers */
  --radius-lg:     14px;   /* large soft surfaces */
  --radius-xl:     20px;
  --radius-pill:   999px;
  --radius-circle: 50%;

  /* Buttons, inputs and cards read as crisp and squared-off rather than soft.
     They get their own tokens so this can be tuned without also reshaping
     dropdown panels and popovers, which stay softer. */
  --radius-button: 3px;
  --radius-input:  3px;
  --radius-card:   4px;

  --border-hairline: 1px;
  --border-thick:    2px;

  /* ------------------------------------------------------------------------
     1.12 Elevation
     One shadow family, warm black to match the site's warm neutrals.
     ---------------------------------------------------------------------- */

  --shadow-sm: 0 2px 8px rgba(24, 24, 24, .06);
  --shadow-md: 0 6px 24px rgba(24, 24, 24, .09);
  --shadow-lg: 0 18px 44px rgba(24, 24, 24, .13);
  --shadow-xl: 0 28px 64px rgba(24, 24, 24, .18);

  /* ------------------------------------------------------------------------
     1.13 Motion
     ---------------------------------------------------------------------- */

  --ease:      cubic-bezier(.4, 0, .2, 1);
  --ease-out:  cubic-bezier(0, 0, .2, 1);
  --duration-fast: 150ms;
  --duration:      250ms;
  --duration-slow: 400ms;

  /* ------------------------------------------------------------------------
     1.14 Layering
     ---------------------------------------------------------------------- */

  --z-base:     1;
  --z-nav:      1000;
  --z-dropdown: 1010;
  --z-overlay:  1100;
  --z-modal:    1200;
  --z-toast:    1300;
}

/* --------------------------------------------------------------------------
   Breakpoints
   Media queries cannot read custom properties, so these are fixed and
   documented here. Use these four and no others.

     --  991px   tablet and below (the nav collapses to a burger here)
     --  767px   mobile landscape and below
     --  479px   mobile portrait and below
     -- 1240px   the point at which the widest container stops growing

   Do not introduce 768px or 500px queries; they overlap the ladder above.
   -------------------------------------------------------------------------- */


/* --------------------------------------------------------------------------
   Display font
   One family, one file. The previous declaration listed a second .woff as an
   alternate source for the same family and format, so it could never render.
   font-display: swap keeps text visible while the 279 KB face downloads.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Bradley Hand";
  src: url("/css/fonts/BradleyHandITCTT-Bold.woff") format("woff");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}


/* ==========================================================================
   2. COMPONENTS
   Built only from the tokens above.
   ========================================================================== */

/* --------------------------------------------------------------------------
   2.0 Base
   Every component here sizes with width: 100% alongside its own padding, so
   border-box is a hard requirement rather than a preference.

   The other rules in this section are what the pages used to take from the
   Webflow base sheet without anyone deciding to: a page background, a cap on
   image width, and zero margins on the type roles so that a parent (.stack,
   .prose, a grid gap) owns every gap. See BRAND.md section 5.
   -------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

/* The cascade starts from the brand, so the type roles override sizes rather
   than repair a font. Any text outside a .t-* role still reads as body copy.

   The page ground is mist, not white. Every band that wants another colour
   says so (.brand-section--cream, --teal, --white), and a .brand-section with
   no modifier shows the ground, which is how the content pages alternate
   cream and mist. The Webflow sheet used to supply this through a .body
   class; it is a brand decision, so it lives here. */
body {
  margin: 0;
  background-color: var(--surface-mist);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

/* Never wider than its box. Component images set their own display and
   aspect-ratio; this is the guard for any image that has not taken a class. */
img {
  max-width: 100%;
  height: auto;
  vertical-align: middle;
}

/* The user agent sets address in italic. Marcellus SC has no italic, so the
   browser slants the regular, which is exactly the synthesis BRAND.md says to
   avoid leaning on. */
address { font-style: inherit; }

/* A guard against sideways scroll. `overflow-x: hidden`, which the export used,
   makes the body its own scroll container: the scrollport is then the body
   rather than the viewport, and the window stops emitting scroll events, so
   the header's condense-on-scroll listener was attached and correct and simply
   never called. `clip` blocks the sideways overflow exactly the same way
   without creating a scroll container. */
body { overflow-x: clip; }

/* --------------------------------------------------------------------------
   2.1 Layout primitives
   Named with a brand- prefix. The names were chosen while the Webflow sheets
   were still loaded and .section and .container were both taken; they have
   stayed, because renaming layout primitives across thirteen pages buys
   nothing.
   -------------------------------------------------------------------------- */

.brand-container {
  width: 100%;
  max-width: var(--container-lg);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.brand-container--xl { max-width: var(--container-xl); }
.brand-container--md { max-width: var(--container-md); }
.brand-container--sm { max-width: var(--container-sm); }

/* The brand face is set here as well as on body. Several roles (.t-small,
   .section-head__lead, .stat__label and others) only declare size and colour,
   so this is what keeps them on the brand face should a page ever set another
   family on body, which the Webflow pages did. */
.brand-section,
.brand-container {
  font-family: var(--font-body);
}

.brand-section {
  padding-block: var(--section-y);
}

.brand-section--tight { padding-block: var(--section-y-tight); }

/* Drops the closing padding so the last row of content finishes flush with the
   section edge. Needed when the following band is pulled up over this one:
   any padding here would eat the overlap instead of the content. */
.brand-section--flush-bottom { padding-bottom: 0; }

/* Rides up over the section before it, so content in that section marked
   .overlap-layer straddles the join. The top padding carries the overlap plus
   a normal gap, so this band's own content still clears whatever is hanging
   into it. Pair with .brand-section--flush-bottom on the section above. */
.brand-section--overlap-prev {
  margin-top: calc(var(--overlap) * -1);
  padding-top: calc(var(--overlap) + var(--section-y-tight));
}

/* Lifts content into its own layer so a following band pulled up underneath it
   passes behind rather than over. The legacy markup got this by accident, from
   a position: relative that was set for a different reason. */
.overlap-layer {
  position: relative;
  z-index: var(--z-base);
}
/* The page ground is mist (see 2.0), so a band that wants to be white has to
   say so. Content pages alternate cream and mist and never need this; a page
   made of one section does. */
.brand-section--white { background-color: var(--surface-white); }
.brand-section--cream { background-color: var(--surface-cream); }
.brand-section--mist  { background-color: var(--surface-mist); }

/* The teal band carries all of its text in white.
   This uses teal-500, not the teal-400 the legacy pages use. On teal-400
   white body copy is 3.71:1 and fails AA, which forced an ugly split where
   headings were white and paragraphs black. One step darker is visually the
   same band and puts white body copy at 5.70:1, so the whole section reads as
   one piece. Use --teal-pale only where every piece of text is 24px or more. */
.brand-section--teal {
  background-color: var(--surface-teal);
  color: var(--color-text-inverse);
}

.brand-section--teal .t-display,
.brand-section--teal .t-h1, .brand-section--teal .t-h2,
.brand-section--teal .t-h3, .brand-section--teal .t-h4,
.brand-section--teal .t-h5, .brand-section--teal .t-h6,
.brand-section--teal .t-body,
.brand-section--teal .t-lead,
.brand-section--teal .prose,
.brand-section--teal .tick-list > li,
.brand-section--teal .area-list > li,
.brand-section--teal .section-head__title,
.brand-section--teal .section-head__lead { color: var(--color-text-inverse); }

.brand-section--teal .t-small,
.brand-section--teal .t-fine,
.brand-section--teal .t-muted { color: rgba(255, 255, 255, .85); }

/* The original lighter band, kept for large-text-only use. */
.brand-section--teal-pale { background-color: var(--teal-400); }

.brand-section--ink {
  background-color: var(--surface-ink);
  color: var(--color-text-inverse);
}

/* Vertical rhythm without per-element margins: the parent owns the spacing. */
.stack       > * + * { margin-top: var(--space-4); }
.stack--sm   > * + * { margin-top: var(--space-2); }
.stack--lg   > * + * { margin-top: var(--space-6); }
.stack--xl   > * + * { margin-top: var(--space-8); }

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

/* A row of tags is a list, so the markup says so. Flex does not suppress the
   marker on its own, which left a bullet floating beside the first item on
   each wrapped line. */
ul.cluster,
ol.cluster {
  margin: 0;
  padding: 0;
  list-style: none;
}

.cluster--center { justify-content: center; }

/* Page heroes centre their copy but the CTA row is a flex cluster, which does
   not inherit text-align. Without this the Book consultation and Call buttons
   sat left-aligned under a centred headline. */
.page-hero .cluster { justify-content: center; }

.grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-5);
}

/* Three fixed columns, for a set of exactly six or nine items. `auto-fit` gave
   the six sector tiles four columns on a wide screen and left the second row
   two-thirds empty, and it makes photographs smaller than they need to be.
   Falls back to the auto behaviour below the tablet breakpoint. */
.grid-auto--thirds { grid-template-columns: repeat(3, 1fr); }

@media screen and (max-width: 991px) {
  .grid-auto--thirds { grid-template-columns: repeat(2, 1fr); }
}

@media screen and (max-width: 767px) {
  .grid-auto--thirds { grid-template-columns: 1fr; }
}

/* A grid of things that is genuinely a list keeps its list semantics without
   showing markers. */
ul.grid-auto, ol.grid-auto { margin: 0; padding: 0; list-style: none; }

/* The list item is the grid cell, so the card inside it has to be told to fill
   that cell or a short card sits at the top of a tall row. */
ul.grid-auto > li, ol.grid-auto > li { display: grid; }

/* --------------------------------------------------------------------------
   2.2 Typography
   Role classes rather than element selectors, so they can be applied to
   whichever heading level the document outline actually needs. Never pick a
   heading level for its size. Pick it for the outline, then apply a role.
   -------------------------------------------------------------------------- */

.t-display,
.t-h1, .t-h2, .t-h3, .t-h4, .t-h5, .t-h6 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--weight-regular);
  color: var(--color-heading);
}

.t-display {
  font-size: var(--text-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.t-h1 {
  font-size: var(--text-5xl);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.t-h2 {
  font-size: var(--text-4xl);
  line-height: var(--leading-snug);
}

.t-h3 {
  font-size: var(--text-3xl);
  line-height: var(--leading-snug);
}

.t-h4 {
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
}

.t-h5 {
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
}

.t-h6 {
  font-size: var(--text-lg);
  line-height: var(--leading-normal);
}

/* The handwritten display face. Short phrases only, because it is far less legible
   than Vollkorn, so never set a sentence or a paragraph in it. */
.t-script {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-normal);
}

.t-body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

/* These three declare their own family and a zero margin rather than inheriting
   either. Relying on an ancestor meant they only got the brand face inside
   .brand-section or .brand-container: in the hero, which is neither, .t-lead
   once fell back to the page default and the strapline rendered in Arial. */
.t-lead {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

.t-small {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

.t-fine {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

.t-muted { color: var(--color-text-muted); }

/* The small uppercase label that sits above a headline. */
.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--color-accent-strong);
}

/* orange-600 is tuned for white backgrounds and goes muddy on a dark scrim.
   orange-300 keeps the accent reading as orange while clearing contrast against
   a known dark colour such as teal-500 or ink-900. */
.eyebrow--on-dark { color: var(--orange-300); }

/* Over a photograph or a video the backdrop is not a known colour, so the tint
   cannot be relied on. Against the brightest highlight in the fleet photo,
   under the scrim, orange-300 measures 2.35:1 at 13px. White measures 4.74:1 in
   the same spot, so on media the eyebrow gives up its tint. */
.hero .eyebrow,
.page-hero .eyebrow { color: var(--surface-white); }

/* Scope for any dark surface. The type roles each set their own colour, so
   inheriting white from a parent is not enough: without this the headline in
   a dark band renders in --color-heading, which is near black. Applied once
   on the container rather than adding an on-dark variant to every role. */
.surface-dark,
.surface-dark .t-display,
.surface-dark .t-h1, .surface-dark .t-h2, .surface-dark .t-h3,
.surface-dark .t-h4, .surface-dark .t-h5, .surface-dark .t-h6,
.surface-dark .t-body,
.surface-dark .t-lead,
.surface-dark .prose { color: var(--color-text-inverse); }

/* Muted text still needs to read as secondary, without dropping below 4.5:1. */
.surface-dark .t-small,
.surface-dark .t-fine,
.surface-dark .t-muted { color: rgba(255, 255, 255, .82); }

/* Long-form copy: legal pages, blog posts, anything author-written. */
.prose {
  max-width: var(--measure);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

.prose > * + *      { margin-top: var(--space-4); }
.prose h2           { margin: var(--space-8) 0 0; font-family: var(--font-heading); font-size: var(--text-3xl); line-height: var(--leading-snug); font-weight: var(--weight-regular); color: var(--color-heading); }
.prose h3           { margin: var(--space-6) 0 0; font-family: var(--font-heading); font-size: var(--text-xl);  line-height: var(--leading-snug); font-weight: var(--weight-regular); color: var(--color-heading); }
.prose ul, .prose ol { padding-left: var(--space-5); }
.prose li + li      { margin-top: var(--space-2); }
.prose a            { color: var(--color-link); text-decoration: underline; text-underline-offset: 2px; }
.prose a:hover      { color: var(--color-link-hover); }
.prose strong       { font-weight: var(--weight-bold); }

/* --------------------------------------------------------------------------
   2.3 Buttons
   One component, four variants, three sizes. Replaces the four unrelated
   button treatments the site had grown.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* The padding alone came to 41px, just under the minimum target. */
  min-height: 44px;
  padding: var(--space-3) var(--space-5);
  border: var(--border-thick) solid transparent;
  border-radius: var(--radius-button);
  background-color: transparent;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--duration) var(--ease),
              border-color var(--duration) var(--ease),
              color var(--duration) var(--ease);
}

/* Primary: the accessible orange, because it carries white text. */
.btn--primary {
  background-color: var(--color-accent-strong);
  border-color: var(--color-accent-strong);
  color: var(--color-text-inverse);
}

.btn--primary:hover {
  background-color: var(--orange-700);
  border-color: var(--orange-700);
  color: var(--color-text-inverse);
}

.btn--secondary {
  background-color: var(--teal-900);
  border-color: var(--teal-900);
  color: var(--color-text-inverse);
}

.btn--secondary:hover {
  background-color: var(--teal-800);
  border-color: var(--teal-800);
}

.btn--ghost {
  border-color: var(--ink-900);
  color: var(--ink-900);
}

.btn--ghost:hover {
  background-color: var(--ink-900);
  color: var(--color-text-inverse);
}

/* For use over photography, video and dark surfaces.
   The scrim is not decoration. White on the mid-teal band measures 3.71:1 and
   over a bright patch of photography it can fall further, so the button darkens
   whatever sits behind it. At 45% it clears 4.5:1 on every surface the site
   uses, including the lightest highlights in the hero video. */
.btn--on-dark {
  background-color: rgba(0, 0, 0, .45);
  border-color: var(--surface-white);
  color: var(--surface-white);
}

.btn--on-dark:hover {
  background-color: var(--surface-white);
  color: var(--ink-900);
}

.btn--sm    { padding: var(--space-2) var(--space-4); font-size: var(--text-2xs); }
.btn--lg    { padding: var(--space-4) var(--space-7); font-size: var(--text-sm); }
.btn--block { display: flex; width: 100%; }

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
}

/* --------------------------------------------------------------------------
   2.4 Cards and tiles
   -------------------------------------------------------------------------- */

.card {
  display: flex;
  flex-direction: column;
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-card);
  background-color: var(--surface-white);
  transition: box-shadow var(--duration) var(--ease),
              transform var(--duration) var(--ease);
}

.card__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  color: var(--color-heading);
}

/* The card owns the rhythm between its children, so a title followed by a
   .t-body paragraph or a .tick-list gets the same gap as one followed by
   .card__body. Without this the title and the copy under it touched. */
.card > * + * { margin-top: var(--space-3); }

.card__body {
  margin: var(--space-3) 0 0;
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

/* Only apply to a card that is itself a link or contains a single link. */
/* colour: inherit because the whole card is the anchor. Without it the card
   carries the browser default #0000EE, which is invisible today only because
   the title and the description each set their own colour, and would surface
   as blue the moment anyone put a bare line of text in a card. */
.card--interactive { color: inherit; text-decoration: none; }

.card--interactive:hover {
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}

/* The image-led service tile, with its caption over the foot of the photo. */
.tile {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--radius-card);
  background-color: var(--teal-800);
  text-decoration: none;
}

/* height: auto is doing real work here, not padding out the rule. The width and
   height attributes on an img become presentational hints, so an image that
   declares its intrinsic size (which all of ours do, to reserve layout space)
   arrives with a specified height and aspect-ratio is then ignored entirely.
   Without this the tiles rendered at their full intrinsic height, up to 1600px.
   The same applies to .card__media and .gallery__item img below. */
.tile__media {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.tile__caption {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  margin: 0;
  padding: var(--space-7) var(--space-4) var(--space-4);
  background: linear-gradient(to top, rgba(6, 55, 60, .92), rgba(6, 55, 60, 0));
  color: var(--color-text-inverse);
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-wide);
}

/* Portrait crop, for a row of tiles that needs more height than the default
   landscape frame gives. */
.tile--tall .tile__media { aspect-ratio: 8 / 9; }

/* The bordered treatment the homepage service tiles use. teal-700 is the
   --line-strong value, so the frame reads on both the mist and teal bands the
   row straddles. */
.tile--framed { border: var(--border-thick) solid var(--line-strong); }

/* The photo grows inside the frame rather than the whole tile scaling, so a
   tile that overlaps the band below cannot grow over its neighbour. */
.tile__media { transition: transform var(--duration-slow) var(--ease); }

.tile:hover { box-shadow: var(--shadow-md); }
.tile:hover .tile__media { transform: scale(1.05); }

/* --------------------------------------------------------------------------
   2.5 Pills and badges
   -------------------------------------------------------------------------- */

.pill {
  display: inline-block;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-pill);
  background-color: var(--teal-500);
  color: var(--color-text-inverse);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}

.pill--outline {
  background-color: transparent;
  border: var(--border-hairline) solid var(--line-strong);
  color: var(--color-text);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-button);
  background-color: var(--orange-100);
  color: var(--orange-700);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   2.6 Tick list
   The bulleted benefit list used across the service pages.
   -------------------------------------------------------------------------- */

.tick-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tick-list > li {
  position: relative;
  padding-left: var(--space-6);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

.tick-list > li + li { margin-top: var(--space-3); }

/* The tick is masked rather than drawn as an image, because the source file is
   a solid white glyph and was invisible on any light surface. Masking makes it
   take the colour of the text beside it, so one rule serves both bands. */
.tick-list > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .55em;
  width: 18px;
  height: 18px;
  transform: translateY(-50%);
  background-color: currentColor;
  -webkit-mask: url("/images/lets-icons_check-fill-1.svg") center / contain no-repeat;
          mask: url("/images/lets-icons_check-fill-1.svg") center / contain no-repeat;
}

/* --------------------------------------------------------------------------
   2.7 Quote / review
   -------------------------------------------------------------------------- */

.quote {
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-card);
  background-color: rgba(255, 255, 255, .15);
}

.quote__text {
  margin: 0;
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

.quote__author {
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  color: var(--color-text-muted);
}

/* On a teal or ink band. A white wash would lighten the surface and drop white
   text to about 2.9:1, so the card darkens instead, the same way .stat--on-dark
   and .contact-item--on-dark do. */
.quote--on-dark {
  background-color: rgba(0, 0, 0, .25);
  border-color: rgba(255, 255, 255, .45);
}

.quote--on-dark .quote__text   { color: var(--color-text-inverse); }
.quote--on-dark .quote__author { color: rgba(255, 255, 255, .85); }

/* Caps the body at a fixed number of lines so a row of quotes is even. Reviews
   run from one line to forty, and without a cap matching their heights means
   matching the longest, which made every card as tall as the worst case. */
.quote--clamp .quote__text {
  display: -webkit-box;
  -webkit-line-clamp: 7;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The author is pushed to the foot so it lines up across the row. */
.quote--clamp {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.quote--clamp .quote__author { margin-top: auto; padding-top: var(--space-4); }

/* --------------------------------------------------------------------------
   2.7b Quote carousel
   Interop with Swiper, which sizes slides itself. Scoped to the carousel so it
   cannot reach any other Swiper instance.

   The row used to be three cards that filled the container edge to edge, with
   nothing beside them, nothing under them, and the next card entirely out of
   view: a static grid that happened to shuffle itself every five seconds. The
   things that say "this moves" are all here now: the edge of the next card
   showing on phones and tablets, a pair of arrows, a running count, and a grab
   cursor over the cards.
   -------------------------------------------------------------------------- */

.quote-carousel .swiper-slide {
  height: auto;
  overflow: visible;
  display: flex;
}

.quote-carousel .swiper-wrapper { align-items: stretch; }

/* Controls under the row: previous, count, next. */
.reviews-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-5);
}

.reviews-nav__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: var(--border-hairline) solid rgba(255, 255, 255, .45);
  border-radius: var(--radius-circle);
  background-color: rgba(0, 0, 0, .25);
  color: var(--surface-white);
  font-size: var(--text-xl);
  cursor: pointer;
  transition: background-color var(--duration) var(--ease),
              color var(--duration) var(--ease),
              border-color var(--duration) var(--ease);
}

.reviews-nav__btn:hover {
  background-color: var(--surface-white);
  border-color: var(--surface-white);
  color: var(--teal-900);
}

.reviews-nav__btn:focus-visible {
  outline: var(--border-thick) solid var(--surface-white);
  outline-offset: 3px;
}

/* Swiper marks the ends of a non-looping row. This one loops, so the class
   should never land, but the state is drawn in case that ever changes. */
.reviews-nav__btn.swiper-button-disabled {
  opacity: .4;
  cursor: not-allowed;
}

/* The dot strip. Swiper draws the bullets and scales them; only their colour,
   size and spacing are decided here, through the custom properties its
   stylesheet reads. White on the teal band, like the hero dots. */
.reviews-nav__dots {
  --swiper-pagination-color: var(--surface-white);
  --swiper-pagination-bullet-inactive-color: var(--surface-white);
  --swiper-pagination-bullet-inactive-opacity: .45;
  --swiper-pagination-bullet-size: 12px;
  --swiper-pagination-bullet-horizontal-gap: var(--space-1);
  flex: 0 0 auto;
  /* Swiper sets the strip's width to the five visible dots and slides the rest
     along behind it. Its own rule for that also kept the dots on one line and
     clipped the overflow; renaming horizontalClass in the script sidesteps that
     rule, so the two properties it supplied are restated here. Without them all
     158 dots wrapped into a grid and the row grew tall enough to push the
     arrows off screen. */
  height: var(--swiper-pagination-bullet-size);
  overflow: hidden;
  white-space: nowrap;
  line-height: 0;
}

/* Rating and date, above the words. */
.quote__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

.quote--on-dark .quote__meta { color: rgba(255, 255, 255, .85); }

.quote__stars {
  display: inline-flex;
  gap: 2px;
  /* Decorative, so it may use the accent that carries no text. On the teal
     band the lighter tint reads as orange without going muddy. */
  color: var(--orange-300);
}

.quote__star {
  width: 1.125em;
  height: 1.125em;
  fill: currentColor;
}

.quote__star--empty {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  opacity: .6;
}

/* Opens a clamped review in place. Only added by the script to cards whose
   text actually overflows. A text button, underlined like a link because it
   sits in running copy, but a button because it changes the page rather than
   leaving it. */
.quote__more {
  align-self: flex-start;
  min-height: 44px;
  margin: var(--space-1) 0 0;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.quote__more:hover { text-decoration-thickness: 2px; }

.quote__more:focus-visible {
  outline: var(--border-thick) solid var(--surface-white);
  outline-offset: 2px;
}

.quote--clamp.quote--expanded .quote__text {
  display: block;
  -webkit-line-clamp: none;
  overflow: visible;
}

/* --------------------------------------------------------------------------
   2.8 Forms
   -------------------------------------------------------------------------- */

.field { display: block; }

.field + .field { margin-top: var(--space-5); }

.field__label {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  color: var(--color-text-strong);
}

.field__hint {
  margin-top: var(--space-2);
  font-size: var(--text-2xs);
  color: var(--color-text-muted);
}

.field__error {
  margin-top: var(--space-2);
  font-size: var(--text-2xs);
  color: var(--color-error);
}

.field__control {
  display: block;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-input);
  background-color: var(--surface-white);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text);
  transition: border-color var(--duration-fast) var(--ease);
}

.field__control::placeholder { color: var(--ink-300); }
.field__control:hover        { border-color: var(--ink-300); }

/* A colour change alone is not a focus indicator: it fails for anyone who
   cannot distinguish the two border colours. Draw a real ring. */
.field__control:focus {
  border-color: var(--color-accent-strong);
  outline: var(--border-thick) solid var(--color-focus);
  outline-offset: 1px;
}

textarea.field__control {
  min-height: 140px;
  resize: vertical;
}

.field__control[aria-invalid="true"] { border-color: var(--color-error); }

/* --------------------------------------------------------------------------
   2.9 Dividers
   -------------------------------------------------------------------------- */

.rule {
  height: var(--border-hairline);
  margin: 0;
  border: 0;
  background-color: var(--color-border);
}

.rule--accent {
  width: 64px;
  height: 3px;
  border-radius: var(--radius-pill);
  background-color: var(--color-accent);
}

/* --------------------------------------------------------------------------
   2.10 Links
   The site currently has nine unrelated link treatments and no shared base,
   including inline body links that carry no class at all and fall back to the
   browser default blue, on dark panels as well as light.

   These are not called `.link`: when they were written the Webflow sheet
   defined that name at 28px and both files were loaded together. The sheet is
   gone; the names have stayed.
   -------------------------------------------------------------------------- */

/* Inline link inside a sentence. Underlined, because colour alone is not a
   sufficient signal that something is clickable. */
.link-inline {
  color: var(--color-link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: color var(--duration-fast) var(--ease),
              text-decoration-color var(--duration-fast) var(--ease);
}

.link-inline:hover {
  color: var(--color-link-hover);
  text-decoration-color: var(--color-link-hover);
}

/* A bare <a> written inside body copy takes the same treatment. The comment
   above described this as a problem to fix by hand, and the migration then
   left fifteen content links across the service pages, the two hubs and the
   homepage with no class, rendering in the browser default #0000EE: the only
   blue anywhere on the site, and on a cream panel at that. Relying on every
   future link remembering a class was the fragile half of the plan, so the
   type roles adopt the treatment instead. */
.t-body a:not([class]),
.t-lead a:not([class]),
.t-small a:not([class]),
.section-head__lead a:not([class]),
.card a:not([class]),
.faq a:not([class]),
.quote a:not([class]) {
  color: var(--color-link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: color var(--duration-fast) var(--ease),
              text-decoration-color var(--duration-fast) var(--ease);
}

.t-body a:not([class]):hover,
.t-lead a:not([class]):hover,
.t-small a:not([class]):hover,
.section-head__lead a:not([class]):hover,
.card a:not([class]):hover,
.faq a:not([class]):hover,
.quote a:not([class]):hover {
  color: var(--color-link-hover);
  text-decoration-color: var(--color-link-hover);
}

/* On a teal or ink band the near-black link colour is close to invisible, so
   inline links there run white and lean on the underline. */
.brand-section--teal .t-body a:not([class]),
.brand-section--teal .t-lead a:not([class]),
.brand-section--teal .section-head__lead a:not([class]),
.brand-section--ink .t-body a:not([class]),
.brand-section--ink .t-lead a:not([class]),
.brand-section--ink .section-head__lead a:not([class]) {
  color: var(--color-text-inverse);
}

.brand-section--teal .t-body a:not([class]):hover,
.brand-section--teal .t-lead a:not([class]):hover,
.brand-section--teal .section-head__lead a:not([class]):hover,
.brand-section--ink .t-body a:not([class]):hover,
.brand-section--ink .t-lead a:not([class]):hover,
.brand-section--ink .section-head__lead a:not([class]):hover {
  color: var(--orange-300);
  text-decoration-color: var(--orange-300);
}

/* Standalone call-to-action link, the lightest weight of action, below a
   ghost button. The arrow is drawn from a character so it needs no asset. */
.link-action {
  display: inline-flex;
  align-items: center;
  /* Standalone call to action, so it needs a real target, unlike .link-inline
     which sits inside a sentence and is exempt. */
  min-height: 44px;
  gap: var(--space-2);
  font-family: var(--font-body);
  /* Was --text-sm, which put a standalone call to action below the 16px floor. */
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  color: var(--color-link-hover);
  text-decoration: none;
}

.link-action::after {
  content: "\2192";
  transition: transform var(--duration) var(--ease);
}

.link-action:hover::after { transform: translateX(3px); }

.link-action:hover { text-decoration: underline; text-underline-offset: 3px; }

/* Muted list link: footers, sidebars, area lists. */
.link-quiet {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  text-decoration: none;
}

.link-quiet:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Any of the above, placed on a teal or ink surface. */
/* Links inside a dark scope take the on-dark treatment automatically. The
   default link colour is ink-700, which is 2.66:1 on the teal band, so
   relying on an author remembering to add .on-dark leaves an unreadable link
   the one time they forget. */
.link-inline.on-dark,
.link-quiet.on-dark,
.link-action.on-dark,
.surface-dark .link-inline,
.surface-dark .link-quiet,
.surface-dark .link-action,
.brand-section--teal .link-inline,
.brand-section--teal .link-quiet,
.brand-section--teal .link-action,
.brand-section--ink .link-inline,
.brand-section--ink .link-quiet,
.brand-section--ink .link-action { color: var(--surface-white); }

/* Hover was orange-300, which is only 2.82:1 on teal-500 and failed. orange-100
   is a warm off-white that reads as a clear change and holds 4.97:1. The
   underline also thickens, so the state is not signalled by colour alone. */
.link-inline.on-dark:hover,
.link-quiet.on-dark:hover,
.link-action.on-dark:hover,
.surface-dark .link-inline:hover,
.surface-dark .link-quiet:hover,
.surface-dark .link-action:hover,
.brand-section--teal .link-inline:hover,
.brand-section--teal .link-quiet:hover,
.brand-section--teal .link-action:hover,
.brand-section--ink .link-inline:hover,
.brand-section--ink .link-quiet:hover,
.brand-section--ink .link-action:hover {
  color: var(--orange-100);
  text-decoration-color: var(--orange-100);
  text-decoration-thickness: 2px;
}

/* --------------------------------------------------------------------------
   2.11 Statistics
   The "195 Floors Restored" trio. In the current markup the number and its
   label are both <h2>, which puts six content-free headings in the page
   outline; these are paragraphs, because a statistic is not a section title.
   -------------------------------------------------------------------------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}

.stat {
  padding: var(--space-6) var(--space-4);
  border: var(--border-hairline) solid var(--line-strong);
  border-radius: var(--radius-card);
  background-color: var(--surface-cream);
  text-align: center;
}

.stat__value {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--leading-tight);
  color: var(--color-heading);
}

.stat__label {
  margin: var(--space-2) 0 0;
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text);
}

/* On a teal or ink band. The card darkens its surface rather than lightening
   it: the homepage version tinted 15% white over teal-400, which pushed white
   text down to 2.92:1. A 25% black tint puts it at 5.94:1 and keeps the same
   translucent-panel look. */
.stat--on-dark {
  border-color: rgba(255, 255, 255, .35);
  background-color: rgba(0, 0, 0, .25);
}

.stat--on-dark .stat__value,
.stat--on-dark .stat__label { color: var(--surface-white); }

/* --------------------------------------------------------------------------
   2.12 Process steps
   Two presentations of the same idea, both already used on the site: a card
   grid (sanding, oak, solid wood, parquet) and a badge-and-row list
   (restoration, repairs).
   -------------------------------------------------------------------------- */

/* Both forms are ordered lists in the markup, since the order is the point. The
   generated number stands in for the marker, so the marker itself comes off. */
.step-grid,
.step-rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

.step-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-5);
  counter-reset: step;
}

.step {
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-card);
  background-color: var(--surface-white);
  counter-increment: step;
}

/* The number is generated, so the markup carries no content-free headings and
   renumbering is automatic when a step is added or removed. */
.step::before {
  content: counter(step);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-bottom: var(--space-4);
  border-radius: var(--radius-circle);
  background-color: var(--orange-600);
  color: var(--surface-white);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  line-height: 1;
}

.step__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  color: var(--color-heading);
}

.step__body {
  margin: var(--space-3) 0 0;
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

/* Row form: a badge in a narrow column, content beside it. Also the shape the
   FAQ uses, with a "Q" in place of the number. */
.step-rows { counter-reset: step; }

.step-row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: var(--space-5);
  padding-block: var(--space-5);
  counter-increment: step;
}

.step-row + .step-row { border-top: var(--border-hairline) solid var(--color-border); }

.step-row::before {
  content: counter(step);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-circle);
  background-color: var(--teal-100);
  color: var(--teal-900);
  font-size: var(--text-xl);
  line-height: 1;
}

/* --------------------------------------------------------------------------
   2.13 FAQ
   Built on <details>/<summary>, so it opens and closes, is keyboard operable
   and is announced correctly with no JavaScript at all. The current pages use
   a plain heading and paragraph with a "Q" glyph beside them.
   -------------------------------------------------------------------------- */

.faq { border-top: var(--border-hairline) solid var(--color-border); }

.faq__item { border-bottom: var(--border-hairline) solid var(--color-border); }

.faq__question {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) 0;
  font-family: var(--font-body);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  color: var(--color-heading);
  cursor: pointer;
  list-style: none;
}

.faq__question::-webkit-details-marker { display: none; }

/* Drawn chevron, so there is no icon file to keep in sync. */
.faq__question::after {
  content: "";
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  margin-top: .4em;
  border-right: 2px solid var(--color-accent-strong);
  border-bottom: 2px solid var(--color-accent-strong);
  transform: rotate(45deg);
  transition: transform var(--duration) var(--ease);
}

.faq__item[open] .faq__question::after { transform: rotate(-135deg); }

.faq__answer {
  max-width: var(--measure);
  margin: 0;
  padding-bottom: var(--space-5);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   2.14 Section header
   The eyebrow + heading + intro grouping that opens most sections. Roughly
   forty instances across the site currently rebuild this from modifier
   classes (h-dark, h-pad, m-pad, p-pad, p-dark, text-left).
   -------------------------------------------------------------------------- */

.section-head { max-width: var(--measure); }

.section-head--center {
  max-width: var(--measure);
  margin-inline: auto;
  text-align: center;
}

/* Lets the head span the container instead of clamping to the reading
   measure. The title benefits; a long paragraph set this wide runs past a
   comfortable line length, so --wide caps the lead a little short of the
   full width rather than letting it run edge to edge. */
.section-head--wide { max-width: none; }
/* 92ch let these leads run the full width of the xl container, 140 characters to
   the line. 50ch measures 83, which is as wide as a centred intro can go before
   the eye loses the start of the next line, without turning three lines into
   seven. See the note on --measure for why the number reads low. */
.section-head--wide .section-head__lead { max-width: 50ch; }

/* Only centre the lead when the head itself is centred. On a left-aligned head
   the auto margins would pull the paragraph off the title's left edge. */
.section-head--wide.section-head--center .section-head__lead { margin-inline: auto; }

.section-head__title {
  margin: var(--space-3) 0 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  color: var(--color-heading);
}

.section-head__lead {
  margin: var(--space-4) 0 0;
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  color: var(--color-text-muted);
}

/* On teal or ink. The muted grey would not have enough contrast there. */
.section-head--on-dark .section-head__title,
.section-head--on-dark .section-head__lead { color: var(--surface-white); }

.section-head--on-dark .eyebrow { color: var(--orange-300); }

/* Rhythm between the intro block and whatever follows. Many pages put
   .section-head and a .grid-auto as siblings inside .brand-container without
   a .stack wrapper, so the cards sat flush under the lead with only the lead's
   own top margin between them. Matches .stack--xl. */
.section-head + .grid-auto,
.section-head + ul.grid-auto,
.section-head + .split,
.section-head + .process,
.section-head + .contact-grid,
.section-head + .logo-strip,
.section-head + .faq,
.section-head + .gallery {
  margin-top: var(--space-8);
}

/* --------------------------------------------------------------------------
   2.15 Contact band and contact items
   The "call us today" band that closes ten of the twelve pages, plus the
   icon-and-text rows on the contact page. Those are currently two unrelated
   implementations of the same thing.
   -------------------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
}

.contact-item {
  display: flex;
  align-items: center;
  justify-content: center;
  /* The email address is one unbreakable token. Without these it overflowed
     the card and, because the row is centred, pushed the icon out past the
     left edge of the page. */
  flex-wrap: wrap;
  overflow-wrap: anywhere;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-card);
  background-color: var(--surface-white);
  font-size: var(--text-lg);
  color: var(--color-text-strong);
  text-decoration: none;
  transition: border-color var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease);
}

.contact-item:hover {
  border-color: var(--color-accent-strong);
  box-shadow: var(--shadow-sm);
}

/* Inline SVG rather than an image file. The legacy icons were white PNGs
   baked for the teal band, so they vanished on any light surface. Drawing
   with currentColor means one icon works on every background. */
.contact-item__icon {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--color-accent-strong);
}

/* Compact left-aligned form, for sidebars and the contact page. */
.contact-item--row {
  justify-content: flex-start;
  padding: var(--space-3) 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: var(--text-base);
}

.contact-item--row:hover {
  border-color: transparent;
  box-shadow: none;
  color: var(--color-link-hover);
}

/* For the teal contact band that closes most pages. */
.contact-item--on-dark {
  border-color: rgba(255, 255, 255, .45);
  background-color: rgba(0, 0, 0, .28);
  color: var(--color-text-inverse);
}

.contact-item--on-dark:hover  { border-color: var(--surface-white); }
.contact-item--on-dark .contact-item__icon { color: var(--color-text-inverse); }

/* Generic inline-SVG icon. Scales with the text beside it and takes its
   colour from the parent, so it never needs a light and a dark copy. */
.icon {
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --------------------------------------------------------------------------
   2.16 Media split
   Image one side, copy the other. Used for the service-area map and the
   sustainable-flooring row. Reverses with a modifier rather than a copy.
   -------------------------------------------------------------------------- */

/* 400px, not 320px. At 320 the two columns survived down to about 690px, where
   the heading broke onto three lines, list items wrapped mid-phrase and the
   image left a block of dead space beside the taller copy. */
.split {
  display: grid;
  /* min(400px, 100%): a hard 400px minimum is wider than a 390px phone once the
     gutters are taken off, so the single column overflowed the viewport and
     the right-hand edge of every paragraph was clipped. */
  grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr));
  gap: var(--space-8);
  align-items: center;
}

/* Cropped rather than left at its own proportions. Most of the photography is
   portrait, shot on a phone looking down at a floor, and at `height: auto` a
   900 by 1600 source rendered as a column roughly twice the height of the copy
   beside it, which dragged the section to three or four screens. */
.split__media {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-card);
}

/* The county map is a diagram, not a photograph: cropping it would cut off
   Gloucestershire, so it keeps its own shape. */
.split__media--contain {
  aspect-ratio: auto;
  object-fit: contain;
}

.split--reverse .split__media { order: 2; }

@media screen and (max-width: 767px) {
  /* Keep the image above the copy on mobile regardless of the desktop order. */
  .split--reverse .split__media { order: 0; }
}

/* --------------------------------------------------------------------------
   2.17 Area list
   The list of towns served. Currently a single <p> of <br>-separated lines,
   which reads as one run-on sentence to a screen reader.
   -------------------------------------------------------------------------- */

.area-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-2) var(--space-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.area-list > li {
  padding-left: var(--space-4);
  border-left: 2px solid var(--color-accent);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   2.18 Gallery
   -------------------------------------------------------------------------- */

/* Four across, not `auto-fill`. The gallery holds eight photographs, and
   auto-fill packed five into a 1440px viewport, so the set broke as a row of
   five and an orphan row of three sitting under a stretch of empty teal. Eight
   over four columns is two full rows at every width where four columns fit,
   and the step down is to two, then one, so the grid is never ragged. */
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
}

@media screen and (max-width: 767px) {
  .gallery { grid-template-columns: repeat(2, 1fr); }
}

@media screen and (max-width: 479px) {
  .gallery { grid-template-columns: 1fr; }
}

.gallery__item {
  display: block;
  overflow: hidden;
  border-radius: var(--radius-card);
}

.gallery__item img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform var(--duration-slow) var(--ease);
}

.gallery__item:hover img { transform: scale(1.04); }

/* Focus lands on the link, which is the full tile, so the ring goes round the
   photo rather than inside it. */
.gallery__item:focus-visible {
  outline: var(--border-thick) solid var(--color-focus);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   2.18a Lightbox
   The overlay that js/site.js builds when a gallery photo is opened.

   These three classes had no styles at all. The script appended the overlay to
   the end of <body>, where, with no positioning and no backdrop, it rendered as
   a stray photograph below the footer: clicking a gallery tile looked like it
   did nothing.

   The image is capped at 540px, which is as large as these files can be shown
   without visibly falling apart. Every gallery source is only 270px wide, so on
   a 2x screen even the 270px thumbnail is already being upscaled; 540 CSS px is
   the same 2x ratio, so the lightbox is no softer than the tile it opened from
   while still being worth opening. A viewport-filling lightbox would be four or
   five times the source.

   Re-export the eight gallery photographs at around 1600px wide, as JPEG or
   WebP rather than PNG, and this cap can be lifted to 90vw. That is the one
   change that would make the gallery genuinely sharp.
   -------------------------------------------------------------------------- */

.lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: grid;
  place-items: center;
  padding: var(--gutter);
  background-color: rgba(6, 55, 60, .88);
}

.lightbox-content {
  position: relative;
  max-width: 100%;
}

.lightbox-content img {
  display: block;
  /* Sized rather than left at `auto`: at its intrinsic 270px the photo sat in
     the middle of a full-screen overlay looking like a loading failure. */
  width: min(100%, 540px);
  height: auto;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-xl);
}

.lightbox-close {
  position: absolute;
  top: calc(var(--space-6) * -1);
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-button);
  background-color: transparent;
  color: var(--surface-white);
  font-family: var(--font-body);
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--duration) var(--ease);
}

.lightbox-close:hover { background-color: rgba(255, 255, 255, .16); }

.lightbox-close:focus-visible {
  outline: var(--border-thick) solid var(--surface-white);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   2.19 Logo strip
   A continuous marquee of supplier brands.
   -------------------------------------------------------------------------- */

.logo-strip {
  position: relative;
  overflow: hidden;
  padding-block: var(--space-5);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}

.logo-strip__track {
  display: flex;
  align-items: center;
  gap: var(--space-9);
  width: max-content;
  animation: logo-scroll 40s linear infinite;
}

.logo-strip:hover .logo-strip__track { animation-play-state: paused; }

.logo-strip__item {
  flex: 0 0 auto;
  max-height: 56px;
  width: auto;
  filter: grayscale(1);
  opacity: .65;
  transition: filter var(--duration) var(--ease), opacity var(--duration) var(--ease);
}

.logo-strip__item:hover { filter: grayscale(0); opacity: 1; }

/* Translating exactly -50% requires the track to contain the logo set twice,
   which is what makes the loop seamless. */
@keyframes logo-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* --------------------------------------------------------------------------
   2.19b Site footer
   The legacy footer set its links at 14px, below the 16px floor, and left
   every href as "#".
   -------------------------------------------------------------------------- */

.site-footer {
  padding-block: var(--space-9) var(--space-5);
  background-color: var(--surface-mist);
}

/* Grid, not flex with space-between. As a flex row the link groups shrank to
   their content width, which collapsed the inner auto-fit grid to a single
   column and left a large hole between the logo and the stacked lists. */
.site-footer__top {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-8) var(--space-10);
  align-items: start;
}

.site-footer__marks {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
}

/* The full logo, mark and wordmark. 160px is the narrowest at which the two
   lines of the wordmark stay comfortably legible. */
.site-footer__logo {
  display: block;
  width: 160px;
  height: auto;
}

/* The Bona Certified Contractor badge, a circle on a white ground. Clipped to
   the circle so the white ground never shows on the mist footer. */
.site-footer__badge {
  display: block;
  width: 96px;
  height: 96px;
  border-radius: var(--radius-circle);
}

.site-footer__groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-8);
}

@media screen and (max-width: 767px) {
  .site-footer__top { grid-template-columns: 1fr; }
}

.site-footer__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-heading);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-strong);
}

.site-footer__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer__link {
  display: inline-block;
  /* space-2 gave a 40px row. The target is met by the padding, not the text. */
  padding-block: var(--space-3);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text);
  text-decoration: none;
}

.site-footer__link:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
}

.site-footer__divider {
  height: 1px;
  margin-block: var(--space-7) var(--space-4);
  border: 0;
  background-color: var(--color-border);
}

.site-footer__legal {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  text-align: center;
  color: var(--color-text-muted);
}

/* --------------------------------------------------------------------------
   2.20 Breadcrumbs
   Every page already publishes BreadcrumbList structured data with no visible
   counterpart, so search results promise a trail the page does not show.
   -------------------------------------------------------------------------- */

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
}

.breadcrumb > li { display: flex; align-items: center; gap: var(--space-2); }

.breadcrumb > li + li::before {
  content: "/";
  color: var(--ink-300);
}

/* The bar stays slim, but the target does not: padding on the anchor takes each
   crumb past the 24px minimum without changing how the row looks. */
.breadcrumb a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding-block: var(--space-1);
  color: var(--color-text-muted);
  text-decoration: none;
}

.breadcrumb a:hover { color: var(--color-link-hover); text-decoration: underline; }

.breadcrumb [aria-current="page"] { color: var(--color-text-strong); }

/* --------------------------------------------------------------------------
   2.21 Notice
   Form success and failure messaging. Carries an icon as well as a colour, so
   the state is not signalled by colour alone.
   -------------------------------------------------------------------------- */

/* display: flex outranks the user-agent rule for [hidden], so the empty status
   region rendered as a blank cream box under the submit button until this. */
.notice[hidden] { display: none; }

.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-card);
  background-color: var(--surface-cream);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-text);
}

.notice__icon { flex: 0 0 auto; font-weight: var(--weight-bold); }

.notice--success {
  border-left-color: var(--color-success);
  background-color: #eef5ee;
}

.notice--success .notice__icon { color: var(--color-success); }

.notice--error {
  border-left-color: var(--color-error);
  background-color: #fbeeed;
}

.notice--error .notice__icon { color: var(--color-error); }

/* --------------------------------------------------------------------------
   2.22 Card media
   Lets the existing card carry an image above its title.
   -------------------------------------------------------------------------- */

/* Negative margins pull the image out to the card's edges, cancelling the
   card's own padding on three sides. */
.card__media {
  display: block;
  width: calc(100% + var(--space-5) * 2);
  height: auto;
  margin: calc(var(--space-5) * -1) calc(var(--space-5) * -1) var(--space-5);
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
}

/* --------------------------------------------------------------------------
   2.23 Hero
   Full-bleed media with a scrim and a rotating message. Replaces the page
   level <style> block on the homepage, which carried a duplicate
   .carousel-container rule, a 768px breakpoint outside the four allowed, and
   a .hero-h1 set to 100px type on 85px leading (lines physically collided).
   The .carousel-* names are kept as JavaScript hooks only; all presentation
   now comes from the .hero__* classes.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 70vh;
  overflow: hidden;
  font-family: var(--font-body);
}

/* One photograph per slide, stacked; the script marks the one that matches the
   message showing. The crop is a per-image variable, as it is on .page-hero. */
.hero__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-media-position, center);
  z-index: 0;
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease);
}

.hero__media.active { opacity: 1; }

/* The photographs are not chosen for their brightness, so the scrim has to be
   dark enough to carry white text over the lightest of them. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: rgba(0, 0, 0, .55);
}

.hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--container-xl);
  margin-inline: auto;
  padding: var(--section-y) var(--gutter);
  text-align: center;
  color: var(--color-text-inverse);
}

/* Slides stack, so the tallest one sets the height and the hero does not
   jump as the message rotates. */
.hero__stage {
  display: grid;
  align-items: center;
}

.hero__slide {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-slow) var(--ease),
              visibility var(--duration-slow) var(--ease);
}

.hero__slide.active { opacity: 1; visibility: visible; }

/* The brand face is all capitals, so the tight tracking used elsewhere on
   display type would jam the letterforms together here. */
.hero__title {
  letter-spacing: var(--tracking-normal);
  text-wrap: balance;
}

.hero__lead { max-width: 46ch; }

/* In the flow under the slides, not absolutely positioned at the foot of the
   hero. Pinned to the bottom they sat on top of the slide's button once the
   headline wrapped onto four lines, which is what happens below about 500px. */
.hero__dots {
  position: relative;
  z-index: 3;
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* A 44px button carrying a 12px dot, so the target meets the minimum without
   the indicator itself growing. */
.hero__dot {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-circle);
  background: none;
  cursor: pointer;
}

.hero__dot::before {
  content: "";
  width: 12px;
  height: 12px;
  border-radius: var(--radius-circle);
  background-color: rgba(255, 255, 255, .4);
  transition: background-color var(--duration) var(--ease),
              transform var(--duration) var(--ease);
}

.hero__dot:hover::before          { background-color: rgba(255, 255, 255, .7); }
.hero__dot[aria-current="true"]::before {
  background-color: var(--surface-white);
  transform: scale(1.3);
}

.hero__dot:focus-visible {
  outline: var(--border-thick) solid var(--color-focus);
  outline-offset: 2px;
}

@media screen and (max-width: 767px) {
  .hero { min-height: 60vh; }
  .hero__inner { padding-block: var(--section-y-tight); }
}

/* --------------------------------------------------------------------------
   2.24 Page hero
   The interior-page counterpart to the homepage hero: one background image,
   no rotating message. Replaces .hero-sub, whose .hero-h1 was 100px type on
   85px leading, so the two lines of every page title physically crossed. It
   also leaned on a text-shadow rather than a scrim, which left white type
   sitting on whatever the image happened to be doing underneath.

   The image is the only per-page variable, so it arrives as a custom property
   set on the element itself:

     <section class="page-hero" style="--page-hero-image: url(/images/x.webp)">

   Most of the photography is portrait and shot looking down at a floor, so the
   floor sits in the lower half. A 420px band cropped from the centre of a
   1600px-tall photograph can miss it. --page-hero-position moves the crop and
   defaults to centre:

     <section class="page-hero" style="--page-hero-image: url(...); --page-hero-position: center 80%">
   -------------------------------------------------------------------------- */

.page-hero {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 420px;
  padding-block: var(--section-y);
  background-color: var(--ink-900);
  background-image: var(--page-hero-image);
  background-position: var(--page-hero-position, center);
  background-size: cover;
  background-repeat: no-repeat;
  font-family: var(--font-body);
}

/* An element rather than a background, for the pages whose hero is a video. */
.page-hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Same reasoning as the homepage scrim: the media is uncontrolled, so the
   overlay has to be dark enough to carry white text over the lightest frame. */
.page-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: rgba(0, 0, 0, .55);
}

.page-hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--container-lg);
  margin-inline: auto;
  padding-inline: var(--gutter);
  text-align: center;
  color: var(--color-text-inverse);
}

.page-hero__title {
  letter-spacing: var(--tracking-normal);
  text-wrap: balance;
}

.page-hero__lead {
  max-width: 54ch;
  margin-inline: auto;
}

@media screen and (max-width: 767px) {
  .page-hero { min-height: 340px; padding-block: var(--section-y-tight); }
}

/* --------------------------------------------------------------------------
   2.25 Breadcrumb bar
   Sits between the hero and the first content band. On its own light strip
   rather than inside the hero, so the trail is not another thing competing
   with the title for contrast against the image.
   -------------------------------------------------------------------------- */

.breadcrumb-bar {
  border-bottom: var(--border-hairline) solid var(--color-border);
  background-color: var(--surface-cream);
}

.breadcrumb-bar .brand-container { padding-block: var(--space-2); }

/* --------------------------------------------------------------------------
   2.26 Focus
   A single visible focus ring everywhere, replacing the two competing
   treatments (Webflow's blue and the nav's orange).
   -------------------------------------------------------------------------- */

.btn:focus-visible,
.pill:focus-visible,
.card:focus-visible,
.tile:focus-visible,
.field__control:focus-visible,
.prose a:focus-visible {
  outline: var(--border-thick) solid var(--color-focus);
  outline-offset: 3px;
}

/* Keyboard-only skip target. */
.skip-link {
  position: absolute;
  left: var(--space-4);
  top: calc(var(--space-4) * -6);
  z-index: var(--z-toast);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-button);
  background-color: var(--ink-900);
  color: var(--surface-white);
  font-size: var(--text-sm);
  transition: top var(--duration) var(--ease);
}

.skip-link:focus { top: var(--space-4); }

/* Visually hidden but available to screen readers. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   2.27 Motion preference
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .btn,
  .card,
  .field__control,
  .skip-link,
  .link-inline,
  .link-action::after,
  .contact-item,
  .reviews-nav__btn,
  .quote__more,
  .hero__slide,
  .hero__media,
  .hero__dot::before,
  .faq__question::after,
  .gallery__item img,
  .tile__media,
  .logo-strip__item {
    transition: none;
  }

  .card--interactive:hover     { transform: none; }
  .link-action:hover::after    { transform: none; }
  .gallery__item:hover img     { transform: none; }
  .tile:hover .tile__media     { transform: none; }

  /* The marquee is continuous motion with no way to pause it from the
     keyboard, so it stops entirely rather than merely slowing. */
  .logo-strip__track { animation: none; }
}
