/* Pulfrich & Hafer GmbH — site styles. Mobile first. Tokens on :root.
   Spacing scale --s1…--s6, one surface padding, one elevation language:
   flat at rest with a 1px line, lift + shadow only on hover of clickable surfaces. */

@font-face { font-family: "Barlow"; font-weight: 400; font-style: normal; font-display: swap; src: url("../fonts/Barlow-400.woff2") format("woff2"); }
@font-face { font-family: "Barlow"; font-weight: 500; font-style: normal; font-display: swap; src: url("../fonts/Barlow-500.woff2") format("woff2"); }
@font-face { font-family: "Barlow"; font-weight: 600; font-style: normal; font-display: swap; src: url("../fonts/Barlow-600.woff2") format("woff2"); }
@font-face { font-family: "Barlow Semi Condensed"; font-weight: 600; font-style: normal; font-display: swap; src: url("../fonts/Barlow-Semi-Condensed-600.woff2") format("woff2"); }
@font-face { font-family: "Barlow Semi Condensed"; font-weight: 700; font-style: normal; font-display: swap; src: url("../fonts/Barlow-Semi-Condensed-700.woff2") format("woff2"); }

:root {
  /* colour */
  --navy: #182858;
  --navy-deep: #0f1a3d;
  --navy-soft: #24376f;
  --navy-rgb: 15, 26, 61;
  --taupe: #b0a090;
  --sand: #f4f0ea;
  --cta: #c4460a;
  --cta-hover: #a33806;
  --cta-text: #a83a06;
  --hero-kicker: #ffb37a;
  --whatsapp: #0f7a3f;
  --whatsapp-hover: #0b5f31;
  --error: #b3261e;
  --note-bg: #fff4e5;
  --note-text: #6b3a10;
  --white: #ffffff;
  --ink: #1c2130;
  --ink-muted: #545a68;
  --line: #d9d5ce;
  /* type */
  --font-body: "Barlow", "Segoe UI", Arial, sans-serif;
  --font-head: "Barlow Semi Condensed", "Arial Narrow", Arial, sans-serif;
  /* Split ratio: 1.2 above body, 1.09 below. A single 1.2 would drag --fs-label to
     ~11.8px, too small to read outdoors and under the AA floor, so small text is
     deliberately larger than a pure geometric scale. Min ends use a gentler 1.14 so
     headings stay inside German compound nouns at 390px. */
  --fs-h1: clamp(2.35rem, 1.5rem + 3.4vw, 3.4rem);
  --fs-h2: clamp(1.85rem, 1.45rem + 1.8vw, 2.2rem);
  --fs-h2-small: clamp(1.55rem, 1.3rem + 1vw, 1.836rem);
  --fs-h3: clamp(1.381rem, 1.2rem + 0.6vw, 1.53rem);
  --fs-lead: clamp(1.211rem, 1.05rem + 0.6vw, 1.275rem);
  --fs-small: 0.975rem;
  --fs-label: 0.894rem;
  /* spacing */
  --s1: 0.5rem;
  --s2: 0.75rem;
  --s3: 1rem;
  --s4: 1.5rem;
  --s5: 2rem;
  --s6: 3rem;
  --surface-pad: var(--s4);
  --gap-icon: var(--s1);
  --gap-grid: var(--s4);
  --section: clamp(3rem, 6vw, 5.5rem);
  /* Varied rhythm: the page should breathe differently around a dominant section
     than around a quiet one. Uniform padding on every band is the page-builder tell. */
  --section-tight: clamp(2.25rem, 4vw, 3.75rem);
  --section-loose: clamp(4rem, 8.5vw, 7.5rem);
  /* shape */
  --wrap: 1180px;
  --gutter: 16px;
  --radius: 6px;
  --radius-pill: 999px;
  --shadow: 0 2px 8px rgba(var(--navy-rgb), 0.08), 0 12px 32px rgba(var(--navy-rgb), 0.1);
  --shadow-header: 0 2px 12px rgba(var(--navy-rgb), 0.12);
  --ease: 180ms ease-out;
  --header-h: 68px;
  /* Motion. Three durations and three curves, so the whole site moves in one language.
     --ease stays the utility transition for colour and small state changes.
     Springs proper live in site.js; these cover the cases CSS can own outright.
     --ease-out-soft is the CSS approximation of a critically damped spring: fast
     departure, long quiet settle, no overshoot. --ease-spring carries a small overshoot
     and is reserved for elements a gesture just released. */
  --dur-fast: 140ms;
  --dur-base: 280ms;
  --dur-slow: 520ms;
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-in-out-soft: cubic-bezier(0.65, 0, 0.35, 1);
  /* Distance a revealing element travels. Small on purpose: a long slide reads as a
     page-builder effect and costs more than it communicates. */
  --reveal-rise: 14px;
  --stagger: 60ms;
}
@media (min-width: 900px) { :root { --header-h: 84px; --gap-icon: var(--s2); } }

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; scroll-padding-top: calc(var(--header-h) + 16px); }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--ink);
  background: var(--white);
}
img, picture { display: block; max-width: 100%; height: auto; }
h1, h2, h3, h4 { font-family: var(--font-head); font-weight: 700; line-height: 1.12; margin: 0 0 0.5em; color: var(--navy); letter-spacing: 0.005em; text-wrap: balance; }
/* Tracking is size-specific, never one value for the whole scale: letters read as too
   far apart as they grow, and too tight as they shrink. Large display type therefore
   takes negative tracking and small labels take positive, which is why --fs-label and
   .kicker carry 0.08em while h1 carries -0.018em.
   Leading moves inversely to size for the same reason: 1.02 on the h1, 1.55 on body. */
h1 { letter-spacing: -0.018em; line-height: 1.04; }
h2 { letter-spacing: -0.012em; line-height: 1.08; }
h3 { letter-spacing: -0.004em; }
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
p { margin: 0 0 1em; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: var(--navy); text-underline-offset: 0.15em; }
a:hover { color: var(--cta-text); }
address { font-style: normal; }
svg { flex: none; }
strong { font-weight: 600; }

:focus-visible { outline: 3px solid var(--cta); outline-offset: 3px; box-shadow: 0 0 0 3px var(--white); border-radius: 2px; }
.skip { position: absolute; left: var(--gutter); top: -100px; z-index: 100; padding: var(--s1) var(--s3); background: var(--navy); color: var(--white); font-weight: 600; border-radius: var(--radius); }
.skip:focus { top: var(--gutter); }
.visually-hidden { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
main:focus { outline: none; }

/* Layout primitives */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }
.narrow { max-width: 780px; }
.section { padding-block: var(--section); }
.section-tight { padding-block: var(--section-tight); }
.section-loose { padding-block: var(--section-loose); }
.section-sand { background: var(--sand); }
.section-navy { background: var(--navy); color: var(--white); }
.section-navy h2 { color: var(--white); }
.section-head { max-width: 720px; margin-bottom: var(--s5); }
.section-head p { font-size: 1.15rem; color: var(--ink-muted); }
.section-foot { margin-top: var(--s5); }
.kicker { font-family: var(--font-head); font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; font-size: var(--fs-label); color: var(--ink-muted); margin-bottom: var(--s1); }
.kicker a { color: inherit; text-decoration: none; }
.kicker a:hover { text-decoration: underline; }
.lead { font-size: var(--fs-lead); color: var(--ink-muted); max-width: 40em; text-wrap: pretty; }
.surface { background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); }
.icon-row { display: flex; align-items: center; gap: var(--gap-icon); }

/* Buttons */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--s1); min-height: 48px; padding: 0.7rem 1.25rem; border-radius: var(--radius); font-family: var(--font-head); font-weight: 700; font-size: 1.1rem; letter-spacing: 0.01em; text-decoration: none; border: 2px solid transparent; cursor: pointer; transition: background-color var(--ease), color var(--ease), border-color var(--ease), transform var(--ease), box-shadow var(--ease); }
@media (any-hover: hover) { .btn:hover { transform: translateY(-1px); } }
.btn:active { transform: translateY(1px) scale(0.98); }
.btn-lg { min-height: 56px; padding: 0.85rem 1.6rem; font-size: 1.2rem; }
.btn-cta { background: var(--cta); color: var(--white); border-color: var(--cta); }
.btn-cta:hover { background: var(--cta-hover); border-color: var(--cta-hover); color: var(--white); }
.btn-light { background: var(--white); color: var(--navy); border-color: var(--white); }
.btn-light:hover { background: var(--sand); border-color: var(--sand); color: var(--navy); }
.btn-outline { background: transparent; color: var(--navy); border-color: var(--navy); }
.btn-outline:hover { background: var(--navy); color: var(--white); }
.btn-outline-light { background: transparent; color: var(--white); border-color: rgba(255, 255, 255, 0.7); }
.btn-outline-light:hover { background: rgba(255, 255, 255, 0.12); color: var(--white); border-color: var(--white); }
.btn-whatsapp { background: var(--whatsapp); color: var(--white); border-color: var(--whatsapp); }
.btn-whatsapp:hover { background: var(--whatsapp-hover); border-color: var(--whatsapp-hover); color: var(--white); }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s4); }
.link-arrow { display: inline-flex; align-items: center; gap: var(--s1); font-weight: 600; }

/* Header */
.site-header { position: sticky; top: 0; z-index: 50; background: var(--white); border-bottom: 1px solid var(--line); transition: box-shadow var(--ease); }
.site-header.is-scrolled { box-shadow: var(--shadow-header); }
.header-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); min-height: var(--header-h); }
.brand { display: flex; flex-direction: column; gap: 2px; text-decoration: none; color: var(--navy); }
.brand img { height: 44px; width: auto; }
.brand-tagline { display: none; font-family: var(--font-head); font-weight: 600; font-size: 0.58rem; letter-spacing: 0.02em; text-transform: uppercase; color: var(--ink-muted); padding-left: 2px; white-space: nowrap; }
.header-actions { display: flex; align-items: center; gap: var(--s1); }
.btn-phone { min-height: 44px; padding: 0.5rem 0.9rem; font-size: 1rem; }
.btn-phone span { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.nav-toggle { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); cursor: pointer; position: relative; }
.nav-toggle-bar, .nav-toggle-bar::before, .nav-toggle-bar::after { display: block; width: 22px; height: 2px; background: var(--navy); transition: transform var(--ease), opacity var(--ease); }
.nav-toggle-bar { position: relative; }
.nav-toggle-bar::before, .nav-toggle-bar::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bar::before { top: -7px; }
.nav-toggle-bar::after { top: 7px; }
.nav-toggle:active { background: var(--sand); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar::before { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar::after { transform: translateY(-7px) rotate(-45deg); }
.nav { display: none; position: absolute; left: 0; right: 0; top: 100%; background: var(--white); border-bottom: 1px solid var(--line); box-shadow: var(--shadow); }
.nav.is-open { display: block; }
.nav ul { padding: var(--s1) var(--gutter) var(--s3); }
.nav a { display: block; padding: 0.85rem var(--s1); font-family: var(--font-head); font-weight: 600; font-size: 1.25rem; text-decoration: none; border-bottom: 1px solid var(--line); }
.nav a[aria-current="page"] { color: var(--cta-text); }

/* Submenu. No JS: the nested list is simply visible and every link is reachable.
   With JS the toggle button collapses it (html.js-nav), so nothing is ever unreachable
   because a script failed. */
.submenu-head { display: flex; align-items: center; }
.submenu-head a { flex: 1; }
.submenu-toggle { display: flex; align-items: center; justify-content: center; width: 48px; min-height: 48px; padding: 0; border: 0; background: none; color: var(--navy); cursor: pointer; border-bottom: 1px solid var(--line); }
.submenu-toggle svg { transition: transform var(--ease); }
.submenu-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.submenu-toggle:active { background: var(--sand); }
.submenu { padding: 0 0 0 var(--s3); }
.submenu a { font-size: 1.05rem; font-weight: 500; padding-block: 0.7rem; }
.js-nav .has-submenu .submenu { display: none; }
.js-nav .has-submenu .submenu.is-open { display: block; }

@media (min-width: 900px) {
  .brand img { height: 62px; }
  .brand-tagline { display: block; }
  .nav { display: block; position: static; background: none; border: 0; box-shadow: none; }
  .nav ul { display: flex; gap: 0.25rem; padding: 0; }
  .nav a { border: 0; padding: 0.6rem 0.9rem; font-size: 1.15rem; border-radius: var(--radius); }
  .nav a:hover { background: var(--sand); }
  /* Desktop dropdown. The panel is positioned under its item; it opens on hover and on
     keyboard focus anywhere inside, so it is reachable without a pointer. */
  .has-submenu { position: relative; }
  .submenu-head { gap: 0; }
  .submenu-head a { flex: 0 0 auto; }
  .submenu-toggle { width: auto; min-width: 24px; min-height: 24px; padding: 0 0.3rem 0 0.1rem; border: 0; }
  /* No `display: none` here: without .js-nav this must stay visible, or a desktop
     visitor with JS off loses all eight service links. Only .js-nav collapses it. */
  .nav .submenu { position: absolute; top: 100%; left: 0; z-index: 60; min-width: 16rem; padding: var(--s1); background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
  .nav .submenu a { border: 0; border-radius: var(--radius); font-size: 1rem; padding: 0.55rem 0.7rem; white-space: nowrap; }
  .js-nav .has-submenu .submenu { display: none; }
  .js-nav .has-submenu:hover .submenu,
  .js-nav .has-submenu:focus-within .submenu,
  .js-nav .has-submenu .submenu.is-open { display: block; }
  .nav a:active { background: var(--line); }
  .nav-toggle { display: none; }
  .btn-phone { min-height: 48px; padding: 0.6rem 1.1rem; font-size: 1.1rem; }
  .btn-phone span { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; }
}

/* Hero video. Sits above the photo but starts at opacity 0 and is only revealed once
   the browser reports it can play through (.is-playing, set in site.js). If the file
   fails, the connection is slow, the visitor saves data, or JS is off, the photo hero
   below stays visible and nothing is lost. Never the LCP element: the <picture> is
   first in the markup and carries the preload. */
.hero-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 600ms ease-out; pointer-events: none; }
.hero-video.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .hero-video { display: none; } }

/* Header over the hero: transparent at the top, solid white once scrolled. Only where a
   photographic hero sits behind it - the sand-coloured .page-head would leave white text
   on a pale ground. The logo and phone button invert with it. */
.is-home .site-header:not(.is-scrolled) { background: linear-gradient(to bottom, rgba(var(--navy-rgb), 0.55), rgba(var(--navy-rgb), 0.15)); border-bottom-color: transparent; }
/* White only on the desktop top-level links. The dropdown panel and the phone menu are white
   panels, so their links keep the default navy: a plain `.nav a` here turned them white on
   white, and the whole menu looked empty while the page sat at the top. */
.is-home .site-header:not(.is-scrolled) .brand,
.is-home .site-header:not(.is-scrolled) .brand-tagline { color: var(--white); }
.is-home .site-header:not(.is-scrolled) .nav-toggle { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.45); }
.is-home .site-header:not(.is-scrolled) .nav-toggle-bar,
.is-home .site-header:not(.is-scrolled) .nav-toggle-bar::before,
.is-home .site-header:not(.is-scrolled) .nav-toggle-bar::after { background: var(--white); }
.is-home .site-header:not(.is-scrolled) .brand img { filter: brightness(0) invert(1); }
@media (min-width: 900px) {
  .is-home .site-header:not(.is-scrolled) .nav > ul > li > a,
  .is-home .site-header:not(.is-scrolled) .submenu-head > a,
  .is-home .site-header:not(.is-scrolled) .submenu-toggle { color: var(--white); }
  .is-home .site-header:not(.is-scrolled) .nav > ul > li > a:hover,
  .is-home .site-header:not(.is-scrolled) .submenu-head > a:hover { background: rgba(255, 255, 255, 0.15); }
}
/* The hero starts under the header instead of below it, so the photo runs to the top. */
.is-home .hero { margin-top: calc(var(--header-h) * -1); padding-top: calc(var(--header-h) + clamp(3rem, 8vw, 6rem)); }

/* Hero */
.hero { position: relative; isolation: isolate; overflow: hidden; color: var(--white); min-height: min(84vh, 760px); min-height: min(84svh, 760px); display: flex; align-items: flex-end; padding-block: clamp(3rem, 8vw, 6rem) clamp(2.5rem, 6vw, 5rem); }
.hero-media { position: absolute; inset: 0; z-index: -2; }
.hero-media picture { height: 100%; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; object-position: center 40%; }
/* mobile: text sits at the bottom, so darken downward; desktop: text sits left, so darken leftward and let the excavator show */
.hero::before { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(var(--navy-rgb), 0.45) 0%, rgba(var(--navy-rgb), 0.62) 45%, rgba(var(--navy-rgb), 0.92) 100%); }
@media (min-width: 900px) {
  .hero::before { background: linear-gradient(90deg, rgba(var(--navy-rgb), 0.94) 0%, rgba(var(--navy-rgb), 0.88) 38%, rgba(var(--navy-rgb), 0.5) 62%, rgba(var(--navy-rgb), 0.25) 100%), linear-gradient(180deg, rgba(var(--navy-rgb), 0) 55%, rgba(var(--navy-rgb), 0.55) 100%); }
}
.hero-content { max-width: var(--wrap); }
.hero h1 { color: var(--white); max-width: 14em; }
.hero .kicker { color: var(--hero-kicker); }
.hero .lead { color: rgba(255, 255, 255, 0.92); }
.hero-trust { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s4); margin-top: var(--s4); padding: 0; font-weight: 500; font-size: var(--fs-small); }
.hero-trust li { display: flex; align-items: center; gap: var(--s1); color: rgba(255, 255, 255, 0.92); }
.hero-trust svg { color: var(--hero-kicker); }
/* four items: one column on phones, two-by-two on tablets, one row on desktop; never a 1-2-1 wrap */
@media (max-width: 599px) { .hero-trust { flex-direction: column; gap: var(--s1); } }
@media (min-width: 600px) and (max-width: 899px) { .hero-trust { display: grid; grid-template-columns: repeat(2, max-content); } }
.nobr { white-space: nowrap; }

/* Proof strip */
.proof { background: var(--navy); color: var(--white); border-top: 4px solid var(--cta); }
.proof-list { display: grid; gap: var(--s3); padding-block: var(--s4); }
.proof-list li { display: flex; flex-direction: column; gap: 0.1rem; border-left: 3px solid var(--taupe); padding-left: var(--s2); }
.proof-list strong { font-family: var(--font-head); font-weight: 700; font-size: 1.5rem; line-height: 1.1; }
.proof-list span { color: rgba(255, 255, 255, 0.85); }
@media (min-width: 640px) { .proof-list { grid-template-columns: repeat(3, 1fr); padding-block: var(--s4); } }

/* Figures band. The numeral is the loudest type on the page after the h1, set in the
   condensed cut with tabular figures so the width does not jitter while counting up —
   a proportional 1 is narrower than a 0, which makes a counter visibly twitch.
   `ch`-based min-width reserves the final width from the first frame so neither the
   number nor anything beside it reflows mid-count. */
/* Photo band with translucent boxes (reference Michael supplied, 2026-09-24). The photo is an
   <img> in .figures-media rather than a CSS background, so it gets srcset, lazy loading and the
   same metadata-stripped variants as every other photo. Contrast: the navy overlay keeps the
   white heading and intro above 4.5:1 even over the bright sky, and each box adds 80 % navy on
   top, so the small labels pass wherever they land on the photo. */
.figures { position: relative; isolation: isolate; overflow: hidden; background: var(--navy); color: var(--white); padding-block: clamp(3.5rem, 8vw, 6rem); }
.figures-media { position: absolute; inset: 0; z-index: -2; }
.figures-media picture, .figures-media img { display: block; width: 100%; height: 100%; }
.figures-media img { object-fit: cover; object-position: center 40%; }
/* Scrim only where the heading sits; it fades out behind the boxes so the photo stays vivid
   there, as in the reference. The boxes carry their own contrast. */
.figures::before { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(var(--navy-rgb), 0.78) 0%, rgba(var(--navy-rgb), 0.55) 32%, rgba(var(--navy-rgb), 0.12) 62%, rgba(var(--navy-rgb), 0.2) 100%); }
.figures h2 { color: var(--white); }
.figures .section-head .kicker { color: var(--hero-kicker); }
.figures .section-head p { color: rgba(255, 255, 255, 0.9); }
.figure-list { display: grid; gap: var(--s3); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 900px) { .figure-list { grid-template-columns: repeat(4, 1fr); gap: var(--s4); } }
/* 72 % navy, no blur: the photo shows through like in the reference. Worst case, white
   label text over a white patch of sky still measures about 6:1. Content starts at the top
   so the numbers line up even when one label wraps to more lines. */
.figure { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; min-height: 12rem; padding: var(--s5) var(--s2) var(--s4); text-align: center; background: rgba(24, 40, 88, 0.72); border-radius: var(--radius); overflow: hidden; }
@media (min-width: 900px) { .figure { min-height: 17rem; padding: 3.75rem var(--s4) var(--s5); } }
.figure::after { content: ""; position: absolute; top: 0; left: 0; width: 0; height: 3px; background: var(--cta); transition: width var(--dur-slow) var(--ease-out-soft); }
.figure-value { display: flex; align-items: baseline; justify-content: center; gap: 0.1em; margin: 0; font-family: var(--font-head); font-weight: 700; font-size: clamp(3rem, 2rem + 4.4vw, 4.6rem); line-height: 0.95; letter-spacing: -0.03em; color: var(--white); }
.figure-num { font-variant-numeric: tabular-nums; min-width: 1.6ch; }
.figure-suffix { font-size: 0.55em; color: var(--hero-kicker); }
.figure-label { margin: var(--s2) 0 0; font-family: var(--font-head); font-weight: 600; font-size: 1.15rem; line-height: 1.2; color: var(--white); }
.figure-note { margin: 0.25rem 0 0; font-size: var(--fs-small); color: rgba(255, 255, 255, 0.82); line-height: 1.4; }

/* The rule fills as the number counts. Driven by .is-visible on the section (the reveal
   observer) rather than by JS on each figure, so it still runs when the counter itself
   is skipped for reduced motion — with animation disabled the rule is simply full. */
.figures.is-visible .figure::after,
html:not(.js-reveal) .figure::after { width: 100%; }
.figure:nth-child(2)::after { transition-delay: var(--stagger); }
.figure:nth-child(3)::after { transition-delay: calc(var(--stagger) * 2); }
.figure:nth-child(4)::after { transition-delay: calc(var(--stagger) * 3); }

/* "So läuft es ab" on the home page: the same photo band and translucent navy boxes as the
   figures band (Michael's request, 2026-09-24), so the two dark bands read as one family:
   72 % navy boxes, no border. The photo is platten-04 (base course with pavers waiting on
   pallets) - work in progress, and not one of the reference tiles directly below. It is
   cropped from the right (78 %) because a worker stands at its left edge. Body text contrast
   on the boxes was measured on the rendered pixels, not assumed. The service pages keep the compact light
   variant; this only applies when the section has a photo. */
.process-band { position: relative; isolation: isolate; overflow: hidden; background: var(--navy); color: var(--white); }
.process-media { position: absolute; inset: 0; z-index: -2; }
.process-media picture, .process-media img { display: block; width: 100%; height: 100%; }
/* On wide screens the photo is cropped top and bottom, never left and right, so
   object-position alone cannot hide the worker; the 8 % scale anchored at the right edge
   pushes the left edge out of frame at every width. */
.process-media img { object-fit: cover; object-position: 78% 60%; transform: scale(1.08); transform-origin: 100% 60%; }
.process-band::before { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(var(--navy-rgb), 0.84) 0%, rgba(var(--navy-rgb), 0.62) 34%, rgba(var(--navy-rgb), 0.3) 70%, rgba(var(--navy-rgb), 0.42) 100%); }
.process-band h2 { color: var(--white); }
.process-band .section-head .kicker { color: var(--hero-kicker); }
.process-band .section-head p { color: rgba(255, 255, 255, 0.9); }
.process-band .step { overflow: hidden; background: rgba(24, 40, 88, 0.72); border-color: transparent; }
.process-band .step::before { color: var(--hero-kicker); }
.process-band .step h3 { color: var(--white); }
.process-band .step p { color: rgba(255, 255, 255, 0.88); }
/* Same orange rule as the figures, filling in step order as the band scrolls in. */
.process-band .step::after { content: ""; position: absolute; top: 0; left: 0; width: 0; height: 3px; background: var(--cta); transition: width var(--dur-slow) var(--ease-out-soft); }
.process-band.is-visible .step::after,
html:not(.js-reveal) .process-band .step::after { width: 100%; }
.process-band .step:nth-child(2)::after { transition-delay: var(--stagger); }
.process-band .step:nth-child(3)::after { transition-delay: calc(var(--stagger) * 2); }
.process-band .step:nth-child(4)::after { transition-delay: calc(var(--stagger) * 3); }

/* Hover: the box grows a little (3 %) and deepens. `scale` is the independent property, not
   `transform`, so it never competes with the transform the grid reveal animates on the same
   element. The reveal's shorthand sets `transition` with a per-item delay, so it is restated
   here with the hover properties added and their delay pinned to 0 - otherwise the fourth
   box would wait 180 ms before reacting to the pointer. 220 ms strong ease-out: fast on
   contact, soft landing, well under the 300 ms where hover starts to feel laggy. */
.process-band .step { transition: scale 220ms var(--ease-out-soft), background-color 220ms var(--ease-out-soft); }
.js-reveal .reveal.is-visible .stagger > .step {
  transition: opacity var(--dur-slow) var(--ease-out-soft) calc(var(--i, 0) * var(--stagger)),
    transform var(--dur-slow) var(--ease-out-soft) calc(var(--i, 0) * var(--stagger)),
    scale 220ms var(--ease-out-soft) 0s,
    background-color 220ms var(--ease-out-soft) 0s;
}
@media (any-hover: hover) {
  .process-band .step:hover { scale: 1.03; background-color: rgba(24, 40, 88, 0.9); }
}

/* Service cards */
.cards { display: grid; gap: var(--gap-grid); }
.card { position: relative; display: flex; flex-direction: column; background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; transition: box-shadow var(--ease), transform var(--ease), border-color var(--ease); }
@media (any-hover: hover) { .card:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: transparent; } }
.card:active { box-shadow: var(--shadow); transform: translateY(0); border-color: transparent; }
.card:focus-within { outline: 3px solid var(--cta); outline-offset: 2px; box-shadow: 0 0 0 3px var(--white); }
.card img { aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
.card-body { padding: var(--surface-pad); display: flex; flex-direction: column; gap: var(--s1); flex: 1; }
.card-h { margin: 0; }
.card-h a { text-decoration: none; }
.card-h a::after { content: ""; position: absolute; inset: 0; }
.card-h a:focus-visible { outline: none; }
.card-body p { color: var(--ink-muted); margin: 0; flex: 1; }
/* navy, not --cta: eight of these on the home page outshouted the two real CTAs */
.card-more { display: inline-flex; align-items: center; gap: var(--s1); margin-top: var(--s1); font-family: var(--font-head); font-weight: 600; color: var(--navy); }
@media (any-hover: hover) { .card:hover .card-more { color: var(--cta-text); } }
.card:active .card-more { color: var(--cta-text); }
/* Nine services: 3 x 3 on desktop. In the two-column range an odd last card would sit
   alone, so it spans both columns and turns into a side-by-side card instead. */
@media (min-width: 640px) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 640px) and (max-width: 899px) {
  .cards > .card:last-child:nth-child(odd) { grid-column: 1 / -1; flex-direction: row; }
  .cards > .card:last-child:nth-child(odd) picture { flex: 0 0 50%; }
  .cards > .card:last-child:nth-child(odd) img { height: 100%; }
}
@media (min-width: 900px) { .cards { grid-template-columns: repeat(3, 1fr); } }

/* Process steps */
.steps { display: grid; gap: var(--gap-grid); counter-reset: step; }
.step { position: relative; padding: var(--surface-pad); padding-top: calc(var(--surface-pad) + 2.6rem); background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); }
.step::before { counter-increment: step; content: counter(step, decimal-leading-zero); position: absolute; top: var(--s3); left: var(--surface-pad); font-family: var(--font-head); font-weight: 700; font-size: 2.2rem; line-height: 1; color: var(--navy-soft); }
.step h3 { margin-bottom: var(--s1); }
.step p { margin: 0; color: var(--ink-muted); }
.steps-compact { gap: var(--s3); }
.steps-compact .step { padding: var(--s3) var(--s3) var(--s3) calc(var(--s3) + 2.4rem); }
.steps-compact .step::before { left: var(--s3); top: var(--s3); font-size: 1.5rem; }
.steps-compact .step h3 { font-size: 1.05rem; margin-bottom: 0.15rem; }
.steps-compact .step p { font-size: var(--fs-small); }
@media (min-width: 640px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .steps { grid-template-columns: repeat(4, 1fr); } }

/* References */
.refs { display: grid; gap: var(--gap-grid); }
.refs figure { margin: 0; background: var(--white); border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); transition: box-shadow var(--ease), transform var(--ease), border-color var(--ease); }
@media (any-hover: hover) { .refs figure:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: transparent; } }
.refs figure:active { box-shadow: var(--shadow); transform: translateY(0); border-color: transparent; }
.refs img { aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
.refs figcaption { padding: var(--s2) var(--surface-pad); font-family: var(--font-head); font-weight: 600; color: var(--navy); }
@media (min-width: 600px) { .refs { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .refs { grid-template-columns: repeat(3, 1fr); } }

/* Lightbox triggers and dialog */
.lb-open { display: block; width: 100%; padding: 0; margin: 0; border: 0; background: none; cursor: zoom-in; font: inherit; color: inherit; }
.lb-open:focus-visible { outline-offset: -3px; }
/* About / team */
.about { display: grid; gap: var(--s5); }
.checks { display: grid; gap: var(--s2); margin-top: var(--s4); }
.checks li { display: flex; gap: var(--s2); align-items: flex-start; }
.checks svg { color: var(--navy); margin-top: 0.1rem; }
.people { display: grid; gap: var(--s3); }
.person { display: flex; gap: var(--s3); align-items: center; padding: var(--surface-pad); background: var(--sand); border-radius: var(--radius); }
.person-avatar { display: grid; place-items: center; width: 64px; height: 64px; border-radius: 50%; background: var(--navy); color: var(--white); font-family: var(--font-head); font-weight: 700; font-size: 1.4rem; flex: none; overflow: hidden; }
.person-avatar-photo { display: block; width: 72px; height: 72px; }
.person-avatar-photo img { width: 100%; height: 100%; object-fit: cover; object-position: center 22%; border-radius: 50%; }
.person p { margin: 0; }
.person-name { font-family: var(--font-head); font-weight: 700; font-size: 1.25rem; color: var(--navy); }
.person-role { color: var(--ink-muted); font-size: var(--fs-small); }
.person-links a { display: inline-flex; align-items: center; gap: var(--s1); font-weight: 600; margin-top: 0.25rem; }
.team { display: grid; gap: var(--gap-grid); }
.team-member { display: grid; grid-template-columns: 120px 1fr; gap: var(--s4); align-items: center; padding: var(--surface-pad); background: var(--sand); border-radius: var(--radius); }
.team-member .person-avatar, .team-member .person-avatar-photo { width: 120px; height: 120px; font-size: 2.2rem; }
.team-member p { margin: 0; }
.team-member .person-bio { margin-top: var(--s1); color: var(--ink-muted); font-size: var(--fs-small); }
.team-kit { margin-top: var(--s4); }
.team-kit h3 { font-size: 1.05rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-muted); margin-bottom: var(--s2); }
.team-kit ul { display: flex; flex-wrap: wrap; gap: var(--s1); }
.team-kit li { padding: 0.35rem 0.8rem; border: 1px solid var(--line); border-radius: var(--radius-pill); font-size: var(--fs-small); font-weight: 500; background: var(--white); }
@media (min-width: 900px) { .about { grid-template-columns: 3fr 2fr; align-items: start; } .about-wide { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .team-member { grid-template-columns: 1fr; text-align: center; justify-items: center; } }

/* Before / after story */
.story { display: grid; gap: var(--s5); align-items: center; }
@media (min-width: 900px) { .story { grid-template-columns: 2fr 3fr; gap: var(--s6); } }
.ba-note { font-size: var(--fs-small); color: var(--ink-muted); max-width: 46em; margin-top: calc(var(--s3) * -0.5); }
.ba { --pos: 50%; position: relative; isolation: isolate; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); user-select: none; -webkit-user-select: none; touch-action: pan-y; }
.ba picture, .ba img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.ba-before { position: absolute; inset: 0; clip-path: inset(0 calc(100% - var(--pos)) 0 0); }
.ba-handle { position: absolute; top: 0; bottom: 0; left: var(--pos); width: 3px; background: var(--white); transform: translateX(-50%); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), 0 0 12px rgba(0, 0, 0, 0.35); pointer-events: none; }
.ba-handle::after { content: ""; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--white); border: 3px solid var(--cta); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); background-image: linear-gradient(var(--navy), var(--navy)), linear-gradient(var(--navy), var(--navy)); background-repeat: no-repeat; background-size: 2px 14px; background-position: 14px center, 26px center; }
.ba-label { position: absolute; top: var(--s2); padding: 0.3rem 0.7rem; font-family: var(--font-head); font-weight: 600; font-size: var(--fs-label); letter-spacing: 0.06em; text-transform: uppercase; color: var(--white); background: rgba(var(--navy-rgb), 0.78); border-radius: var(--radius); pointer-events: none; }
.ba-label-before { left: var(--s2); }
.ba-label-after { right: var(--s2); }
.ba-range { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize; -webkit-appearance: none; appearance: none; }
.ba:focus-within { outline: 3px solid var(--cta); outline-offset: 3px; box-shadow: 0 0 0 3px var(--white); }

/* Testimonials */
.demo-note { display: flex; align-items: flex-start; gap: var(--s2); margin: 0 0 var(--s4); padding: var(--s2) var(--s3); background: var(--note-bg); border-left: 4px solid var(--cta); border-radius: 0 var(--radius) var(--radius) 0; color: var(--note-text); font-weight: 500; font-size: var(--fs-small); max-width: 62ch; }
.demo-badge { position: absolute; top: var(--s2); right: var(--s2); padding: 0.2rem 0.6rem; background: var(--cta); color: var(--white); font-family: var(--font-head); font-weight: 600; font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase; border-radius: var(--radius-pill); }
.quote-demo { border-style: dashed; border-color: var(--cta); }
.quotes { display: grid; gap: var(--gap-grid); }
@media (min-width: 720px) { .quotes { grid-template-columns: repeat(3, 1fr); } }
.quote { display: flex; flex-direction: column; background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); padding: calc(var(--surface-pad) + 1.1rem) var(--surface-pad) var(--surface-pad); position: relative; }
/* the low German opening quote sits on the baseline, so the glyph box is lifted to keep its ink clear of the first text line */
.quote::before { content: "\201E"; position: absolute; top: -0.55rem; left: var(--surface-pad); font-family: var(--font-head); font-size: 3rem; line-height: 1; color: var(--taupe); opacity: 0.55; pointer-events: none; }
.quote blockquote { margin: 0; flex: 1; }
.quote blockquote p { margin: 0 0 var(--s3); font-size: 1.05rem; line-height: 1.5; }
.quote-by { margin: 0; color: var(--ink-muted); font-size: var(--fs-small); border-top: 1px solid var(--line); padding-top: var(--s2); }
.quote-by strong { color: var(--navy); }

/* Chips */
.chips { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: var(--s3); }
.chips li { padding: 0.45rem 0.9rem; background: var(--white); border: 1px solid var(--line); border-radius: var(--radius-pill); font-weight: 500; }
/* "und viele weitere Orte dazwischen" is a note, not a place. No border or surface, so it
   reads as a continuation of the list rather than another chip a visitor might expect to
   act on. Italic and muted, but kept at --fs-small rather than smaller: it still has to
   pass 4.5:1 and be readable outdoors. */
.chip-more { background: none !important; border-color: transparent !important; color: var(--ink-muted); font-style: italic; font-weight: 400 !important; align-self: center; }
.chips-links li { padding: 0; }
.chips-links a { display: inline-block; padding: 0.55rem 1rem; text-decoration: none; font-weight: 600; border-radius: var(--radius-pill); }
.chips-links a:hover { background: var(--sand); }
.chips-links a:active { background: var(--line); }

/* Area + map */
.area { display: grid; gap: var(--s5); align-items: center; }
@media (min-width: 900px) { .area { grid-template-columns: 3fr 2fr; } }
.map { margin: 0; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); background: var(--white); }
.map img { width: 100%; height: auto; display: block; }
.map figcaption { padding: var(--s1) var(--s3); font-size: 0.8rem; color: var(--ink-muted); background: var(--white); }
.map figcaption a { color: inherit; }

/* CTA band */
.cta-band { background: var(--navy); color: var(--white); padding-block: clamp(3rem, 6vw, 4.5rem); }
.cta-band h2 { color: var(--white); }
.cta-band p { font-size: 1.15rem; color: rgba(255, 255, 255, 0.88); max-width: 40em; }

/* Page head (subpages) */
.page-head { background: var(--sand); padding-block: clamp(2.5rem, 5vw, 4.5rem); }
.page-head-grid { display: grid; gap: var(--s5); align-items: center; }
.page-head-img { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); }
.page-head-img img { aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
@media (min-width: 900px) { .page-head-grid { grid-template-columns: 1.1fr 1fr; } }
.two-col { display: grid; gap: var(--s5); }
@media (min-width: 900px) { .two-col { grid-template-columns: 3fr 2fr; align-items: start; } }
.callout { background: var(--sand); border-left: 4px solid var(--navy); padding: var(--surface-pad); border-radius: 0 var(--radius) var(--radius) 0; }
.callout h3 { margin-bottom: var(--s1); }
.callout p { margin: 0; }
.gallery { display: grid; gap: var(--gap-grid); }
.gallery li { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); transition: box-shadow var(--ease), transform var(--ease), border-color var(--ease); }
@media (any-hover: hover) { .gallery li:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: transparent; } }
.gallery li:active { box-shadow: var(--shadow); transform: translateY(0); border-color: transparent; }
/* position: relative keeps each tile's absolutely positioned .visually-hidden label inside
   the tile. Without it the labels escaped the phone scroller below and widened the whole
   page (2138 px at 390 on Poolbau), which dragged the fixed action bar and the lightbox
   off-screen with it. */
.gallery li { position: relative; }
.gallery img { aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
/* Under 600px the gallery was a single stacked column, so eight photos meant eight
   screens of scrolling. A horizontal snap track shows them as a swipeable strip instead:
   native momentum on Android, zero JS, and the track stays scrollable even where snap is
   unsupported. Peeking the next tile (85vw) signals there is more to swipe. */
@media (max-width: 599px) {
  .gallery { grid-auto-flow: column; grid-auto-columns: 85%; overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding-inline: var(--gutter); padding-bottom: var(--s1); margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter); overscroll-behavior-x: contain; }
  .gallery li { scroll-snap-align: start; }
}
@media (min-width: 600px) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .gallery { grid-template-columns: repeat(3, 1fr); } }
/* two leftover photos under the project cards: two columns, not a 3-grid with a hole */
@media (min-width: 900px) { .gallery:has(> li:nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); max-width: calc(66.66% + var(--gap-grid) / 3); } }

/* Project cards on the service pages. Cover photo over a solid navy panel: white text on
   --navy is about 14:1 whatever the photo does, which a gradient over sky could not promise.
   The button covers the photo; its ::after stretches over the whole card so the panel is
   clickable too, and the focus ring goes on the card via :focus-within. */
.project-cards { display: grid; gap: var(--gap-grid); }
.project-card { position: relative; display: flex; flex-direction: column; background: var(--navy); color: var(--white); border-radius: var(--radius); overflow: hidden; transition: box-shadow var(--ease), transform var(--ease); }
.project-open { cursor: pointer; }
.project-open::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.project-open:focus-visible { outline: none; }
.project-card:has(.project-open:focus-visible) { outline: 3px solid var(--cta); outline-offset: 2px; box-shadow: 0 0 0 3px var(--white); }
.project-card img { aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
.project-body { padding: var(--s3) var(--s4) var(--s4); display: flex; flex-direction: column; gap: var(--s1); flex: 1; }
.project-kicker { margin: 0; font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.02em; color: var(--hero-kicker); }
.project-h { margin: 0; color: var(--white); font-size: var(--fs-h3); line-height: 1.15; }
.project-text { margin: 0; color: rgba(255, 255, 255, 0.88); flex: 1; }
.project-more { display: inline-flex; align-items: center; gap: var(--s1); margin: var(--s1) 0 0; font-family: var(--font-head); font-weight: 600; color: var(--white); }
.project-more svg { transition: transform var(--ease); }
@media (any-hover: hover) {
  .project-card:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
  .project-card:hover .project-more svg { transform: translateX(3px); }
}
.project-card:active { transform: translateY(0); }
/* phones: one swipeable strip like the gallery, next card peeking */
@media (max-width: 599px) {
  /* padding-top leaves room for the focus ring, which the scroller would otherwise clip */
  .project-cards { grid-auto-flow: column; grid-auto-columns: 85%; overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding-inline: var(--gutter); padding-top: 6px; padding-bottom: var(--s1); margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter); overscroll-behavior-x: contain; }
  .project-card { scroll-snap-align: start; }
}
@media (min-width: 600px) { .project-cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .project-cards { grid-template-columns: repeat(4, 1fr); } }
.gallery-more { margin: var(--s6) 0 var(--s4); font-size: var(--fs-h3); }
.faq { border-top: 1px solid var(--line); }
.faq:last-of-type { border-bottom: 1px solid var(--line); }
.faq summary { cursor: pointer; padding: var(--s3) 0; font-family: var(--font-head); font-weight: 600; font-size: 1.25rem; color: var(--navy); list-style: none; display: flex; justify-content: space-between; gap: var(--s3); }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; font-size: 1.6rem; line-height: 1; color: var(--navy); flex: none; }
.faq[open] summary::after { content: "–"; }
.faq p { padding-bottom: var(--s4); margin: 0; max-width: 60ch; }

/* Contact */
.contact-grid { display: grid; gap: var(--s6); }
@media (min-width: 900px) { .contact-grid { grid-template-columns: 2fr 3fr; align-items: start; } }
.contact-list { display: grid; gap: var(--s2); margin-top: var(--s4); }
.contact-list li { display: flex; align-items: center; gap: var(--gap-icon); }
.contact-list svg { color: var(--navy); }
.contact-list .btn { width: 100%; }
.form { margin-top: var(--s3); }
.form-hint { color: var(--ink-muted); font-size: var(--fs-small); }
.field { margin-bottom: var(--s3); }
.field-row { display: grid; gap: 0 var(--s3); }
@media (min-width: 600px) { .field-row { grid-template-columns: 1fr 1fr; } }
label { display: block; font-weight: 600; margin-bottom: 0.35rem; }
input:not([type="checkbox"]), select, textarea { width: 100%; font: inherit; padding: 0.7rem 0.85rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); color: var(--ink); min-height: 48px; transition: border-color var(--ease), box-shadow var(--ease); }
textarea { resize: vertical; }
input:not([type="checkbox"]):focus-visible, select:focus-visible, textarea:focus-visible { outline: none; border-color: var(--navy); box-shadow: 0 0 0 1px var(--navy); }
[aria-invalid="true"] { border-color: var(--error); box-shadow: 0 0 0 1px var(--error); }
.error { display: none; color: var(--error); font-weight: 600; margin: 0.35rem 0 0; font-size: var(--fs-small); }
.error.is-visible { display: block; }
.field-check { display: grid; grid-template-columns: auto 1fr; gap: 0.3rem var(--s2); align-items: start; }
.field-check input { width: 22px; height: 22px; margin-top: 0.2rem; accent-color: var(--navy); }
.field-check label { font-weight: 400; margin: 0; }
.field-check .error { grid-column: 2; }
.hp { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }
.form-status { min-height: 1.5em; font-weight: 600; color: var(--error); }
.form-status:empty { margin: 0; }

/* Next steps (Danke page) */
.next-steps { margin-top: var(--s5); display: grid; gap: var(--s3); counter-reset: step; text-align: left; }
.next-steps li { display: grid; grid-template-columns: 2.2rem 1fr; gap: var(--s2); align-items: baseline; }
.next-steps li::before { counter-increment: step; content: counter(step); font-family: var(--font-head); font-weight: 700; font-size: 1.5rem; color: var(--navy); }

/* Prose (legal) */
.prose h2 { font-size: var(--fs-h2-small); margin-top: var(--s5); }
.prose ul { list-style: disc; padding-left: 1.4rem; margin-bottom: 1em; }
.prose li { margin-bottom: 0.3em; }

/* Sticky mobile action bar */
.action-bar { position: fixed; inset: auto 0 0; z-index: 40; display: none; grid-template-columns: repeat(3, 1fr); background: var(--white); border-top: 1px solid var(--line); box-shadow: 0 -2px 12px rgba(var(--navy-rgb), 0.12); padding-bottom: env(safe-area-inset-bottom); transform: translateY(100%); visibility: hidden; transition: transform var(--ease), visibility 0s var(--ease); }
.action-bar a { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; min-height: 58px; padding: var(--s1); text-decoration: none; font-family: var(--font-head); font-weight: 600; font-size: 0.9rem; color: var(--navy); }
.action-bar a + a { border-left: 1px solid var(--line); }
.action-bar a svg { width: 22px; height: 22px; }
.action-bar .is-primary { background: var(--cta); color: var(--white); }
/* the mobile action bar is the primary call path: it must confirm every tap */
.action-bar a:active { background: var(--sand); }
.action-bar .is-primary:active { background: var(--cta-hover); }
.action-bar .is-whatsapp { color: var(--whatsapp); }
@media (max-width: 899px) {
  .action-bar { display: grid; }
  .action-bar.is-visible { transform: none; visibility: visible; transition: transform var(--ease), visibility 0s; }
  body.has-bar { padding-bottom: calc(60px + env(safe-area-inset-bottom)); }
}
/* WhatsApp moved out of the bar into the floating button (2026-09-24), so it is never offered
   twice; the bar keeps call and enquiry. */
.action-bar { grid-template-columns: repeat(2, 1fr); }

/* Floating WhatsApp button, phones and small tablets only (the desktop header carries the
   phone number and every page has a WhatsApp button in its CTA band).
   Colour: the site's --whatsapp green, not the brand #25D366 - white on the brand green is
   under 2:1, white on #0f7a3f is 5.6:1, and the circle itself needs 3:1 against the page
   (WCAG 1.4.11). The glyph alone identifies it; the label is for screen readers.
   Motion: it arrives once (scale 0.9 + fade, 320 ms ease-out, after the page has settled -
   an entrance is fine because it plays once per page). It rises above the action bar when the
   bar slides in, on the same curve, so the two never overlap. Pressing shrinks it to 0.94 for
   contact feedback; a pointer hover grows it slightly. Nothing loops: a pulsing chat bubble
   on a trade site reads as a sales widget, not as the owners' phone. While a form field has
   focus it steps aside, because on a phone it would sit on top of the keyboard's input. */
.wa-fab { display: none; }
@media (max-width: 899px) {
  .wa-fab {
    position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 45;
    display: grid; place-items: center; width: 58px; height: 58px; border-radius: 50%;
    background: var(--whatsapp); color: var(--white);
    box-shadow: 0 6px 18px rgba(var(--navy-rgb), 0.28), 0 2px 4px rgba(var(--navy-rgb), 0.18);
    text-decoration: none;
    transition: translate 280ms var(--ease-out-soft), scale 160ms var(--ease-out-soft), opacity 200ms ease, background-color 160ms ease;
  }
  .wa-fab svg { width: 30px; height: 30px; }
  .wa-fab:active { scale: 0.94; background: var(--whatsapp-hover); }
  .wa-fab:focus-visible { outline: 3px solid var(--navy); outline-offset: 3px; }
  body.has-bar .wa-fab { translate: 0 calc(-60px); }
  /* At the very bottom the button would sit on the copyright line; leave it room. */
  .site-footer .footer-legal { padding-bottom: 76px; }
  body:has(input:focus, textarea:focus, select:focus) .wa-fab { opacity: 0; pointer-events: none; }
}
@media (max-width: 899px) and (any-hover: hover) {
  .wa-fab:hover { scale: 1.06; background: var(--whatsapp-hover); }
}
@media (max-width: 899px) and (prefers-reduced-motion: no-preference) {
  /* backwards, not both: a held fill would pin scale at 1 and silently cancel :active and :hover. */
  .wa-fab { animation: wa-fab-in 320ms var(--ease-out-soft) 600ms backwards; }
}
@keyframes wa-fab-in { from { opacity: 0; scale: 0.9; } to { opacity: 1; scale: 1; } }

/* Footer */
.site-footer { background: var(--navy-deep); color: rgba(255, 255, 255, 0.85); padding-top: clamp(2.5rem, 5vw, 4rem); }
.site-footer a { color: var(--white); }
.site-footer a:hover { color: var(--hero-kicker); }
.footer-grid { display: grid; gap: var(--s5); padding-bottom: var(--s5); }
@media (min-width: 720px) { .footer-grid { grid-template-columns: 1.4fr 1fr 1fr; } }
.footer-mark { height: 56px; width: auto; margin-bottom: var(--s2); }
.footer-name { font-family: var(--font-head); font-weight: 700; font-size: 1.2rem; color: var(--white); margin-bottom: 0.25rem; }
.footer-tagline { color: var(--taupe); margin-top: var(--s2); font-weight: 500; }
.footer-h { font-size: 1rem; font-weight: 700; color: var(--taupe); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: var(--s2); }
.footer-list { display: grid; gap: 0.4rem; }
.footer-legal { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--s2); padding-block: var(--s4); border-top: 1px solid rgba(255, 255, 255, 0.15); font-size: var(--fs-small); }
.footer-legal p { margin: 0; }
.footer-legal ul { display: flex; gap: var(--s4); }

/* Scroll reveal. Values from the subtle tier: opacity 0->1, y 12px, 350ms ease-out,
   triggered at top 90%. The hidden state is applied by JS only (html.js-reveal), so with
   JS off or before the script runs every section is fully visible and nothing is lost.
   Never applied above the fold: it must not delay the LCP paint. */
@media (prefers-reduced-motion: no-preference) {
  .js-reveal .reveal { opacity: 0; transform: translateY(12px); transition: opacity 350ms ease-out, transform 350ms ease-out; }
  .js-reveal .reveal.is-visible { opacity: 1; transform: none; }
}

/* Cross-document view transitions. Two lines of real work: the browser cross-fades
   between same-origin pages on a normal link click. Unsupported browsers ignore this
   entirely and navigate as before, so there is nothing to fall back to. The header logo
   is named so it persists across the navigation instead of fading with the rest. */
@view-transition { navigation: auto; }
.brand img { view-transition-name: brand-logo; }

/* Staggered grid reveal. The section-level .reveal fades the whole band; this delays
   each child a little behind the one before so a grid assembles rather than appears.
   --i is capped at 6 in JS: eight cards at 60ms each would leave the last waiting half a
   second, which reads as slow rather than choreographed.
   Guarded by .js-reveal, the class only the script sets, so JS off leaves all visible. */
.js-reveal .reveal .stagger > * { opacity: 0; transform: translateY(var(--reveal-rise)); }
.js-reveal .reveal.is-visible .stagger > * {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-slow) var(--ease-out-soft), transform var(--dur-slow) var(--ease-out-soft);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

/* Press states that track the pointer. Separate from the hover lift so touch gets
   feedback on contact rather than on release — the moment lag appears the interface
   stops feeling direct. `scale` (independent property) so it composes with any
   translate rather than replacing it. */
.card, .refs figure, .figure, .quote { transition: box-shadow var(--dur-base) var(--ease-out-soft), transform var(--dur-base) var(--ease-out-soft), border-color var(--dur-base) var(--ease-out-soft); }

/* Translucent header. The bar floats over content rather than sitting on an opaque
   strip, with a scroll-edge fade instead of a hard 1px divider where it overlaps.
   Only once scrolled: over the hero the existing gradient treatment still applies.
   @supports because a browser without backdrop-filter must get the solid bar, not a
   see-through one with unreadable type over photos. */
@supports (backdrop-filter: blur(1px)) {
  .site-header.is-scrolled { background: rgba(255, 255, 255, 0.72); backdrop-filter: blur(18px) saturate(180%); border-bottom-color: rgba(217, 213, 206, 0.6); }
  /* Vibrancy: over a translucent bar flat grey loses legibility, so the type goes
     heavier and darker rather than lighter. */
  .site-header.is-scrolled .nav a { color: var(--navy); font-weight: 600; }
}
/* A reader who asks for less transparency gets the solid bar back. */
@media (prefers-reduced-transparency: reduce) {
  .site-header.is-scrolled { background: var(--white); backdrop-filter: none; }
  .lb-btn { background: rgba(31, 40, 70, 0.92); backdrop-filter: none; }
}

/* ---------------------------------------------------------------------------
   Gallery carousel. Deliberately the LAST component block in this stylesheet.

   Position, not size and not the rules themselves, is what this costs. Measured on
   byte-identical stylesheets differing only in the ORDER of these rules: with the
   carousel block in the middle of the sheet the home page scored 73 (LCP 11.4 s); with
   exactly the same rules moved to the end, 84-85 (LCP 4.1-4.4 s). Padding the baseline
   sheet with inert bytes to the same total size scored 83-87, so it is not weight, and
   removing EITHER this block or the figures block recovered the score, so it is not any
   single rule.

   Nothing here applies until a visitor taps a photo. Keep it after everything the first
   screen needs, and re-run a Lighthouse A/B if it ever moves.
   --------------------------------------------------------------------------- */
/* Gallery carousel. The dialog fills the viewport so the drag has room; the photo
   itself is capped. --lb-dim is written by the dismiss gesture (1 at rest, falling as
   the figure travels) so the backdrop thins in step with the finger. */
/* The dialog is a column: figure takes the space that is left, the thumb strip keeps its
   own. Sizing the figure with height:100% instead would push the strip off the bottom.
   `display` MUST be scoped to [open]: the UA sheet closes a dialog with display:none, so
   an unscoped `display: flex` here re-opens it on every page — measured, that painted the
   opaque scrim and the fixed prev/next buttons over the whole site at every viewport. */
/* EVERYTHING here is scoped to [open]. A closed dialog is display:none, but these rules
   still resolve against it, and the full-viewport sizing plus a position:fixed ::before
   scrim was enough to cost the home page 10 Lighthouse points and double LCP (73-75 /
   8-11 s against 85 / 4.2 s). Proven by two independent one-variable tests on the same
   build: serving the OLD stylesheet with the new HTML scored 85, and deleting only the
   dialog markup from the new HTML scored 84-86. Neither the carousel JS nor the slide
   images were responsible - both were ruled out separately. */
.lb { padding: 0; border: 0; background: transparent; color: var(--white); --lb-dim: 1; }
.lb[open] { display: flex; flex-direction: column; width: 100%; max-width: 100%; height: 100%; max-height: 100%; overflow: hidden; }
/* Two scrims on purpose. ::backdrop covers the viewport in browsers that paint it, but
   it cannot read a custom property from the dialog, so it cannot thin out as the dismiss
   gesture travels. The ::before overlay is the one the gesture drives (--lb-dim: 1 at
   rest, falling toward 0 as the figure is dragged away) and it is also what covers the
   page in engines that do not paint ::backdrop — verified necessary: without it the page
   behind showed straight through the open dialog. It sits behind the figure, which
   .lb-figure's own stacking context puts above it. */
.lb::backdrop { background: rgba(var(--navy-rgb), 0.92); }
.lb[open]::before { content: ""; position: fixed; inset: 0; z-index: 0; background: rgb(var(--navy-rgb)); opacity: var(--lb-dim); pointer-events: none; }
.lb[open]::before { animation: lb-backdrop var(--dur-base) var(--ease-out-soft); }
/* z-index lifts the figure above the ::before scrim. min-height: 0 on the viewport plus
   flex: none on the caption is what keeps the caption on screen: without it the image
   claims the full column and pushes the text past the bottom edge. */
.lb-figure { position: relative; z-index: 1; margin: 0; flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s2); padding: calc(var(--s6) + var(--s2)) var(--s3) var(--s2); will-change: transform; }
.lb-viewport { position: relative; width: 100%; flex: 1; min-height: 0; overflow: hidden; touch-action: pan-y; cursor: grab; }
.lb.is-dragging .lb-viewport { cursor: grabbing; }
/* Three slots (prev | curr | next), so the resting position is one slot to the left: at 0
   the prev slot is on screen and the lightbox showed the photo *before* the one clicked.
   `translate` (the independent property) adds to the drag `transform` the script writes. */
.lb-track { display: flex; height: 100%; will-change: transform; translate: -100% 0; }
.lb-slide { flex: 0 0 100%; height: 100%; display: grid; place-items: center; padding-inline: var(--s2); }
.lb-slide img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; border-radius: var(--radius); background: var(--navy-deep); -webkit-user-drag: none; user-select: none; }
/* A thumbnail jump has no neighbour to travel through, so the new photo materialises
   in place rather than pretending to slide in from somewhere. */
.lb-slide-curr.is-swapped img { animation: lb-swap var(--dur-base) var(--ease-out-soft); }
.lb figcaption { font-size: var(--fs-small); color: rgba(255, 255, 255, 0.85); text-align: center; max-width: 60ch; flex: none; }
.lb[open] .lb-chrome { position: fixed; top: 0; left: 0; right: 0; z-index: 3; display: flex; align-items: center; justify-content: space-between; gap: var(--s3); padding: var(--s3); }
.lb-index { margin: 0; font-family: var(--font-head); font-weight: 600; font-size: var(--fs-small); letter-spacing: 0.06em; color: rgba(255, 255, 255, 0.85); font-variant-numeric: tabular-nums; }
.lb[open] .lb-btn { position: fixed; z-index: 3; display: grid; place-items: center; width: 48px; height: 48px; border: 0; border-radius: 50%; background: rgba(255, 255, 255, 0.14); color: var(--white); cursor: pointer; transition: background-color var(--ease), scale var(--dur-fast) var(--ease-out-soft); }
@supports (backdrop-filter: blur(1px)) { .lb-btn { background: rgba(255, 255, 255, 0.12); backdrop-filter: blur(12px) saturate(140%); } }
.lb-btn:hover { background: rgba(255, 255, 255, 0.28); }
/* `scale` (the independent property), not `transform: scale()`: prev/next are centred
   with transform: translateY(-50%), which a transform here would replace. */
.lb-btn:active { background: rgba(255, 255, 255, 0.4); scale: 0.94; }
.lb-close { position: static; flex: none; }
.lb-prev { left: var(--s3); top: 50%; transform: translateY(-50%); }
.lb-next { right: var(--s3); top: 50%; transform: translateY(-50%); }
.lb-btn[hidden] { display: none; }
/* Thumbnail strip. Horizontal scroll rather than a wrap: a long gallery must not push
   the photo off screen. */
.lb-thumbs { position: relative; z-index: 2; flex: none; display: flex; gap: var(--s1); max-width: 100%; margin-inline: auto; padding: var(--s2) var(--s3) calc(var(--s2) + env(safe-area-inset-bottom, 0px)); overflow-x: auto; scrollbar-width: none; scroll-behavior: smooth; }
.lb-thumbs::-webkit-scrollbar { display: none; }
.lb-thumbs:empty { display: none; }
.lb-thumb { flex: none; width: 60px; height: 44px; padding: 0; border: 0; border-radius: 4px; overflow: hidden; background: var(--navy-deep); cursor: pointer; opacity: 0.45; outline-offset: 2px; transition: opacity var(--dur-base) var(--ease-out-soft), scale var(--dur-base) var(--ease-out-soft); }
.lb-thumb img { width: 100%; height: 100%; object-fit: cover; }
.lb-thumb:hover { opacity: 0.8; }
.lb-thumb.is-current { opacity: 1; scale: 1.06; box-shadow: 0 0 0 2px var(--cta); }
/* Materialise rather than fade: blur and scale together read as a surface arriving. */
.lb[open] .lb-figure { animation: lb-in var(--dur-base) var(--ease-out-soft); }
@keyframes lb-in { from { opacity: 0; scale: 0.94; filter: blur(6px); } to { opacity: 1; scale: 1; filter: blur(0); } }
@keyframes lb-backdrop { from { opacity: 0; } to { opacity: 1; } }
@keyframes lb-swap { from { opacity: 0; scale: 1.02; } to { opacity: 1; scale: 1; } }
html:has(dialog.lb[open]) { overflow: hidden; scrollbar-gutter: stable; }
@media (min-width: 900px) {
  .lb-slide img { max-width: min(92vw, 1500px); max-height: 78vh; }
  .lb-thumb { width: 78px; height: 56px; }
}


@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* The stagger must resolve to visible, never leave a grid stranded at opacity 0. */
  .js-reveal .reveal .stagger > *, .js-reveal .reveal.is-visible .stagger > * { opacity: 1; transform: none; transition: none; }
  /* The parallax sets an inline transform; neutralise it so the hero photo sits still. */
  .hero .hero-media { transform: none !important; }
  *, *::before, *::after { transition: none !important; animation: none !important; }
  .btn:hover, .card:hover, .refs figure:hover, .gallery li:hover,
  .btn:active, .card:active, .refs figure:active, .gallery li:active { transform: none; }
  /* .lb-btn uses the `scale` property, not a transform, so prev/next keep their
     translateY(-50%) centring. Neutralise the right one. */
  .lb-btn:active { scale: 1; }
  /* The carousel keeps working — the drag still tracks the finger, which is direct
     manipulation rather than animation — but nothing moves on its own: the track jumps
     between photos and the entrance materialise is dropped. The `* { animation: none }`
     above already kills the keyframes; these cover the scale/blur left on elements. */
  .lb-thumb.is-current { scale: 1; }
  .lb-thumbs { scroll-behavior: auto; }
  .figure::after, .process-band .step::after { transition: none; width: 100%; }
  /* No growth on hover; the deeper navy alone still confirms the pointer. */
  .process-band .step:hover { scale: 1; }
  .wa-fab:hover, .wa-fab:active { scale: 1; }
  /* A counter that cannot count must still show its number, and the reveal must not
     leave a section stranded at opacity 0. */
  .figure-num { opacity: 1; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}
@media print {
  /* A section the visitor never scrolled to still carries opacity:0 from the reveal.
     Without this it prints as blank paper. */
  .js-reveal .reveal { opacity: 1 !important; transform: none !important; }
  .site-header, .site-footer, .cta-band, .hero-media, .btn, .action-bar, .lb { display: none !important; }
  .hero { color: var(--ink); min-height: 0; }
  .hero h1 { color: var(--navy); }
}

.h3-size { font-size: var(--fs-h3); }
