/* ============================================================================
   Public landing page sections.
   Written against the Reco Tak Light Design System: 1140px container, 96px
   section rhythm, 6/10/16px radii, 160ms cubic-bezier(.2,.7,.3,1) motion,
   hover = darken one step or lift 1px, press = translateY(1px). Buttons are
   6px — the design system explicitly forbids pill buttons.
   ========================================================================== */

/* ── Section scaffolding ────────────────────────────────────── */

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

.section--tight {
  padding-block: var(--sp-7);
}

.section--tint {
  background: var(--paper-2);
}

.section-head {
  max-width: 68ch;
  margin-bottom: var(--sp-7);
}

.section-intro {
  font: var(--body-lg);
  color: var(--text-body);
  margin-top: var(--sp-4);
  text-wrap: pretty;
}

.section-label--light {
  color: var(--blue-300);
}

.section-title--light {
  color: #fff;
}

/* ── Buttons ────────────────────────────────────────────────── */

.btn-primary--sm {
  font-size: 14px;
  padding: 10px var(--sp-4);
}

.btn-primary--lg {
  font-size: 16px;
  padding: 15px var(--sp-6);
}

.btn-primary--block {
  width: 100%;
}

.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  background: var(--paper-0);
  color: var(--blue-600);
  border: 1px solid var(--blue-600);
  font: 600 15px/1 var(--font-body);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-sm);
  white-space: nowrap;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease), transform var(--speed) var(--ease);
}

.btn-secondary:hover {
  background: var(--blue-100);
  color: var(--blue-700);
}

.btn-secondary:active {
  transform: translateY(1px);
}

.btn-secondary--lg {
  font-size: 16px;
  padding: 15px var(--sp-6);
}

/* ── Hero ───────────────────────────────────────────────────── */

.hero {
  position: relative;
}

/* The nav is sticky, which means it OCCUPIES FLOW SPACE at the top of the
   document — the hero starts below it. Sizing the stage to a bare 100svh
   therefore pushed its bottom --nav-total px past the fold, and since the
   focal point anchors the picture's bottom edge to the stage's bottom edge,
   the part that disappeared was the most important part of the frame: the
   crew, cut off at the waist. The stage is the viewport MINUS the nav.

   Both variants — the scrubbed video and the still image — use this same
   height, uncapped. The still briefly had the old 88svh "ordinary hero"
   height, which left the stat band peeking in at the bottom and made the
   demo toggle compare two different layouts instead of two heroes. */
.hero-stage {
  position: relative;
  height: calc(100svh - var(--nav-total));
  min-height: 460px;
  overflow: hidden;
  background: var(--ink-900);
}

.hero-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The design system's left scrim: dark enough at the copy, gone by the right
   edge so the photograph is still a photograph. */
.hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    rgba(16, 24, 32, 0.82) 0%,
    rgba(16, 24, 32, 0.52) 52%,
    rgba(16, 24, 32, 0.06) 84%
  );
}

.hero-content {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
}

.hero-copy {
  max-width: 640px;
}

.hero-badge {
  display: inline-block;
  background: var(--blue-100);
  color: var(--blue-800);
  font: 700 13px/1 var(--font-body);
  padding: 9px 13px;
  border-radius: var(--radius-sm);
  margin-bottom: var(--sp-5);
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 6.4vw, 4rem);
  line-height: 1.04;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: #fff;
  margin-bottom: var(--sp-4);
  /* Barlow Condensed's descenders overrun a 1.04 line box; the padding gives
     them room without loosening the leading anyone sees. */
  padding-block: 0.04em;
}

.hero-ingress {
  font: var(--body-lg);
  color: rgba(255, 255, 255, 0.88);
  margin-bottom: var(--sp-6);
  text-wrap: pretty;
}

.hero-actions {
  display: flex;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

.hero-scroll {
  position: absolute;
  left: 50%;
  bottom: var(--sp-6);
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.7);
  animation: hero-bob 2.4s var(--ease) infinite;
}

@keyframes hero-bob {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 6px); }
}

/* ── Scroll-scrub hero ──────────────────────────────────────── */
/*
   Armed: the section is three screens tall and its stage is pinned, so scroll
   distance becomes video time. Static (the initial state, and the permanent
   one for reduced-motion or a failed load): an ordinary one-screen hero showing
   the poster. Starting static and upgrading is what stops a slow connection
   from showing a pinned, faded, frozen half-state.
*/
.hero-scrub {
  height: 300svh;
}

.hero-scrub .hero-stage {
  position: sticky;
  /* Sticks directly under the nav rather than behind it, and fills exactly the
     space that leaves — so the frame the visitor sees at scroll 0 is the same
     frame they see once it pins. */
  top: var(--nav-total);
  height: calc(100svh - var(--nav-total));
  max-height: none;
}

.hero-scrub-static {
  height: auto;
}

.hero-scrub-static .hero-stage {
  position: relative;
  /* MUST reset top. The armed rule above sets `top: var(--nav-total)` for the
     sticky pin, and both selectors match in the static state — so without this
     the stage keeps a 65px offset that `position: relative` happily applies as
     a real downward shift, leaving a band of bare page between the nav and the
     hero. It is invisible wherever the scrub arms, which is why it survived:
     desktop Chrome arms and looks perfect, while iOS Safari (whose ~739px
     usable viewport keeps the hero static) showed the gap on every load.
     The sibling project gets away with the same override only because its
     sticky offset is `top: 0`, where the leak is a no-op. */
  top: auto;
  height: calc(100svh - var(--nav-total));
}

/* The video element is NOT hidden in the static state. It shows its own poster
   attribute, which looks identical — and hiding it would stop the browser
   decoding the clip's metadata, so the scrub could never arm. */
.hero-scrub-static .hero-scroll {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .hero-scroll {
    animation: none;
  }
}

/* ── Trust band ─────────────────────────────────────────────── */

.stat-band {
  background: var(--brand-gradient);
}

.stat-band-inner {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-5);
  padding-block: var(--sp-7);
}

.stat {
  text-align: center;
}

.stat-value {
  font: var(--display-md);
  line-height: 1.2;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: #fff;
}

.stat-label {
  font: var(--body-sm);
  color: rgba(255, 255, 255, 0.86);
  margin-top: var(--sp-1);
  text-wrap: balance;
}

/* ── Varför välja oss ───────────────────────────────────────── */

.why-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: var(--sp-8);
  align-items: center;
}

.why-media img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line-1);
}

.why-para {
  font: var(--body-md);
  color: var(--text-body);
  margin-top: var(--sp-4);
  text-wrap: pretty;
}

.check-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

.check-list li {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  font: var(--body-md);
  color: var(--text-body);
}

.check-icon {
  flex: 0 0 auto;
  margin-top: 3px;
}

/* ── Hur det funkar ─────────────────────────────────────────── */

.process-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-5);
  counter-reset: none;
}

.process-step {
  background: var(--surface-card);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--sp-6);
  transition: transform var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}

.process-step:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-raised);
}

.process-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: var(--brand-gradient);
  color: #fff;
  font: 700 18px/1 var(--font-display);
  letter-spacing: var(--display-tracking);
  margin-bottom: var(--sp-4);
}

.process-title {
  font: var(--display-sm);
  line-height: 1.25;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: var(--ink-900);
  margin-bottom: var(--sp-2);
}

.process-body {
  font: var(--body-sm);
  color: var(--text-body);
  text-wrap: pretty;
}

/* ── Samarbetspartners ──────────────────────────────────────── */

.partners-heading {
  font: var(--kicker);
  letter-spacing: var(--kicker-tracking);
  text-transform: uppercase;
  color: var(--ink-500);
  text-align: center;
  margin-bottom: var(--sp-5);
}

.partners-strip {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-7);
}

/* Logos are supplier artwork: contained, never cropped, and desaturated so
   five different brand palettes do not fight the page. Colour returns on
   hover so the strip still reads as real partnerships. */
.partners-strip img {
  height: 40px;
  width: auto;
  max-width: 160px;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.62;
  transition: filter var(--speed) var(--ease), opacity var(--speed) var(--ease);
}

.partners-strip img:hover {
  filter: grayscale(0);
  opacity: 1;
}

/* ── Tidigare jobb ──────────────────────────────────────────── */

.gallery-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
}

.gallery-item {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--line-1);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--paper-2);
  transition: transform var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}

.gallery-item img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform 400ms var(--ease);
}

.gallery-item:hover {
  box-shadow: var(--shadow-raised);
}

.gallery-item:hover img {
  transform: scale(1.03);
}

/* Lightbox (gallery.js). A native <dialog> so Escape, focus trapping and the
   top-layer stacking come from the platform rather than from us. */
.gallery-lightbox {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: 100vw;
  max-height: 100svh;
  width: 100%;
  height: 100%;
}

.gallery-lightbox::backdrop {
  background: rgba(16, 24, 32, 0.92);
}

.gallery-lightbox-inner {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-7) var(--sp-5);
}

.gallery-lightbox img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: var(--radius-md);
}

.gallery-lightbox button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  transition: background var(--speed) var(--ease);
}

.gallery-lightbox button:hover {
  background: rgba(255, 255, 255, 0.24);
}

.gallery-prev { left: var(--sp-4); }
.gallery-next { right: var(--sp-4); }

.gallery-close {
  top: var(--sp-4) !important;
  right: var(--sp-4);
  transform: none !important;
}

/* ── Recensioner ────────────────────────────────────────────── */

.reviews-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-5);
}

.review-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  background: var(--surface-card);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--sp-6);
}

.review-text {
  font: var(--body-md);
  color: var(--text-body);
  flex: 1;
  text-wrap: pretty;
}

.review-text::before {
  content: "\201D";
  display: block;
  font-family: var(--font-display);
  font-size: 2.4rem;
  line-height: 0.7;
  color: var(--blue-200);
  margin-bottom: var(--sp-2);
}

.review-meta {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  border-top: 1px solid var(--line-2);
  padding-top: var(--sp-3);
}

.review-author {
  font: 600 15px/1.2 var(--font-body);
  color: var(--ink-900);
}

.review-source {
  font: var(--body-sm);
  /* ink-300 is the hairline/placeholder grey and measured 2.30 against the
     white card, less than half the 4.5 this 14px text needs. It is readable
     text naming where a review came from, so it takes the muted TEXT token,
     which is 5.50. ink-300 stays correct on the dark footer, where it is the
     light end of the scale rather than the faint one. */
  color: var(--ink-500);
  margin-left: auto;
}

/* ── Vehicle band ───────────────────────────────────────────── */

.fleet {
  background: var(--blue-800);
  padding-block: var(--section-pad);
}

.fleet-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: var(--sp-8);
  align-items: center;
}

.fleet-media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.15);
}

.fleet-text {
  font: var(--body-lg);
  color: rgba(255, 255, 255, 0.86);
  margin-top: var(--sp-4);
  text-wrap: pretty;
}

/* ── Kontakt ────────────────────────────────────────────────── */

.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--sp-8);
  align-items: start;
}

.contact-body {
  font: var(--body-lg);
  color: var(--text-body);
  margin-top: var(--sp-4);
  text-wrap: pretty;
}

.contact-promise {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  background: var(--blue-100);
  border: 1px solid var(--blue-200);
  border-radius: var(--radius-sm);
  font: 600 15px/1.4 var(--font-body);
  color: var(--blue-800);
}

.contact-promise iconify-icon {
  color: var(--blue-600);
  flex: 0 0 auto;
}

.contact-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

.contact-list li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font: var(--body-md);
  color: var(--text-body);
}

.contact-list iconify-icon {
  color: var(--blue-600);
  flex: 0 0 auto;
}

.contact-list a {
  color: var(--ink-900);
  font-weight: 600;
  border-bottom: 1px solid var(--blue-200);
  transition: border-color var(--speed) var(--ease);
}

.contact-list a:hover {
  border-bottom-color: var(--blue-600);
}

/* The form column: the reassurance row and the card, stacked. The row is a
   sibling of #lead-form rather than a child, because that id is what the
   submission patches. */
.contact-form-col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* Flex with wrap so a narrow column breaks BETWEEN the claims and never inside
   one. The bullet belongs to the claim that FOLLOWS it, as a ::before inside
   the same flex item: written as a separate element it becomes its own wrap
   opportunity, and a phone leaves it dangling at the end of a line, which reads
   as text cut off rather than as a separator. */
.contact-trust {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  font: var(--body-sm);
  color: var(--text-muted);
}

.contact-trust-item + .contact-trust-item::before {
  content: "•";
  color: var(--blue-400);
  margin-right: var(--sp-2);
}

.contact-card {
  background: var(--surface-card);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--sp-6);
}

.lead-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.lead-field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

.lead-field {
  display: flex;
  flex-direction: column;
}

.lead-form textarea.field-input {
  resize: vertical;
  min-height: 110px;
}

.lead-consent {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  font: var(--body-sm);
  color: var(--text-muted);
}

.lead-consent input {
  /* 24px is the floor WCAG 2.2 target-size (AA) sets, and it is measured on the
     input's own box: the surrounding <label> means tapping the sentence toggles
     this too, but no automated check credits that, and on a phone the box is
     what a thumb aims at. It was 18px, which failed both. The box is 3px taller
     than the 14px/1.5 line beside it, so it sits flush with the first line
     rather than needing the old nudge. */
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  accent-color: var(--blue-600);
}

.lead-consent a {
  color: var(--blue-600);
  text-decoration: underline;
}

.lead-fineprint {
  font: var(--body-sm);
  color: var(--text-muted);
  text-align: center;
}

.lead-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  background: color-mix(in oklch, var(--danger) 10%, var(--paper-0));
  border: 1px solid color-mix(in oklch, var(--danger) 35%, var(--paper-0));
  color: var(--danger);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
  font: var(--body-sm);
  margin-bottom: var(--sp-4);
}

.lead-error iconify-icon {
  flex: 0 0 auto;
  margin-top: 2px;
}

.lead-success {
  text-align: center;
  padding: var(--sp-5) var(--sp-2);
}

.lead-success-icon {
  width: 56px;
  height: 56px;
  margin: 0 auto var(--sp-5);
  border-radius: 50%;
  background: var(--brand-gradient);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lead-success h3 {
  font: var(--display-sm);
  line-height: 1.3;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: var(--ink-900);
  margin-bottom: var(--sp-3);
}

.lead-success p {
  font: var(--body-md);
  color: var(--text-body);
}

.lead-success-sub {
  font: var(--body-sm) !important;
  color: var(--text-muted) !important;
  margin-top: var(--sp-3);
}

/* Postnummer och ort. The postal code is a fixed five digits and the town is
   not, so an even 1fr 1fr split leaves a stretched box next to a cramped one.
   The minmax floor is what keeps "Södertälje" from wrapping under its own
   label on a narrow phone, where the row folds to one column anyway. */
.lead-field-row--postal {
  grid-template-columns: minmax(7.5rem, 0.6fr) minmax(0, 1fr);
}

/* ── The confirmation popup ─────────────────────────────────── */

/* A real <dialog>, opened with showModal() - so the browser supplies the top
   layer, the backdrop, the focus trap and Escape. Everything here is presentation
   on top of that; nothing here is what makes it modal. */
.lead-modal {
  width: min(28rem, calc(100vw - 2 * var(--sp-4)));
  padding: var(--sp-6);
  border: 0;
  border-radius: 12px;
  background: #fff;
  color: var(--text-body);
  text-align: center;
  box-shadow: 0 24px 60px rgb(16 24 32 / 22%);
}

/* Not `background` shorthand: ::backdrop inherits nothing, and a shorthand here
   would also reset the browser's own painting of the layer underneath. */
.lead-modal::backdrop {
  background-color: rgb(16 24 32 / 55%);
}

.lead-modal-icon {
  width: 56px;
  height: 56px;
  margin: 0 auto var(--sp-5);
  border-radius: 50%;
  background: var(--brand-gradient);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lead-modal-title {
  font: var(--display-sm);
  line-height: 1.3;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: var(--ink-900);
  margin-bottom: var(--sp-3);
}

.lead-modal-body {
  font: var(--body-md);
  color: var(--text-body);
  margin-bottom: var(--sp-5);
}

/* ── Prose page (integritetspolicy) ─────────────────────────── */

.prose-page {
  background: var(--paper-1);
}

.prose {
  max-width: 72ch;
}

.prose h1 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: var(--ink-900);
  padding-block: 0.04em;
  margin-bottom: var(--sp-5);
}

.prose h2 {
  font: var(--display-sm);
  line-height: 1.3;
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: var(--ink-900);
  margin-top: var(--sp-7);
  margin-bottom: var(--sp-3);
}

.prose p,
.prose li {
  font: var(--body-md);
  color: var(--text-body);
  text-wrap: pretty;
}

.prose p + p {
  margin-top: var(--sp-4);
}

.prose-lead {
  font: var(--body-lg) !important;
  color: var(--text-body);
}

.prose ul {
  margin-top: var(--sp-4);
  padding-left: 1.2em;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.prose a {
  /* .prose-page sits on --paper-1, where blue-600 is 4.48 against the 4.5 this
     text needs. Same surface dependency as .prose-body a in pages.css: the two
     prose stylesheets are separate but they render on the same tint, so a fix
     to one that skips the other leaves the privacy policy failing on its own. */
  color: var(--blue-700);
  text-decoration: underline;
}

/* ── Responsive ─────────────────────────────────────────────── */

@media (max-width: 1040px) {
  .process-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .reviews-grid,
  .gallery-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 860px) {
  .section {
    padding-block: var(--sp-8);
  }

  .fleet {
    padding-block: var(--sp-8);
  }

  .why-grid,
  .fleet-grid,
  .contact-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-6);
  }

  /* Copy before the photograph on a phone: the text is what sells, and a
     full-width image above it pushes it below the fold. */
  .why-media {
    order: 2;
  }

  .why-media img {
    aspect-ratio: 16 / 10;
  }

  .stat-band-inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-6);
  }
}

@media (max-width: 600px) {
  /* Cloudflare's widget is a fixed 300px iframe that cannot shrink. At 390px
     the default container + card padding left it 293px of room, so the card
     scrolled sideways by 7px. Tightening both to 16px gives it 324px — the one
     element on the page whose width we do not control dictates the minimum. */
  .container {
    padding-inline: var(--sp-4);
  }

  /* On a phone the hero copy (badge + two-line headline + ingress + two
     stacked buttons) is taller than any fixed stage height, and a centred flex
     child that overflows spills out BOTH ends — the headline slid up under the
     sticky nav. So on mobile the copy flows and the stage grows to fit it,
     with a floor rather than a fixed height. This also survives the copy being
     edited: it is CMS text, and a longer ingress must not reintroduce the
     overlap.

     align-items: flex-start is the other half, and it is about the photograph
     rather than the text. The picture is landscape and the viewport is
     portrait, so `cover` crops the WIDTH and always shows the full frame
     height — which fixes the crew at 72–90 % of the stage on every phone, with
     their faces right at the top of that band. A centred copy block ran
     19–81 % and put the second button squarely across those faces. Anchored to
     the top it runs ~4–71 % instead, ending just above them: the empty sky the
     old layout wasted is exactly the room the text needed. */
  .hero-stage,
  .hero-scrub-static .hero-stage {
    height: auto;
    min-height: calc(100svh - var(--nav-total));
    max-height: none;
    display: flex;
    align-items: flex-start;
  }

  /* Because the stage no longer stretches this, the band is exactly as tall as
     the copy — which is what lets it carry the scrim itself, below.

     --hero-reserve is the empty space kept below the copy for the crew, and it
     is why this holds on a small phone as well as a large one. Top-aligning
     alone does not: the copy is a fixed ~520px of text while the stage shrinks
     with the viewport, so on a 667px-tall iPhone SE the buttons landed 89px
     INTO the crew again. As padding it becomes part of the band's height, so
     when the viewport is too short to fit copy + reserve the stage grows to
     fit instead of overlapping — the picture extends a little past the fold,
     which costs a short scroll and keeps the faces clear.

     The size is arithmetic, not taste. The crew's heads sit at 70.5 % of the
     picture, which fills the stage height, so the copy must end above 0.705·H
     where H = copy + reserve. Solving for a ~30px margin at this copy length
     gives ~17rem; in rem rather than px so it tracks the type it is making
     room for, including when the reader has scaled it up. */
  .hero-content {
    --hero-reserve: 17rem;
    position: relative;
    inset: auto;
    width: 100%;
    padding-block: var(--sp-4) var(--hero-reserve);
    /* The overlay travels WITH the copy instead of covering the photograph.
       A full-stage scrim had to be dark enough for text at the bottom too, so
       it dimmed all of it — the crew included, which is the one thing in the
       frame worth seeing. This gradient is opaque behind the badge, headline
       and ingress, then fades out across the buttons (which are solid-filled
       and need no help) and is gone before the band ends. Everything below —
       the crew, the vans, the asphalt — is the undimmed photograph.

       background-size stops it at the reserve rather than letting it run the
       full height of the band: the reserve exists precisely to be undimmed
       picture, so a gradient painted over it would undo the whole point.

       Anchoring it to the copy rather than to the stage also means it cannot
       drift out of step with the text: a longer CMS ingress makes the band
       taller and the scrim grows with it. */
    background-image: linear-gradient(
      180deg,
      rgba(16, 24, 32, 0.88) 0%,
      rgba(16, 24, 32, 0.86) 52%,
      rgba(16, 24, 32, 0.66) 78%,
      rgba(16, 24, 32, 0) 100%
    );
    background-repeat: no-repeat;
    background-size: 100% calc(100% - var(--hero-reserve));
  }

  /* Tighter vertical rhythm inside the copy. This buys the reserve room to
     work with in the static hero, and it is also what decides whether the
     pinned scrub can run at all on a phone: every pixel saved here is a pixel
     of clearance above the crew in a stage that cannot grow. The desktop
     spacing is unchanged. */
  .hero-badge {
    margin-bottom: var(--sp-3);
  }

  .hero-title {
    margin-bottom: var(--sp-3);
  }

  .hero-ingress {
    margin-bottom: var(--sp-4);
  }

  /* Replaced by the gradient above. The design system's scrim runs
     left-to-right, which works when the copy occupies the left half of a wide
     frame; in a 390px column the text spans the full width and there is no
     bright side to fade towards. */
  .hero-scrim {
    display: none;
  }

  .hero-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .hero-actions a {
    justify-content: center;
  }

  .process-grid,
  .reviews-grid,
  .gallery-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .lead-field-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .partners-strip {
    gap: var(--sp-5);
  }

  .partners-strip img {
    height: 32px;
  }

  .contact-card {
    padding: var(--sp-5) var(--sp-4);
  }

  /* The phone number and e-mail are the two things a visitor taps instead of
     filling in the form. Give them a full 44px row on touch. */
  .contact-list {
    gap: 0;
  }

  .contact-list li {
    min-height: 44px;
  }

  .lead-form .cf-turnstile {
    /* Belt and braces: if a future widget size still overruns, contain it here
       rather than letting the whole page scroll sideways. */
    max-width: 100%;
    overflow-x: auto;
  }
}

