/* Trade site template v2 — merged with the Foreman mockup design language.
   Palette per trade via variables; all pairs verified WCAG AA (see DESIGN-REVIEW.md). */
:root {
  --ink: #0E2E3D;            /* deep teal-navy — headings, footer ground */
  --charcoal: #23303A;       /* body text */
  --stone: #F7F5F0;          /* warm page ground */
  --paper: #ffffff;
  --accent: #19567C;         /* links, icons — 7.24:1 on stone, WCAG AAA (darkened from #1B5E86, 17 Aug 2026) */
  --cta: #8F4E15;            /* copper CTA — white on this is 6.4:1 */
  --cta-dark: #7a4312;
  --cta-text: #ffffff;       /* CTA button label colour — a per-site brand override with a
                                 lighter --cta (e.g. a bright brand blue) may need to set this
                                 to a dark colour instead, since white-on-light fails contrast */
  --muted: #485359;          /* 7.25:1 on stone, WCAG AAA (darkened from #57666D, 17 Aug 2026) */
  --hairline: rgba(14, 46, 61, .14);
  --radius: 2px;
  --font-head: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-body: "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-logo: "Fredoka", "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Heading-treatment axis (A10a design presets) — "relaxed" values below are
     the pre-existing defaults; a per-site override style block (see
     index.html <head>) can set these to the "tight" preset instead. */
  --h-weight-1: 700;
  --h-weight-2: 650;
  --h-tracking: .01em;
}
@font-face { font-family: "Space Grotesk"; src: url("fonts/space-grotesk-var.woff2") format("woff2");
  font-weight: 300 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Sans 3"; src: url("fonts/source-sans-3-var.woff2") format("woff2");
  font-weight: 200 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Fredoka"; src: url("fonts/fredoka-var.woff2") format("woff2");
  font-weight: 400 700; font-style: normal; font-display: swap; }

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } * { transition: none !important; animation: none !important; } }

/* Scroll-reveal (visual overhaul, 10 Aug 2026). Safe by construction: the
   hidden state only ever applies once `.js-reveal` is present on <html>,
   which site.js only adds after confirming JS ran AND the visitor hasn't
   asked for reduced motion. No JS, JS error, or reduced-motion = content
   is visible immediately, full stop — this can never hide real content,
   only ever add a fade-in on top of content that was already showing. */
.js-reveal .reveal { opacity: 0; transform: translateY(18px); transition: opacity .5s ease, transform .5s ease; }
.js-reveal .reveal.is-visible { opacity: 1; transform: none; }
body { font-family: var(--font-body); color: var(--charcoal); background: var(--stone); line-height: 1.6; padding-bottom: calc(84px + env(safe-area-inset-bottom)); }
@media (min-width: 720px) { body { padding-bottom: 0; } }
img { max-width: 100%; display: block; height: auto; }
h1, h2, h3 { font-family: var(--font-head); color: var(--ink); letter-spacing: var(--h-tracking); line-height: 1.2; text-wrap: balance; }
h1 { font-size: clamp(1.9rem, 4.2vw, 2.7rem); font-weight: var(--h-weight-1); }
h2 { font-size: clamp(1.35rem, 3.2vw, 1.8rem); font-weight: var(--h-weight-2); }
h3 { font-size: 1.05rem; font-weight: var(--h-weight-2); }
a { color: var(--accent); }
a:focus-visible, button:focus-visible, summary:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.wrap { max-width: 1080px; margin: 0 auto; padding: 0 22px; }
.wrap.narrow { max-width: 680px; }
section { padding: 52px 0; }
section.alt { background: var(--paper); border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
.section-head { margin-bottom: 24px; max-width: 60ch; }
.section-head p { margin-top: 10px; color: var(--muted); }
.note { color: var(--muted); font-size: .92rem; }
.sub-h { margin-bottom: 6px; }

.skip { position: absolute; left: -999px; top: 0; background: var(--ink); color: #fff; padding: 10px 18px; z-index: 99; }
.skip:focus { left: 0; }

/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 22px; border-radius: var(--radius); font-weight: 600; text-decoration: none;
  border: 0; cursor: pointer; font-size: 1rem; }
.btn-cta { background: var(--cta); color: var(--cta-text); transition: background .18s ease, transform .18s ease, box-shadow .18s ease; }
.btn-cta:hover { background: var(--cta-dark); transform: translateY(-2px); box-shadow: 0 6px 14px rgba(0,0,0,.14); }
.btn-call { background: var(--ink); color: #fff; white-space: nowrap; }
.btn-block { width: 100%; }
.link-quiet { color: inherit; text-decoration: underline; text-underline-offset: 3px; }

/* Top bar */
.topbar { background: var(--paper); border-bottom: 1px solid var(--hairline); position: sticky; top: 0; z-index: 10; }
.topbar-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 22px; }
.logo { font-family: var(--font-head); font-weight: 700; font-size: 1.1rem; color: var(--ink); letter-spacing: .01em;
  line-height: 1.15; flex-shrink: 1; min-width: 0; }
  /* Pre-existing bug fixed 10 Aug 2026 — was flex-shrink:0 + single-line nowrap, which forced the
     CTA button off-screen on narrow phones for any longer trading name ("X Plumbing & Heating"
     etc.), caught via mobile testing of the A10a preview tool. Revised twice same session:
     first pass truncated to one line (bad — hides part of the name); second pass capped a
     2-line wrap with an ellipsis (still hides text, still reads as an unfinished name, per
     Thomas). Final: wraps naturally, NO truncation, NO ellipsis, ever — a taller header for a
     long name is an acceptable trade-off, a name that looks cut off is not. The real guardrail
     against an unreasonably long name is at signup (see build.py's `logo_svg_inline`
     "and"->"&" normalisation and the recommended character-limit guidance in EXECUTION-MAP.md
     A10a), not a CSS hack hiding characters after the fact. */
.head-ctas { display: flex; align-items: center; gap: 14px; flex-shrink: 0; }
.head-ctas .btn-cta { white-space: nowrap; }
.head-phone { font-size: .9rem; color: var(--muted); }

/* Hero */
.hero { padding: 56px 0 48px; }
.hero-grid { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 44px; align-items: center; }
.chips { list-style: none; display: flex; flex-wrap: wrap; gap: 8px 18px; margin-bottom: 18px; }
.chips li { text-transform: uppercase; letter-spacing: .12em; font-size: .74rem; font-weight: 650; color: var(--accent); }
.chips li::before { content: "› "; color: var(--muted); margin-right: 14px; }
.hero h1 { max-width: 23ch; }
.lede { font-size: 1.12rem; margin: 16px 0 24px; max-width: 32em; color: var(--charcoal); }
.hero-ctas { display: flex; flex-wrap: wrap; gap: 12px; }
/* Hidden below 720px (11 Aug 2026, Thomas) — mobile already has a sticky
   header CTA AND the fixed sticky-call bottom bar (below), both always on
   screen; the hero's own button made a third identical "Get a quote"
   visible at once before a visitor even scrolls, genuine overkill. Kept on
   desktop, where there's no sticky bottom bar and the hero button is the
   only in-content CTA besides the sticky header. Threshold matches
   .sticky-call's own 720px breakpoint below, since it's the bottom bar
   taking over this job on mobile. */
@media (max-width: 719px) { .hero-ctas { display: none; } }
.hero-fallback { margin-top: 12px; font-size: .95rem; color: var(--muted); }
.hero-photo { border-radius: var(--radius); border: 1px solid var(--hairline); aspect-ratio: 1; object-fit: cover;
  position: relative; z-index: 1; }
/* Branded hero-photo treatment (visual overhaul, 10 Aug 2026) — a colour
   block in the site's own CTA colour, offset behind the photo, ties the
   photo to the brand instead of it floating as a generic stock image.
   Needs a wrapper since the effect sits BEHIND the <img>, not on it. */
.hero-photo-wrap { position: relative; }
.hero-photo-wrap::before { content: ''; position: absolute; z-index: 0; inset: 14px -14px -14px 14px;
  background: var(--cta); border-radius: var(--radius); }
@media (max-width: 719px) { .hero-photo-wrap::before { inset: 10px -10px -10px 10px; } }
/* Mirror the offset for the photo-left layout so the colour block still
   reads as "behind" the photo, not awkwardly overlapping the copy beside it. */
.hero-grid.reverse .hero-photo-wrap::before { inset: 14px 14px -14px -14px; }
@media (max-width: 719px) { .hero-grid.reverse .hero-photo-wrap::before { inset: 10px 10px -10px -10px; } }
/* Skip the offset block for the photo-top banner layout — a full-width
   banner already carries enough visual weight; an offset block behind it
   would just look like a misaligned border, not a deliberate accent. */
.hero-grid.stacked .hero-photo-wrap::before { display: none; }
/* Hero photo carousel (17 Aug 2026) — same zero-JS-required scroll-snap
   pattern as the jobs gallery, applied to the hero photo slot when a site
   sets hero_images[] instead of a single hero_image. .hero-photo-band is
   natively swipeable/scrollable with no JS; the arrows are a progressive-
   enhancement convenience layered on top (site.js). Falls back to the
   plain single-image markup untouched for every site that doesn't set
   hero_images — no behaviour change there. */
.hero-photo-band { display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; border-radius: var(--radius); position: relative; z-index: 1; }
.hero-photo-slide { flex: 0 0 100%; scroll-snap-align: start; }
.hero-photo-band .hero-photo { width: 100%; }
.hero-photo-arrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  /* 44px, not 40 — WCAG 2.5.5 Target Size (Enhanced, AAA) wants 44x44 CSS
     px minimum for a standalone control; free to do, no visual/brand cost. */
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--hairline);
  background: rgba(255,255,255,.92); color: var(--ink); display: inline-flex;
  align-items: center; justify-content: center; cursor: pointer; padding: 0;
  box-shadow: 0 2px 8px rgba(0,0,0,.14); }
.hero-photo-arrow:hover { background: #fff; }
.hero-photo-arrow-prev { left: 10px; }
.hero-photo-arrow-next { right: 10px; }
/* Native swipe already covers touch on a narrow screen — arrows would just
   sit awkwardly on top of a small image there for no real benefit. */
@media (max-width: 560px) { .hero-photo-arrow { display: none; } }
/* Layout axis (A10a) — mirrors the hero: photo swaps to the left visually
   without reordering the DOM, so screen-reader/keyboard order stays
   copy-first regardless of which layout look is picked.
   REGRESSION FIXED 10 Aug 2026: these `order` rules targeted `.hero-photo`
   (the <img> itself), which worked when the img was a direct grid child —
   but wrapping it in `.hero-photo-wrap` (for the branded offset-block
   feature, same session) made the WRAPPER the actual grid item, silently
   breaking both reverse and stacked layouts (order on a non-grid-item
   child does nothing — caught by re-verifying every layout in the browser
   after the wrap was added, not assumed safe because the CSS "looked"
   right). Rules now target `.hero-photo-wrap`. */
.hero-grid.reverse .hero-photo-wrap { order: -1; }
@media (min-width: 720px) { .hero-grid.reverse { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); } }
/* Third layout option (Thomas, 10 Aug 2026): a wide banner photo above the
   headline rather than beside it — single column at every width (not just
   mobile, where the base .hero-grid already collapses to one column), photo
   pulled to the top of source order visually via `order`, wider/shorter
   aspect ratio than the 1:1 side-by-side crop since a square photo stacked
   above text reads as boxy rather than banner-like. */
.hero-grid.stacked { grid-template-columns: 1fr; }
.hero-grid.stacked .hero-photo-wrap { order: -1; margin-bottom: 4px; }
.hero-grid.stacked .hero-photo { aspect-ratio: 21 / 9; width: 100%; }
@media (max-width: 719px) { .hero-grid.stacked .hero-photo { aspect-ratio: 16 / 10; } }

/* Trust band */
.trustband { background: var(--ink); color: #E7EDF1; padding: 16px 0; }
.trust-inner { display: flex; flex-wrap: wrap; gap: 10px 34px; }
.trust-item { font-size: .9rem; }
.trust-item::before { content: "✓ "; color: #9fd8a4; font-weight: 700; }

/* Why us */
.why-copy { max-width: 780px; }
.tick-list { list-style: none; columns: 2; gap: 40px; max-width: 780px; }
.tick-list li { margin-bottom: 10px; break-inside: avoid; padding-left: 28px; position: relative; }
.tick-list li::before { content: "✓"; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
.promise { margin-top: 22px; padding: 14px 18px; background: var(--paper); border-left: 3px solid var(--cta); max-width: 640px; }
/* Owner photo (11 Aug 2026 rework) — was a large 200px portrait in its own
   flex column beside the tick-list/promise copy, with a full-width mobile
   override only below 560px. That left a dead zone (roughly 560-715px
   container width, the flex row's own natural wrap point) where the photo
   wrapped onto its own row alone with nothing to fill the rest of it —
   a real layout bug, not just a style preference, caught by Thomas
   reviewing the dev site at ~645px. Folded into the promise box as a
   small "signed by" avatar instead: no separate column ever exists, so
   there's nothing left to strand at any width, and it reads as more
   directly tied to the promise it's attached to rather than a floating
   headshot elsewhere in the section. */
.promise-signoff { display: flex; align-items: center; gap: 12px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--hairline); }
.owner-avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; border: 1px solid var(--hairline); flex-shrink: 0; }
.owner-byline { font-size: .88rem; font-weight: 650; color: var(--ink); }
.owner-credential { display: block; margin-top: 1px; font-size: .8rem; font-weight: 500; color: var(--accent); }

/* Services */
.services-list { display: grid; gap: 0; border-top: 1px solid var(--hairline); max-width: 860px; }
.service-row { display: flex; gap: 20px; align-items: flex-start; padding: 18px 0; border-bottom: 1px solid var(--hairline); }
.service-photo { width: 96px; height: 96px; flex-shrink: 0; border-radius: var(--radius); border: 1px solid var(--hairline); object-fit: cover; }
.service-copy { flex: 1; min-width: 0; }
.service-row p { color: var(--muted); font-size: .95rem; margin-top: 4px; max-width: 62ch; }

/* Photo band — single scrollable row instead of a wrapping grid (17 Aug
   2026, Thomas: cut the scroll depth between the jobs gallery and the
   enquiry form below it). .photo-band is natively horizontally-scrollable
   (touch swipe, trackpad, scrollbar, arrow keys once focused) with zero
   JS — .photo-arrow buttons are a progressive-enhancement convenience
   layered on top in site.js, never required to reach any photo. */
.photo-carousel { display: flex; align-items: center; gap: 10px; margin-top: 18px; }
.photo-band { display: flex; gap: 16px; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; padding-bottom: 6px; flex: 1; min-width: 0; }
.photo-band figure { flex: 0 0 200px; scroll-snap-align: start; margin: 0; }
.photo-band figure img, .job-ph { border-radius: var(--radius); border: 1px solid var(--hairline); aspect-ratio: 1; object-fit: cover; width: 100%; }
.job-ph { background: linear-gradient(145deg, #d7dfe6 0%, #b3c2cd 100%); }
.photo-band figcaption { text-transform: uppercase; letter-spacing: .1em; font-size: .72rem; font-weight: 650; color: var(--muted); margin-top: 8px; }
.photo-arrow { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  /* 44px, matching the hero carousel's arrows — WCAG 2.5.5 AAA target size. */
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--hairline);
  background: var(--paper); color: var(--ink); cursor: pointer; padding: 0; }
.photo-arrow:hover { background: var(--stone); }
/* Native swipe/scroll already covers touch — arrows would just eat width
   on a narrow screen for no real benefit there. */
@media (max-width: 560px) { .photo-arrow { display: none; } }

/* Credentials */
.cred-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; max-width: 780px; }
.cred-item { display: flex; align-items: center; gap: 10px; background: var(--paper); border: 1px solid var(--hairline); border-radius: var(--radius); padding: 12px 15px; font-size: .95rem;
  transition: transform .18s ease, box-shadow .18s ease; }
.cred-item:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,.07); }
.cred-item svg { color: var(--accent); flex: 0 0 auto; }

/* Reviews */
.review-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; }
.review { background: var(--stone); border: 1px solid var(--hairline); border-radius: var(--radius); padding: 20px;
  transition: transform .18s ease, box-shadow .18s ease; }
.review:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0,0,0,.08); }
.review blockquote { font-style: italic; margin-bottom: 10px; }
.review figcaption { color: var(--muted); font-size: .9rem; }

/* Live reviews (REVIEWS-WORKER-SPEC.md, 15 Aug 2026). [data-reviews-live]
   reserves roughly the height a populated grid ends up at, so the
   placeholder->real-cards swap (which only ever happens well after initial
   load, on scroll-into-view) doesn't register as a Cumulative Layout Shift
   the way the async-CSS regression documented elsewhere in this template
   did — same lesson, applied here before it becomes a repeat finding. */
[data-reviews-live] { min-height: 96px; }
.review-author { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.review-author img { width: 36px; height: 36px; border-radius: 50%; flex: 0 0 auto; }
.review-attribution { color: var(--muted); font-size: .8rem; margin-top: 8px; }
.note-inline { margin-top: 14px; font-size: .92rem; }

/* Reviews carousel (15 Aug 2026, Thomas: 3 cards left an orphan on a
   2-column grid on mobile). Native CSS scroll-snap, not a JS library — 2
   cards fully in view keeps first-scroll depth down; more reachable by
   scrolling right. Works with JS fully off via touch/trackpad drag alone;
   the prev/next buttons in site.js are a mouse-user affordance on top, not
   a requirement for the carousel to function. Shared by both review
   sources via the plain .review-carousel class — the static hand-entered
   `reviews` array (`has_reviews`) and the live Worker-fed block
   (`data-reviews-live`) get identical treatment, since the whole point is
   that a mockup shown for sign-off looks pixel-identical to the eventual
   live version. */
.review-grid.review-carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; -webkit-overflow-scrolling: touch; padding-bottom: 6px; }
.review-grid.review-carousel .review { flex: 0 0 calc(50% - 8px); min-width: 220px; scroll-snap-align: start; }
@media (max-width: 640px) {
  /* A visible sliver of the next card is the scrollability hint on mobile —
     an edge-to-edge single card gives no clue there's more to see. */
  .review-grid.review-carousel .review { flex: 0 0 85%; }
}
.review-carousel-nav { display: flex; gap: 8px; margin-top: 10px; }
.review-carousel-nav button { width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--hairline);
  background: var(--paper); color: var(--ink); cursor: pointer; font-size: 1rem; line-height: 1; }
.review-carousel-nav button:hover { background: var(--stone); }
.review-carousel-nav button:disabled { opacity: .35; cursor: default; }

/* Areas */
.chip-list { list-style: none; display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0; }
.chip-list li { background: var(--paper); border: 1px solid var(--hairline); padding: 6px 14px; border-radius: 999px; font-size: .92rem; }
.area-more summary { cursor: pointer; font-weight: 650; color: var(--ink); margin-bottom: 6px; }
.area-more .chip-list { margin-top: 10px; }

/* FAQ */
.faq-item { border-bottom: 1px solid var(--hairline); }
.faq-item summary { cursor: pointer; padding: 14px 0; font-weight: 650; color: var(--ink); list-style-position: outside; }
.faq-item p { padding: 0 0 16px; color: var(--charcoal); max-width: 65ch; }

/* How it works — sits directly above the enquiry form (1-2-3 flow into the
   form itself). Giant numeral is the dominant visual element, copy sits
   directly under it in the same block so "1" and its two lines read as one
   unit, not a separate caption. Arrows (not a dot/line timeline) connect
   the steps — Thomas's preference, reads as "flow" rather than a progress
   bar. Ported from site-template/preview/how-it-works-mockup.html, confirmed
   by Thomas 11 Aug 2026. */
.how { padding: 36px 0 4px; }
.how .wrap { max-width: 900px; }
.how-grid { display: flex; align-items: flex-start; gap: 4px; }
.how-step { flex: 1 1 0; min-width: 0; text-align: center; }
.how-arrow { flex: 0 0 auto; align-self: flex-end; margin-bottom: .5rem; opacity: .55; }
/* Bottom-aligned with the step's description line, not up by the numeral.
   Inline SVG, not a text arrow glyph — a text "→" rendered as barely more
   than a line on Thomas's mobile (font-fallback substituting a minimal
   glyph for the custom webfont stack). An SVG can't have that problem. */
.how-arrow svg { display: block; width: 28px; height: 28px; }
.how-num { font-family: var(--font-head); font-weight: 800; line-height: 1;
  font-size: clamp(3rem, 8vw, 4.5rem); color: var(--cta); margin-bottom: 2px; }
.how-step h3 { font-size: .95rem; margin-bottom: 2px; }
.how-step p { font-size: .82rem; color: var(--muted); max-width: 20ch; margin: 0 auto; line-height: 1.35; }

/* Mobile: 3 fully-stacked centred blocks add real scroll length even though
   the text itself is short — swap to compact left-aligned rows (smaller
   inline number beside the title, one description line under), with a
   downward arrow between rows instead of the horizontal one between
   columns — same "flow" motif, rotated to match the stacking. */
@media (max-width: 719px) {
  .how-grid { flex-direction: column; gap: 0; }
  /* Arrow sits directly under the numbers — same 44px width as the number
     column above it, so it continues the flow straight down from each
     numeral rather than floating in the middle of the full row. */
  .how-arrow { align-self: flex-start; width: 44px; display: flex;
    justify-content: center; margin: 4px 0; }
  .how-arrow svg { transform: rotate(90deg); }
  .how-step { display: grid; grid-template-columns: 44px 1fr; column-gap: 12px;
    text-align: left; align-items: start; width: 100%; }
  /* Numeral centred within its 44px column (inherits text-align:left from
     .how-step otherwise, throwing it flush left and out of step with the
     arrow, which IS centred in that column) — centres the numeral itself,
     not just the arrow, so both align on the same visual middle. */
  .how-num { font-size: 1.9rem; grid-row: 1 / 3; margin-bottom: 0; text-align: center; }
  .how-step h3 { margin-bottom: 1px; align-self: end; }
  .how-step p { max-width: none; margin: 0 0 6px; }
}

/* Form */
.enquiry-form { display: grid; gap: 18px; background: var(--paper); border: 1px solid var(--hairline); border-radius: var(--radius); padding: 26px 24px; }
.field label, .field legend { font-weight: 650; display: block; margin-bottom: 6px; color: var(--ink); }
.field input, .field select, .field textarea {
  width: 100%; padding: 12px 14px; border: 1px solid var(--hairline); border-radius: var(--radius);
  font: inherit; background: var(--paper); color: var(--charcoal); }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); }
.field .hint { font-size: .85rem; color: var(--muted); margin-top: 6px; }
.hint.centre { text-align: center; font-size: .85rem; color: var(--muted); }
/* Availability note: a temporary, trader-editable heads-up (high demand, on
   leave, etc.) — separate from response_promise, which is the standing
   promise. Warm tint so it reads as "worth noting" without looking like an
   error/warning state. Empty by default; only shows when a trader sets it. */
.availability-note { margin-top: 14px; padding: 12px 16px; background: #FBF3EA; border-left: 3px solid var(--cta); border-radius: var(--radius); font-size: .92rem; font-weight: 650; color: var(--ink); }
/* Demo-mode notice: a draft/preview site's form isn't wired to the trader's
   inbox yet (see build.py's form_demo_notice). Neutral grey, not the warm
   availability-note tint — this isn't trader-facing content, it's a build-
   state fact for whoever's reviewing the draft, so it reads as informational
   rather than "worth noting" business copy. */
.demo-note { margin-top: 14px; padding: 12px 16px; background: var(--stone); border-left: 3px solid var(--muted); border-radius: var(--radius); font-size: .92rem; font-weight: 650; color: var(--muted); }
/* Logo mark image (a trader's real logo icon, cropped from their own brand
   asset) sitting inline before the trading-name text — header uses an
   ink-coloured version for the light topbar, footer uses a white version
   for the dark ink footer (see build.py's site-specific assets convention). */
.logo-mark-img { height: 24px; width: auto; vertical-align: -6px; margin-right: 8px; }
.foot-mark-img { height: 22px; width: auto; vertical-align: -5px; margin-right: 8px; }
/* Full logomark+wordmark lockup, cropped whole from a trader's own real
   logo asset (mark + real typography, not our own text approximating their
   font) rather than icon-plus-separately-set text — see build.py's site-
   specific assets convention. Ink version for the light topbar, white for
   the dark footer. max-width + .topbar-inner's flex-wrap (below) let it
   shrink and, if truly squeezed, drop to its own row above the CTA button
   rather than overflow or clip. */
.logo-lockup-img { height: 52px; width: auto; max-width: 100%; display: block; }
.foot-lockup-img { height: 42px; width: auto; max-width: 100%; display: block; }
/* Header CTA sized up to match the larger lockup (Thomas, 17 Aug 2026) —
   scoped to .head-ctas only, not the shared .btn base, so the hero CTA and
   sticky mobile bar are untouched. */
.head-ctas .btn-cta { padding: 16px 26px; font-size: 1.06rem; }
/* Turnstile: appearance=interaction-only means this renders at zero height for
   the vast majority of visitors — no box, no branding line, nothing in the way
   of filling out the form. It only expands if Cloudflare decides an actual
   interactive check is needed, so this margin only ever applies in that rare
   case rather than adding scroll depth for everyone. */
.cf-turnstile:not(:empty) { margin: 4px 0; }
.optional { font-weight: 400; color: var(--muted); }
fieldset.field { border: 0; }
.radio { display: flex; align-items: center; gap: 10px; font-weight: 400; padding: 7px 0; }
.radio input { width: auto; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.form-msg { padding: 12px 16px; border-radius: var(--radius); background: #e7f3e9; border: 1px solid #a9d3b0; }
.hp { position: absolute; left: -9999px; height: 0; overflow: hidden; }

/* Footer */
.site-foot { background: var(--ink); color: #C9D4DC; padding: 44px 0 90px; }
.site-foot a { color: #fff; }
.foot-name { font-weight: 700; color: #fff; margin-bottom: 6px; }
.logo-img { height: 28px; width: auto; display: block; }
/* Icon-beside-two-line-text lockup (foreman-header-logo-2line.svg), built from the
   real icon geometry + Fredoka rather than cropping the stacked full-lockup file —
   far more height-efficient (text sits beside the icon, not below it), so true
   16px CTA-text parity only needs ~74px, not the ~143px the stacked layout required. */
.logo-img-2line-flat { height: 74px; width: auto; max-width: none; flex-shrink: 0; display: block; }
.foot-logo-img { height: 32px; width: auto; max-width: 100%; display: block; }
.foot-logo-img-2line-flat { height: 60px; width: auto; max-width: 100%; display: block; }
.foot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 26px; }
.foot-label { text-transform: uppercase; letter-spacing: .1em; font-size: .72rem; font-weight: 650; color: #C9D4DC; opacity: .8; margin-bottom: 10px; }
.foot-creds { list-style: none; font-size: .88rem; }
/* Wiring the existing .cred-grid/.cred-item component into the footer
   (11 Aug 2026) — it was styled for a light section (white card, dark
   border/text) and had never actually been used anywhere; the footer is
   dark, so a straight reuse would put light-grey footer text on a white
   card at broken contrast. Overridden here for the dark context instead
   of building a second component — reuses colours already proven safe
   in this exact footer (the "Hosted on 100% renewable energy" badge
   below uses the same #9fd8a4 icon green and translucent-white border). */
.foot-creds .cred-item { background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.18); color: #C9D4DC; font-size: .88rem; padding: 10px 13px; }
.foot-creds .cred-item svg { color: #9fd8a4; }
/* Credentials pulled OUT of .foot-grid into its own full-width row (11 Aug
   2026, Thomas) — with up to 7 cards it was the tallest thing in the
   3-column grid by far, and CSS grid rows share height across every column,
   so the two shorter columns (business info, contact info) were left with a
   large empty gap matching the credentials column's height. As a full-width
   block below the 2-column row instead, .cred-grid's own
   repeat(auto-fit, minmax(240px, 1fr)) finally gets the whole footer's width
   to flow into — several columns wide on desktop, same single-column stack
   as before on mobile (unchanged, still responsive) — rather than being
   squeezed into one ~250px grid column and forced to stack tall. */
.foot-creds-block { margin-top: 26px; padding-top: 22px; border-top: 1px solid rgba(255,255,255,.12); }
.foot-badges { margin-top: 26px; display: flex; flex-wrap: wrap; gap: 10px; }
.badge { display: inline-flex; align-items: center; gap: 7px; font-size: .8rem; color: #C9D4DC;
  border: 1px solid rgba(255,255,255,.28); border-radius: 999px; padding: 6px 14px; text-decoration: none; }
.badge:hover { border-color: rgba(255,255,255,.6); }
.badge svg { color: #9fd8a4; }
/* Platform badge (Website by {{platform_name}}) given more visual weight
   than the neutral outline badges beside it (18 Aug 2026, Thomas) — this
   is the platform's own stage-1 acquisition loop (A19-MARKETING-PLAN.md),
   worth it standing out a little rather than blending in as just another
   footer credential. Filled instead of outline, bolder text, white
   instead of the muted footer grey; still restrained, not a button. */
.badge-platform { background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.5);
  color: #fff; font-weight: 700; }
.badge-platform:hover { background: rgba(255,255,255,.22); border-color: rgba(255,255,255,.8); }
.foot-legal { margin-top: 18px; font-size: .82rem; opacity: .75; }

/* Sticky mobile bar — enquiry primary, call fallback */
.sticky-call { position: fixed; bottom: 0; left: 0; right: 0; display: flex; gap: 10px;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: rgba(255,255,255,.96); backdrop-filter: blur(6px); border-top: 1px solid var(--hairline); z-index: 20; }
.sticky-main { flex: 1; }
.sticky-side { flex: 0 0 auto; }
@media (min-width: 720px) { .sticky-call { display: none; } .site-foot { padding-bottom: 44px; } }

@media (max-width: 860px) {
  .hero-grid { grid-template-columns: 1fr; gap: 28px; }
  .hero h1 { max-width: none; }
  .hero-photo { aspect-ratio: 16 / 10; width: 100%; }
}
@media (max-width: 480px) {
  .service-row { flex-direction: column; gap: 10px; }
  .service-photo { width: 100%; height: auto; aspect-ratio: 16 / 9; }
}
@media (max-width: 560px) {
  .tick-list { columns: 1; }
  .field-row { grid-template-columns: 1fr; }
  .head-phone { display: none; }
  section { padding: 38px 0; }
  .logo-img { height: 22px; }
  .logo-img-2line-flat { height: 58px; }
  .logo-lockup-img { height: 42px; }
  .head-ctas .btn-cta { padding: 13px 20px; font-size: 1rem; }
}
@media (max-width: 340px) {
  .topbar-inner { gap: 8px; padding: 12px 14px; }
  .logo-img { height: 18px; }
  .logo-img-2line-flat { height: 48px; }
  .logo-lockup-img { height: 34px; }
  .head-ctas .btn-cta { padding: 11px 16px; font-size: .94rem; }
}
