/* ============================================================
   PCG — Pricing components

   Two objects, and they divide the work between them:
     .ledger   the fee schedule. A quick overview: one entry point per
               discipline, and nothing to compare cell by cell.
     .est      the estimator. Where the detail lives, as a machined
               instrument plate, ending in a scoped request.
   Plus .conditions, .price-head and the .rq__ request modal.

   The tier matrices, add-on grids, bundle cards, AI bento, hourly-rate block,
   category sub-nav and configurator styles that used to live here are gone with
   the seven pricing pages they served: 82 rule blocks, about 15% of this file.
   Don't reinstate a tier grid or a per-unit rate card; see the note above .est
   for why the offer is no longer published that way.
   ============================================================ */

/* ============================================================
   The ledger — the fee schedule on tarieven.html

   This replaces the package matrix and the add-on grid that used to run across
   eight pricing pages. The reason is commercial rather than visual: a grid of
   tiers plus a per-unit rate card published the firm's whole position, invited a
   competitor to undercut it line by line, and contradicted the site's own promise
   that a price follows from a conversation. A ruled schedule states the LEVEL
   instead: one figure per discipline, and nothing to compare cell by cell.

   Set like a fee schedule on purpose. Rules rather than boxes, the figure in the
   display face and hung right where a reader's eye goes for a number, and lining
   tabular figures so the amounts align down the column. That is the typographic
   difference between a schedule and a shop.
   ============================================================ */
.ledger{display:grid;margin-top:clamp(30px,3.4vw,48px)}
.ledger__row{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  gap:clamp(20px,3vw,56px);
  align-items:baseline;
  padding-block:clamp(24px,2.6vw,34px);
  border-top:1px solid var(--rule);
  transition:border-color .55s var(--ease);
}
.ledger__row:last-child{border-bottom:1px solid var(--rule)}
/* The rule above a hovered row warms, rather than the row filling in: a schedule
   must not look clickable, because these rows are not links. */
.ledger__row:hover{border-top-color:var(--gold-text)}

.ledger__name{display:grid;gap:7px;min-width:0}
/* Tracked micro-numeral, the same 01-05 language the service cards and the
   process ladder use. aria-hidden in the markup: it is ordering, not content. */
.ledger__no{
  font-family:var(--ff-body);font-weight:600;
  font-size:11.5px;letter-spacing:.18em;
  color:var(--gold-text);opacity:.85;
}
.ledger__name .display-face{
  font-size:clamp(21px,2.1vw,27px);line-height:1.16;
  letter-spacing:-.012em;color:var(--ink);
}
.ledger__note{
  font-size:14.5px;line-height:1.55;color:var(--muted);
  max-width:52ch;text-wrap:pretty;
}

.ledger__fig{
  display:grid;justify-items:end;gap:4px;
  text-align:right;white-space:nowrap;
}
/* "vanaf" is apparatus, so it is smaller and tracked rather than the same size as
   the number it qualifies. */
.ledger__from{
  font-family:var(--ff-body);font-weight:500;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--muted);
}
.ledger__amt{
  font-family:var(--ff-display);font-weight:600;
  font-size:clamp(30px,3.4vw,46px);line-height:1;
  letter-spacing:-.02em;color:var(--gold-text);
  /* Lining tabular figures so 999 and 349 align on the same stem down the
     column. A didone's proportional figures would ripple here. */
  font-variant-numeric:lining-nums tabular-nums;
}
.ledger__per{
  font-family:var(--ff-body);font-weight:500;
  font-size:13px;letter-spacing:.02em;color:var(--muted);
}
/* The row whose value is a word rather than a number. It occupies the same slot
   as .ledger__amt so it aligns with the figures above and below it, but it is set
   smaller: "scope" at 46px would shout where €999 merely states. Muted rather
   than gold, because gold is the ink for money on this page. */
.ledger__amt--word{
  font-size:clamp(20px,2vw,27px);
  letter-spacing:-.008em;
  color:var(--muted);
  font-variant-numeric:normal;
}

@media (max-width:640px){
  /* Stack. At a phone width the figure cannot sit beside a wrapping name without
     squeezing both, so it moves under the name and back to the left margin. */
  .ledger__row{grid-template-columns:1fr;gap:14px}
  .ledger__fig{justify-items:start;text-align:left}
  .ledger__amt{font-size:32px}
}

@media (prefers-reduced-motion: reduce){
  .ledger__row{transition:none}
}

/* ============================================================
   The estimator (#pcg-builder) — the second half of the ledger

   WHAT THIS REPLACED, AND WHY. Two versions ago every choice was its own card:
   five groups x four tiers plus ten add-ons, thirty near-identical rectangles.
   One version ago it was a two-column plate: a form on the left and a 340px
   readout rail on the right that measured 340x1133 and held 247px of content —
   78% void, with a full-height hairline drawn alongside 886px of nothing.

   THE IDIOM NOW: the ledger printed 400px up this page, continued. One
   full-measure column of numbered schedule lines, each line a discipline set to
   a level, with the running total pinned in a sticky head above them and a
   terminating seam and foot below. Lines 01-05 carry the ledger's own numbers,
   so the same discipline is the same number in both objects.

   THE RAIL IS GONE, AND SO IS THE RULE THAT DEFENDED IT. The old sheet forbade
   align-items:start here, because the readout column's divider had to run the
   full height or it read as an unfinished box edge. That rule is retired by
   removing its SUBJECT: with one column there is no divider to defend. Do not
   reinstate a rail in order to satisfy a rule about rails.

   THE BOX IS STILL DELIBERATELY GONE. The host section carries the colour
   (.skin-char, set in tarieven.html) and publishes every token this object
   reads — --ink, --muted, --gold-text, --rule, --rule-soft, --surface. The
   estimator is a band in the page's own dark/light rhythm, not an inverted
   island, so --sheen / --lip-tl / --lift-2 are not used and no skin class is
   applied to the mount. The detent pocket is deepened to compensate for sitting
   on houtskool rather than onyx.

   ONE TYPEFACE. --ff-display and --ff-body both resolve to Switzer, so the
   hierarchy here is carried by weight, size, tracking and colour only. There is
   no display-vs-body distinction to lean on and no italic to reach for.
   ============================================================ */

/* ---------- The root must be a BLOCK ----------
   .est is display:block, NOT a grid. A position:sticky GRID ITEM takes its own
   grid area as containing block and will not travel — the head would appear to
   stick and then scroll away with its own row. Each line carries its own grid
   instead. */
.est{display:block}
.est--pending{display:block}
.est__nojs{max-width:52ch;margin-bottom:20px;color:var(--muted)}

/* ---------- The sticky running head ----------
   Sticky rather than fixed, and that is the whole reason this shape was chosen.
   js/pcg.js builds a site-wide .cta-dock on every page but contact.html, shown
   past scrollY 620 at z-index 85, and css/components.css turns it into a
   full-width fixed bottom bar at <=640px. The estimator sits far below 620px,
   so that dock is on screen the entire time a visitor operates this. A fixed
   carry bar at the foot would stack on it, paint under it, or have to suppress
   the site's primary conversion affordance. A head at the top collides with
   nothing, is one implementation at 1440 and at 390, and lands the figure
   directly above the column the gauges are right-aligned to.

   HEIGHT BUDGET, stated because it is a permanent cost: ~90px at 1440 and ~84px
   stacked at 390, i.e. about 10% of the viewport, permanently. That is the trade
   for a figure that is on screen from the first tap to the last, against a
   readout that used to start 1252px below the top of the plate with its sticky
   cancelled. Take the trade; do not shrink the figure below the ledger's own
   figure step. */
/* THE HEAD MUST BE A DIFFERENT SURFACE FROM THE ONE IT FLOATS OVER, and the
   first build got this exactly wrong: background rgb(20,20,22) on a section of
   rgb(20,20,22), box-shadow none, backdrop-filter none, no seam, and a 1px gold
   hairline underneath. Byte-identical material means there is no plate — so the
   Onderhoud gauge passing beneath it did not read as sliding under chrome, it
   read as a control CHOPPED IN HALF, with STARTER / PRO / PRESTIGE sliced
   through the middle of the glyphs and "05 Onderhoud" simply gone. A running
   head that reads as a rendering bug is worse than no running head.

   Three things fix it, and all three are needed:
     1. ITS OWN TONE. #1A1A1E against the section's #141416 — a small step, but
        enough that the band is an object rather than a hole. Contrast is
        unaffected in the direction that matters: --steen #918A79 measures 5.11:1
        here against 5.36:1 on the section, still past 4.5, and --champagne and
        --creme-d only gain.
     2. A CAST SHADOW BELOW IT. Dark on dark, short and soft, so the content
        entering the seam is darkened by something above it. That is the cue that
        says "under", and it is the one that was missing entirely.
     3. A LONGER RAMP. 14px is shorter than one line of body text, so a 13px
        label had its top clipped hard and its bottom faded — half a glyph. 26px
        covers a full 44px control's upper third as a gradient rather than a cut.

   The inline padding is paid for with an equal negative margin so the type still
   lands on the measure: the plate reaches 14px past the text on both sides,
   which is well inside --gutter's 22px floor at 390 and therefore cannot
   overflow. Do not widen it without re-checking that. */
.est__head{
  position:sticky;top:calc(var(--nav-h) + 8px);z-index:40;
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  align-items:end;column-gap:clamp(20px,3vw,56px);
  padding-block:13px;padding-inline:14px;margin-inline:-14px;
  background:#1A1A1E;
  box-shadow:0 12px 18px -14px rgba(0,0,0,.95);
  border-bottom:1px solid rgba(201,161,74,.28);
}
/* z-index 40 is under .subnav (60), .drawer (80), .cta-dock (85) and .nav (90).
   All four are correct: this head must never win against navigation. */

/* The ramp. A sticky head with a hard edge severs the line passing behind it —
   the same defect components.css documents for .cta-dock, and the same fix: the
   head's OWN material carried beyond its edge and dying to zero, now with a
   short dark cast over it so the seam reads as depth and not as a mask. Neither
   layer is a second colour, so neither needs a contrast ratio of its own.
   `sticky` already establishes the containing block, so no position:relative is
   needed, and left/right:0 resolves to the padding box, i.e. the full plate. */
.est__feather{
  position:absolute;left:0;right:0;top:100%;height:26px;pointer-events:none;
  background:
    linear-gradient(to bottom,rgba(0,0,0,.42),rgba(0,0,0,0) 62%),
    linear-gradient(to bottom,#17171A 0%,rgba(20,20,22,.9) 30%,rgba(20,20,22,0) 100%);
}

.est__lbl{
  font-family:var(--ff-body);font-weight:600;
  font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--steen);
}
/* A schedule counts its items. The cheapest always-true acknowledgement there
   is, native to the metaphor, and it moves on every interaction including a
   stepper click — which is what stops the instrument reading as dead when
   rounding absorbs a small add-on. */
.est__count{
  display:block;margin-top:4px;
  font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--champagne);opacity:.8;
}
.est__head-r{display:grid;justify-items:end;gap:3px;text-align:right}
.est__fig{
  font-family:var(--ff-display);font-weight:600;
  font-size:clamp(34px,3.4vw,46px);line-height:.95;letter-spacing:-.026em;
  color:var(--champagne);
  /* Tabular is load-bearing here: this element re-renders on every click under
     a sticky head, and proportional digits would jitter its right edge. */
  font-variant-numeric:lining-nums tabular-nums;
  transition:color .22s var(--ease);
}
.est__fig.is-rest{opacity:.45}
/* COLOUR ONLY. No transform, no size change, no layout property, so it cannot
   cause CLS and cannot jitter the tabular column. It fires whether or not the
   digits moved: the acknowledgement is "we registered that", not "the number
   changed". No count-up — rolling digits fake a precision this figure
   explicitly does not have. */
.est__fig.is-restruck{animation:est-restrike .22s var(--ease)}
@keyframes est-restrike{50%{color:var(--creme-d)}}
.est__mo{
  font-family:var(--ff-body);font-weight:600;
  font-size:13px;letter-spacing:.02em;color:var(--gold-text);
  font-variant-numeric:lining-nums tabular-nums;
}
/* THE UNIT IS STRUCTURAL. .est__per is only ever created as a CHILD of the node
   carrying the amount it qualifies — .est__mo when a one-off leads, .est__fig
   when the monthly figure IS the headline. It is never an independently
   hideable sibling, because that is precisely how "Onderhoud > Starter" came to
   print a bare 72px figure on screen while the brief in the mailbox correctly
   said "/ maand". A conditional fix can be deleted by the next editor; a
   structural one cannot. */
.est__per{font-weight:500;color:var(--steen)}
.est__fig .est__per{
  font-family:var(--ff-body);font-size:13px;letter-spacing:.02em;
  font-variant-numeric:normal;
}

.est__intro{
  margin:clamp(18px,2vw,26px) 0 clamp(18px,2vw,26px);max-width:56ch;
  font-size:13.5px;line-height:1.55;color:var(--steen);
}

/* ---------- A schedule line ----------
   The tracks, the gap, the hover and the micro-numeral are the ledger's own, so
   the two halves of the page read as one document rather than as a schedule
   with a widget under it. */
.eline{
  --tracks:56px 1fr 1.12fr 1.26fr;
  display:grid;
  grid-template-columns:3.2ch minmax(0,1fr) minmax(300px,420px);
  column-gap:clamp(20px,3vw,56px);
  align-items:start;
  padding-block:clamp(20px,2.2vw,26px);
  border-top:1px solid rgba(201,161,74,.16);
  transition:border-color .55s var(--ease);
}
/* The rule above a hovered line warms; the line does not fill in. Identical to
   .ledger__row:hover above — a schedule must not look clickable. */
.eline:hover{border-top-color:var(--gold-text)}
/* :first-of-type would match .est__head, which is also a div. */
.est__intro + .eline{border-top:0;padding-top:0}
/* AI is three different tracks, not three levels of one thing, so its detents
   are equal and it carries no graduation strip. See the comment on the group. */
.eline--unranked{--tracks:repeat(4,minmax(0,1fr))}

.eline__ref{
  font-family:var(--ff-body);font-weight:600;
  font-size:11.5px;letter-spacing:.18em;
  color:var(--gold-text);opacity:.85;
}
.eline__body{display:grid;gap:7px;min-width:0}
.eline__name{
  font-family:var(--ff-display);font-weight:600;
  font-size:clamp(19px,1.9vw,25px);line-height:1.16;letter-spacing:-.014em;
  color:var(--creme-d);
  hyphens:manual;
}
/* The bordered pill is retired. It carried two unrelated meanings in one object
   — load-bearing billing information on care, a mood label on the add-ons head
   — in identical border, colour, tracking and position, and it was the only
   bordered object on the plate. This is plain tracked apparatus type; the
   "optioneel" label is deleted outright, because a line headed "Website-opties"
   does not need to be told it is optional. */
.eline__unit{
  font-family:var(--ff-body);font-weight:500;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--champagne);
}
/* NEVER EMPTY, and NO RESERVED HEIGHT. The old .egrp__desc reserved 1.5em and
   blanked itself on "Geen", so the plate opened with 5 x 57px of empty prose
   lane and only paid off after a click nobody had made. This prints the group's
   own hint at rest — data that was in the file from the first version and was
   never rendered — and the chosen setting once one is set. Row height is
   max(name-block, control) and the control is the shorter of the two, so
   nothing is reserved anywhere. */
.eline__desc{
  margin:0;font-size:13px;line-height:1.5;color:var(--steen);
  max-width:56ch;hyphens:manual;
}
/* Text drawn across the line by a pen — the schedule's own gesture. It fires on
   every level change, including ones that move no money. */
.eline__desc.is-wipe{animation:est-wipe .2s ease-out}
@keyframes est-wipe{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}

.eline__ctrl{min-width:0}

/* ---------- The gauge ----------
   Four positions cut into the plate with one machined indicator that MOVES to
   the chosen setting. A sliding indicator is the whole gesture: it reads as a
   control being set, where four backgrounds fading in and out reads as tabs.

   THREE REDUNDANT, NON-NUMERIC RANK CHANNELS, and no figure anywhere:
     1. Track width — the detent physically widens as the level rises. Ordinal
        ratios 1 : 1.12 : 1.26, hand-set, NEVER derived from the weight. A
        proportional bar is a per-item price readable with a ruler.
     2. The graduation strip — a ruler's minor-to-major marks.
     3. Typographic ascent across the labels.
   All three are drawn AT REST, which is what finally kills the "five identical
   grey strips" first impression: with every line at nil the plate still reads
   as five calibrated scales. No motion required, no click required.

   .escale and .gauge MUST share identical column tracks, so --tracks is
   declared once on .eline and read by both. A future five-detent line sets
   56px 1fr 1.09fr 1.18fr 1.30fr. */
.escale{
  display:grid;grid-template-columns:var(--tracks);
  height:17px;align-items:end;margin-bottom:6px;
}
.escale__tk{
  justify-self:center;width:1px;background:rgba(255,255,255,.16);
  transition:height .3s var(--ease),background .3s var(--ease);
}
.escale__tk[data-lvl="0"]{height:5px}
.escale__tk[data-lvl="1"]{height:9px}
.escale__tk[data-lvl="2"]{height:13px}
.escale__tk[data-lvl="3"]{height:17px}
.escale__tk.is-passed{background:rgba(201,161,74,.45)}
.escale__tk.is-set{background:var(--champagne)}

.gauge{
  position:relative;border:0;padding:0;margin:0;min-width:0;
  display:grid;grid-template-columns:var(--tracks);height:44px;
}
.gauge__pocket{
  position:absolute;inset:0;
  /* Deepened from .34: this sits on --houtskool now, not on --onyx, and it has
     to keep reading as a milled recess. --steen on --houtskool is 5.36:1, so
     label contrast is unaffected. */
  background:rgba(0,0,0,.42);
  box-shadow:inset 0 1px 0 rgba(0,0,0,.5), inset 0 -1px 0 rgba(255,255,255,.045);
}
.gauge__ind{
  position:absolute;left:0;top:0;width:0;height:44px;
  background:var(--gold-grad);
  box-shadow:0 1px 10px -2px rgba(201,161,74,.5);
  transform:translate3d(0,0,0);z-index:0;
  /* No transition until .is-ready: the first placement is measured, and
     animating it from zero width would read as a glitch on load. */
  transition:none;
}
.est.is-ready .gauge__ind{
  transition:transform .42s var(--ease),width .42s var(--ease),height .42s var(--ease);
}
/* "Geen" is a real setting, so the indicator travels there too, but it is not a
   gold commitment: it goes quiet rather than disappearing. */
.gauge__ind--nil{background:rgba(255,255,255,.07);box-shadow:none}

.gauge__d{
  position:relative;z-index:1;
  display:grid;place-items:center;min-width:0;
  padding:11px 4px;cursor:pointer;min-height:44px;
}
.gauge__d + .gauge__d{box-shadow:inset 1px 0 0 rgba(255,255,255,.05)}
/* KEEP THIS RECIPE EXACTLY. opacity:0 plus a 1x1 box — not display:none, not
   visibility:hidden — keeps the input focusable and announced; pointer-events:
   none makes the LABEL the only hit target. tools/verify.mjs resolves a
   pointer-events:none control to its labelling ancestor precisely because of
   this shape: measuring the inputs themselves reported 10 phantom tap-target
   failures on this page. Change it and that carve-out is invalidated. */
.gauge__d input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.gauge__t{
  font-family:var(--ff-body);text-transform:uppercase;color:var(--steen);
  text-align:center;line-height:1.15;min-width:0;hyphens:manual;
  transition:color .3s var(--ease);
}
/* Typographic ascent: SIZE, WEIGHT AND TRACKING ONLY. NEVER COLOUR.
   ############  INK IS THE STATE CHANNEL. RANK MAY NOT SPEND IT.  ############
   The top detent used to be set in --creme-d at weight 800 — the exact ink of
   the selected state, heavier and larger than it. Measured: with nothing chosen
   on the Website gauge, the nil detent read rgb(244,236,216)/lum .842 and so did
   PRESTIGE, against rgb(145,138,121)/lum .256 for BASIC and PRO. So at rest all
   four gauges read as if PRESTIGE were already picked, and after a real choice
   the unselected PRESTIGE was still the brightest text in the row — brighter
   than the cell the visitor had just set. A control that answers "what is
   selected?" wrongly is worse than a control with no rank ladder at all.

   Every unselected label is --steen, at every level. The rank ramp survives in
   the three channels that do not collide with state, and the selected cell wins
   outright because it is dark type on a lit gold detent. */
.gauge__t--l0{font-size:11.5px;font-weight:500;letter-spacing:.14em}
.gauge__t--l1{font-size:12px;font-weight:500;letter-spacing:.08em}
.gauge__t--l2{font-size:12.5px;font-weight:600;letter-spacing:.06em}
.gauge__t--l3{font-size:13px;font-weight:700;letter-spacing:.02em}
/* Unranked: no ascent, because there is no rank. The nil detent still recedes. */
.gauge__t--u{font-size:12.5px;font-weight:600;letter-spacing:.06em}
.eline--unranked .gauge__d:first-of-type .gauge__t{font-size:11.5px;font-weight:500;letter-spacing:.14em}
.gauge__d:hover .gauge__t{color:var(--creme-d)}
/* On the gold detent the label inverts to the plate colour: champagne on gold
   is unreadable. Measured 12.9:1 against the ramp's mid stop. */
.gauge__d:has(input:checked) .gauge__t{color:var(--onyx-deep)}
.gauge__d:has(input[value="none"]:checked) .gauge__t{color:var(--creme-d)}
/* Focus goes on the LABEL: the input itself is off-screen. */
.gauge__d:has(input:focus-visible){outline:2px solid var(--champagne);outline-offset:-2px}

/* ---------- A scoped line ----------
   "op basis van scope" is word for word the phrase the ledger uses for row 01,
   so the estimator states the absence of a price in the page's own language.
   Full opacity: this is information, not a disabled state. */
.escope__note{
  display:block;
  font-family:var(--ff-body);font-weight:500;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--steen);
}
.escope{
  position:relative;display:flex;align-items:center;gap:11px;
  min-height:44px;margin-top:6px;cursor:pointer;
}
.escope input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
/* A BOX, WITH THE SITE'S DIAMOND STRUCK INSIDE IT WHEN IT IS ON.
   The bare 7px diamond was the site's decorative tick — the same mark the trust
   row and the process ladder use to bullet a STATEMENT — so a column of ten of
   them beside ten names read as a table of contents, not as ten switches. There
   was no box, no tick, nothing that says "this is a thing you set". Worse, the
   identical mark carried the scope line's "Meenemen in de aanvraag", so one
   token appeared to mean two different things inside one instrument.

   The box fixes both at once: it is unmistakably a checkbox, and both places
   that use it now genuinely ARE checkboxes, so the shared token is telling the
   truth. The diamond is not lost — it is what fills the box when it is set. */
.escope__mk,.eadd__mk{
  position:relative;flex:none;width:16px;height:16px;
  border:1px solid rgba(201,161,74,.34);
  background:transparent;
  transition:background .25s var(--ease),border-color .25s var(--ease);
}
.escope__mk::after,.eadd__mk::after{
  content:"";position:absolute;inset:0;margin:auto;width:6px;height:6px;
  background:var(--onyx-deep);
  transform:rotate(45deg) scale(0);
  transition:transform .22s var(--ease);
}
.escope__n,.eadd__n{
  font-family:var(--ff-body);font-weight:500;
  font-size:13.5px;line-height:1.3;color:var(--steen);
  hyphens:manual;
  transition:color .25s var(--ease);
}
.escope:hover .escope__n,.eadd:hover .eadd__n{color:var(--creme-d)}
.escope:hover .escope__mk,.eadd:hover .eadd__mk{border-color:var(--champagne);background:rgba(201,161,74,.14)}
.escope:has(input:checked) .escope__mk,
.eadd:has(input:checked) .eadd__mk{background:var(--gold-grad);border-color:transparent}
.escope:has(input:checked) .escope__mk::after,
.eadd:has(input:checked) .eadd__mk::after{transform:rotate(45deg) scale(1)}
.escope:has(input:checked) .escope__n,
.eadd:has(input:checked) .eadd__n{color:var(--creme-d)}
.escope:has(input:focus-visible),
.eadd:has(input:focus-visible){outline:2px solid var(--champagne);outline-offset:2px}

/* ---------- Line 07: the website options ----------
   NOTHING IS EVER DISABLED AND NOTHING RENDERS AT opacity:.34. The old gate
   signalled three times over — a sentence appeared, the list dropped to .34,
   and every input and stepper button was set disabled — and it composited
   --steen #918A79 to roughly 1.6:1 across TEN LABELLED CONTROLS, 190px tall at
   1440 and 390px tall at 390. That block was the loudest thing in the lower
   half of the object and it was unusable on arrival. One legible sentence, and
   the rows simply do not exist until they apply. */
.eopts__state{
  display:block;
  font-family:var(--ff-body);font-weight:500;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--steen);
}
/* Unfolds at the BOTTOM of the schedule, below the line the visitor just
   touched, so the first interaction no longer reflows the page under the
   pointer. The old gate sentence disappearing took 22-30px out of the column on
   the very first click. */
.eopts__bay{display:grid;grid-template-rows:0fr}
.est.is-ready .eopts__bay{transition:grid-template-rows .34s var(--ease)}
.eopts__bay.is-open{grid-template-rows:1fr}
/* ---------- Line 07 SPANS THE MEASURE ----------
   A 442px COLUMN VOID is what the control track cost here. Measured at 1440:
   line 07's description column was 56px tall and its control column 461px, so
   roughly 360x710 of empty black sat in the left half of the instrument at the
   exact moment the visitor is making the most granular decisions on the page.
   Every other line is balanced inside 80px; this one was not a line, it was a
   panel wearing a line's grid.

   So the bay leaves the 420px track and takes its own row across the measure.
   .eline__ctrl goes display:contents — its two children become grid items of
   the line itself — which is why they are placed explicitly here and again in
   the <=720 block. Custom properties still inherit through display:contents, and
   nothing in this line reads --tracks anyway. */
.eline--opts .eline__ctrl{display:contents}
.eline--opts .eopts__state{grid-column:3;justify-self:start}
.eline--opts .eopts__bay{grid-column:2 / -1;min-width:0}

/* COLUMNS, WITH A 250px FLOOR, AND auto-fit RATHER THAN A BREAKPOINT.
   The one-column rule this replaces was measured against the old 420px track,
   where a name plus a leader plus a 32/24/32 stepper could not be halved: a
   two-up list reported scrollWidth 447 against clientWidth 420, 27px hanging
   out and clipped only by body{overflow-x:hidden} — the same "mask over a bug"
   the .rate band further down documents. That measurement is still true, which
   is why the floor is 250px and not a fraction: auto-fit only ever lays down
   columns that actually fit, so it is one column at 390 and three across the
   full measure at 1440, and there is no width at which a row is squeezed under
   its own minimum. */
.eopts__list{
  overflow:hidden;min-height:0;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));
  column-gap:clamp(20px,2.6vw,44px);row-gap:clamp(16px,1.8vw,24px);
  align-content:start;
}
.eaddgrp{min-width:0}
/* The set's name, in the schedule's own apparatus type — the same size, tracking
   and case as .eopts__state and .escope__note, because it is the same kind of
   object: a label on a group of controls, not a heading in the document. */
.eaddgrp__h{
  display:block;padding-bottom:2px;
  font-family:var(--ff-body);font-weight:600;
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold-text);opacity:.85;
}

.eadd{
  position:relative;display:flex;align-items:center;gap:11px;
  min-height:44px;padding-block:6px;cursor:pointer;
  border-top:1px solid rgba(255,255,255,.07);
}
.eadd input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
/* A printed schedule's dotted leader: it lands every stepper on the same right
   edge as every gauge above it, so one vertical rule of alignment runs down the
   whole object.
   ############  --rake-soft IS A GRADIENT, NOT A COLOUR  ############
   Same warning as --rake in pcg.css: `border:1px solid var(--rake-soft)` is not
   a valid <color> and the border silently disappears. This is a
   background-IMAGE on a 1px strip. */
.eadd__lead{
  flex:1;min-width:18px;height:1px;align-self:center;
  background-image:var(--rake-soft);
}
/* Reserved with visibility, not `hidden`, so ticking a line does not shift its
   leader sideways. */
.eadd__q{display:flex;align-items:center;gap:1px;flex:none}
.eadd__q.is-off{visibility:hidden}
.eadd__q .qb{
  width:32px;height:32px;line-height:1;
  border:1px solid rgba(201,161,74,.3);background:transparent;
  color:var(--champagne);font-size:14px;cursor:pointer;
  display:grid;place-items:center;
  transition:background .22s var(--ease),border-color .22s var(--ease);
}
.eadd__q .qb:hover{background:rgba(201,161,74,.14);border-color:var(--champagne)}
.eadd__q .qn{
  min-width:24px;text-align:center;
  font-family:var(--ff-body);font-weight:600;font-size:12.5px;color:var(--creme-d);
  font-variant-numeric:lining-nums tabular-nums;
}

/* ---------- The terminating seam ----------
   The band is `.seam` in css/components.css and it is carried on the same
   element; all this adds is the space above it and a quieter opacity, so this
   is not a third copy of that recipe. Do not clone the gradient.

   IT FINALLY DOES ITS JOB. In the previous build it was the last child of a
   247px column inside an 1133px panel: it landed 247px down and left 886px —
   78%, not the "lower third" its comment claimed — blank BELOW it, which reads
   as a rule with its content missing. Here it is the last decorative element of
   a completed object, spanning the full measure, with only the foot beneath
   it. Decorative, so it is aria-hidden and drops in forced colours, both
   inherited from .seam. */
.est__seam{margin-top:clamp(24px,2.6vw,34px);opacity:.55}

/* ---------- The foot ----------
   Both cells carry content in EVERY state, including the empty state, so the
   foot has no void — the structural opposite of the rail it replaces. */
.est__foot{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(300px,420px);
  column-gap:clamp(20px,3vw,56px);
  padding-top:clamp(20px,2.2vw,26px);
}
/* Rule 2 requires that a small add-on move nothing, and the old build shipped
   that silence with no compensating feedback: Website Basic read the same
   figure before and after the single most common first gesture on the page.
   The answer is a transient clause written into a standing span at the end of
   .est__fine when a selection is absorbed, styled here. It rides an existing
   sentence, so the foot does not change height for the 2400ms it is up. The
   span is always in the DOM and empty at rest, which is what keeps it from
   accumulating: sync() overwrites its text rather than appending a second copy.
   Do not weaken ROUND_TO to paper over the silence, and do not give this its
   own block: an empty slot would twitch the layout, which at <=960 moves the
   CTA under the visitor's thumb. */
.est__absorb{color:var(--champagne)}
/* .est__note is the FIRST child of .est__foot-l, so it carries no top margin:
   anything here would start the left column below the CTA in the right one.
   The separation lives on .est__fine, which always follows it, and is dropped
   again when the note is hidden so the two columns still start level. */
.est__note{
  margin:0;font-size:12px;line-height:1.5;color:var(--champagne);
  padding-left:11px;position:relative;max-width:52ch;
}
/* A hairline, not a side-stripe: 1px, and it marks a note rather than
   decorating a card. The ban is on thick coloured accent borders as ornament. */
.est__note::before{
  content:"";position:absolute;left:0;top:.3em;bottom:.3em;width:1px;
  background:rgba(201,161,74,.5);
}
.est__fine{margin:14px 0 0;font-size:11.5px;line-height:1.55;color:var(--steen);max-width:52ch}
.est__note[hidden] + .est__fine{margin-top:0}
.est__foot-r{display:grid;align-content:start;justify-items:stretch}
/* Short enough to set on one line. "Vraag de exacte prijs aan" wrapped to two,
   which made the button look like a paragraph. */
.est__cta{max-width:320px;justify-content:center}

/* Screen-reader-only, for the fieldset legends and detent names that duplicate
   a visible mark, and for the estimator's single live region. */
.u-sr{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
/* Several nodes in here declare a `display`, which outranks the UA's [hidden]
   rule on equal specificity. State the intent once rather than per node. */
.est [hidden]{display:none !important}

/* ---------- <=960: the head restacks and reserves the top-right corner ----------
   js/pcg-whatsapp.js pins the WhatsApp seal at top:calc(var(--nav-h) + 14px),
   right:var(--gutter), 52px square (48 below 640), z-index 84, at EVERY width
   <=960px. A head pinned at y=90 occupies y 90-174; the seal occupies y 96-148
   at the right edge. So the figure moves to the LEFT of the head and the head's
   right 66px (52 + 14 clearance) is deliberately left empty, which puts the
   seal over blank head rather than over the number. Measured, not guessed: do
   not "reclaim" that padding.

   ACCEPTED CONSEQUENCE, stated so nobody rediscovers it: between 721 and 960
   the head's figure no longer aligns with the right-aligned gauge column. That
   is 240px of viewport width, against a fixed element that cannot be moved from
   a stylesheet. */
@media (max-width:960px){
  .est__head{
    grid-template-columns:1fr;row-gap:6px;
    padding-right:66px;align-items:start;
  }
  .est__head-r{justify-items:start;text-align:left}
}

/* ---------- <=720: the lines restack, and the gauge becomes 2x2 ----------
   A 2x2 grid makes the 390px overflow STRUCTURALLY IMPOSSIBLE at any arity or
   label length: no cell ever has to fit four siblings across 346px. The
   previous build's AI control measured clientWidth 346 against scrollWidth 359,
   with detents at 47/90/103/118 instead of four equal 87, and the fourth
   hanging 13px past the edge — clipped only by body{overflow-x:hidden}, which
   is why tools/verify.mjs reported the page clean (its escapes() walk stops at
   the hidden body). That is the same "mask over a bug" failure the .rate band
   further down this sheet already documents.

   The graduation strip drops here because rank is still carried by typographic
   ascent and by ordinal position. The indicator's diagonal travel between rows
   is correct and legible: it is one control being moved to a setting. */
@media (max-width:720px){
  .eline{grid-template-columns:auto minmax(0,1fr);column-gap:.7ch;row-gap:12px}
  /* --tracks is set on the CONTROL rather than on the line, so it reaches both
     the strip and the gauge by inheritance and still beats .eline--unranked,
     which sits one level further out. */
  .eline__ctrl{grid-column:1 / -1;--tracks:repeat(2,minmax(0,1fr))}
  /* Line 07's control is display:contents, so it cannot be placed — its two
     children are the grid items here. `grid-column:3` from the wide rule would
     otherwise open a third, implicit column on a two-column line. */
  .eline--opts .eopts__state,.eline--opts .eopts__bay{grid-column:1 / -1}
  .escale{display:none}
  .gauge{height:88px;grid-auto-rows:44px}
  /* Both selectors in one list: `.gauge__d + .gauge__d` is (0,2,0) and would
     otherwise out-specify a bare `.gauge__d` declared after it and blank the
     hairlines on every detent but the first. */
  .gauge__d,.gauge__d + .gauge__d{
    box-shadow:inset 1px 0 0 rgba(255,255,255,.05),inset 0 1px 0 rgba(255,255,255,.05);
  }

  /* ---------- A RANKED LINE BECOMES A LADDER, NOT A 2x2 ----------
     A 2x2 grid has no axis. All three rank channels the ranked lines exist to
     express die in it: the graduated track widths equalise (56/108/121/136 at
     1440 became four identical cells), the graduation strip is hidden, and what
     is left reads as a matrix of four unrelated options. It also parks the
     fourth and highest detent in the bottom-right corner, which is where every
     dialogue on earth puts the confirm button, so the top of the range sat in
     the "already chosen" position while the actual selection sat bottom-left.

     One column, four rows, top to bottom, is an axis a reader already owns. It
     is also structurally safer than the 2x2 it replaces on the thing that
     motivated the 2x2 in the first place: one label per row can never make a
     cell fit four siblings across 346px, at any arity or label length.

     The width channel comes back as a graduated RULE before each label — the
     same ordinal 1:2:3:4 idea drawn on the axis that a stacked list actually
     has. Hand-set, never derived from the weight, exactly like the tracks it
     stands in for. The strip stays hidden: two ruler metaphors in one control
     is one too many.

     COST, stated: a ranked line goes from 88px to 176px, and there are three of
     them, so the page grows ~264px at <=720. That is the price of a control
     that reports its own state correctly. */
  .eline--ranked .eline__ctrl{--tracks:minmax(0,1fr)}
  .eline--ranked .gauge{height:auto;grid-auto-rows:44px}
  .eline--ranked .gauge__d{justify-items:start;padding-inline:14px}
  /* The left hairline is a column divider and there are no columns here; the
     rows need a top rule instead. Same one-list trick as above, for the same
     specificity reason. */
  .eline--ranked .gauge__d,.eline--ranked .gauge__d + .gauge__d{
    box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
  }
  .eline--ranked .gauge__d:first-of-type{box-shadow:none}
  .eline--ranked .gauge__t{display:flex;align-items:center;gap:11px;text-align:left}
  .eline--ranked .gauge__t::before{
    content:"";flex:none;height:1px;background:currentColor;opacity:.55;
  }
  /* Inert unless the rule above gives ::before a content, and these classes
     exist only on ranked lines, so there is no width at which they leak. */
  .gauge__t--l0::before{width:9px}
  .gauge__t--l1::before{width:17px}
  .gauge__t--l2::before{width:25px}
  .gauge__t--l3::before{width:33px}

  .est__foot{grid-template-columns:1fr;row-gap:22px}
  .est__cta{max-width:none}
}

/* ---------- The site dock stands down while the estimator is in use ----------
   THE ESTIMATOR'S OWN PRIMARY CTA WAS 100% COVERED BY THE FIXED .cta-dock.
   Measured at 1440 with .est__foot scrolled to the viewport bottom: button
   x868-1188 / y808-853, dock x875-1410 / y805-870, overlap 45 of the button's
   45px, and elementFromPoint at the button's centre returned the dock's own
   label. Both buttons are the same gold "Plan een gesprek →", so the collision
   is invisible — the visitor clicks what looks like the right button and loses
   the entire selection the instrument just helped them build, because the dock's
   href is a bare contact.html. The bay's last add-on was unreachable behind the
   dock at 1440 and at 390 for the same reason.

   js/pcg.js already tucks this dock when the footer's CTA scrolls into view.
   This is that same rule for the other competing CTA on this page, and it reuses
   .cta-dock's own resting transform and transition so it is the dock's normal
   gesture rather than a second animation. The class is set by js/pcg-builder.js
   from an IntersectionObserver on the bottom 40% of the viewport; the dock
   returns as soon as the instrument has scrolled past. (0,2,1) beats
   .cta-dock.show at (0,2,0), and no other page loads this sheet. */
body.est-dock-off .cta-dock{
  transform:translateY(140%);opacity:0;pointer-events:none;
}

@media (prefers-reduced-motion: reduce){
  /* The indicator still MOVES, because its position is the state; it just stops
     travelling. It is never hidden and never left unpositioned. */
  .est.is-ready .gauge__ind{transition:none}
  .gauge__t,.escale__tk,.eline,.eadd__mk,.escope__mk,.escope__n,.eadd__n,
  .eadd__mk::after,.escope__mk::after,
  .eadd__q .qb,.est__fig{transition:none}
  .est.is-ready .eopts__bay{transition:none}
  .eline__desc.is-wipe,.est__fig.is-restruck{animation:none}
}

/* ---------- Sticky category sub-nav ---------- */
.subnav{
  position:sticky;top:0;z-index:60;
  background:rgba(8,8,9,.82);backdrop-filter:blur(16px) saturate(1.2);
  -webkit-backdrop-filter:blur(16px) saturate(1.2);
  border-bottom:1px solid rgba(201,161,74,.16);
}
/* Build It is pinned beside the scrollable strip, always reachable, and reads
   as a primary action rather than one more tab. On its own page it fills in. */
.subnav__build{flex:none;align-self:center}

/* ---------- Sub-nav: the scroll affordance ----------
   The scrollbar above is deliberately hidden, so without this the strip lies
   about its own length. MEASURED on tarieven.html: scrollWidth 1171 against a
   390 client (only ~3 of the 8 tabs fit, the 4th starts 10px past the edge and
   is invisible), and 1209 against 820 (the 6th tab is severed mid-word as
   "AI EN AUTOMAT"). At 1440 scrollWidth === clientWidth === 1280, i.e. no
   overflow, which is why the fade must be conditional and not decorative.

   It is a MASK on the track, not a gradient overlay: .subnav's background is
   translucent (rgba(8,8,9,.82) + backdrop-filter) over whatever section is
   passing behind it, so an opaque overlay would read as a dark smudge crossing
   the cream sections. Fading the tabs themselves to transparency introduces NO
   new colour, so there is no new contrast pair to measure here — every tab that
   remains legible keeps its existing ink (--steen / --champagne / --creme-d) on
   the existing bar. A tab inside the fade is a peeking tab, never the sole
   carrier of anything: the same label is fully legible once swiped in.

   .is-lead / .is-trail are set by the sub-nav script in tarieven.html, per side,
   and only when the track genuinely overflows on that side. That script reads
   --sn-fade below to keep its scroll target clear of the fade, so the width
   lives here only and the two cannot drift apart.

   Why it widens on a phone: MEASURED at 390 with the strip at rest, the third
   tab ends at 370 and the fourth does not begin until 400 — 10px past the edge —
   so a narrow fade has nothing under it and the strip still reads as a complete
   list of three categories. At 64px the tail of the third label visibly
   dissolves, which is the cue, and it holds wherever the strip happens to rest
   instead of depending on a tab landing in the fade. Even with both fades lit
   the clear span is 390 - 128 = 262px, wider than the widest tab (174px), so a
   revealed tab is never trapped between them. */
.subnav__track{--sn-fade:44px}
.subnav__track:is(.is-lead,.is-trail){
  --sn-fade-l:0px;--sn-fade-r:0px;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 var(--sn-fade-l),#000 calc(100% - var(--sn-fade-r)),transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 var(--sn-fade-l),#000 calc(100% - var(--sn-fade-r)),transparent 100%);
}
/* A mask clips paint, so a focus ring landing inside the fade would be faded
   with it. Drop the fade entirely while a tab holds keyboard focus. Scoped to
   :focus-visible so a tap or click — which also focuses the link — keeps the
   swipe affordance on screen, which is exactly where it is needed. */
.subnav__track:has(a:focus-visible){-webkit-mask-image:none;mask-image:none}
/* Same specificity as the gate, declared later, so it wins: in forced colours
   the system palette is the contract and nothing may be dimmed out of it. */
@media (forced-colors:active){
  .subnav__track:is(.is-lead,.is-trail){-webkit-mask-image:none;mask-image:none}
}
/* letter-spacing routed to the declared label step (.22em, was .2em). NOTE for
   whoever re-measures the fade widths documented above: this widens the widest
   tab, "AI EN AUTOMATISERING", by ~4px to ~178px — still well inside the
   390 - 128 = 262px clear span between two lit fades, so the affordance's
   guarantee holds. */
.subnav__track a{
  flex:none;padding:18px 2px;font-size:11.5px;letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--steen);position:relative;white-space:nowrap;transition:color .4s
}

/* ---------- Section header (pricing) ---------- */
.price-head{display:flex;justify-content:space-between;align-items:flex-end;gap:28px;flex-wrap:wrap;margin-bottom:clamp(40px,4.5vw,64px)}
.price-head h2{max-width:18ch}
.price-head__meta{font-size:13px;letter-spacing:.04em;color:var(--muted);max-width:34ch;text-align:right}

/* ============================================================
   ONE RULED PLATE — the pricing page's structure
   ============================================================
   WHAT THIS REPLACES: fourteen bordered rectangles, a rectangle inside a
   rectangle in every case, each tier column outlined so that the Basic
   column had to carry ~200px of dead air to match Pro's height. Boxes of
   unequal content forced into equal boxes.

   THE IDIOM INSTEAD: one plate, ruled. A grid of tiers is a single object
   with full-width rules above and below it and fine lit cuts between the
   columns. Ragged bottoms stop mattering, because the columns are cells of
   one plate rather than three objects pretending to float side by side.
   Hovering LIGHTS a cell (a pocket catches light) instead of lifting a card
   (a plate does not rise off the page).

   WHY THE CUTS ARE PAINTED BY THE CELLS AND NOT BY THE GRID'S GROUND
   The obvious build is the site's own `gap:1px;background:var(--rake)` grid
   idiom — the fill shows through the gaps and paints the cuts. It CANNOT be
   used here, and this is measured, not theoretical: every grid on this page
   is a reveal host whose children start hidden. tarieven.html:170/230/292/
   349/414/470/535 carry data-stagger, :267 carries data-stagger-mode="wipe",
   and pcg.css:961 starts `[data-stagger]>*` at opacity:0 (wipe mode starts
   at clip-path:inset(0 0 100% 0), i.e. fully clipped). A ground fill under
   hidden cells is a bare translucent-gold slab the full size of the grid,
   for the ~1s the stagger takes — which is exactly the defect that had to be
   chased off over.html and aanpak.html. So the cuts live on the CELLS: each
   cell after the first paints its own 1px edge, that edge is inside the
   cell, and it therefore fades, slides and clips WITH the cell it belongs
   to. There is no ground to leak, at any moment of any reveal.

   Consequence worth knowing: gap is 0, not 1px. The cut is the cell's own
   first pixel column, so the columns are genuinely adjacent and the cut is
   exactly 1px at every zoom instead of a gap that a fractional layout can
   round to 0 or 2.

   --pc-cut is --rake turned to run DOWN a vertical cut instead of along a
   horizontal rule. Same hue, same alpha family, same one light: --rake is
   105deg peaking at 52% of its length; a vertical cut lit by the same high
   source peaks nearer its top, so this is 180deg peaking at 42%. It is a
   SURFACE fill and never text — peak alpha .34 gold flattens to ~1.6:1 on
   --onyx and ~1.5:1 on --papier, in the same band --rule (2.69:1 on
   --papier) and --rake (mean alpha .19) already occupy. It separates cells
   that are also separated by whitespace, headings and content, so it is
   never the sole carrier of anything.
   ############  --pc-cut IS A GRADIENT, NOT A COLOUR  ############
   Same warning as --rake in pcg.css: never `border:1px solid var(--pc-cut)`.
   A gradient is not a valid <color> and the border silently disappears. */
.tiers,.combos,.addons,.price-rows,.rate,.conditions{
  --pc-cut:linear-gradient(180deg,rgba(201,161,74,.06) 0%,rgba(201,161,74,.18) 22%,rgba(201,161,74,.34) 42%,rgba(201,161,74,.16) 72%,rgba(201,161,74,.05) 100%);
}
/* ONE cell padding per plate, declared on the plate. The cells read it, the
   internal rules cancel it, and the plate's outward bleed is exactly minus it —
   three rules that must agree, so they consume one value instead of three
   copies of a clamp() that would drift apart on the first edit. */
.tiers,.combos{--pad-cell:clamp(26px,2.4vw,38px)}
.addons{--pad-cell:clamp(22px,2.2vw,30px)}
/* THE PAD MAY NEVER EXCEED THE GUTTER, and this one needed the clamp.
   The bleed rule below is margin-inline:-1 * --pad-cell, so a pad wider than
   --gutter (clamp(22px,5vw,72px)) pushes the plate PAST the viewport edge.
   MEASURED before this min(): at 390 the rate band rendered L-8.0 R398.0 —
   8px off each side, invisible only because body carries overflow-x:hidden,
   which is a mask over a bug and not a fix. This plate's raw pad floors at
   30px while the gutter floors at 22px, so it overflowed at every width
   below 600 (where 5vw drops under 30). min() also KEEPS THE ALIGNMENT the
   bleed exists for: content still lands at gutter - pad + pad = --gutter,
   i.e. on the section's text column, at every width.
   The other three plates already satisfy the invariant and are left alone:
   .addons floors at 22px = the gutter floor and rises at 2.2vw against the
   gutter's 5vw, and .tiers/.combos stop bleeding entirely below 961px (the
   media query re-centres them), where their 26px floor would first bite. */
.rate{--pad-cell:min(clamp(30px,3.4vw,52px),var(--gutter))}
:is(.skin-creme,.skin-creme-deep) :is(.tiers,.combos,.addons,.price-rows,.rate,.conditions){
  --pc-cut:linear-gradient(180deg,rgba(122,94,28,.08) 0%,rgba(122,94,28,.22) 22%,rgba(122,94,28,.40) 42%,rgba(122,94,28,.20) 72%,rgba(122,94,28,.07) 100%);
}

/* The plate's own rules: full width, above and below, machined not printed.
   They are ::before/::after on the CONTAINER, absolutely positioned — which
   is also what keeps them out of the grid. A static pseudo-element of a grid
   container is a grid ITEM and would claim a cell; position:absolute takes
   it out of flow. They are not children either, so `[data-stagger]>*` never
   touches them: the plate is struck first and the columns settle into it. */
.tiers,.combos,.addons,.price-rows,.rate{position:relative}

/* ONE LIGHT ON ONE OBJECT, and this is a correction made at the render.
   The plan put background-image:var(--sheen) on the CELLS. Rendered at 1440
   that is wrong twice over: --sheen fades out at 44% of the box it is painted
   on, so on a ~715px tier column it becomes a 315px white wash with a visible
   horizontal edge (it landed almost exactly on the internal rule and read as a
   gradient artefact, not as light) — and lighting each cell separately makes
   three cells read as three tiles, which is the exact impression this step
   exists to remove. The light belongs to the PLATE: one lit top edge, height
   pinned in px so it stays a lit edge whether the plate is 300px or 900px tall.
   No new colour — it is the same --sheen token, cropped.

   NOT .addons, and that is deliberate rather than an omission. A plate light
   is only visible where the CELLS are transparent and let the plate show
   through: .tier and .combo compute to rgba(0,0,0,0), but .addon paints an
   opaque ground (var(--papier) / var(--onyx), MEASURED rgb(248,243,233) on
   cream) and its gap is 0, so the cells cover the plate completely and the
   light would never be seen once the grid has settled. It would only ever
   appear for the ~1s of the stagger — and .addons is the one grid here that
   reveals with data-stagger-mode="wipe", i.e. children start FULLY clipped,
   so the single moment the light could show is the exact moment a bare
   container fill is the defect this whole sheet is built to avoid. */
.tiers,.combos{
  background-image:var(--sheen);
  background-repeat:no-repeat;
  background-position:top;
  background-size:100% 110px;
}

/* CELL PADDING BLEEDS OUTWARD. Without this the plate's rules line up with the
   section's text column but the cell CONTENT sits ~36px inside it, so the tier
   names do not start on the same line as the h2 and lede above them. Pulling
   the plate out by exactly one cell padding puts the outer columns' content
   back on the text column and lets the rules run into the gutter, which is
   what a full-bleed ruled table does in print. All three columns keep
   identical widths, so bullets wrap and CTAs size the same in every column.
   SAFE AT EVERY WIDTH IT APPLIES TO: --gutter is clamp(22px,5vw,72px) and the
   pads below are clamp(26px,2.4vw,38px)/clamp(22px,2.2vw,30px)/
   clamp(30px,3.4vw,52px), so at the narrowest width this rule is live (961px,
   below which the grids collapse and re-centre) the bleed is 26/22/30px into a
   48px gutter, and at 1440 it is 35/32/49px into 72px. It never reaches the
   viewport edge. */
.tiers,.combos,.addons,.rate{margin-inline:calc(var(--pad-cell) * -1)}

.tiers::before,.tiers::after,
.combos::before,.combos::after,
.addons::before,.addons::after,
.rate::before,.rate::after,
.price-rows::before{
  content:"";position:absolute;left:0;right:0;height:1px;
  background:var(--rake);pointer-events:none;
}
.tiers::before,.combos::before,.addons::before,.rate::before,.price-rows::before{top:0}
.tiers::after,.combos::after,.addons::after,.rate::after{bottom:0}

/* ---------- Pricing tiers ---------- */
/* minmax(0,1fr) rather than 1fr: a bare 1fr track has an `auto` minimum, so a
   card whose min-content is wider than its share pushes the track past the
   container instead of shrinking. */
.tiers{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;align-items:stretch}
.tiers.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
/* The cut between two columns, drawn by the right-hand one. */
.tiers>.tier+.tier::after{
  content:"";position:absolute;left:0;top:0;bottom:0;width:1px;
  background:var(--pc-cut);pointer-events:none;
}
/* A pocket catches light; it does not rise. :focus-within is here so tabbing
   to a column's CTA lights the same column a mouse would.
   MEASURED on the lit surfaces — nothing drops below AA:
     dark  #100F0D : --creme-d #F4ECD8 16.40:1, --steen #918A79 5.58:1,
                     --champagne #E6CF8F 12.49:1
     cream --creme #EFE6D3 : --inkt 11.03:1, --muted #665C4B 5.29:1,
                     --brons #7A5E1C 4.91:1  (all three already published
                     against --creme in pcg.css) */
.tier:hover,.tier:focus-within{background-color:#100F0D}
.skin-creme .tier:hover,.skin-creme .tier:focus-within,
.skin-creme-deep .tier:hover,.skin-creme-deep .tier:focus-within{background-color:var(--creme)}
/* Ledger figures: the declared figure step, the one negative tracking step,
   and lining tabular digits. The tabular-nums is NOT a fix for the webfont —
   Switzer already ships fixed-width lining figures, so it is inert there and
   is declared purely so the system fallback stack lines its columns up too. */
.tier__price{font-family:var(--ff-display);font-weight:800;font-size:var(--fs-figure);line-height:1;margin-top:20px;color:var(--ink);letter-spacing:var(--ls-figure);font-variant-numeric:lining-nums tabular-nums}
/* Two lines are RESERVED whether or not they are used. The rules below these
   blocks have to land on one line across all three columns or the plate stops
   reading as ruled, and these strings wrap at one column width and not at
   another (at ~961px a 3-up column gives the unit only ~247px). Reserving the
   taller case makes the plate's internal rule level at every width. */
.tier__unit{font-size:12.5px;color:var(--muted);margin-top:14px;letter-spacing:.04em;line-height:1.5;min-height:calc(12.5px * 1.5 * 2)}
/* Bleeds to the cell edges so the three internal rules read as ONE rule
   crossing the plate, interrupted only by the cuts. */
.tier__rule{height:1px;background:var(--rake);margin:26px calc(var(--pad-cell) * -1)}
/* A tier with no .tier__unit (tarieven.html has two) would otherwise pull its
   rule up by the unit's reserved height + margin and break the level. */
.tier__price+.tier__rule{margin-top:calc(26px + 14px + 12.5px * 1.5 * 2)}

/* featured tier — background-image, never the `background` shorthand: the
   shorthand resets background-color and would eat the hover light above. */
.skin-onyx .tier--feat,.skin-char .tier--feat{background-image:linear-gradient(180deg,rgba(201,161,74,.07),rgba(201,161,74,.02) 40%,transparent)}
.skin-creme .tier--feat,.skin-creme-deep .tier--feat{background-image:linear-gradient(180deg,rgba(184,144,47,.08),rgba(184,144,47,.02) 40%,transparent)}
/* The one place on the page a 2px gold bar is earned: it marks the featured
   column on the plate's top rule, where the outline used to do that job. */
.tier--feat::before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--gold-grad)}
/* The badge was a filled gold pill straddling the box's top border — a
   sticker on a card, and the last hard-coded #15110a ink in this sheet (that
   literal measures 3.87:1 on the old gradient's dark end). With no box to
   straddle it becomes what it always was: an engraved label, in gold ink on
   the plate, struck under the featured column's bar and marked with the same
   16px tick .frame__cap uses. Contrast is now the published gold-INK pair —
   --brons 5.51:1 on --papier, --champagne well past AA on onyx — instead of
   dark ink on metal.

   It stays OUT OF FLOW, and every column in a plate that carries a badge
   reserves the lane it sits in (:has() is already load-bearing in this
   codebase — components.css:188, hero-canvas.css:198). If the label were a
   flow child it would push the featured column's name, price, unit and rule
   ~22px below the other two, and on one shared plate a rule that is level in
   two columns and dropped in the third is far more visible than it was when
   each column had its own outline. */
.tiers:has(.tier__badge)>.tier{padding-top:calc(var(--pad-cell) + 22px)}

/* ---------- Service rows (op offerte / v.a.) ---------- */
/* The container's top rule is its ::before (above); every row draws its own
   bottom rule, so the rule belongs to the row and travels with it through the
   stagger instead of standing in an empty ledger. */
.price-rows{border-top:0}
.price-row{
  position:relative;
  display:grid;grid-template-columns:minmax(0,1fr) max-content;gap:24px;align-items:baseline;
  padding:clamp(22px,2.4vw,30px) 0;border-bottom:0;
}
.price-row::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--rake);pointer-events:none}
.price-row__main h3{font-size:clamp(20px,2vw,26px)}
.price-row__main p{color:var(--muted);font-size:14.5px;margin-top:8px;max-width:62ch;line-height:1.5}
/* A real digit track. Right-aligned in a max-content column so every figure
   on the page shares one right edge; 4ch is the widest figure on the site
   (€3.499) so a short value can never make its own narrower column. */
.price-row__price{font-family:var(--ff-display);font-weight:700;font-size:clamp(26px,2.6vw,34px);letter-spacing:var(--ls-figure);font-variant-numeric:lining-nums tabular-nums;color:var(--gold-text);white-space:nowrap;text-align:right;min-width:4ch}
.price-row__price .pre{display:block;font-family:var(--ff-body);font-size:11px;letter-spacing:var(--ls-label);text-transform:uppercase;color:var(--muted);font-weight:400;margin-bottom:4px;font-variant-numeric:normal}

/* ---------- Add-ons grid ---------- */
/* Same plate, same reason the cuts are on the cells: this grid reveals with
   data-stagger-mode="wipe", i.e. its children start FULLY clipped, so a
   ground fill would be a bare gold slab for the length of the stagger. */
/* No container background at all — see the plate-light rule above for why this
   grid is the one that carries none. The `background:none` that used to sit
   here was cancelling the light by shorthand from 145 lines away; the decision
   now lives in one place, at the selector that grants it. */
.addons{display:grid;grid-template-columns:repeat(2,1fr);gap:0;border:0}
.skin-creme .addon,.skin-creme-deep .addon{background:var(--papier)}
/* Right-hand column: the vertical cut. Third cell onward: the horizontal one.
   Both are the cell's own, both clip with it. */
.addons>.addon:nth-child(2n)::before{content:"";position:absolute;left:0;top:0;bottom:0;width:1px;background:var(--pc-cut);pointer-events:none}
.addons>.addon:nth-child(n+3)::after{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--rake);pointer-events:none}
/* Stays at row scale. --fs-figure would put a 46px number next to a 19px
   heading in a dense two-up grid; it is a ledger figure, not a readout. */
.addon__price{font-family:var(--ff-display);font-weight:700;font-size:24px;letter-spacing:var(--ls-figure);font-variant-numeric:lining-nums tabular-nums;color:var(--gold-text);white-space:nowrap}

/* ---------- Combo deals strip ---------- */
.combos{display:grid;grid-template-columns:repeat(3,1fr);gap:0}
.combos>.combo+.combo::after{content:"";position:absolute;left:0;top:0;bottom:0;width:1px;background:var(--pc-cut);pointer-events:none}
/* Same lit cell, same measured surfaces as .tier:hover above. */
.combo:hover,.combo:focus-within{background-color:#100F0D}
.skin-creme .combo:hover,.skin-creme .combo:focus-within,
.skin-creme-deep .combo:hover,.skin-creme-deep .combo:focus-within{background-color:var(--creme)}

/* ---------- Request pop-up ---------- */
.rq{
  position:fixed;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;
  padding:clamp(16px,4vw,40px);opacity:0;pointer-events:none;transition:opacity .4s var(--ease);
}
.rq.show{opacity:1;pointer-events:auto}
.rq__scrim{position:absolute;inset:0;background:rgba(5,5,6,.74);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.rq__dialog{
  position:relative;width:min(520px,100%);max-height:92vh;overflow:auto;
  background:#0e0d0c;border:1px solid var(--gold-text);
  padding:clamp(28px,3.4vw,46px);box-shadow:0 40px 120px rgba(0,0,0,.6);
  transform:translateY(18px) scale(.985);transition:transform .5s var(--ease);
}
.rq.show .rq__dialog{transform:none}
.rq__dialog::before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--gold-grad)}
.rq__close{position:absolute;top:12px;right:14px;background:none;border:none;color:var(--steen);font-size:26px;line-height:1;cursor:pointer;padding:4px 8px;transition:color .3s;z-index:2}
.rq__close:hover{color:var(--champagne)}
.rq__eyebrow{font-size:10.5px;letter-spacing:var(--ls-eyebrow);text-transform:uppercase;color:var(--gold-text)}
.rq__title{font-size:clamp(28px,3.4vw,40px);margin-top:10px;line-height:1.02;color:var(--ink)}
.rq__price{font-family:var(--ff-display);font-weight:700;font-size:clamp(20px,2.2vw,26px);letter-spacing:var(--ls-figure);font-variant-numeric:lining-nums tabular-nums;color:var(--gold-text);margin-top:8px}
.rq__lead{color:var(--steen);font-size:14.5px;line-height:1.55;margin-top:16px;max-width:44ch}
.rq__fields{margin-top:28px}
.rq__fields .field{margin-bottom:28px}
.rq__alt{margin-top:20px;font-size:13px;color:var(--steen);text-align:center;letter-spacing:.02em}
.rq__alt a{color:var(--gold-text);margin-left:.45em;display:inline-flex;align-items:center;min-height:24px}

/* The table-of-contents heading is an h2 so the document outline runs h1 -> h2
   without skipping, but it is a small utility label, not a section title. */
.pol-toc h2{font-family:var(--ff-body);font-size:1em;font-weight:700;line-height:1.5;letter-spacing:normal}
.rq__alt a:hover{text-decoration:underline}
.rq__success{text-align:center;padding:14px 6px 6px}
.rq__success .muted{max-width:40ch;margin-inline:auto;line-height:1.55}
@media (prefers-reduced-motion:reduce){
  .rq,.rq__dialog{transition:none}
  .rq__dialog{transform:none}
}

/* ---------- Conditions / fine print ----------
   HONESTY GUARD, and it applies to every label/value grid in this sheet.
   A 4-up (or any n-up) label/value strip on this page keeps its values at
   FACT scale — never --fs-figure, never --fs-readout, never display scale —
   and every value stays a project fact a reader can check: a price from the
   list, a scope, a duration, a language count. The big-number-plus-small-
   label band is banned outright, including at n=3, and a grid of quiet facts
   is one restyle away from becoming one. If a figure here ever wants to be
   large, that is the signal it has stopped being a fact and started being a
   claim. A graduated scale is permitted only where a real quantity is already
   in the DOM and the literal figure is printed beside it — there is no such
   quantity on this page, so there is no scale on this page. */
.conditions{display:grid;grid-template-columns:repeat(2,1fr);gap:14px 40px;list-style:none}
/* The rule was `border-left:1px solid var(--rule)` — a flat printed line. It
   is a 1px fill now so it can take the same one light as everything else. */
.conditions li{position:relative;display:flex;gap:.8em;font-size:14px;color:var(--muted);line-height:1.5;border-left:0;padding-left:18px}
.conditions li::before{content:"";position:absolute;left:0;top:0;bottom:0;width:1px;background:var(--pc-cut);pointer-events:none}
.conditions li b{color:var(--ink);font-weight:500}

/* ---------- Hourly rate banner ---------- */
.rate{
  display:flex;align-items:center;justify-content:space-between;gap:32px;flex-wrap:wrap;
  border:0;padding:var(--pad-cell);position:relative;overflow:hidden
}
/* The page's one readout, so it takes the readout step rather than the figure
   step — and drops from a 84px shout to 66px. It is a single real quantity
   with its unit printed beside it, which is why it is allowed to be the
   largest figure on the page. */
.rate__num{font-family:var(--ff-display);font-weight:800;font-size:var(--fs-readout);letter-spacing:var(--ls-figure);font-variant-numeric:lining-nums tabular-nums;line-height:1;color:var(--ink)}

@media (max-width:960px){
  /* .tiers.cols-2 is (0,2,0) and a media query adds no specificity, so plain
     `.tiers` here would lose to it and the two-column grid would never collapse.
     THE SAME TRAP APPLIES TO EVERY MODIFIER ADDED BELOW: a media query buys no
     specificity at all, so each rule here has to out-specify, or exactly match
     and post-date, the base rule it is overriding. That is why the cut rules
     below repeat their full base selectors instead of being written short. */
  .tiers,.tiers.cols-2,.combos{grid-template-columns:minmax(0,1fr);max-width:520px;margin-inline:auto}
  .addons{grid-template-columns:1fr}
  .conditions{grid-template-columns:1fr}
  .price-head__meta{text-align:left}

  /* ONE COLUMN, SO THE CUTS TURN. Stacked cells are separated across, not
     down: every cut becomes a horizontal rule at the top of the cell that
     owns it, and the plate reads as a ruled list — which is what a price
     list should look like on a phone. Same selectors as the base rules, so
     they win on order rather than on weight. */
  .tiers>.tier+.tier::after,
  .combos>.combo+.combo::after{
    left:0;right:0;top:0;bottom:auto;width:auto;height:1px;background:var(--rake);
  }
  /* The add-ons grid indexes its cuts by column and row, so in one column the
     two-axis scheme collapses to the simple one: no vertical cut at all, and
     one rule on every cell but the first. Same weight as the :nth-child(n+3)
     base rule (0,3,1) and declared after it, so it wins on order. */
  .addons>.addon:nth-child(2n)::before{display:none}
  .addons>.addon+.addon::after{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--rake);pointer-events:none}

  /* Reserved lanes and reserved unit lines only exist to keep three columns
     level with each other. In one column there is nothing to be level with,
     so the air comes back out. */
  .tiers:has(.tier__badge)>.tier{padding-top:var(--pad-cell)}
}
@media (max-width:640px){
  .price-row{grid-template-columns:minmax(0,1fr);gap:12px}
  .price-row__price{text-align:left;min-width:0}
}

/* @aibento-start
   ============================================================
   AI & Automation — capability bento
   Scoped to .aicap; ships with pricing.css, used only on
   tarieven-ai-automatisering.html. Price-free, editorial.
   ============================================================ */
.aicap__head{max-width:46ch}

/* a fine gold rule draws across the top edge on hover — the site's own idiom */
.aicard::before{
  content:"";position:absolute;left:0;top:0;height:1px;width:100%;z-index:2;
  background:var(--gold-grad);transform:scaleX(0);transform-origin:left;
  transition:transform .6s var(--ease);
}

/* closing statement */
.aicap__cta{margin-top:clamp(52px,6.5vw,96px);display:flex;flex-direction:column;align-items:center;text-align:center;gap:clamp(24px,3vw,38px)}
/* @aibento-end */
