/* ------------------------------------------------------------------ tokens */

:root {
  --veld: #3f4a2a;          /* the committed field colour */
  --veld-diep: #333c22;
  --grond: #e9ecec;         /* cool plaster, deliberately not warm cream */
  --grond-op: #f4f6f6;
  --inkt: #1c211e;
  --inkt-zacht: #4a534d;
  --op-veld: #e9ecec;
  --op-veld-zacht: #c9d2bc;
  --rand: #cdd3d3;
  --rand-veld: #5c6742;

  --font-kop: "Familjen Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-tekst: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* every corner is square. the trade sells exact edges. */
  --radius: 0;

  --maat-1: 0.25rem;
  --maat-2: 0.5rem;
  --maat-3: 1rem;
  --maat-4: 1.5rem;
  --maat-5: 2.5rem;
  --maat-6: 4rem;
  --maat-7: 6rem;

  --breed: 76rem;
  --meet: 34rem;             /* reading measure */

  --soepel: cubic-bezier(0.32, 0.72, 0, 1);
}

@font-face {
  font-family: "Familjen Grotesk";
  src: url("../fonts/familjen-grotesk-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Familjen Grotesk";
  src: url("../fonts/familjen-grotesk-latin-ext.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------------- reset */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--grond);
  color: var(--inkt);
  font-family: var(--font-tekst);
  font-size: 1.0625rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--font-kop);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.022em;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; }

a { color: inherit; }

:focus-visible {
  outline: 3px solid var(--veld);
  outline-offset: 3px;
}

.veld :focus-visible { outline-color: var(--op-veld); }

.wrap {
  width: 100%;
  max-width: var(--breed);
  margin-inline: auto;
  padding-inline: var(--maat-3);
}

.overslaan {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--veld);
  color: var(--op-veld);
  padding: var(--maat-3);
  z-index: 60;
}
.overslaan:focus { left: 0; }

/* --------------------------------------------------------- demo banner
   Legal guardrail: exact text, on every page, first element. Set on
   the field colour so it reads as part of the design rather than a bolted-on
   strip, but it is never allowed to be subtle: 1rem minimum, full contrast. */

.demo-banner {
  background: var(--veld-diep);
  color: var(--op-veld);
  font-size: 1rem;
  line-height: 1.45;
}
.demo-banner p {
  max-width: var(--breed);
  margin-inline: auto;
  padding: var(--maat-3);
}

/* ------------------------------------------------------------------ header */

.site-header {
  background: var(--grond);
  border-bottom: 1px solid var(--rand);
  position: relative;
  z-index: 40;
}
.site-header__inner {
  max-width: var(--breed);
  margin-inline: auto;
  padding: var(--maat-2) var(--maat-3);
  display: flex;
  align-items: center;
  gap: var(--maat-4);
  min-height: 64px;
}
.site-header__logo {
  font-family: var(--font-kop);
  font-weight: 700;
  font-size: 1.3rem;
  letter-spacing: -0.03em;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  /* the wordmark is two flex items, and a flex container drops the whitespace
     text node between them, so the space has to be a gap or it reads "RojBouw" */
  gap: 0.26em;
  min-height: 44px;
  margin-right: auto;
}
.site-header__logo span { color: var(--veld); }

.site-header__nav ul {
  list-style: none;
  display: flex;
  gap: var(--maat-4);
  margin: 0;
  padding: 0;
}
.site-header__nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;   /* "Home" is 43px wide; a tap target needs 44 both ways */
  text-decoration: none;
  font-size: 1rem;
  color: var(--inkt-zacht);
  transition: color 0.25s var(--soepel);
}
.site-header__nav a:hover,
.site-header__nav a[aria-current="page"] { color: var(--veld); }
.site-header__nav a[aria-current="page"] { font-weight: 700; }

.nav-knop {
  display: none;
  min-height: 44px;
  min-width: 44px;
  align-items: center;
  justify-content: center;
  gap: var(--maat-2);
  background: none;
  border: 1px solid var(--rand);
  color: var(--inkt);
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
  padding-inline: var(--maat-3);
}

/* The mobile call affordance is icon + label, never display:none. Hiding a
   call button on small screens is a serious mistake for a local trade business:
   tap-to-call is plausibly the top mobile conversion path. */
.bel-knop {
  display: inline-flex;
  align-items: center;
  gap: var(--maat-2);
  min-height: 44px;
  padding-inline: var(--maat-3);
  background: var(--veld);
  color: var(--op-veld);
  text-decoration: none;
  font-weight: 700;
  font-size: 1rem;
  transition: background 0.25s var(--soepel);
}
.bel-knop:hover { background: var(--veld-diep); }
.bel-knop svg { width: 18px; height: 18px; flex: none; fill: currentColor; }

/* -------------------------------------------------------------------- knop */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 0 var(--maat-5);
  background: var(--veld);
  color: var(--op-veld);
  border: 1px solid var(--veld);
  font-family: var(--font-kop);
  font-weight: 700;
  font-size: 1.0625rem;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.3s var(--soepel), transform 0.15s var(--soepel);
}
.btn:hover { background: var(--veld-diep); }
.btn:active { transform: translateY(1px); }

.btn--op-veld {
  background: var(--op-veld);
  color: var(--veld-diep);
  border-color: var(--op-veld);
}
.btn--op-veld:hover { background: #fff; }

.btn--lijn {
  background: transparent;
  color: var(--op-veld);
  border-color: var(--rand-veld);
}
.btn--lijn:hover { background: rgba(233, 236, 236, 0.12); }

/* ================================================================== F1 hero
   A photograph hung on a painted wall. The field's asymmetry lives in its own
   padding; the photograph is a bare <img> with no background of its own, so it
   can never register as an opaque stacked child sitting off-centre inside a
   painted parent. That shape is what exposes a strip of the parent down one
   side. */

.hero {
  background: var(--veld);
  color: var(--op-veld);
  overflow: hidden;
}
.hero__inner {
  max-width: var(--breed);
  margin-inline: auto;
  padding: var(--maat-5) var(--maat-3) var(--maat-4);
  display: grid;
  gap: var(--maat-5);
}
.hero__tekst { max-width: 30rem; }

.hero h1 {
  font-size: clamp(2.1rem, 7.4vw, 4.15rem);
  margin-bottom: var(--maat-3);
}
.hero__sub {
  font-size: 1.0625rem;
  color: var(--op-veld-zacht);
  max-width: 27rem;
  margin-bottom: var(--maat-4);
}
.hero__acties {
  display: flex;
  flex-wrap: wrap;
  gap: var(--maat-3);
}
/* The photograph is INSET in the field, not a column beside the text.
   Review finding: with align-items:center the image was the tallest grid item, so
   it defined the row height and had no independent inset at all. The margin was
   just the section padding every other section also has, which is why it read
   as a two-column split on a green background rather than as the FORM. It now
   carries its own offset on both axes, at both breakpoints. */
.hero__beeld {
  /* phone: field visible on all four sides, and unequally. Without this the
     photo shares the text gutter exactly and the composition disappears on the
     device most of these are opened on.
     The width has to subtract the margin: `width: 100%` plus a left margin
     overflows the cell and the photo bleeds off the right edge, which
     `.hero { overflow: hidden }` then hides instead of reporting. */
  width: calc(100% - var(--maat-5));
  margin-left: var(--maat-5);
  margin-top: var(--maat-2);
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

/* ============================================================ F2 the number
   One oversized numeral against a paragraph. No card, no border, no icon:
   the fact is the graphic. */

.cijfer {
  padding-block: var(--maat-6);
}
.cijfer__inner {
  display: grid;
  gap: var(--maat-4);
  align-items: start;
}
.cijfer__getal {
  font-family: var(--font-kop);
  font-weight: 700;
  font-size: clamp(4.5rem, 17vw, 9rem);
  line-height: 0.82;
  letter-spacing: -0.05em;
  color: var(--veld);
}
.cijfer__bron {
  font-size: 1rem;
  color: var(--inkt-zacht);
  margin-top: var(--maat-2);
}
.cijfer__tekst p + p { margin-top: var(--maat-3); }
.cijfer__tekst { max-width: var(--meet); }

/* ========================================================== F3 diensten grid
   Five cells: one photograph and four services. Every cell is filled, none is
   an empty tile. Deliberately not three equal cards. */

/* The section is a painted wall and the service panels are mounted on it. That
   is the hero's own idea at section scale, and it is also what carries the
   Committed colour strategy: measured as AREA rather than presence, the olive
   was only reaching about 20% of the page before this section became a field.
   A strategy that is true of the loudest section and false everywhere else is
   an accent pretending to be a field. */
.diensten {
  padding-block: var(--maat-6);
  background: var(--veld);
  color: var(--op-veld);
}
.diensten__kop { max-width: var(--meet); margin-bottom: var(--maat-5); }
.diensten__kop h2 { font-size: clamp(1.75rem, 4.6vw, 2.75rem); }
.diensten__kop p { margin-top: var(--maat-3); color: var(--op-veld-zacht); }

.rooster {
  display: grid;
  gap: 1px;
  background: var(--rand-veld);
  border: 1px solid var(--rand-veld);
}
.dienst {
  background: var(--grond-op);
  padding: var(--maat-4);
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--maat-2);
}
.dienst h3 {
  font-size: 1.35rem;
  letter-spacing: -0.015em;
  color: var(--inkt);
}
.dienst p {
  color: var(--inkt-zacht);
  font-size: 1rem;
}
.dienst--beeld {
  padding: 0;
  background: var(--veld);
  color: var(--op-veld);
  overflow: hidden;
}
.dienst--beeld img {
  width: 100%;
  height: 100%;
  min-height: 220px;
  object-fit: cover;
}
.dienst--groot { justify-content: flex-end; }
.dienst--groot h3 { font-size: clamp(1.6rem, 4vw, 2.1rem); }
.dienst--breed { grid-column: 1 / -1; }

/* ============================================================== F4 the band
   A breathing beat between two ground sections. Minimum height is set so the
   band can never crop to an unreadable sliver. */

.band {
  display: block;
  width: 100%;
  height: clamp(200px, 26vw, 340px);
  object-fit: cover;
}

/* ============================================================ F5 the reviews
   Full-bleed field. This band is the bulk of the Committed colour area and is
   measured as area, not presence. */

.reviews {
  background: var(--veld);
  color: var(--op-veld);
  padding-block: var(--maat-6);
}
.reviews h2 {
  font-size: clamp(1.6rem, 4.2vw, 2.4rem);
  margin-bottom: var(--maat-5);
  max-width: 26rem;
}
.reviews__paar {
  display: grid;
  gap: var(--maat-5);
}
.review blockquote {
  margin: 0;
  font-family: var(--font-kop);
  font-size: clamp(1.25rem, 3.1vw, 1.7rem);
  line-height: 1.28;
  letter-spacing: -0.015em;
}
.review figcaption {
  margin-top: var(--maat-3);
  font-size: 1rem;
  color: var(--op-veld-zacht);
}

/* ================================================================= F6 offerte */

.offerte { padding-block: var(--maat-6); }
.offerte__kop { max-width: var(--meet); margin-bottom: var(--maat-5); }
.offerte__kop h2 { font-size: clamp(1.75rem, 4.6vw, 2.75rem); }
.offerte__kop p { margin-top: var(--maat-3); color: var(--inkt-zacht); }

.offerte__lijf { display: grid; gap: var(--maat-6); align-items: start; }

.formulier { display: block; max-width: 34rem; }
.formulier label {
  display: block;
  font-weight: 700;
  font-size: 1rem;
  margin-bottom: var(--maat-2);
}
.formulier label + p { margin-bottom: var(--maat-2); }
.formulier input[type="text"],
.formulier textarea {
  width: 100%;
  font: inherit;
  font-size: 1.0625rem;
  color: var(--inkt);
  background: #fff;
  border: 1px solid var(--inkt-zacht);
  border-radius: var(--radius);
  padding: 0.7rem 0.8rem;
  min-height: 48px;
  margin-bottom: var(--maat-4);
}
.formulier textarea { min-height: 7.5rem; resize: vertical; margin-bottom: var(--maat-2); }
.formulier input:focus, .formulier textarea:focus { border-color: var(--veld); }
.formulier .btn { margin-top: var(--maat-2); }

.formulier__disclaimer {
  font-size: 1rem;
  color: var(--inkt-zacht);
  margin-bottom: var(--maat-4);
}
.formulier__disclaimer--na { margin-top: var(--maat-3); margin-bottom: 0; }

.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.naast {
  border-top: 3px solid var(--veld);
  padding-top: var(--maat-4);
  max-width: 22rem;
}
/* the heading level varies by page so the outline never skips a level; the
   visual size is decoupled from it deliberately */
.naast :is(h2, h3) { font-size: 1.3rem; margin-bottom: var(--maat-3); }
.naast p { color: var(--inkt-zacht); }
.naast p + p { margin-top: var(--maat-3); }
.naast .telefoon {
  font-family: var(--font-kop);
  font-weight: 700;
  font-size: 1.6rem;
  color: var(--veld);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* -------------------------------------------------------- inner page header */

.paginakop {
  background: var(--veld);
  color: var(--op-veld);
  padding-block: var(--maat-6) var(--maat-5);
}
.paginakop h1 {
  font-size: clamp(2rem, 6.4vw, 3.5rem);
  max-width: 24ch;
}
.paginakop p {
  margin-top: var(--maat-3);
  color: var(--op-veld-zacht);
  max-width: var(--meet);
}

/* ------------------------------------------------------------ prose + lists */

.prose { padding-block: var(--maat-6); }
.prose--op { background: var(--grond-op); }
.prose__lijf { max-width: var(--meet); }
.prose__lijf h2 { font-size: clamp(1.6rem, 4.2vw, 2.3rem); margin-bottom: var(--maat-3); }
.prose__lijf p + p { margin-top: var(--maat-3); }
.prose__lijf p + h2 { margin-top: var(--maat-5); }

.split {
  display: grid;
  gap: var(--maat-5);
  align-items: center;
}
.split img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }

.werkwijze {
  display: grid;
  gap: 1px;
  background: var(--rand);
  border: 1px solid var(--rand);
  margin-top: var(--maat-5);
}
.stap {
  background: var(--grond-op);
  padding: var(--maat-4);
  min-width: 0;
}
.stap h3 { font-size: 1.2rem; margin-bottom: var(--maat-2); }
.stap p { color: var(--inkt-zacht); font-size: 1rem; }

/* ------------------------------------------------------------------ footer */

.site-footer {
  background: var(--veld);
  color: var(--op-veld);
  padding-block: var(--maat-6) var(--maat-4);
}
.site-footer__grid {
  display: grid;
  gap: var(--maat-5);
}
.site-footer__naam {
  font-family: var(--font-kop);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: -0.03em;
  margin-bottom: var(--maat-3);
}
.site-footer a { color: var(--op-veld); }
.site-footer p { color: var(--op-veld-zacht); }
.site-footer p + p { margin-top: var(--maat-2); }
.site-footer h3 {
  font-size: 1.05rem;
  margin-bottom: var(--maat-2);
  color: var(--op-veld);
}
.site-footer ul { list-style: none; margin: 0; padding: 0; }
/* min-width as well as min-height: a short label like "Home" is 43px wide and
   a tap target has to clear 44px in BOTH dimensions. Caught by the gate. */
.site-footer li a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  text-decoration: none;
}
.site-footer li a:hover { text-decoration: underline; }
.site-footer__telefoon {
  font-family: var(--font-kop);
  font-weight: 700;
  font-size: 1.5rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.site-footer__staart {
  margin-top: var(--maat-5);
  padding-top: var(--maat-4);
  border-top: 1px solid var(--rand-veld);
  font-size: 1rem;
  color: var(--op-veld-zacht);
}
.site-footer__staart p + p { margin-top: var(--maat-2); }

/* ----------------------------------------------------------------- bedankt */

.bedankt { padding-block: var(--maat-7); }
.bedankt__lijf { max-width: var(--meet); }
.bedankt h1 { font-size: clamp(2rem, 6vw, 3.2rem); margin-bottom: var(--maat-3); }
.bedankt p { color: var(--inkt-zacht); }
.bedankt p + p { margin-top: var(--maat-3); }
.bedankt .btn { margin-top: var(--maat-5); }

/* ================================================================== motion
   MOTION 4. One scroll device: a surface revealed as you reach it, which is the
   one thing this trade's work does in life. Nothing loops.
   Hover feedback is colour-only on every interactive element (nav links, both
   button variants, the call button, footer links), plus a single 1px nudge on
   :active. Nothing translates or scales on hover.
   Honest note: an independent review measured the delivered motion at about 2
   against a target of 4. The gap is left open deliberately rather than closed
   by inventing animation late in the build. */

/* Scoped to .js, which an inline script in <head> sets before first paint.
   Without that scope, a visitor with JavaScript off or a script that fails to
   load gets a homepage whose intro, all four services, both reviews and the
   contact block are permanently at opacity 0. Verified: JS disabled left 8 of 8
   blocks invisible. Content must never depend on script to become visible. */
@media (prefers-reduced-motion: no-preference) {
  .js .onthul {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.7s var(--soepel), transform 0.7s var(--soepel);
  }
  .js .onthul.is-zichtbaar {
    opacity: 1;
    transform: none;
  }
}

/* ============================================================== breakpoints */

@media (min-width: 40rem) {
  .wrap, .hero__inner, .site-header__inner, .demo-banner p { padding-inline: var(--maat-4); }
  .reviews__paar { grid-template-columns: 1fr 1fr; gap: var(--maat-6); }
  .rooster { grid-template-columns: 1fr 1fr; }
  .werkwijze { grid-template-columns: 1fr 1fr; }
  .site-footer__grid { grid-template-columns: 1.4fr 1fr 1fr; }
}

@media (min-width: 60rem) {
  .wrap, .hero__inner, .site-header__inner, .demo-banner p { padding-inline: var(--maat-5); }

  /* The field's unequal padding plus the photo's own offset ARE the
     composition. What actually ships, measured at 1440: roughly 190px of field
     above the photo, 152px to its right, 64px below. Generous top, tight
     bottom. The photo is a little over half the field height rather than
     filling it, so there is wall around the picture. */
  .hero__inner {
    grid-template-columns: 1fr 0.78fr;
    align-items: start;
    gap: var(--maat-7);
    padding: var(--maat-7) var(--maat-5) var(--maat-6);
  }
  .hero h1 { font-size: clamp(3rem, 4.6vw, 4.15rem); }
  .hero__beeld {
    aspect-ratio: 4 / 3;
    width: 100%;
    margin-left: 0;
    margin-top: 6.5rem;
  }

  .cijfer__inner { grid-template-columns: auto 1fr; gap: var(--maat-7); }
  .cijfer { padding-block: var(--maat-7); }

  /* the wide cell has to earn its span: without a row minimum its height was
     set by its own two lines and came out shorter than the row beneath it,
     so justify-content had no spare height to work with */
  .rooster {
    grid-template-columns: 1.6fr 1fr 1fr;
    grid-template-rows: minmax(9rem, auto) auto auto;
  }
  .dienst--beeld { grid-row: span 2; }
  .dienst--groot { grid-column: span 2; }
  .diensten, .offerte, .reviews, .prose { padding-block: var(--maat-7); }

  .offerte__lijf { grid-template-columns: 1.5fr 1fr; gap: var(--maat-7); }
  .split { grid-template-columns: 1fr 1fr; gap: var(--maat-6); }
  .werkwijze { grid-template-columns: repeat(4, 1fr); }
}

/* Below 40rem the primary actions span the column.
   Design reason: on a 360px screen a full-width action is the easiest thing to
   hit, and this trade's whole funnel is one tap.
   Structural reason: an opaque button that wraps onto its own row is a stacked
   child of a painted section, and left-aligning it leaves an asymmetric strip
   of that section showing beside it, which is very visible on a real phone.
   Spanning the column is symmetric, so there is no strip. */
@media (max-width: 39.99rem) {
  .btn { width: 100%; }
  .hero__acties { flex-direction: column; align-items: stretch; }
}

/* mobile nav: the list collapses behind a labelled button, never a bare icon */
@media (max-width: 47.99rem) {
  .nav-knop { display: inline-flex; }
  .site-header__inner { flex-wrap: wrap; row-gap: var(--maat-2); }
  .site-header__nav {
    flex-basis: 100%;
    display: none;
  }
  .site-header__nav.is-open { display: block; }
  .site-header__nav ul {
    flex-direction: column;
    gap: 0;
    padding-bottom: var(--maat-2);
  }
  .site-header__nav a {
    width: 100%;
    border-top: 1px solid var(--rand);
  }
  /* Full width, not floated left. Two reasons, and the first is the one that
     matters: a left-aligned opaque button on its own wrapped row leaves a strip
     of header showing down its right side, which is the exact defect a person
     very visible on a real phone. Spanning the row is
     symmetric, so there is no strip to see. The second reason is that this is
     the primary mobile action for a trade business, and it should be the widest
     thing in the header rather than a small tab in the corner. */
  .bel-knop {
    order: 3;
    flex-basis: 100%;
    justify-content: center;
  }
}
