/* Cache-doorbraak 23 sep 2026. De productmuur stond live zonder zijn stijl:
   index.html verwees al naar style.css?v=3ad5fa8db9 terwijl er achter díé URL
   nog de vorige stylesheet zat. Omdat /css/* in _headers `immutable` is, pinde
   elke browser die in dat venster laadde die oude bytes een jaar vast — een
   nieuwe build helpt dan niet, want de inhoudshash en dus de URL blijft
   gelijk. Deze regel verandert de inhoud en daarmee de hash, zodat het bestand
   onder een adres komt dat nog nergens in een cache staat. Weghalen mag zodra
   de site een keer of wat probleemloos is uitgerold.
   ========================================================================== */
/* ==========================================================================
   Shiny Trading Company — design tokens
   Ink/Paper alternating sections, foil-holo signature, manifest/ledger motif
   ========================================================================== */
:root{
  /* De hoogte die de in- en uitschuivende browserbalk van een telefoon
     inneemt: 0 zolang hij weg is, zijn eigen hoogte zodra hij er staat.
     Alleen gezet onder @media (pointer:coarse), helemaal onderaan dit
     bestand; overal elders is hij 0 en verandert er dus niets. Waar hij
     voor dient staat daar. */
  --deck-bar:0px;

  /* De kleur van de onderrand van het paneel waar je naar kijkt. Wordt door
     initEdgeColour() (js/main.js §6d) op <html> gezet en is daarmee de
     canvaskleur van het document: precies wat iOS Safari onder zijn
     adresbalk schildert, in de strook boven de home-indicator. Stond die op
     `body`'s papierkleur, dan zie je daar een lichte band terwijl je naar
     een donker paneel kijkt — het leest als een kier onder de banner. */
  --edge:#F6F4EE;

  --ink:        #0D0F12;
  --ink-soft:   #14171D;
  --ink-line:   #262A32;
  --ink-text:   #ECEAE3;
  --ink-dim:    #9AA0AC;

  --paper:      #F6F4EE;
  --paper-soft: #EFEBE0;
  --paper-line: #DAD4C3;
  --paper-text: #17181B;
  --paper-dim:  #6B6A63;

  --gold:       #C9A227;
  --gold-bright:#E4BC3E;

  /* Showcase band — a warm charcoal "shelf" pulled toward the gold hue, so it
     separates from the cool near-black hero instead of blending into it. */
  --shelf:      #1E1913;
  --shelf-line: rgba(201,162,39,.16);
  --violet:     #7B5FFF;
  --teal:       #2FD9C4;
  --red-stamp:  #B43B2F;
  /* The counter-stamp for a finished order. Deep enough on white to read as
     ink rather than a status LED, and it clears 4.5:1 against the panel. */
  --green-stamp:#2E7A4E;

  --font-display: "Instrument Serif", Georgia, serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace;

  --container: 1180px;
  --radius: 3px;
  --shadow-lg: 0 24px 60px -20px rgba(0,0,0,.45);
  --ease: cubic-bezier(.16,.8,.28,1);
}

*,*::before,*::after{ box-sizing:border-box; }
html{ scroll-behavior:smooth; }

/* Homepage only: no double-tap zoom.
   The panels are tapped, swiped and arrowed through, and on a phone a hurried
   second tap on the book's arrows was being read as "zoom in" — you end up
   magnified on half a card with no idea why. `manipulation` takes double-tap
   zoom off the table while leaving panning and *pinch* zoom alone, so anyone
   who wants to zoom still can. That distinction matters: switching zoom off
   wholesale (`user-scalable=no`) is a WCAG failure, and Safari on iOS has
   ignored it since iOS 10 in any case, so it would not even have worked here.
   Only .home carries it — the shop, the product pages and the forms are
   ordinary documents and are left alone. */
body.home{ touch-action:manipulation; }
/* The controls themselves, for the same reason plus two of their own: no blue
   tap flash on a button being pressed in a hurry, and no text selection when
   a tap lands slightly long. */
.home .book-arrow,
.home .book-progress button,
.home .fold-cta,
.home .fold-tile,
.home .hero-scroll,
.home .btn{
  touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
  -webkit-user-select:none;
          user-select:none;
}
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } }

/* `clip`, not `hidden` — setting overflow-x:hidden while overflow-y stays at
   its default computes overflow-y to `auto` per spec (browsers can't mix
   visible with a non-clip overflow value), which silently disables every
   position:sticky element on the page, including the book's pinned section.
   `clip` is explicitly exempt from that auto-promotion, so it blocks the
   off-canvas mobile nav's horizontal overflow without touching the y-axis. */
html{ overflow-x:clip; }
body{
  margin:0;
  overflow-x:clip;   /* the off-canvas mobile nav sits translateX(100%) off
                        the right edge; without this it still registers as
                        scrollable area and the whole page gains a
                        horizontal scrollbar on phones. */
  background:var(--paper);
  color:var(--paper-text);
  font-family:var(--font-body);
  font-size:16px;
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}

img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
ul{ margin:0; padding:0; list-style:none; }
button{ font:inherit; cursor:pointer; }
input,select,textarea{ font:inherit; }

h1,h2,h3,h4{
  font-family:var(--font-display);
  font-weight:400;
  line-height:1.05;
  margin:0;
  letter-spacing:-.01em;
}

.container{ max-width:var(--container); margin:0 auto; padding:0 28px; }
@media (max-width:640px){ .container{ padding:0 20px; } }

section{ position:relative; }
.section-pad{ padding:110px 0; }
@media (max-width:900px){ .section-pad{ padding:76px 0; } }

.bg-ink{ background:var(--ink); color:var(--ink-text); }
.bg-paper{ background:var(--paper); color:var(--paper-text); }
.bg-ink-soft{ background:var(--ink-soft); color:var(--ink-text); }

.mono{ font-family:var(--font-mono); }

/* Present for the document outline and for search, absent from the page. Not
   display:none, which would take it out of the accessibility tree too. */
.u-sr-only{
  position:absolute;
  width:1px; height:1px;
  padding:0; margin:-1px;
  overflow:hidden;
  clip:rect(0 0 0 0);
  white-space:nowrap;
  border:0;
}

:focus-visible{
  outline:2px solid var(--gold-bright);
  outline-offset:3px;
  border-radius:2px;
}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;
  align-items:center;
  gap:10px;
  padding:16px 32px;
  /* A rounded rectangle, not a stadium — measured off the owner's reference:
     the corner runs to roughly 0.18 of the button's height, so at ~52px tall
     that is 10px. A full pill was tried first and read as too soft. */
  border-radius:10px;
  /* The three properties the foil hover depends on: a positioning context, a
     clip so the sweep stops at the pill's edge, and an isolated stacking
     context so the z-index:-1 layers land above the button's own background
     but still behind its label instead of escaping to the page. */
  position:relative;
  overflow:hidden;
  isolation:isolate;
  /* The nav's typeface, at the nav's weight — the owner's call (sep 2026).
     Mono uppercase is the ledger voice this site uses for labels and data;
     a button is neither, and sentence case in Inter is what the reference
     shows. */
  font-family:var(--font-body);
  font-size:15px;
  font-weight:500;
  letter-spacing:normal;
  text-transform:none;
  border:1px solid transparent;
  transition:transform .35s var(--ease), background .3s, border-color .3s, color .3s;
  white-space:nowrap;
  justify-content:center;
}
.btn:hover{ transform:translateY(-2px); }

/* --- foil hover -----------------------------------------------------------
   The company is called Shiny and its wordmark is a holo gradient; a button
   that lights up like the card it sells is the one hover state this site can
   claim as its own. Two layers, both on z-index:-1 so the label stays crisp
   over them:

     ::before  the holo tint — literally the .logo em gradient, drifting
     ::after   the specular band travelling across, as on .foil-card

   Tuned per variant through --foil-tint and --foil-band, because a white band
   that reads as light over gold or ink is invisible over paper. */
.btn, .add-btn, .gate-btn, .header-account{
  --foil-tint:.34;
  --foil-band:rgba(255,255,255,.62);
}
.btn::before, .add-btn::before, .gate-btn::before, .header-account::before,
.btn::after,  .add-btn::after,  .gate-btn::after,  .header-account::after{
  content:"";
  position:absolute; inset:0; z-index:-1;
  border-radius:inherit;
  opacity:0;
  pointer-events:none;
  transition:opacity .3s var(--ease);
}
.btn::before, .add-btn::before, .gate-btn::before, .header-account::before{
  background:linear-gradient(100deg,
    var(--gold) 0%, var(--gold-bright) 22%, var(--teal) 46%,
    var(--violet) 68%, var(--gold) 100%);
  background-size:230% 100%;
  background-position:100% 0;
}
.btn::after, .add-btn::after, .gate-btn::after, .header-account::after{
  background:linear-gradient(112deg,
    transparent 38%, var(--foil-band) 50%, transparent 62%);
  background-size:260% 100%;
  background-position:140% 0;
}
.btn:hover::before, .btn:focus-visible::before,
.add-btn:hover::before, .add-btn:focus-visible::before,
.gate-btn:hover::before, .gate-btn:focus-visible::before,
.header-account:hover::before, .header-account:focus-visible::before{
  opacity:var(--foil-tint);
  animation:foilDrift 1.1s var(--ease) forwards;
}
.btn:hover::after, .btn:focus-visible::after,
.add-btn:hover::after, .add-btn:focus-visible::after,
.gate-btn:hover::after, .gate-btn:focus-visible::after,
.header-account:hover::after, .header-account:focus-visible::after{
  opacity:1;
  animation:foilSweep .95s var(--ease) forwards;
}
/* The tilt of the holo as the angle of view changes, and the light crossing
   it. Both end off the far edge, so the button settles back to flat rather
   than holding a highlight that never leaves. */
@keyframes foilDrift{ from{ background-position:100% 0; } to{ background-position:0% 0; } }
@keyframes foilSweep{ from{ background-position:140% 0; } to{ background-position:-40% 0; } }

/* Nothing that cannot be pressed gets to shine. De glans blijft wél op een
   knop die net "Toegevoegd" heeft geflitst: die staat zo weer op zijn
   gewone label en nodigt dan uit tot een volgende klik. */
.btn.is-soldout::before, .btn.is-soldout::after,
.add-btn.is-soldout::before, .add-btn.is-soldout::after,
.btn[disabled]::before, .btn[disabled]::after{ display:none; }

/* The sweep is the effect; without it the tint alone reads as a colour change
   rather than as foil, so under a motion preference both layers simply stay
   off and the variants' own hover colours do the work. */
@media (prefers-reduced-motion:reduce){
  .btn::before, .btn::after, .add-btn::before, .add-btn::after,
  .gate-btn::before, .gate-btn::after,
  .header-account::before, .header-account::after{ display:none; }
  .btn:hover{ transform:none; }
}
.btn-gold{
  background:var(--gold);
  color:#171204;
  font-weight:600;
}
.btn-gold:hover{ background:var(--gold-bright); }
.btn-outline-dark{
  border-color:rgba(236,234,227,.44);
  color:var(--ink-text);
}
.btn-outline-dark:hover{ border-color:var(--gold); color:var(--gold-bright); }
.btn-outline-light{
  border-color:rgba(23,24,27,.28);
  color:var(--paper-text);
  /* On paper there is no darkness for a white band to brighten, so the holo
     carries the effect and the band only softens its edge. */
  --foil-tint:.5;
  --foil-band:rgba(255,255,255,.5);
}
.btn-outline-light:hover{ border-color:var(--paper-text); }
/* Afwijzen staat naast accepteren en moet daar duidelijk van verschillen,
   maar niet mee concurreren: accepteren is de hoofdhandeling en houdt het
   volle goud. Omlijnd rood dus, dat pas bij aanwijzen dichtloopt. */
.btn-decline{
  border-color:rgba(180,59,47,.45);
  color:var(--red-stamp);
  background:none;
}
.btn-decline:hover{ background:var(--red-stamp); border-color:var(--red-stamp); color:#fff; }

.btn-block{ width:100%; justify-content:center; }
.btn[disabled]{ opacity:.45; cursor:not-allowed; transform:none !important; }

/* ==========================================================================
   Header
   ========================================================================== */
.site-header{
  position:sticky; top:0; z-index:200;
  background:rgba(13,15,18,.86);
  backdrop-filter:blur(10px);
  border-bottom:1px solid var(--ink-line);
}
/* Homepage: the header floats on the hero instead of sitting in a bar of its
   own — same as corporate.pokemon.co.jp. Fixed (not sticky) so the hero art
   runs all the way up behind it; the hero gets the height back as padding.
   The soft top scrim on ::before is the only thing keeping the logo legible
   over the art, and it lifts once .is-solid takes over (see js/main.js). */
body.home .site-header{
  position:fixed; left:0; right:0; top:0;
  background:transparent;
  backdrop-filter:none;
  border-bottom-color:transparent;
  transition:background .35s ease, backdrop-filter .35s ease, border-color .35s ease;
}
body.home .site-header::before{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(to bottom, rgba(13,15,18,.55), rgba(13,15,18,0));
  transition:opacity .35s ease;
}
body.home .site-header.is-solid{
  background:rgba(13,15,18,.86);
  backdrop-filter:blur(10px);
  border-bottom-color:var(--ink-line);
}
body.home .site-header.is-solid::before{ opacity:0; }
/* The header no longer takes up space in the flow, so the hero carries it.
   The hero itself fills the viewport — full width, full height — with the
   artwork running edge to edge behind it and the copy centred in what is
   left below the header. */
body.home .hero{
  --edge:#0D0F12;
  /* Vertical padding is a *budget*, not a fixed inset. The header is 77px, so
     118px is the least this can be and still clear it with air; 172px is what
     it wants on a tall screen. Fixed 172/110 px asked for 282px of chrome out
     of a 720px window, and together with the card stage that made the panel
     802px tall on a 720px screen — one screen plus 82. Anything expressed in
     px here has to be able to give way when the window is short. */
  padding-top:clamp(118px,16svh,172px);
  padding-bottom:clamp(64px,10svh,110px);
  min-height:100svh;
  display:flex; flex-direction:column; justify-content:center;
}
@media (max-width:760px){
  body.home .hero{ padding-top:132px; }
}
/* An open mobile menu needs the opaque panel behind it either way. */
body.home.nav-open .site-header{
  background:var(--ink); backdrop-filter:none; border-bottom-color:transparent;
}
body.home.nav-open .site-header::before{ opacity:0; }

.header-inner{
  display:flex; align-items:center; justify-content:space-between;
  height:76px;
  gap:24px;
}
.logo{
  font-family:var(--font-display);
  font-size:22px;
  color:var(--ink-text);
  display:flex; align-items:baseline; gap:8px;
  flex-shrink:0;
}
.logo em{
  font-style:italic;
  background:linear-gradient(100deg,var(--gold) 0%,var(--gold-bright) 30%,var(--teal) 55%,var(--violet) 75%,var(--gold) 100%);
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
}
.logo small{
  font-family:var(--font-mono);
  font-size:10px;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--ink-dim);
  font-style:normal;
}
.main-nav{ display:flex; gap:30px; align-items:center; }
.main-nav a{
  font-size:14px;
  color:var(--ink-dim);
  position:relative;
  padding:6px 0;
  transition:color .25s;
}
.main-nav a:hover, .main-nav a.active{ color:var(--ink-text); }
.main-nav a.active::after{
  content:"";
  position:absolute; left:0; right:0; bottom:0;
  height:1px; background:var(--gold);
}

/* -------------------------------------------------- the 30th bubble in the nav
   A speech bubble hanging off "Catalogus", tail pointing back up at the word,
   with the anniversary mark and three words in it. It is not a separate link:
   it lives inside the nav anchor, so the whole thing already goes to the
   catalogue and the words become part of that link's accessible name —
   "Catalogus, Nieuw: 30th Celebration" — which is what a screen reader and a
   crawler need to know it is there at all.

   Paper on ink, deliberately: every other surface in the header is dark, so a
   light card is the one thing in it that cannot be mistaken for chrome. The
   logo brings its own colour, so the bubble itself stays neutral.

   Held at opacity 0 until js/main.js adds .nav30-ready, which it does from
   bootDeferred() — i.e. only once the intro cascade has finished playing. A
   bubble bouncing in through the site's own fade-in is one more thing moving
   at the moment the page is still settling; this one is meant to catch an eye
   that has already arrived. */
.nav-30{
  position:absolute; top:calc(100% + 11px); left:50%;
  /* An absolutely positioned box shrink-to-fits against its containing block,
     and that block is a 70px nav link — so without max-content the bubble is
     sized by the word above it and the label is cut off mid-sentence. */
  width:max-content;
  display:flex; align-items:center; gap:9px;
  padding:6px 12px 6px 9px;
  background:var(--paper);
  border-radius:8px;
  box-shadow:0 12px 24px -10px rgba(0,0,0,.7);
  white-space:nowrap;
  opacity:0;
  transform:translate(-50%,0);
  /* Pivots on the tail rather than on its own middle, so the scale in the
     keyframes reads as the bubble swinging from the word above it. */
  transform-origin:50% -11px;
}
/* The tail. A rotated square of the same paper, so there is no seam to line
   up — the bubble's own background covers the half that overlaps it. */
.nav-30::before{
  content:"";
  position:absolute; top:-4px; left:50%;
  width:10px; height:10px; margin-left:-5px;
  background:var(--paper);
  border-radius:2px 0 0 0;
  transform:rotate(45deg);
}
/* Not in the catalogue itself — on desktop. The bubble's whole job is to point
   at the catalogue, and on /shop, on a category page and on a product page the
   visitor is already standing in it, where it is an advert for the room you are
   in. `catalogue` is set on the <body> of shop.html and product.html, the two
   shells the category and product pages are generated from, so all three kinds
   are covered by the one class and nothing has to be kept in sync.

   The breakpoint guard is the point of the rule as much as the rule is: below
   it there is no bubble, only the mark sitting quietly beside the word in the
   slide-out, and that is a label on a menu item rather than something shouting
   across the page. The owner wants that one everywhere, catalogue included. */
@media (min-width:941px){
  body.catalogue .nav-30{ display:none; }
}
.nav-30 img{ display:block; width:34px; height:auto; }
.nav-30-t{
  font-family:var(--font-mono); font-size:10px;
  letter-spacing:.09em; text-transform:uppercase;
  color:var(--paper-text);
}
.main-nav a:hover .nav-30{ box-shadow:0 14px 26px -10px rgba(0,0,0,.8); }
html.nav30-ready .nav-30{
  animation:nav30Drop .85s var(--ease) both,
            nav30Hop  2.6s ease-in-out .9s infinite;
}
/* Everywhere but the homepage the bubble is a toast: it arrives, hops three
   times to be noticed, and dismisses itself after ten seconds. The homepage is
   the shop window and can carry a permanent announcement; a product page, the
   FAQ or the quote form is somewhere the visitor came to do something, and an
   ad that never leaves the header on those is an ad in the way.

   Guarded by the breakpoint rather than overridden below it, because this
   selector outranks the mobile block's and would otherwise leak into the
   slide-out menu, where the mark is not a bubble at all. */
@media (min-width:941px){
  html.nav30-ready body:not(.home) .nav-30{
    animation:nav30Drop  .85s var(--ease) both,
              nav30Hop   2.6s ease-in-out .9s 3,
              nav30Leave .55s var(--ease) 10s forwards;
  }
}
/* The exit. It lifts and shrinks slightly as it goes, so it reads as the
   bubble leaving rather than as the page dimming it.

   visibility is in the last keyframe on purpose: opacity:0 alone still takes
   clicks, and the bubble hangs over the page underneath. Visibility holds at
   `visible` for the whole interpolation and only flips at the end, so the
   fade plays in full and the box stops existing for the cursor the moment it
   is invisible. */
@keyframes nav30Leave{
  from{ opacity:1; transform:translate(-50%,0) scale(1); visibility:visible; }
  to  { opacity:0; transform:translate(-50%,-7px) scale(.9); visibility:hidden; }
}
/* Drops in and settles on two diminishing bounces — it arrives, it does not
   fade up. */
@keyframes nav30Drop{
  0%   { opacity:0; transform:translate(-50%,-14px) scale(.6); }
  40%  { opacity:1; transform:translate(-50%,3px)   scale(1.06); }
  56%  { transform:translate(-50%,-6px) scale(.98); }
  72%  { transform:translate(-50%,2px)  scale(1.02); }
  86%  { transform:translate(-50%,-2px) scale(.99); }
  100% { opacity:1; transform:translate(-50%,0) scale(1); }
}
/* Then one small hop every 2.6 seconds, on every page — the owner's call, and
   the same cadence whether the bubble stays (homepage) or leaves after ten
   seconds. If it ever needs to be calmer, widen this cycle rather than making
   the movement smaller: the gap is what keeps a hop from reading as something
   permanently animating, and a smaller hop just looks like a twitch. */
@keyframes nav30Hop{
  0%,80%,100% { transform:translate(-50%,0) scale(1); }
  85%  { transform:translate(-50%,-7px) scale(1.03); }
  90%  { transform:translate(-50%,2px)  scale(.99); }
  95%  { transform:translate(-50%,-2px) scale(1); }
}
@media (prefers-reduced-motion:reduce){
  html.nav30-ready .nav-30{ animation:nav30Fade .5s ease both; }
  @keyframes nav30Fade{ from{ opacity:0; } to{ opacity:1; } }
  /* Still leaves after ten seconds off the homepage — that is behaviour, not
     decoration, so it survives the motion preference. Only the bouncing goes. */
  @media (min-width:941px){
    html.nav30-ready body:not(.home) .nav-30{
      animation:nav30Fade .5s ease both,
                nav30FadeOut .5s ease 10s forwards;
    }
    @keyframes nav30FadeOut{ from{ opacity:1; visibility:visible; } to{ opacity:0; visibility:hidden; } }
  }
}

.icon-basket, .icon-login{ display:block; }
.header-tools{ display:flex; align-items:center; gap:18px; flex-shrink:0; }
.lang-switch{ display:flex; gap:2px; font-family:var(--font-mono); font-size:12px; }
.lang-switch button{
  background:none; border:1px solid var(--ink-line); color:var(--ink-dim);
  padding:5px 9px; border-radius:2px;
}
.lang-switch button.active{ color:var(--gold); border-color:var(--gold); }
.header-icon{ position:relative; display:inline-flex; align-items:center; justify-content:center; width:26px; height:28px; color:var(--ink-dim); transition:color .25s; }
.header-icon:hover, .header-icon:focus-visible, .header-icon.active{ color:var(--ink-text); }
.header-icon.active::after{ content:""; position:absolute; left:3px; right:3px; bottom:0; height:1px; background:var(--gold); }
.header-basket .quote-badge{ position:absolute; top:-3px; right:-7px; width:15px; height:15px; font-size:9px; }

/* "Account aanvragen" — the one thing in the header that is a button rather
   than a destination, so it is the one thing shaped like one. Everything else
   on the site sits on a 3px radius; the full round is what separates the
   action from the ledger it stands on. Outline, not gold: gold belongs to the
   primary call to action inside the page, and a header that competes with the
   hero it floats over wins nothing. */
.header-account{
  display:inline-flex; align-items:center; white-space:nowrap;
  /* Same three as .btn, and they are not optional: without a positioning
     context the foil's inset:0 layers resolve against the sticky header and
     the sweep runs across the whole bar. */
  position:relative;
  overflow:hidden;
  isolation:isolate;
  padding:9px 17px; border:1px solid rgba(236,234,227,.34); border-radius:9px;
  font-family:var(--font-body); font-size:13px; font-weight:500; color:var(--ink-text);
  transition:border-color .3s, color .3s, background .3s;
}
.header-account:hover, .header-account:focus-visible{
  border-color:var(--gold); color:var(--gold-bright);
}
.header-account.active{ border-color:var(--gold); color:var(--gold); }
/* applyAuthHeader() hides it with the hidden attribute once a session is
   confirmed; display:inline-flex above outranks the UA's [hidden] rule. */
.header-account[hidden]{ display:none; }
/* Same reason: .header-icon is display:inline-flex. */
.header-icon[hidden]{ display:none; }
/* Same for the slide-out row; .main-nav .nav-mobile-action sets display:flex
   and would otherwise outrank the UA's [hidden] rule. */
.main-nav .nav-mobile-account[hidden]{ display:none; }

/* The homepage used to keep this folded away for the length of the hero,
   since the hero opens with the same call to action in gold. The owner's
   call (sep 2026): it is now shown there too, so that it is in the bar on
   every page the moment somebody is not signed in. Nothing special is left
   for body.home — the rules above are the whole button everywhere. */

/* The pill is 170-odd pixels the bar did not carry before, and between the
   mobile breakpoint and roughly 1120px that is the difference between "Over
   ons" sitting on one line and breaking onto two. Nothing is dropped; the row
   just closes ranks — tighter gaps and a shorter pill — until there is room
   again. Measured: the header needs 956px laid out loose and 869px tightened,
   against 941px at the narrow end of the band. */
@media (min-width:941px){
  .main-nav a{ white-space:nowrap; }
}
@media (min-width:941px) and (max-width:1120px){
  .main-nav{ gap:20px; }
  .header-tools{ gap:12px; }
  .header-account{ padding:8px 13px; font-size:12px; }
}
.nav-mobile-actions, .nav-mobile-lang{ display:none; }

/* Sign out is an action rather than a destination, so it is a <button> — and
   a button arrives with the browser's own chrome and none of the type the
   links beside it inherit from .main-nav a. These two rules hand it back. */
button.header-icon{ background:none; border:0; padding:0; }
.main-nav button.nav-mobile-action{
  background:none; border:0; border-bottom:1px solid var(--ink-line);
  padding:16px 0; font:inherit; font-size:17px; color:var(--ink-dim);
  text-align:left; transition:color .25s;
}
.main-nav button.nav-mobile-action:hover{ color:var(--ink-text); }
.quote-badge{
  background:var(--gold); color:#171204; font-family:var(--font-mono);
  font-size:10px; font-weight:700; border-radius:50%;
  width:17px; height:17px; display:inline-flex; align-items:center; justify-content:center;
}
.nav-toggle{
  display:none; background:none; border:none; color:var(--ink-text);
  width:32px; height:24px; position:relative;
}
.nav-toggle span, .nav-toggle span::before, .nav-toggle span::after{
  content:""; position:absolute; left:0; right:0; height:1px; background:currentColor;
  transition:transform .3s, opacity .3s;
}
.nav-toggle span{ top:11px; }
.nav-toggle span::before{ top:-8px; }
.nav-toggle span::after{ top:8px; }

@media (max-width:940px){
  .main-nav{
    position:fixed; inset:76px 0 0 0; background:var(--ink);
    flex-direction:column; align-items:stretch; padding:36px 28px; gap:0;
    transform:translateX(100%); transition:transform .4s var(--ease);
    overflow-y:auto;
  }
  .main-nav a{ padding:16px 0; border-bottom:1px solid var(--ink-line); font-size:17px; text-align:left; }
  /* The badge, in the slide-out. No bubble here: the row already names itself,
     so the mark sits beside the word as a plain cut-out and the ballon's chrome
     comes off entirely. The pop fires off body.nav-open, so it replays every
     time the menu is opened rather than once per page load. */
  .main-nav a.nav-shop{ display:flex; align-items:center; gap:12px; }
  html.nav30-ready .nav-30,
  .nav-30{
    position:static; opacity:1; transform:none; animation:none;
    padding:0; background:none; box-shadow:none; border-radius:0;
  }
  .nav-30::before{ display:none; }
  .nav-30 img{ width:46px; }
  .nav-30-t{ display:none; }
  body.nav-open .nav-30{ animation:nav30Pop .6s var(--ease) .3s both; }
  @keyframes nav30Pop{
    0%   { opacity:0; transform:scale(.5) rotate(-10deg); }
    55%  { opacity:1; transform:scale(1.14) rotate(5deg); }
    78%  { transform:scale(.96) rotate(-2deg); }
    100% { opacity:1; transform:scale(1) rotate(0); }
  }
  @media (prefers-reduced-motion:reduce){
    body.nav-open .nav-30{ animation:none; }
  }
  body.nav-open .main-nav{ transform:translateX(0); }
  .nav-toggle{ display:block; }
  .header-tools .lang-switch{ display:none; }
  .header-icon{ display:none; }
  .header-account{ display:none; }
  .nav-mobile-actions{ display:block; margin-top:20px; }
  .main-nav .nav-mobile-action{ display:flex; align-items:center; gap:10px; width:100%; }
  .main-nav .nav-mobile-action .quote-badge{ margin-left:auto; }
  .nav-mobile-lang{ display:flex; align-items:center; justify-content:space-between; gap:16px; margin-top:auto; padding-top:28px; color:var(--ink-dim); font-family:var(--font-mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase; }
  .nav-mobile-lang .lang-switch{ display:flex; }
  /* backdrop-filter creates a containing block for fixed descendants in
     Chromium, which would confine .main-nav's fixed overlay to the header's
     own box instead of the viewport — drop it while the menu is open. */
  body.nav-open .site-header{ backdrop-filter:none; background:var(--ink); }
}
@media (max-width:480px){
  /* "— Wholesale" subtitle and the header gap are the two things that push
     a narrow phone header past its available width. */
  .logo small{ display:none; }
  .header-inner{ gap:14px; }
  .logo{ font-size:19px; }
}

/* ==========================================================================
   Hero + foil signature card
   ========================================================================== */
.hero{
  padding:96px 0 110px;
  position:relative;
  overflow:hidden;
}
.hero::before{
  content:"闪 亮 贸 易";
  position:absolute; top:-40px; right:-20px;
  /* Instrument Serif carries no CJK, so this always rendered in a system
     fallback anyway — but naming the web font made the browser re-measure
     this ~1900px-wide block when the font finally swapped in, and that was
     the page's entire CLS (0.125 on desktop). Naming the fallbacks directly
     is visually identical and takes the swap out of the equation. */
  font-family:"Songti SC","STSong","Noto Serif SC","Source Han Serif SC",serif;
  font-size:min(30vw,420px);
  color:rgba(255,255,255,.025);
  letter-spacing:.05em;
  pointer-events:none;
  white-space:nowrap;
}
/* Manga backdrop -----------------------------------------------------------
   img/hero-manga.webp is the scan, already grayscale + inverted at build time
   (so no per-frame CSS filter on a full-bleed layer). Two stacked layers:
   the art itself, and a scrim on ::after that lays a grey wash over the top
   edge and fades the whole thing back into --ink towards the bottom, so the
   headline, the card scene and the scroll cue all keep their contrast.
   Opacity is deliberately low — this is texture, not an image. */
.hero-bg{
  position:absolute; inset:0; z-index:0;
  pointer-events:none;
  background:url("../img/hero-manga.webp") center 12% / cover no-repeat;
  opacity:.16;
  /* Feather the edges so the panel borders never end in a hard cut. */
  -webkit-mask-image:linear-gradient(to bottom, rgba(0,0,0,.55) 0%, #000 26%, rgba(0,0,0,.7) 62%, transparent 96%);
          mask-image:linear-gradient(to bottom, rgba(0,0,0,.55) 0%, #000 26%, rgba(0,0,0,.7) 62%, transparent 96%);
}
/* The scan is full-bleed but sits at 16% opacity behind everything — at phone
   widths the 1500px master is ~300 KB spent on texture nobody can resolve.
   A 900px cut is indistinguishable there and costs ~180 KB less. A background
   image inside a non-matching media query is never fetched, so only one of
   the two is ever downloaded. */
@media (max-width:820px){
  .hero-bg{ background-image:url("../img/hero-manga-900.webp"); }
}
/* Scrim is a sibling of the art, not a child of it — the mask above would
   otherwise eat the grey band at the very top along with the linework. */
.hero::after{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:
    linear-gradient(to bottom,
      rgba(168,174,186,.40) 0%,
      rgba(160,166,178,.18) 16%,
      rgba(13,15,18,0) 42%,
      rgba(13,15,18,.55) 82%,
      var(--ink) 100%);
}
@media (max-width:760px){
  /* Phone: no card scene competing for the screen, so the manga becomes the
     hero image itself — full-bleed, edge to edge, running right up under the
     logo. Same idea as corporate.pokemon.co.jp's mobile hero. */
  .hero-bg{
    opacity:.22;
    /* Panned to the right-hand page so the spread's centre gutter doesn't run
       straight down the middle of the screen; lands on the Pikachu panel. */
    background-position:76% top;
    background-size:cover;
    /* Only the bottom is feathered now; the top has to reach the very edge. */
    -webkit-mask-image:linear-gradient(to bottom, #000 0%, #000 58%, rgba(0,0,0,.6) 82%, transparent 100%);
            mask-image:linear-gradient(to bottom, #000 0%, #000 58%, rgba(0,0,0,.6) 82%, transparent 100%);
  }
  /* Drop the grey wash at the top — on the phone it read as a bar sitting on
     the art instead of as part of it. Only the fade down into --ink stays. */
  .hero::after{
    background:linear-gradient(to bottom,
      rgba(13,15,18,.35) 0%,
      rgba(13,15,18,.10) 22%,
      rgba(13,15,18,0) 45%,
      rgba(13,15,18,.6) 84%,
      var(--ink) 100%);
  }
  /* The three-card scene is a desktop element; hidden here so the artwork
     gets the full width. It stays in the DOM (aria-hidden already) — no JS
     changes needed. */
  body.home .hero-card-stage{ display:none; }
  body.home .hero-grid{ grid-template-columns:1fr; }
  /* Height/centring are inherited from the desktop rule above; with the
     cards gone the artwork simply gets the whole screen to itself. */
}
@media (prefers-reduced-transparency:reduce){
  .hero-bg{ opacity:.08; }
}
.hero-grid{
  display:grid; grid-template-columns:1.05fr .95fr; gap:60px; align-items:center;
  position:relative; z-index:1;
}
.hero h1{
  /* Sized so the headline always wraps over max 2 lines, from narrow phones
     up to the 1180px container, and stays clearly larger than the lead. */
  font-size:clamp(31px,4.6vw,58px);
  text-wrap:balance;
  margin:0 0 18px;
}
.hero-lead{
  font-size:clamp(15px,1.5vw,19px);
  line-height:1.55;
  max-width:46ch;
  color:var(--ink-dim);
  margin:0 0 36px;
}
.hero h1 i:not(.shine){ font-style:italic; color:var(--gold-bright); }
.hero h1 i.shine{ font-style:italic; }

@media (max-width:640px){
  /* Phones only. With the card scene gone the headline is the hero, so it is
     sized to act like one instead of like slightly-large body copy: it scales
     with the viewport (11.4vw ≈ 43px on a 375px phone, against 31px before),
     the leading is pulled in tight so the two or three lines read as one
     block, and a hair of negative tracking keeps the display face from
     looking loose at that size. */
  .hero h1{
    /* 13.4vw ≈ 50px on a 375px phone. With the card scene gone the headline
       carries the whole screen, so it is sized to fill the measure rather
       than sit politely inside it; the tighter leading and tracking keep the
       extra size from turning three lines into a wall. */
    font-size:clamp(44px, 13.4vw, 62px);
    line-height:.98;
    letter-spacing:-.022em;
    margin-bottom:22px;
  }
  /* The lead has to stay clearly subordinate now that the headline is half
     again as big, so it gives up a little size and takes a little more air. */
  .hero-lead{
    font-size:16px;
    line-height:1.6;
    margin-bottom:32px;
  }
}

/* Gold shine sweeping left→right across a word, then resting before it repeats.
   Tune the cycle with --shine-cycle; the sweep itself is ~30% of that. */
.shine{
  color:var(--gold-bright);
  --shine-cycle:5s; /* full cycle: ~1.5s sweep + ~3.1s rest */
}
@supports ((-webkit-background-clip:text) or (background-clip:text)){
  .shine{
    background-image:linear-gradient(100deg,
      var(--gold) 0%,   var(--gold) 40%,
      #fff3cd 47%,      #ffffff 50%,      #fff3cd 53%,
      var(--gold) 60%,  var(--gold) 100%);
    background-size:250% 100%;
    background-repeat:no-repeat;
    -webkit-background-clip:text;
    background-clip:text;
    color:transparent;
    animation:shineSweep var(--shine-cycle) ease-in-out infinite;
  }
}
/* Both end positions land on flat gold, so the loop restart is invisible. */
@keyframes shineSweep{
  0%,   8%  { background-position:100% 0; }
  38%, 100% { background-position:0% 0; }
}
@media (prefers-reduced-motion: reduce){
  .shine{ animation:none; }
}
/* Grid rather than flex so both buttons take the width of the widest label
   instead of each sizing to its own text. inline-grid + max-content keeps the
   pair shrink-wrapped around the labels; 1fr then equalises the columns. */
.hero-actions{
  display:inline-grid;
  grid-auto-flow:column;
  grid-auto-columns:1fr;
  width:max-content;
  max-width:100%;
  gap:16px;
  margin-bottom:44px;
}
@media (max-width:560px){
  /* Stacked, full width — same reason as the closing CTA below. */
  .hero-actions{ grid-auto-flow:row; width:100%; }
}
/* no trailing gap when the stats row below it isn't present */
.hero-actions:last-child{ margin-bottom:0; }
.hero-meta{
  display:flex; gap:34px; flex-wrap:wrap;
  font-family:var(--font-mono); font-size:12px; color:var(--ink-dim);
  border-top:1px solid var(--ink-line); padding-top:22px;
}
.hero-meta b{ display:block; color:var(--ink-text); font-size:20px; font-family:var(--font-body); font-weight:600; }

/* the foil card */
.foil-stage{ perspective:1400px; display:flex; justify-content:center; }
.foil-card{
  width:min(340px,80vw); aspect-ratio:63/88;
  border-radius:16px;
  position:relative;
  background:
    radial-gradient(120% 140% at var(--mx,50%) var(--my,30%),
      rgba(255,255,255,.55), transparent 45%),
    linear-gradient(135deg,#1a1c22,#0a0b0d 60%);
  border:1px solid rgba(255,255,255,.12);
  box-shadow:var(--shadow-lg);
  transform-style:preserve-3d;
  transition:transform .25s var(--ease);
  overflow:hidden;
}
.foil-card::before{
  content:"";
  position:absolute; inset:0;
  background:conic-gradient(from var(--rot,180deg) at var(--mx,50%) var(--my,50%),
    var(--gold), var(--teal), var(--violet), var(--gold-bright), var(--gold));
  mix-blend-mode:screen;
  opacity:.5;
}
.foil-card::after{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(115deg, transparent 20%, rgba(255,255,255,.5) 36%, transparent 46%);
  background-size:220% 220%;
  background-position:calc(var(--mx,50%) * 1.4) calc(var(--my,50%) * 1.4);
  mix-blend-mode:overlay;
}
.foil-face{
  position:absolute; inset:14px;
  border:1px solid rgba(255,255,255,.5);
  border-radius:10px;
  display:flex; flex-direction:column; justify-content:space-between;
  padding:18px;
  z-index:2;
  pointer-events:none;
}
.foil-face-top{ display:flex; justify-content:space-between; align-items:flex-start; }
.foil-face-top span{ font-family:var(--font-mono); font-size:10px; letter-spacing:.1em; color:rgba(255,255,255,.85); text-transform:uppercase; }
.foil-face-mark{ font-family:var(--font-display); font-style:italic; font-size:15px; color:#fff; }
.foil-face-center{ text-align:center; }
.foil-face-center .cn{ font-size:44px; color:rgba(255,255,255,.92); font-family:var(--font-display); }
.foil-face-center small{ display:block; font-family:var(--font-mono); font-size:10px; letter-spacing:.16em; color:rgba(255,255,255,.6); margin-top:6px; text-transform:uppercase; }
.foil-face-bottom{ display:flex; justify-content:space-between; font-family:var(--font-mono); font-size:9px; color:rgba(255,255,255,.6); letter-spacing:.06em; }
@media (prefers-reduced-motion: reduce){
  .foil-card{ transition:none; }
}
@media (max-width:940px){
  .hero-grid{ grid-template-columns:1fr; }
  .hero{ padding:56px 0 70px; }
  .hero-meta{ gap:22px; }
}
/* Foil card stays for tablet/desktop; on phones it's dropped entirely (not
   just visually hidden) so its layout weight and animation listeners don't
   cost anything on the smallest, most bandwidth-sensitive viewports. */
@media (max-width:767px){
  .foil-stage{ display:none; }
}

/* ==========================================================================
   Hero card scene — 3-card layered TCG composition, GSAP-driven.
   Built from HERO_CARDS in js/main.js §5b. Static layout (position, size,
   base rotation) lives here as inline custom props set by JS; GSAP only
   ever animates transform/opacity on top of that resting layout, via
   quickTo() for pointer parallax+tilt and gsap.to() for hover/intro/idle.
   ========================================================================== */
.hero-card-stage{
  position:relative;
  /* The stage is the tallest thing in the hero, so its height IS the hero's
     height — everything else fits inside the padding. Sizing it off vw alone
     read the wrong axis: a wide-but-short window (i.e. every laptop) asked
     for 520px out of a ~440px budget and pushed the panel past one screen.
     min() puts the height back in the constraint; 62svh keeps the full 520px
     on any window taller than ~840px, so nothing changes on a large display.
     The cards' top/left are % of this box, so the fan compresses with it. */
  height:clamp(240px, min(44vw, 62svh), 520px);
  perspective:1700px;
  perspective-origin:50% 42%;
}
/* One soft backlight behind the whole cluster (as in the owner's mockup) —
   huge radius, long falloff, so it reads as ambient glow with no visible
   edge. It sits behind the stack; it is NOT the shine effect itself. */
/* Disabled: the radial gradient never fully reached transparent before
   hitting its own box edge, so it always rendered as a visible rectangle
   behind the cards no matter how soft the falloff — the exact "box" the
   owner flagged. The element/JS wiring (--lx/--ly) is left in place should
   a genuinely edgeless glow be wanted later; see the site memory. */
.card-light{
  position:absolute; inset:-30%;
  background:none;
  pointer-events:none;
  z-index:0;
}
.card-scene{
  position:absolute; inset:0;
  transform-style:preserve-3d;
}
.hero-card{
  position:absolute;
  top:var(--card-top,50%); left:var(--card-left,50%);
  width:var(--card-w,220px);
  transform-style:preserve-3d;
  will-change:transform;
}
/* Middle wrapper: carries the perpetual float so it can never conflict with
   the pointer parallax (outer .hero-card) or the hover scale (inner shell). */
.hero-card .card-float{
  transform-style:preserve-3d;
  will-change:transform;
}
.hero-card .card-shell{
  position:relative;
  width:100%; aspect-ratio:63/88;
  border-radius:5% / 3.6%;
  overflow:hidden;
  transform-style:preserve-3d;
  box-shadow:
    0 2px 0 rgba(255,255,255,.5) inset,
    0 26px 50px -18px rgba(0,0,0,.65),
    0 10px 22px -10px rgba(0,0,0,.55);
  background:#0a0b0d;
}
.hero-card img{
  width:100%; height:100%; object-fit:cover; display:block;
  -webkit-user-drag:none; user-select:none;
}
/* The shine, per card: a light band that sweeps across each card's face on
   its own slow loop (staggered per card via --sheen-delay), exactly like
   light travelling over real foil. Reuses the proven swatchSheen keyframes. */
.hero-card .card-foil{
  position:absolute; inset:0;
  background:linear-gradient(115deg, transparent 32%, rgba(255,255,255,.42) 46%, transparent 60%);
  background-size:250% 250%;
  mix-blend-mode:overlay;
  opacity:.75;
  pointer-events:none;
  animation:swatchSheen 8s ease-in-out infinite;
  animation-delay:var(--sheen-delay,0s);
}
/* Cursor-driven glare on top of the sweep — fades in only while the pointer
   is inside the stage, and follows it, so interacting adds light instead of
   switching to a different effect. */
.hero-card .card-glare{
  position:absolute; inset:0;
  background:radial-gradient(65% 65% at var(--fx,50%) var(--fy,50%), rgba(255,255,255,.32), transparent 72%);
  mix-blend-mode:overlay;
  opacity:0;
  transition:opacity .4s var(--ease);
  pointer-events:none;
}
.hero-card-stage:hover .card-glare{ opacity:.85; }
.hero-card .card-edge{
  position:absolute; inset:0; border-radius:inherit;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.16);
  pointer-events:none;
}
@media (prefers-reduced-motion: reduce){
  .hero-card, .hero-card *{ transition:none !important; }
  .hero-card .card-foil{ animation:none; }
}

/* ==========================================================================
   Chain — het bewijspaneel onder de hero
   --------------------------------------------------------------------------
   Eén paneel in de deck: full width, full height, snapt mee. Het draagt geen
   prijzen, alleen de opbouw ervan — het probleem hier is niet dat wij duur
   lijken, het is dat niemand kan nagaan waarom wij goedkoper zijn.

   Eén vergelijking van twee kanten. Een eerdere versie zette de ketens, de
   prijsmethode, een belofte-balk en een trust-strip los onder elkaar; dat las
   als een meterkast. Wat er staat is nu: gecentreerde kop, twee kaarten, en
   daaronder één regel plus één knop.

   De kaarten lijnen bovenaan uit en zijn even breed, dus vijf stappen tegen
   drie levert vanzelf een zichtbaar kortere rechterkaart op. Dat verschil is
   het argument; het overleeft het stapelen op een telefoon omdat het in de
   lengte van de lijst zit en niet in de opmaak.

   Er staan bewust geen pijlen of rekentekens tussen de regels: de kaarten
   dragen het verschil al, en elk teken erbij maakt er weer een schema van.
   ========================================================================== */
.chain{
  --edge:#F1F5F0;   /* het einde van het verloop hieronder */
  /* Warm papier aan de bovenkant, een spoor van het stempelgroen aan de
     onderkant — genoeg om de band los te maken van de vlakke paper-kleur
     zonder dat het een tweede merkkleur wordt. */
  background:linear-gradient(180deg,#FAF9F5 0%,#F1F5F0 100%);
  color:var(--paper-text);
  min-height:100svh;
  display:flex;
  /* De header is op de homepage `position:fixed` en neemt dus geen ruimte in
     de stroom in: hij ligt over de bovenste 77px van wat er op dat moment
     bovenaan het paneel staat. Zolang de inhoud kleiner is dan het scherm
     valt dat niet op — `margin:auto` op .chain-inner centreert hem dan en de
     kop komt vanzelf onder de balk uit. Vult de inhoud het paneel, dan is die
     vrije ruimte nul, begint de kop op de padding-rand, en verdwijnt hij
     achter de header: 27px op een 375x812-telefoon, 39px op een iPhone 15 Pro
     Max in Safari. Daarom wordt de hoogte van de balk hier gereserveerd,
     precies zoals .feature dat al deed. */
  /* De afstand van een blokkop tot wat eronder staat, één keer vastgelegd.
     De keten draagt twee van die koppen — "Hoe wij onze prijzen bepalen"
     boven de methodebalk en "Prijsreferenties" boven de twee prijskaarten —
     en die stonden op 24px en 7px. Als losse waarden lopen ze bij de eerste
     de beste aanpassing weer uit elkaar; als token niet. */
  --label-gap:clamp(4px,.8svh,8px);
  padding:calc(77px + clamp(4px,1svh,20px)) 0 clamp(8px,1.2svh,34px);
  overflow:hidden;
  position:relative;
}
/* Gouden haarlijn op de naad met de hero erboven — hetzelfde folie-accent dat
   de site elders voor randen gebruikt. */
.chain::before{
  content:""; position:absolute; inset:0 0 auto; height:1px;
  background:linear-gradient(90deg,transparent,rgba(201,162,39,.45) 30%,rgba(201,162,39,.45) 70%,transparent);
}
/* Hetzelfde watermerk als in de hero, hier in inkt op papier in plaats van in
   wit op inkt. Songti direct benoemd en niet via de displayletter: die draagt
   geen CJK, en het intikken van de fallback kostte de hero ooit zijn hele CLS. */
.chain::after{
  content:"闪 亮 贸 易";
  position:absolute; left:-30px; bottom:-70px;
  font-family:"Songti SC","STSong","Noto Serif SC","Source Han Serif SC",serif;
  font-size:min(26vw,360px);
  line-height:1;
  color:rgba(23,24,27,.028);
  letter-spacing:.05em;
  pointer-events:none;
  white-space:nowrap;
}

.chain-inner{
  position:relative; z-index:1;
  display:flex; flex-direction:column;
  gap:clamp(10px,2.2svh,34px);
  width:100%;
  /* `margin:auto` in plaats van `align-items:center` op de sectie. Beide
     centreren, maar een auto-marge valt terug op nul zodra de inhoud hoger is
     dan het scherm — centreren via align-items duwt de bovenkant dan buiten
     beeld, en daar kun je niet meer naartoe scrollen. */
  margin:auto;
}
/* Zonder dit haalt een brede kaart de sectie uit elkaar: een flexitem krimpt
   van zichzelf niet onder zijn min-content-breedte. */
.chain-inner > *{ min-width:0; }

/* ---------- kop ----------
   `width:100%` hoort erbij: een auto-marge in de kruisrichting zet `stretch`
   van een flexitem uit, waarna het naar zijn inhoud krimpt in plaats van naar
   de max-width die hier staat. */
.chain-head{
  width:100%;
  /* Ruim genoeg voor de kop op één regel; de lead houdt zijn eigen breedte
     hieronder, anders zou die met de kop mee uitlopen. */
  max-width:min(1180px,94vw);
  margin:0 auto;
  text-align:center;
}
/* Op desktop is dit één zin op één regel, en dat blijft zo: de maat schaalt
   met het venster in plaats van dat de zin afbreekt. 3.3vw x ~22em zin is
   ruim binnen de 94vw die .chain-head hier geeft, tot aan het plafond waar
   de maat vast blijft staan. Op de telefoon (blok onderaan) mag hij wel
   gewoon afbreken — daar is één regel onleesbaar klein. */
.chain-head h2{
  font-size:clamp(24px,min(3.3vw,5.4svh),42px);
  white-space:nowrap;
  margin:0 0 10px;
}
/* Het goud van het huis, inclusief de veeg: .shine zet de kleur zelf, dus
   hier alleen de cursief. */
.chain-head h2 i{ font-style:italic; }
.chain-lead{
  max-width:660px;
  margin:0 auto;
  font-size:clamp(14px,1.05vw,15.5px);
  line-height:1.5;
  color:var(--paper-dim);
}

/* ---------- de twee kaarten ---------- */
.chain-compare{
  position:relative;
  width:100%;
  max-width:980px;
  margin:auto;
}
.chain-cards{
  position:relative;                 /* anker voor het VS-penningetje */
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  /* Even lang, altijd. Toen de rechterkaart nog korter was dróeg dat het
     argument; sinds de overgeslagen schakels er als schaduwregels in staan
     doen die dat werk, en moeten de twee kaarten juist hetzelfde kader
     hebben om regel voor regel vergelijkbaar te zijn. `stretch` regelt dat
     op elke breedte en in elke taal, zonder hoogtes uit te rekenen. */
  align-items:stretch;
  gap:clamp(16px,2vw,26px);
  width:100%;
}
/* Het penningetje ligt in de naad tussen de twee kaarten, op halve hoogte van
   de langste. Inkt met goud, zoals de rest van de site zijn stempels zet. */
.chain-vs{
  position:absolute; left:50%; top:50%;
  transform:translate(-50%,-50%);
  align-self:center;
  display:grid; place-items:center;
  width:46px; height:46px;
  border-radius:50%;
  background:var(--ink);
  color:var(--gold-bright);
  font-family:var(--font-mono);
  font-size:12px; letter-spacing:.1em;
  box-shadow:0 8px 22px -10px rgba(13,15,18,.75);
  z-index:2;
  pointer-events:none;
}
.chain-card{
  position:relative;                 /* anker voor de veeg hieronder */
  min-width:0;
  padding:clamp(14px,1.7vh,20px) clamp(16px,1.9vw,24px);
  border-radius:20px;
  background:#fff;
  box-shadow:0 1px 2px rgba(23,24,27,.05), 0 14px 30px -24px rgba(23,24,27,.4);
}
/* De veeg zelf: één laag over de kaart die niets doet tot hij aangezet wordt.
   Zelfde goud en dezelfde vorm als .shine op de koppen, alleen over een vlak
   in plaats van over letters. */
.chain-card::after{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  pointer-events:none;
  opacity:0;
  background:linear-gradient(105deg,
    transparent 34%,
    rgba(201,162,39,.30) 46%,
    rgba(255,255,255,.55) 50%,
    rgba(201,162,39,.30) 54%,
    transparent 66%);
  background-size:260% 100%;
  background-position:100% 0;
}
@keyframes chainSweep{
  from{ background-position:100% 0; opacity:1; }
  to  { background-position:0 0;    opacity:0; }
}
/* De twee kaarten liggen bewust niet op dezelfde hoogte in de stapel. Wat je
   nu betaalt is verzonken: schaduw naar binnen, geen lift, een tint donkerder
   dan de band. Wat je bij ons betaalt ligt er juist bovenop. Een zwaardere
   schaduw naar buiten zou het linkerkaartje juist belangrijker maken — een
   slagschaduw tilt op — dus de richting is omgedraaid: naar binnen drukt weg,
   naar buiten trekt aan. */
.chain-card--long{
  background:#F1EFE8;
  box-shadow:inset 0 4px 12px rgba(23,24,27,.15),
             inset 0 1px 2px rgba(23,24,27,.10),
             inset 0 0 0 1px rgba(23,24,27,.07);
}
/* De regels blijven wit, anders zakt de inhoud mee weg en wordt hij slecht
   leesbaar; het is de kaart die naar achteren moet, niet de tekst. */
.chain-card--long .chain-steps > li{ background:#fff; }
/* De ring staat `inset`, dus binnen de kaart. Buiten de kaart wordt hij op
   mobiel door de carrousel afgesneden — die heeft overflow-x:auto en klipt
   alles wat over de rand steekt. Anderhalve pixel naar binnen is visueel
   hetzelfde en kan niet meer wegvallen. */
.chain-card--short{
  background:#fff;
  box-shadow:inset 0 0 0 1.5px rgba(46,122,78,.5),
             0 2px 4px rgba(23,24,27,.05),
             0 16px 34px -24px rgba(23,24,27,.45);
}
.chain-card h3{
  font-family:var(--font-body);
  font-weight:700;
  font-size:clamp(16px,1.4vw,19px);
  margin:0 0 3px;
}
.chain-card-sub{
  margin:0 0 clamp(10px,1.3vh,15px);
  font-size:12.5px;
  color:var(--paper-dim);
}

.chain-steps{ margin:0; padding:0; list-style:none; }
.chain-steps > li{
  position:relative;
  display:flex; align-items:center; flex-wrap:wrap;
  gap:4px 9px;
  /* Hoogte-bewust: op een laag venster is dit paneel met vijf stappen, een
     methodebalk en de uitdaging het krapste van de deck. De pil mag dan
     smaller, de tekst blijft gelijk. */
  min-height:clamp(32px,5svh,40px);
  padding:clamp(5px,.9svh,7px) 14px;
  border-radius:13px;
  background:var(--paper);
}
.chain-steps > li + li{ margin-top:clamp(4px,.9svh,6px); }
.chain-steps > li b{
  font-size:13.5px; font-weight:700; line-height:1.25;
}
/* Wat de schakel erbovenop legt, als label en niet als zin. */
.chain-tag{
  font-size:12px; line-height:1.3;
  padding:3px 10px;
  border-radius:999px;
  background:#EBE8DE;
  color:var(--paper-dim);
}
/* Alles na de eerste schakel is opslag. Dezelfde stempelrode inkt die de site
   voor een afwijzing gebruikt — vier keer op een rij is het hele punt. */
.chain-card--long .chain-tag{
  background:rgba(180,59,47,.09);
  color:var(--red-stamp);
}
.chain-card--short .chain-steps > li{ background:rgba(46,122,78,.07); }
.chain-card--short .chain-tag{
  background:rgba(46,122,78,.14);
  color:#1d5c3c;
}
/* De overgeslagen schakels. Ze staan er wél, op dezelfde plek in de keten als
   links, maar dan als omtrek in plaats van als vlak: gestippelde rand, geen
   vulling, naam doorgehaald. Zo is het niet "onze lijst is korter" maar
   "dit zijn de drie die wij eruit halen", regel voor regel naast de linker
   kaart te leggen. Hoog genoeg specifiek geschreven om de gevulde
   li-regeling hierboven te overrulen. */
.chain-card--short .chain-steps > li.chain-ghost{
  position:relative;
  /* Zelfde hoogte als een gevulde regel, anders lopen de twee kaarten na een
     paar regels uit de pas en is de vergelijking regel-op-regel weg. */
  padding:5px 13px 5px 31px;
  border:1px dashed rgba(46,122,78,.4);
  background:transparent;
  box-shadow:none;
}
.chain-ghost::before{
  content:"\2715";
  position:absolute; left:12px; top:50%;
  transform:translateY(-50%);
  font-size:10px; line-height:1;
  color:var(--green-stamp);
}
.chain-ghost s{
  font-size:12.5px; font-weight:600;
  color:var(--paper-dim);
  text-decoration-thickness:1.5px;
  text-decoration-color:rgba(23,24,27,.4);
}

.chain-card--short .chain-steps > li:last-child{
  background:rgba(46,122,78,.14);
  box-shadow:inset 0 0 0 1.5px var(--green-stamp);
}

/* ---------- het uitdelen: regel voor regel binnen ----------
   Niet alles ineens weggeven. Zodra de vergelijking in beeld komt, komen de
   regels per paar binnen: eerst links een schakel, dan meteen zijn tegenhanger
   rechts. Dat leert de bezoeker in beweging wat de uitlijning bedoelt — je
   kijkt vanzelf van links naar rechts, tien keer achter elkaar. Het VS-teken
   valt als laatste, als uitspraak.

   De verborgen begintoestand komt van JS (.deal-pre), nooit uit de stylesheet
   zelf: zonder JS of zonder IntersectionObserver staat gewoon alles er, in
   plaats van een lege kaart. */
.chain-cards.deal-pre .chain-steps > li{
  opacity:0;
  transform:translateY(8px);
}
.chain-cards.deal-in .chain-steps > li{
  opacity:1;
  transform:none;
  transition:opacity .5s var(--ease), transform .5s var(--ease);
  transition-delay:var(--d,0ms);
}
.chain-cards.deal-pre .chain-vs{ opacity:0; transform:translate(-50%,-50%) scale(.55); }
.chain-cards.deal-in .chain-vs{
  opacity:1;
  transform:translate(-50%,-50%) scale(1);
  transition:opacity .35s var(--ease), transform .5s cubic-bezier(.34,1.56,.64,1);
  transition-delay:350ms;             /* tussen de twee kaarten in */
}
/* Kaart voor kaart: eerst de linker van boven naar beneden, dan het VS-teken,
   dan de rechter op dezelfde manier. Je leest de dure route dus helemaal uit
   voordat de onze begint, en de leegte die daarna wordt ingevuld is het
   antwoord erop. */
.chain-card--long  .chain-steps > li:nth-child(1){ --d:0ms;   }
.chain-card--long  .chain-steps > li:nth-child(2){ --d:70ms;  }
.chain-card--long  .chain-steps > li:nth-child(3){ --d:140ms; }
.chain-card--long  .chain-steps > li:nth-child(4){ --d:210ms; }
.chain-card--long  .chain-steps > li:nth-child(5){ --d:280ms; }
.chain-card--short .chain-steps > li:nth-child(1){ --d:410ms; }
.chain-card--short .chain-steps > li:nth-child(2){ --d:480ms; }
.chain-card--short .chain-steps > li:nth-child(3){ --d:550ms; }
.chain-card--short .chain-steps > li:nth-child(4){ --d:620ms; }
.chain-card--short .chain-steps > li:nth-child(5){ --d:690ms; }

/* ---------- hover: de keuze voelbaar maken ----------
   Alleen op een echte muis. De twee kaarten reageren tegengesteld: de
   traditionele route zakt verder weg en dooft, de onze komt naar voren. Wie
   met de muis over de dure keten gaat, voelt dat het de verkeerde kant is
   voordat hij het gelezen heeft. */
@media (hover:hover) and (pointer:fine){
  .chain-card{
    transition:transform .35s var(--ease),
               box-shadow .35s var(--ease),
               background-color .35s var(--ease);
  }
  .chain-steps > li{ transition:opacity .35s var(--ease); }

  .chain-card--long:hover{
    transform:scale(.985);
    background:#E9E5DC;
    box-shadow:inset 0 6px 18px rgba(23,24,27,.22),
               inset 0 1px 3px rgba(23,24,27,.14),
               inset 0 0 0 1px rgba(23,24,27,.09);
  }
  /* De inhoud dooft mee, maar niet zo ver dat hij onleesbaar wordt. */
  .chain-card--long:hover .chain-steps > li{ opacity:.68; }

  .chain-card--short:hover{
    transform:translateY(-6px) scale(1.012);
    box-shadow:inset 0 0 0 1.5px var(--green-stamp),
               0 2px 6px rgba(23,24,27,.07),
               0 44px 72px -30px rgba(23,24,27,.55);
  }
}
/* Wie om minder beweging vraagt krijgt het kleurverschil wel en de beweging
   niet: de boodschap zit in het contrast, niet in het schuiven. */
@media (prefers-reduced-motion: reduce){
  .chain-card{ transition:background-color .2s linear, box-shadow .2s linear; }
  .chain-card--short.is-live::after{ animation:none; opacity:0; }
  .chain-cards.deal-pre .chain-steps > li,
  .chain-cards.deal-pre .chain-vs{ opacity:1; transform:none; }
  .chain-cards.deal-pre .chain-vs{ transform:translate(-50%,-50%); }
  .chain-card--long:hover,
  .chain-card--short:hover{ transform:none; }
}

/* ---------- de prijsmethode ----------
   Eén balk onder de vergelijking: hoe die ene opslag tot stand komt. Zelfde
   kaartlogica als de twee kaarten erboven — wit, ronde hoeken, zachte schaduw
   — met het icoon in een groen tegeltje en het nummer in de titelletter. */
.method{
  width:100%;
  max-width:980px;
  margin:auto;
  text-align:center;
}
.method h3{
  font-family:var(--font-body);
  font-weight:700;
  font-size:clamp(15px,min(1.4vw,2.4svh),19px);
  /* Zelfde afstand tot de balk als "Prijsreferenties" tot zijn kaarten; zie
     --label-gap op .chain. Stond op 24px tegen de 7px daar. */
  margin:0 0 var(--label-gap);
}
.method-steps{
  display:grid;
  grid-template-columns:repeat(5,minmax(0,1fr));
  gap:clamp(14px,1.8vw,26px);
  margin:0;
  padding:clamp(7px,1.6svh,15px) clamp(14px,1.6vw,22px);
  list-style:none;
  text-align:left;
  border-radius:18px;
  background:#fff;
  box-shadow:0 1px 2px rgba(23,24,27,.05), 0 14px 32px -24px rgba(23,24,27,.4);
}
.method-step{
  position:relative; min-width:0;
  display:flex; align-items:center;
  gap:clamp(9px,.9vw,13px);
  padding-right:15px;                /* ruimte voor het cijfer rechtsboven */
}
.method-copy{ min-width:0; }
/* De chevron staat in de kolomtussenruimte en heeft dus geen eigen breedte. */
.method-step:not(:last-child)::after{
  content:"";
  position:absolute; top:20px; right:clamp(-13px,-1vw,-9px);
  width:7px; height:7px;
  border-top:2px solid #CFCABB;
  border-right:2px solid #CFCABB;
  border-radius:2px;
  transform:translate(50%,0) rotate(45deg);
}
/* Goud, niet groen: het groen in dit paneel betekent iets — dat is de kant
   van "wij" tegenover de traditionele keten. Deze balk gaat niet over die
   tegenstelling maar over de methode, en hoort dus in de merkkleur. */
.method-tile{
  display:grid; place-items:center;
  width:clamp(34px,6svh,42px); height:clamp(34px,6svh,42px); flex:0 0 auto;
  border-radius:13px;
  background:rgba(201,162,39,.16);
  color:var(--gold);
}
.method-tile svg{
  width:24px; height:24px;
  fill:none; stroke:currentColor; stroke-width:2.1;
  stroke-linecap:round; stroke-linejoin:round;
}
/* Rechtsboven in de hoek in plaats van boven de titel: de tekst staat dan op
   dezelfde hoogte als het pictogram in plaats van eronder weg te zakken. */
.method-num{
  /* Strak in de hoek en klein: zo leest het als een merkteken bij de stap en
     niet als tekst die met de titel om ruimte vecht. Naar binnen schuiven
     maakte het juist erger — dan kruipt het cijfer tegen de kop aan. */
  position:absolute; top:-1px; right:0;
  font-family:var(--font-display); font-style:normal;
  font-size:16px; line-height:1;
  color:var(--gold);
}
.method-step b{
  display:block;
  font-size:13.5px; font-weight:700; line-height:1.25;
  margin:0 0 3px;
}
.method-desc{
  display:block;
  font-size:12px; line-height:1.4;
  color:var(--paper-dim);
}

/* ---------- de uitdaging ----------
   De enige donkere plek in dit paneel, en dat is de bedoeling: hier moet de
   bezoeker stoppen. De knop zit in dezelfde band, zodat de uitdaging en het
   antwoord erop één ding zijn in plaats van twee blokken onder elkaar. */
.chain-dare{
  display:flex; align-items:center;
  gap:clamp(16px,2.4vw,32px);
  text-align:left;
  padding:clamp(10px,1.2vh,14px) clamp(14px,1.7vw,20px);
  border-radius:16px;
  /* --shelf in plaats van --ink: dezelfde warme houtskool die de showcase
     gebruikt, iets naar het goud toe getrokken. Zacht zwart in plaats van
     hard zwart, zodat de band naast het papier niet als een gat leest. */
  background:var(--shelf);
  color:var(--ink-text);
  box-shadow:0 20px 46px -28px rgba(30,25,19,.75);
}
.chain-dare-copy{ flex:1 1 auto; min-width:0; }
.chain-dare-line{
  margin:0 0 5px;
  font-family:var(--font-display);
  font-size:clamp(16px,1.4vw,19px);
  line-height:1.2;
}
/* Cursief, en sinds 12 okt 2026 weer met de gouden glans (.shine in de
   markup): op verzoek van de eigenaar, die hem miste. De kleur komt dan uit
   .shine; zonder die klasse blijft het gewoon cursief in de tekstkleur. */
.chain-dare-line i{ font-style:italic; }
.chain-dare-line i:not(.shine){ color:inherit; }
.chain-dare-sub{
  margin:0;
  font-size:12px; line-height:1.45;
  color:var(--ink-dim);
  max-width:66ch;
}
.chain-dare .btn{ flex:0 0 auto; }

/* ---------- afsluiting ---------- */
.chain-foot{
  width:100%;
  max-width:980px;
  margin:0 auto;
  text-align:center;
}
.chain-facts{
  margin:12px 0 0;
  font-size:12px;
  color:var(--paper-dim);
}

/* ---------- telefoon ----------
   Dezelfde sectie en dezelfde volle hoogte als op desktop. De twee kaarten
   staan naast elkaar in een carrousel in plaats van onder elkaar, en de rest
   is krapper gezet, zodat het geheel ook hier binnen één scherm blijft. */
@media (max-width:760px){
  /* De vaste header ligt over de bovenste 77px van dit paneel — hij staat
     `position:fixed` en neemt geen ruimte in de stroom in. `.chain-inner`
     heeft `margin:auto`, dus de vrije ruimte in het paneel wordt gelijk over
     boven en onder verdeeld; van de bovenste helft verdwijnt vervolgens 77px
     achter de balk. Gemeten op 390x750: 72px boven en 72px onder, en van die
     72 was er dus geen enkele zichtbaar — de kop begon 5px áchter de onderrand
     van de header, terwijl er onderaan 72px leeg bleef. Dat is precies het
     beeld dat gemeld werd: kop tegen de bovenrand, ruimte over onderaan.

     De regel voor panelen is die 77px bovenaan te reserveren (zie .assure en
     .hero). Hier kan dat niet onvoorwaardelijk: op een korte telefoon vult de
     keten het scherm al, en een vaste reservering zou het paneel simpelweg
     77px langer maken. Daarom loopt de reservering mee met de ruimte die er
     ís: 100dvh min ~660px is bij benadering wat dit blok overhoudt (de inhoud
     meet 607-654px, nauwelijks afhankelijk van de hoogte), afgetopt op de
     hoogte van de balk. Op 640px hoog reserveert hij niets en verandert er
     niets; vanaf ~737px staat de volle 77px er, en de kop komt met evenveel
     lucht onder de header te staan als er onder het blok overblijft. */
  .chain{
    padding:calc(clamp(0px, 100dvh - 660px, 77px) + clamp(10px,1.5vh,20px)) 0 clamp(10px,1.5vh,20px);
  }
  .chain-inner{ gap:16px; }
  /* Hier juist wél afbreken: op deze breedte moet de zin zich naar het
     scherm voegen, en balance houdt de regels even lang. */
  .chain-head h2{
    font-size:clamp(23px,6.2vw,28px);
    white-space:normal;
    text-wrap:balance;
    margin-bottom:8px;
  }
  .chain-lead{ font-size:13.5px; }
  /* Naast elkaar blijven, maar dan met de duim. Dezelfde carrousel als
     .na-grid elders op de site: de kaart eronder wordt niet gestapeld maar
     staat een swipe naar rechts, en de strook van de tweede kaart die in
     beeld blijft is wat dat vertelt. `start`, niet `stretch`: als de kaarten
     even hoog worden gemaakt verdwijnt het lengteverschil, en dat verschil
     is het hele argument. */
  /* Precies zoals op desktop: het penningetje ligt over de naad heen en duwt
     de kaarten niet uit elkaar. Dat het hier in een scrollcontainer zit maakt
     niet uit — een absoluut geplaatst kind hoort bij het scrollbare gebied en
     schuift dus gewoon mee. Alleen het ankerpunt verschuift: niet het midden
     van twee gelijke kolommen, maar de naad na de eerste kaart, dus 87% plus
     de helft van de tussenruimte. */
  .chain-vs{
    left:calc(87% + 6px);
  }
  /* Naast elkaar op desktop is er niets te belichten; hier komt er één kaart
     per keer in beeld, en dan mag je merken welke. De klasse wordt gezet door
     initChainSpotlight() in js/main.js en gaat er ook weer af, zodat de veeg
     bij elke terugkeer opnieuw speelt. */
  .chain-card{ transition:opacity .35s var(--ease); }
  .chain-card--long:not(.is-live){ opacity:.6; }
  .chain-card--short.is-live::after{ animation:chainSweep 1s var(--ease) 1; }
  .chain-cards{
    display:grid;
    grid-auto-flow:column;
    grid-template-columns:none;
    grid-auto-columns:87%;
    align-items:stretch;
    gap:12px;
    overflow-x:auto;
    overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory;
    scrollbar-width:none;
    /* Lucht voor de slagschaduwen, met dezelfde marge er weer af zodat de
       kaarten optisch niet verschuiven. Alleen verticaal: horizontale padding
       zou de 87%-kolommen en het ankerpunt van het VS-teken verschuiven. */
    padding:6px 0 9px;
    margin:-6px 0 -9px;
    min-width:0;
  }
  .chain-cards::-webkit-scrollbar{ display:none; }
  .chain-cards:focus-visible{ outline:2px solid var(--gold-bright); outline-offset:4px; border-radius:20px; }
  .chain-card{ scroll-snap-align:start; }
  .chain-card{ padding:12px 12px; border-radius:16px; }
  .chain-card h3{ font-size:16px; }
  .chain-card-sub{ margin-bottom:12px; font-size:12px; }
  /* Krapper dan op desktop: zo staan de twee kaarten dicht genoeg op elkaar
     dat het nog als één vergelijking leest in plaats van als twee lijsten. */
  .chain-steps > li{ min-height:36px; padding:5px 11px; gap:3px 7px; }
  .chain-steps > li + li{ margin-top:5px; }
  .chain-steps > li b{ font-size:12px; }
  .chain-tag{ font-size:11px; padding:2px 8px; }
  .chain-ghost s{ font-size:11.5px; }
  .chain-card--short .chain-steps > li.chain-ghost{ padding:4px 11px 4px 28px; }
  .chain-ghost::before{ left:10px; }
  /* Zelfde carrousel als de kaarten erboven: vijf stappen onder elkaar zouden
     het paneel verdubbelen. */
  .method-steps{
    display:grid;
    grid-auto-flow:column;
    grid-template-columns:none;
    grid-auto-columns:74%;
    gap:10px;
    padding:0;
    background:none;
    box-shadow:none;
    overflow-x:auto;
    overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory;
    scrollbar-width:none;
    min-width:0;
  }
  .method-steps::-webkit-scrollbar{ display:none; }
  .method-step{
    scroll-snap-align:start;
    border-radius:16px;
    background:#fff;
    box-shadow:0 1px 2px rgba(23,24,27,.05), 0 10px 26px -20px rgba(23,24,27,.4);
    padding:12px 14px;
  }
  .method-step:not(:last-child)::after{ display:none; }
  .method-steps:focus-visible{ outline:2px solid var(--gold-bright); outline-offset:4px; border-radius:16px; }
  .chain-dare{ display:block; padding:11px 12px; }
  .chain-dare-line{ font-size:16.5px; margin-bottom:4px; }
  .chain-dare-sub{ font-size:11.5px; line-height:1.42; }
  .chain-foot .btn{ width:100%; justify-content:center; margin-top:10px; }
  .chain-facts{ margin-top:8px; font-size:11.5px; }
}

/* De prijsmethode is een desktopelement. Vijf stappen naast elkaar vragen de
   breedte die een telefoon niet heeft, en onder elkaar gezet kost het blok
   ruim 120px van een paneel dat toch al krap is — terwijl hetzelfde verhaal
   op /about en in de FAQ staat. Blijft in de DOM voor wie hem breed genoeg
   opent; alleen verborgen onder de telefoongrens. */
@media (max-width:760px){
  .method{ display:none; }
}
/* En op elk laag venster. Sinds de prijsvoorbeelden deze band afsluiten draagt
   de keten vijf blokken — kop, twee vergelijkingskaarten, de prijsmethode, de
   twee voorbeelden en de knop — en dat is er één te veel voor één scherm. Bij
   860px vensterhoogte en lager past de methodebalk er niet meer bij zonder dat
   het paneel over de schermrand loopt, en dat is precies wat een paneel in
   deze deck niet mag.

   De methodebalk is daarbij de logische kandidaat: hij legt uit hóé wij aan
   een prijs komen, en de twee voorbeelden eronder laten die prijs nu gewoon
   zien. Op een groot scherm staan ze samen, daaronder wint het bewijs van de
   uitleg. Zie ook de telefoonregel hierboven. */
@media (max-height:860px){
  .method{ display:none; }
}

/* ==========================================================================
   Zekerheidsband — vijf redenen en twee echte prijzen
   --------------------------------------------------------------------------
   Zit tussen de keten (papier) en het fold-scherm (warm donker) en is zelf
   inkt, zodat de stapel blijft afwisselen in plaats van twee lichte panelen
   achter elkaar te zetten. De kop draagt wel de opmaak van de keten: zelfde
   displayletter, zelfde maat, gecentreerd — de twee panelen horen bij elkaar
   en moeten er ook zo uitzien.

   Eén kolom, van boven naar beneden: kop, vijf regels, twee voorbeelden. Geen
   tweekolomsopzet: de vijf regels worden op volgorde gelezen en alles wat er
   naast staat vraagt de aandacht weg op precies de verkeerde plek.

   Alle verticale maten staan in clamp(px, svh, px), niet in vaste px of in
   kale vw. Dit is een paneel in de deck en moet dus binnen één scherm passen;
   een vw-maat leest de verkeerde as en laat het paneel op een laag venster
   overlopen. Zie de paneelbewaker in js/main.js §8.
   ========================================================================== */
.assure{
  --edge:#0D0F12;
  position:relative;
  min-height:100svh;
  display:flex;
  background:var(--ink);
  color:var(--ink-text);
  border-top:1px solid var(--ink-line);
  padding:clamp(84px,11svh,120px) 0 clamp(40px,7svh,80px);
  overflow:hidden;
}
/* Gouden haarlijn op de naad met de keten erboven — hetzelfde folie-accent
   dat de site elders voor randen gebruikt. */
.assure::before{
  content:""; position:absolute; inset:0 0 auto; height:1px;
  background:linear-gradient(90deg,transparent,rgba(201,162,39,.45) 30%,rgba(201,162,39,.45) 70%,transparent);
}
/* Hetzelfde watermerk als in de hero en de keten. */
.assure::after{
  content:"闪 亮 贸 易";
  position:absolute; right:-40px; top:-60px;
  font-family:"Songti SC","STSong","Noto Serif SC","Source Han Serif SC",serif;
  font-size:min(22vw,300px);
  line-height:1;
  color:rgba(236,234,227,.022);
  letter-spacing:.05em;
  pointer-events:none;
  white-space:nowrap;
}
.assure-inner{
  position:relative; z-index:1;
  display:flex; flex-direction:column;
  align-items:center;
  gap:clamp(18px,3.2svh,40px);
  width:100%;
  margin:auto;
}
.assure-inner > *{ min-width:0; }

/* ---------- de kop ---------- */
/* `position:relative` is hier geen opmaak maar het anker van de rollende
   Poké Ball (js/main.js §7e): die staat `position:absolute` en meet zijn
   plek af tegen déze doos. Zonder anker viel hij terug op .assure-inner,
   en dat is de padding-doos van .container — de bal landde precies één
   containerpadding te ver naar links, boven op de laatste letter. */
.assure-head{ width:100%; text-align:center; position:relative; }
.assure-head h2{
  font-family:var(--font-display);
  font-size:clamp(26px,min(3.3vw,5.6svh),42px);
  line-height:1.12;
  letter-spacing:-.01em;
  margin:0;
  text-wrap:balance;
}

/* ---------- de vijf regels ---------- */
.assure-list{
  list-style:none;
  margin:0; padding:0;
  width:100%;
  max-width:900px;
}
.assure-list > li{
  display:grid;
  grid-template-columns:auto minmax(0,15.5em) minmax(0,1fr);
  align-items:baseline;
  gap:4px 14px;
  padding:clamp(8px,1.35svh,14px) 0;
  border-top:1px solid var(--ink-line);
}
.assure-list > li:last-child{ border-bottom:1px solid var(--ink-line); }
/* Pictogram in plaats van een nummer. Geen rondje eromheen: in een rij van
   deze hoogte wordt een badge het zwaarste element op de regel, en dan kijk
   je naar vijf badges in plaats van naar vijf redenen. Lijntekening in goud,
   op de basislijn van de kop uitgelijnd via translateY — een svg lijnt op
   zijn onderrand uit en zou anders te laag hangen. */
.assure-ico{
  display:block;
  width:19px; height:19px;
  transform:translateY(3px);
}
.assure-ico svg{
  width:100%; height:100%;
  fill:none; stroke:var(--gold);
  stroke-width:1.4; stroke-linecap:round; stroke-linejoin:round;
}
.assure-list > li b{
  font-size:clamp(13.5px,min(1.05vw,2svh),15.5px);
  font-weight:650;
  color:var(--ink-text);
}
.assure-list > li span{
  font-size:clamp(12.5px,min(.95vw,1.85svh),14.2px);
  line-height:1.5;
  color:var(--ink-dim);
}
/* Eén voor één van boven naar beneden. De vertraging staat op de zichtbare
   staat, niet op .pre: een transitie gebruikt de eigenschappen van de stand
   waar hij naartoe gaat, dus een delay die alleen in .pre staat werkt op de
   weg naar verborgen en nooit op de weg terug. */
.assure-list.reveal-stagger > li{ transition-delay:var(--d,0s); }
.assure-list.reveal-stagger.pre > li{ transition-delay:0s; }
/* 0,18s uit elkaar, niet 0,10s. Bij een fade die zelf 0,7s duurt overlappen
   vijf regels op een tiende seconde zo sterk dat het als één blok leest dat
   iets ongelijk opkomt; op 0,18s loopt er zichtbaar een regel vóór de
   volgende uit en telt het oog ze mee. Over de vijf is dat 0,72s — lang
   genoeg om te lezen, kort genoeg om niet op te wachten. */
.assure-list > li:nth-child(2){ --d:.18s; }
.assure-list > li:nth-child(3){ --d:.36s; }
.assure-list > li:nth-child(4){ --d:.54s; }
.assure-list > li:nth-child(5){ --d:.72s; }

/* ---------- de twee voorbeelden ---------- */
.assure-cases{
  width:100%;
  max-width:900px;
  text-align:center;
}
.assure-cases-kicker{
  display:block;
  font-family:var(--font-mono);
  font-size:10px;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--gold);
  margin-bottom:clamp(6px,1.1svh,12px);
}
.assure-case-row{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(10px,1.1vw,18px);
}
.assure-case{
  border:1px solid var(--ink-line);
  border-radius:var(--radius);
  background:linear-gradient(180deg,#14171D,#101317);
  padding:clamp(10px,1.5svh,16px) clamp(12px,1.2vw,18px);
  text-align:left;
  position:relative;
  overflow:hidden;          /* houdt de folieveeg binnen de ronding */
  /* De twee kaarten staan naast elkaar en zijn even hoog, maar hun titel niet:
     "Jumbo boosterbox (CSV10C)" breekt op een smalle kaart over twee regels en
     "Gem Pack Vol. 5" niet. Met de prijsregels bovenaan uitgelijnd stond
     "Onze prijs per doos" in de ene kaart dertien pixels hoger dan in de
     andere — precies de twee regels die je naast elkaar wilt kunnen lezen.

     De prijzen worden daarom naar de ónderkant geduwd in plaats van onder de
     titel gehangen. De titel mag dan groeien zonder dat er iets onder hem
     meeschuift, en dat werkt bij elke breedte en bij elke titellengte —
     anders dan twee regels ruimte reserveren, wat op een breed scherm alleen
     maar lege ruimte kost. */
  display:flex;
  flex-direction:column;
}
.assure-case h3{
  position:relative; z-index:1;
  margin:0 0 clamp(4px,.8svh,9px);
  font-family:var(--font-body);
  font-size:clamp(12.5px,min(.95vw,1.85svh),14px);
  font-weight:650;
  color:var(--ink-text);
}
.assure-case dl{ margin:auto 0 0; position:relative; z-index:1; }

/* --------------------------------------------------------------------------
   Folie over de prijskaarten. Hergebruikt `swatchSheen`, dezelfde keyframes
   die de hero-kaarten en de categorieswatches al gebruiken: een diagonale
   band die over een achtergrond van 250% heen en weer schuift, zodat het
   leest als licht dat over folie loopt in plaats van als een blokje dat
   overheen glijdt.

   Twee verschillen met de folie elders op de site. Daar ligt de band in wit
   op inkt met `mix-blend-mode:overlay`; deze kaarten staan wit op papier, en
   daar zou wit op wit niets doen. Dus: de drie huiskleuren op lage sterkte
   met `multiply`, wat op wit een prismatische tint geeft en de donkere cijfers
   met rust laat — overlay zou ze juist uitwassen op het moment dat de band
   eroverheen komt.

   De twee kaarten lopen niet gelijk (`--sheen-delay`): twee tegelijk oplichten
   leest als een knippering van het hele blok, versprongen leest het als twee
   losse kaarten die elk hun eigen licht vangen. */
.assure-case::after{
  content:"";
  position:absolute; inset:0;
  z-index:2;
  pointer-events:none;
  border-radius:inherit;
  background:linear-gradient(115deg,
    transparent 32%,
    rgba(201,162,39,.30) 43%,
    rgba(123,95,255,.16) 50%,
    rgba(47,217,196,.20) 57%,
    transparent 68%);
  background-size:250% 250%;
  mix-blend-mode:multiply;
  animation:swatchSheen 9s ease-in-out infinite;
  animation-delay:var(--sheen-delay,0s);
}
.assure-case:nth-child(2){ --sheen-delay:-3.4s; }
/* De veeg is versiering, geen informatie: wie minder beweging vraagt krijgt
   de folie als een stille tint in plaats van als een lopende band. */
@media (prefers-reduced-motion: reduce){
  .assure-case::after{ animation:none; background-position:38% 38%; }
}
.assure-case dl > div{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:10px;
  padding:clamp(3px,.6svh,6px) 0;
}
.assure-case dt{
  font-size:11.5px;
  color:var(--ink-dim);
}
.assure-case dd{
  margin:0;
  display:flex; align-items:baseline; gap:5px;
  white-space:nowrap;
  font-family:var(--font-mono);
  font-size:clamp(13px,min(1.05vw,2svh),15.5px);
  font-variant-numeric:tabular-nums;
  color:var(--ink-text);
}
/* De onderste regel is waar het om gaat: gouden lijn erboven en het bedrag in
   goud, zodat het oog van de winkelprijs naar onze prijs valt en niet
   andersom. */
.assure-case-ours{
  border-top:1px solid rgba(201,162,39,.3);
  margin-top:2px;
}
.assure-case-ours dt{ color:var(--ink-text); }
.assure-case-ours dd{ color:var(--gold-bright); }
.assure-case-ours dd small{
  font-size:9.5px;
  letter-spacing:.06em;
  color:#6E747F;
}
.assure-cases-note{
  margin:clamp(7px,1.2svh,12px) 0 0;
  font-size:11px;
  line-height:1.5;
  color:#6E747F;
}
.assure-actions{
  display:flex; align-items:center; justify-content:center;
  gap:16px; flex-wrap:wrap;
  margin-top:clamp(10px,1.6svh,18px);
}
.assure-link{
  font-family:var(--font-mono);
  font-size:10.5px;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--ink-dim);
  border-bottom:1px solid var(--ink-line);
  padding-bottom:2px;
  transition:color .25s var(--ease), border-color .25s var(--ease);
}
.assure-link:hover{ color:var(--gold-bright); border-color:rgba(201,162,39,.5); }

/* Op een telefoon loopt dit paneel door tot ongeveer anderhalf scherm, en dat
   is hier de goede afweging. Vijf uitleggen en twee prijsvoorbeelden passen
   alleen in één schermhoogte als de letter naar een maat gaat waarop niemand
   ze nog leest, en dan is de winst een nette snap-rand op een paneel dat zijn
   werk niet meer doet. Hetzelfde is afgesproken voor de hero op een tablet.
   De bewaker (js/main.js §8) meldt dit dus terecht op smalle schermen; het is
   bekend en gewild, geen regressie. Wat hieronder staat haalt wél alle dode
   ruimte weg, zodat het doorlopen zo kort mogelijk blijft. */
@media (max-width:760px){
  /* Zelfde reservering als bij .chain: de header is hier `position:fixed` en
     ligt over de bovenste 77px van het paneel. Vult de inhoud het paneel —
     wat op een telefoon zo is — dan is er geen vrije ruimte die de kop er
     vanzelf onderuit duwt. */
  .assure{ padding:calc(77px + clamp(6px,1.2svh,26px)) 0 clamp(28px,4.5svh,56px); }
  .assure-inner{ gap:clamp(12px,2svh,28px); }
  /* De titelkolom valt weg: op een telefoon staat de uitleg onder zijn eigen
     kop in plaats van ernaast, anders blijven er twee smalle kolommen over
     die allebei afbreken. */
  .assure-list > li{
    grid-template-columns:auto minmax(0,1fr);
    gap:2px 11px;
    padding:clamp(4px,.8svh,10px) 0;
  }
  .assure-list > li span{ grid-column:2; }
  /* De twee voorbeelden naast elkaar in plaats van onder elkaar. Ze zijn kort
     genoeg voor een halve telefoonbreedte zodra het bijschrift bóven het
     bedrag staat in plaats van ernaast, en naast elkaar zijn ze ook waar ze
     voor bedoeld zijn: te vergelijken. Onder elkaar kostte dit blok bijna
     tweehonderd pixels van een paneel dat toch al doorloopt. */
  .assure-case-row{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
  .assure-case{ padding:9px 10px; }
  .assure-case h3{ margin-bottom:4px; }
  .assure-case dl > div{
    display:block;
    padding:3px 0;
  }
  .assure-case dt{ font-size:10px; letter-spacing:.02em; }
  .assure-case dd{ font-size:14px; }
  .assure-case-ours{ padding-top:4px; }
}

/* --------------------------------------------------------------------------
   De twee blokken zijn van paneel geruild: de prijsvoorbeelden sluiten nu de
   keten af — daar staat het verhaal over de winkelprijs in China, dus daar
   horen de twee bedragen die dat verhaal waarmaken — en de uitdaging staat
   onder de vijf redenen, waar hij het paneel afsluit met een vraag in plaats
   van met een opsomming.

   Ze wisselen daarmee ook van ondergrond, en dat is het enige wat hier extra
   werk kost: een kaart die voor inkt is getekend verdwijnt op papier en
   andersom. Hieronder alleen de kleuren, geen enkele maat — de opmaak van
   beide blokken blijft die van hun oorspronkelijke regels.
   -------------------------------------------------------------------------- */

/* De prijsvoorbeelden, nu op papier. Witte kaart met dezelfde ronding en
   schaduw als de ketenkaarten erboven, zodat de band als één geheel leest. */
.chain .assure-cases{ width:100%; max-width:980px; margin:0 auto; }
.chain .assure-cases-kicker{ color:var(--paper-dim); }
.chain .assure-case{
  background:#fff;
  border:0;
  border-radius:16px;
  box-shadow:0 1px 2px rgba(23,24,27,.05), 0 14px 30px -24px rgba(23,24,27,.4);
}
.chain .assure-case h3{ color:var(--paper-text); }
.chain .assure-case dt{ color:var(--paper-dim); }
.chain .assure-case dd{ color:var(--paper-text); }
/* Groen voor onze prijs, niet goud: goud op wit haalt op deze lettergrootte
   de 4.5:1 niet, en groen is op papier al het stempel voor "dit is de onze"
   — zie de rechterkaart in de vergelijking hierboven. */
.chain .assure-case-ours{ border-top-color:rgba(46,122,78,.32); }
.chain .assure-case-ours dd{ color:var(--green-stamp); }
.chain .assure-case-ours dd small{ color:var(--paper-dim); }
.chain .assure-cases-note{ color:var(--paper-dim); }
/* Compacter dan op het inktpaneel: hier is dit blok het bewijs onder een
   argument dat al een half scherm heeft gekost, niet de hoofdzaak. Bijschrift
   náást het bedrag (de standaardregel), krappe regelhoogtes, en geen lucht
   die de band over de schermrand duwt. */
.chain .assure-case{ padding:clamp(8px,1.2svh,13px) clamp(12px,1.2vw,18px); }
.chain .assure-case h3{ margin-bottom:clamp(2px,.5svh,5px); }
.chain .assure-case dl > div{ padding:clamp(2px,.45svh,4px) 0; }
.chain .assure-cases-kicker{ margin-bottom:var(--label-gap); }
.chain .assure-cases-note{ margin-top:clamp(5px,.9svh,9px); }
.chain .assure-actions{ margin-top:clamp(8px,1.2svh,14px); }
.chain .assure-link{ color:var(--paper-dim); border-bottom-color:var(--paper-line); }
.chain .assure-link:hover{ color:var(--paper-text); border-bottom-color:var(--paper-dim); }

/* De uitdaging, nu op inkt. --shelf tegen --ink is te klein een verschil om
   als apart blok te lezen, dus dezelfde kaartvulling als de rest van dit
   paneel plus een gouden haarlijn eromheen. */
/* De uitdaging op het inktpaneel. Ze is getekend voor het papieren paneel —
   16px ronding, warme houtskool, een gouden knop als enige accent op wit — en
   dat is precies wat er hier misging: tussen kaarten van 3px las die ronding
   als een vreemd element, en de mobiele regels die de knop netjes maakten
   hingen aan `.chain-foot`, waar deze box niet meer in zit. De knop stond
   daardoor op zijn natuurlijke breedte tegen de linkerrand, zonder marge.

   Wat hier staat is dezelfde box in de taal van dít paneel: de ronding van de
   prijskaarten, een rand in de inktlijn met alleen bovenlangs de folieveeg die
   de site voor randen gebruikt, en de knop terug op zijn plek — rechts naast
   de tekst waar het past, over de volle breedte eronder waar het niet past. */
.assure .chain-dare{
  position:relative;
  overflow:hidden;
  width:100%;
  max-width:900px;
  align-items:center;
  gap:clamp(16px,2.4vw,36px);
  padding:clamp(11px,1.5svh,18px) clamp(14px,1.7vw,22px);
  border-radius:var(--radius);
  background:linear-gradient(180deg,#14171D,#101317);
  border:1px solid var(--ink-line);
  box-shadow:none;
}
.assure .chain-dare::before{
  content:""; position:absolute; inset:0 0 auto; height:1px;
  background:linear-gradient(90deg,rgba(201,162,39,.05),rgba(201,162,39,.5) 28%,rgba(201,162,39,.5) 72%,rgba(201,162,39,.05));
}
.assure .chain-dare-line{ font-size:clamp(16px,min(1.5vw,2.7svh),21px); }
.assure .chain-dare-sub{ color:#7C828E; }
/* Stapelen bij dezelfde grens als de vijf redenen erboven, zodat het paneel in
   één keer naar zijn telefoonvorm gaat in plaats van in twee stappen. */
@media (max-width:760px){
  .assure .chain-dare{ display:block; padding:12px 14px; }
  .assure .chain-dare .btn{
    display:flex; width:100%; justify-content:center;
    margin-top:clamp(9px,1.3svh,14px);
  }
}

/* ==========================================================================
   Slide scrolling between the full-height panels
   Every panel of the deck is one viewport tall, so the page moves between
   them a panel at a time instead of drifting: the visitor is looking at one
   of them, whole, or at the next. The chain schema is the one panel that may
   run past a screen on a short window; initSnapDown() gives any panel that
   does not fit a second stop at its foot rather than jumping over its lower
   half.

   On a pointer that rule is entirely js/main.js §6c — a tween for the wheel
   and a settle guard for everything else. There used to be a root-level
   `scroll-snap-type:y proximity` behind an html.snap-down class underneath
   it; it is gone. It only pulled on the way down, it re-snapped the page the
   instant the class landed (so it could only be armed at exactly the right
   pixel), and proximity is a hint the engine is free to decline — the same
   reason it was dropped on touch below. `mandatory` is not an option here
   either: it re-snaps programmatic scrolls too, so it would fight the tween
   for every frame of every slide.

   The alignment stays, because touch uses it.
   ========================================================================== */
.hero, .c30-banner, .chain, .assure, .fold, .book, .closing-section{ scroll-snap-align:start; }

/* --- Touch: the deck is a pager, not a scroll -------------------------
   js/main.js §6c hands the whole thing over on a coarse pointer and adds
   this class once, at load. Three things follow from it.

   It is never toggled during a scroll. Writing scroll-snap-type re-snaps
   the scrollport immediately, and doing that while a flick's momentum is
   still running cancels the momentum and re-lands the page — the stutter
   the class-toggling above produced on every phone, several times per
   swipe. Armed from the start there is nothing to interrupt.

   It is CSS and not script: a scroll a finger started is not a scroll JS
   is allowed to take back (see the bail-out at the top of initSnapDown()).

   And it is `mandatory` with `scroll-snap-stop:always` — which is the
   reversal of what stood here before, so the argument it replaces is worth
   keeping. The old pair was `proximity` and no stop, reasoned like this:
   every panel is exactly one viewport tall, so wherever a flick comes to
   rest there is always a panel edge within proximity range. That holds for
   a flick. It does not hold for a finger dragged slowly and lifted, which
   is a scroll that simply ends where it ends — and proximity is a hint the
   engine is free to decline, which on a phone it does. That is the whole of
   "je kan letterlijk scrollen tussen de hero's": the page could be parked
   showing the foot of one panel and the head of the next, and nothing
   pulled it off that position.

   `mandatory` has no such freedom: the scrollport must come to rest on a
   snap position, so between two panels is not a place the page can be.
   `scroll-snap-stop:always` is the other half — it forbids a gesture from
   travelling past a panel it has reached, so one swipe is one panel and a
   hard throw cannot cover two. It was deliberately left out before because
   it can spend a hurried visitor's second flick on re-arriving where they
   were already going; on a phone that is the right price, because "either
   this panel or that one, whole" is exactly what the deck is for.

   Mandatory applies to the whole document, so everything below the deck
   needs snap positions of its own or it stops being reachable: with the
   fold's edge the last one, a scroll into the closing block would be
   snapped straight back up to it. The closing band and the footer each get
   an edge. The footer is taller than a phone screen, and a snap area larger
   than the snapport makes every position that keeps the snapport inside it
   a valid rest — so the foot of the document is read normally, not in jumps.

   The footer aligns `end`, not `start`, and that one word is the difference
   between the document having a bottom and not. Aligned to its top the
   covering range runs to (footer bottom − one screen), and the page's own
   maximum scroll sits a fraction of a pixel past it: measured, scrolling to
   the very end left the page half a pixel outside every valid position and
   it snapped back to the footer's top edge — a 578px jump backwards at the
   exact moment somebody reaches the end of the page. Aligned to its end the
   maximum scroll *is* a snap position. The covering range is unchanged
   (covering does not care about alignment), so the footer still scrolls
   through normally; only its last half pixel behaves differently.

   And `scroll-behavior:auto`: Chrome runs the snap through the scrollport's
   scroll-behavior, so `smooth` turns every native snap into a long scripted
   glide instead of the platform's own quick settle. */
html.snap-touch{
  scroll-snap-type:y mandatory;
  scroll-behavior:auto;
}
html.snap-touch .hero,
html.snap-touch .c30-banner,
html.snap-touch .chain,
html.snap-touch .assure,
html.snap-touch .fold,
html.snap-touch .book,
html.snap-touch .closing-section{ scroll-snap-stop:always; }
/* The tail, so mandatory cannot lock the deck's last panel in place. */
html.snap-touch .site-footer{ scroll-snap-align:end; }
/* Past the top of the last panel nothing snaps: the closing band and the
   footer scroll like a normal page (html.snap-free, set by initSnapDown()). */
html.snap-touch.snap-free{ scroll-snap-type:none; }
/* Lifted while the back-to-top button carries the page up (initBackToTop). */
html.snap-touch.snap-off{ scroll-snap-type:none; }
/* For a moment on arrival at the top, nothing below the hero is a snap
   area, so turning snapping back on cannot re-snap to the panel the trip
   started from (initBackToTop). */
html.snap-touch.snap-retarget .c30-banner,
html.snap-touch.snap-retarget .chain,
html.snap-touch.snap-retarget .assure,
html.snap-touch.snap-retarget .fold,
html.snap-touch.snap-retarget .book,
html.snap-touch.snap-retarget .closing-section,
html.snap-touch.snap-retarget .site-footer{ scroll-snap-align:none; }


/* ==========================================================================
   Fold screen — een muur van producten tussen hero en slotband.

   Zes tot acht kolommen die langs elkaar heen schuiven, om en om omhoog en
   omlaag, elk met een eigen snelheid en met tegels van verschillende hoogte
   en breedte. Er staat geen tekst op: elke tegel is enkel een link naar zijn
   productpagina. Wat het paneel zegt is één ding, en het zegt het door het te
   laten zien: er is veel.

   Wat het hiervoor was — een ring van kaarten in CSS 3D — staat in de
   geschiedenis van dit bestand. De ring toonde er één tegelijk.

   Twee regels zijn hier load-bearing:

   * `overflow:hidden` op .fold. De stroken zijn hoger dan het paneel; dit is
     wat er een venster van maakt in plaats van een pagina die uitdijt.
   * Niets beweegt dan transform. Geen blur op of achter iets dat beweegt —
     een filter die zijn buren bemonstert kan niet aan de compositor worden
     gegeven, en acht kolommen kunnen er geen betalen.
 */
.fold{
  --edge:#07080A;   /* de onderfade loopt naar rgba(7,8,10,.97) */
  position:relative;
  min-height:100vh;
  min-height:100svh;   /* phones: don't count the collapsing URL bar twice */
  background:var(--shelf);
  border-top:1px solid var(--shelf-line);
  border-bottom:1px solid var(--shelf-line);
  /* De enige knip in het paneel, en hij draagt de hele muur: de stroken zijn
     hoger dan het scherm en dit is wat er een venster van maakt. */
  overflow:hidden;
  isolation:isolate;
}
/* De aankomst. Geen `opacity:0` in rust — een pagina waarvan het script nooit
   liep mag nooit iets hebben achtergehouden. Zonder JS is de muur leeg en dus
   is er ook niets te verbergen; mét JS komt hij op zodra hij gebouwd is. */
.fold.is-armed .fold-wall{ animation:wallIn .9s ease both; }
@keyframes wallIn{ from{ opacity:0; } to{ opacity:1; } }

/* ---------- the room ----------
   A pitch-dark room with a sky in it. The still field is a 512px tile of
   near-black and a couple of hundred single-pixel stars (3 KB, written by
   tools/make-night-sky.py), repeated at its own scale rather than stretched —
   a sky that is scaled to the panel either repeats visibly or has to be a
   photograph's worth of bytes.

   Everything else in here is the absence of light. The panel is the darkest
   surface on the site on purpose: it is what makes a lit card at the front of
   the ring read as lit rather than as merely pale. */
.fold-sky{
  position:absolute; inset:0;
  background:#07080A url("/img/showcase/night-sky.webp") repeat 0 0;
  background-size:1024px 1024px;
}
/* Pikdonker, en verder niets. Hier lag een lage gouden gloed onderin om de
   ruimte een vloer te geven; die is er op verzoek uit. Wat overblijft is een
   vignet in dezelfde zwart als de rest — het sluit het paneel aan de randen af
   zonder ergens licht toe te voegen. De enige lichtbronnen in dit paneel zijn
   nu de producten zelf en een vallende ster. */
.fold-sky::after{
  content:"";
  position:absolute; inset:0;
  background:radial-gradient(130% 95% at 50% 50%,
    rgba(5,6,7,0) 42%, rgba(4,4,5,.92) 100%);
}

/* ---------- vallende sterren ----------
   Een ster licht eerst op waar hij staat, blijft daar een tel glimmen, en
   schiet dan pas weg. Dat is de volgorde die de eigenaar vroeg, en het is ook
   waarom het opvalt in een donkere ruimte: een streep die zomaar begint mis je
   als je net ergens anders keek, een lichtje dat aangaat niet.

   Allemaal uit hetzelfde punt. js/main.js kiest per ster een plek ergens langs
   een lijn vanaf de radiant en laat hem daar recht vandaan lopen, zoals een
   echte zwerm. Het moment, de afstand, de lengte en de snelheid zijn wel
   willekeurig, en er zit geen ritme in.

   Eén element. De streep is een verloop in een balkje dat om zijn linkeruiteinde
   draait: tijdens het glimmen staat het op scaleX(.02), dus je ziet alleen de
   heldere kop als een punt, en bij het wegschieten groeit de staart met scaleX
   mee. Alleen transform en opacity bewegen — geen blur, en niets dat de
   lay-out aanraakt. */
.fold-meteor{
  position:absolute;
  left:0; top:0;
  width:170px; height:2px;
  border-radius:2px;
  transform-origin:0 50%;
  opacity:0;
  pointer-events:none;
  /* De kop is het heldere eind; de staart loopt weg in niets. */
  background:linear-gradient(to right,
    rgba(255,255,255,0) 0%,
    rgba(228,188,62,.32) 44%,
    rgba(255,255,255,.98) 100%);
  animation:meteorFly var(--dur,2200ms) linear both;
}
@keyframes meteorFly{
  /* aan */
  0%{
    opacity:0;
    transform:translate3d(var(--x,0px), var(--y,0px), 0) rotate(var(--rot,28deg))
              translateX(0px) scaleX(.02);
  }
  /* glimmen, ter plekke */
  9%{
    opacity:.55;
    transform:translate3d(var(--x,0px), var(--y,0px), 0) rotate(var(--rot,28deg))
              translateX(0px) scaleX(.02);
  }
  20%{
    opacity:1;
    transform:translate3d(var(--x,0px), var(--y,0px), 0) rotate(var(--rot,28deg))
              translateX(0px) scaleX(.035);
  }
  /* en weg */
  34%{
    opacity:1;
    transform:translate3d(var(--x,0px), var(--y,0px), 0) rotate(var(--rot,28deg))
              translateX(calc(var(--fly,600px) * .06)) scaleX(.16);
  }
  88%{ opacity:1; }
  100%{
    opacity:0;
    transform:translate3d(var(--x,0px), var(--y,0px), 0) rotate(var(--rot,28deg))
              translateX(var(--fly,600px)) scaleX(var(--len,1));
  }
}

/* ---------- de muur ----------
   Zes tot acht kolommen naast elkaar, elk een eindeloze strook producten. De
   oneven kolommen lopen omhoog, de even omlaag, en elke kolom heeft zijn eigen
   snelheid — dat laatste is wat het verschil maakt tussen "veel voorraad" en
   "een lopende band die scheef staat".

   Drie dingen dragen dit en alle drie zijn ze bewust:

   * De beweging is één keyframe-animatie per kolom op `translate3d`, en verder
     niets. Geen animatielus, geen bibliotheek: acht kolommen kosten acht
     composited layers en nul frames op de hoofdthread. GSAP zou hier hetzelfde
     beeld geven voor zeventig kilobyte en een rAF-lus die elke frame acht
     transforms schrijft.
   * Elke kolom staat er **twee keer** in en schuift precies één kopie op
     (-50% van zijn eigen hoogte). Daardoor valt het einde van de tweede kopie
     samen met het begin van de eerste en is de sprong terug onzichtbaar.
   * Daarom ook `margin-bottom` op elke tegel in plaats van `gap` op de strook.
     Met gap zit er tussen de twee kopieën één tussenruimte meer dan binnen een
     kopie, en dan is -50% een halve tussenruimte naast de naad — één keer per
     ronde een zichtbare schok. Met margin hoort de ruimte bij de tegel en
     klopt de helft exact.

   Geen blur op of achter iets dat beweegt, ook niet op de twee CTA-kaarten:
   een backdrop-filter boven een bewegende muur moet elk frame opnieuw
   bemonsteren, en dat is precies het soort werk dat de compositor niet kan
   overnemen. */
.fold-wall{
  position:absolute; inset:0;
  z-index:1;
  display:flex;
  justify-content:center;
  --wall-gap:clamp(8px,1vw,16px);
  gap:var(--wall-gap);
  padding:0 var(--wall-gap);
  /* De muur zelf vangt niets op; de tegels zetten het per stuk weer aan. Zo
     blijft alles tussen de kolommen door gewoon het paneel. */
  pointer-events:none;
}
.fold-col{
  position:relative;
  flex:1 1 0;
  min-width:0;
  /* Op een heel breed scherm mag een kolom niet meegroeien tot een banier:
     boven deze breedte komen er liever meer kolommen dan bredere. */
  max-width:260px;
}
.fold-col-track{
  position:absolute;
  left:0; right:0; top:0;
  display:flex;
  flex-direction:column;
  /* De enige eigenschap die hier beweegt. */
  will-change:transform;
  animation:wallUp var(--dur,40s) linear infinite;
  animation-delay:var(--delay,0s);
}
.fold-col--down .fold-col-track{ animation-name:wallDown; }
@keyframes wallUp{
  from{ transform:translate3d(0,0,0); }
  to  { transform:translate3d(0,-50%,0); }
}
@keyframes wallDown{
  from{ transform:translate3d(0,-50%,0); }
  to  { transform:translate3d(0,0,0); }
}
/* Een kolom waar iemand naar kijkt staat stil. Een bewegend doel aanklikken is
   vervelend, en dit is goedkoper dan het langzaam maken: de andere kolommen
   lopen door, dus de muur blijft leven. */
.fold-col:hover .fold-col-track,
.fold-col:focus-within .fold-col-track{ animation-play-state:paused; }

.fold-tile{
  position:relative;
  display:block;
  height:var(--h,180px);
  width:var(--w,100%);
  margin-left:var(--ml,0);
  margin-bottom:var(--wall-gap,14px);
  flex:0 0 auto;
  pointer-events:auto;
  text-decoration:none;
  -webkit-tap-highlight-color:transparent;
  /* Gedempt in rust, vol licht onder de muis. Dat dimmen doet twee dingen
     tegelijk: het geeft de muur diepte, en het houdt de twee CTA-kaarten
     leesbaar zonder dat er een waas overheen hoeft. */
  filter:brightness(.82);
  transition:transform .32s var(--ease), filter .32s ease;
}
.fold-tile:hover,
.fold-tile:focus-visible{ transform:scale(1.07); filter:brightness(1.12); z-index:2; }
.fold-tile:focus-visible{ outline:2px solid var(--gold-bright); outline-offset:4px; border-radius:6px; }
/* Dezelfde vlek eigen kleur als de ring had: een uitsnede met transparante
   lucht eromheen heeft anders niets om op te staan. */
.fold-tile::before{
  content:"";
  position:absolute;
  inset:-8% -12%;
  background:radial-gradient(closest-side ellipse,
              color-mix(in srgb, var(--sw-a,#1b1d24) 78%, transparent) 0%,
              rgba(13,15,18,0) 100%);
  opacity:.5;
  z-index:-1;
}
.fold-tile img{
  display:block;
  width:100%; height:100%;
  object-fit:contain;
}

/* De randen van het paneel. Zonder deze twee stroken hakt de muur er bovenaan
   en onderaan zomaar af; met is het een doorlopende stroom waar je een stuk
   van ziet. Verlopen, geen mask: een verloop is verf en wordt één keer
   getekend. */
.fold::before,
.fold::after{
  content:"";
  position:absolute; left:0; right:0;
  height:clamp(90px,14vh,190px);
  z-index:3;
  pointer-events:none;
}
.fold::before{
  top:0;
  background:linear-gradient(to bottom, rgba(7,8,10,.97) 0%, rgba(7,8,10,.72) 45%, rgba(7,8,10,0) 100%);
}
.fold::after{
  /* Eén pixel voorbij de onderrand, niet erop. Deze fade is wat de afgeknipte
     tegels onderaan wegwerkt, en hij stond precies op de rand: met een
     --deck-bar die geen rond getal is (op Chrome iOS is het verschil tussen
     100vh en 100dvh ongeveer één pixel) valt hij een subpixel te hoog en
     blijft er onderaan een haarlijn tegelwerk staan — de "kleine opening
     onderaan de balk". `overflow:hidden` op .fold knipt de overlap weg, dus
     hij kost verder niets. */
  bottom:calc(var(--deck-bar) - 2px);
  background:linear-gradient(to top, rgba(7,8,10,.97) 0%, rgba(7,8,10,.72) 45%, rgba(7,8,10,0) 100%);
}

/* ---------- de twee ingangen ----------
   Geen kaarten. Een dichte kaart bovenop de muur is een gat in het effect:
   precies daar waar het paneel zijn werk doet staat dan een rechthoek die
   niets laat zien. Wat er staat is nu alleen typografie, en die staat in de
   twee randstroken die er toch al waren — boven en onder faden de kolommen
   al weg, dus daar is de achtergrond al donker en kost een woord niets.

   De leesbaarheid komt van een vignet en niet van een vlak: een zacht
   ellipsvormig verloop dat ruim buiten de tekst doorloopt en nergens een rand
   heeft. Daardoor blijft de muur er dwars doorheen te zien — dat is het
   verschil tussen tekst óp het paneel en tekst ín het paneel.

   Sealed linksboven, singles rechtsonder. Diagonaal, zodat de hele middenbaan
   van de muur vrij blijft en de twee ingangen ver genoeg uit elkaar staan om
   twee keuzes te zijn in plaats van een knoppenbalk. */
.fold-cta{
  position:absolute;
  z-index:6;
  display:block;
  max-width:min(340px,70vw);
  color:var(--ink-text);
  text-decoration:none;
  transition:transform .4s var(--ease);
}
/* Het vignet. Ruim, zacht en zonder rand — inset ver buiten de tekst, zodat
   je nergens kunt aanwijzen waar het ophoudt. */
.fold-cta::before{
  content:"";
  position:absolute;
  inset:-70% -45%;
  z-index:-1;
  pointer-events:none;
  /* Dicht in de kern en pas laat loslatend: de grote woorden steken boven de
     randstroken uit en staan dus deels op de volle muur. Waar het vignet
     ophoudt mag je nergens kunnen aanwijzen — vandaar vier stops in plaats
     van twee, en een staart die tot nul doorloopt. */
  background:radial-gradient(closest-side ellipse at center,
    rgba(7,8,10,.97) 0%,
    rgba(7,8,10,.92) 34%,
    rgba(7,8,10,.68) 58%,
    rgba(7,8,10,.28) 78%,
    rgba(7,8,10,0) 100%);
}
.fold-cta:hover{ transform:translateY(-2px); }
.fold-cta--sealed{
  /* De vaste header ligt over de bovenste 77px van elk paneel dat tegen de
     bovenrand gesnapt staat — zie .assure, .feature en .chain. */
  top:calc(77px + clamp(14px,3.5vh,48px));
  left:clamp(20px,5.5vw,84px);
}
.fold-cta--single{
  bottom:calc(clamp(24px,5vh,62px) + var(--deck-bar));
  right:clamp(20px,5.5vw,84px);
  text-align:right;
}
.fold-cta-title{
  display:block;
  font-family:var(--font-display);
  /* Groter dan het in een kaart kon. Een woord van deze maat leest als een
     opschrift in de ruimte en niet als het etiket op een knop. */
  font-size:clamp(38px,5.2vw,68px);
  line-height:.98;
  color:var(--ink-text);
  text-shadow:0 2px 24px rgba(0,0,0,.9);
}
.fold-cta-sub{
  display:block;
  margin-top:8px;
  max-width:30ch;
  font-size:clamp(12.5px,1.05vw,14.5px);
  line-height:1.4;
  color:var(--ink-dim);
  text-shadow:0 1px 14px rgba(0,0,0,.95);
}
.fold-cta--single .fold-cta-sub{ margin-left:auto; }
.fold-cta-go{
  display:inline-flex; align-items:center; gap:9px;
  margin-top:clamp(10px,1.4vh,16px);
  padding-bottom:3px;
  border-bottom:1px solid rgba(228,188,62,.32);
  font-family:var(--font-mono);
  font-size:10.5px;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--gold-bright);
  transition:border-color .35s ease;
}
.fold-cta:hover .fold-cta-go{ border-bottom-color:var(--gold-bright); }
.fold-cta-go svg{
  width:15px; height:15px;
  fill:none; stroke:currentColor; stroke-width:1.7;
  transition:transform .35s var(--ease);
}
.fold-cta:hover .fold-cta-go svg{ transform:translateX(5px); }
.fold-cta:focus-visible{
  outline:2px solid var(--gold-bright);
  outline-offset:10px;
  border-radius:4px;
}

/* ---------- de muur op een telefoon ----------
   Drie kolommen, niet vier. Bij vier is een kolom op een 375px-scherm nog maar
   76px breed, en dan is een kaart geen kaart meer maar een postzegel — het
   paneel moet laten zien wát er is, niet alleen dat het veel is.

   De twee ingangen gaan van diagonaal naar boven-en-onder: op deze breedte is
   er geen diagonaal die twee leesbare kaarten uit elkaar houdt. Ze worden
   allebei zo breed als het scherm, met de muur die er tussendoor en
   achterlangs doorloopt. Dat is meteen de duimvriendelijke indeling: de twee
   ingangen aan de twee randen waar een duim ze zoekt. */
@media (max-width:760px) and (orientation:portrait){
  .fold-wall{ --wall-gap:8px; }
  .fold-col{ max-width:none; }
  /* Allebei op de onderrand, naast elkaar in de twee hoeken. De diagonaal die
     op een breed scherm werkt heeft hier geen ruimte, en boven-en-onder liet
     de muur maar over één strook vrij lopen. Zo naast elkaar is de hele
     bovenkant van het paneel weer muur, staan de twee ingangen precies waar
     een duim ze zoekt, en ligt er tussen de twee woorden nog een stuk muur
     dat gewoon doorloopt.

     De subregel gaat er hier uit. In een halve schermbreedte wordt "Voor
     retailers die voorraad willen opbouwen" vier regels, en dan is het geen
     onderschrift meer maar een alinea — precies het soort blok dat we net
     hebben weggehaald. Het woord en de pijl zeggen genoeg. */
  .fold-cta{
    top:auto;
    bottom:calc(clamp(14px,2.6vh,28px) + var(--deck-bar));
    max-width:46vw;
  }
  .fold-cta--sealed{ left:16px; right:auto; text-align:left; }
  .fold-cta--single{ right:16px; left:auto; text-align:right; }
  .fold-cta-title{ font-size:clamp(28px,8.5vw,42px); }
  .fold-cta-sub{ display:none; }
  .fold-cta-go{ margin-top:8px; font-size:9.5px; }
  /* Ruim, want de twee blokken staan nu in de hoeken en het vignet moet de
     hoek zelf mee afdekken — anders staat het woord half op een kaart. */
  .fold-cta::before{ inset:-80% -42%; }
  .fold::before,
  .fold::after{ height:clamp(90px,15vh,150px); }
}
/* Liggend op een telefoon is de hoogte het probleem: de twee kaarten gaan
   naast elkaar op de onderrand staan, zodat de muur de volle hoogte houdt. */
@media (max-width:760px) and (orientation:landscape){
  .fold-cta{
    top:auto;
    bottom:calc(14px + var(--deck-bar));
    max-width:42vw;
  }
  .fold-cta--sealed{ left:16px; }
  .fold-cta--single{ right:16px; }
  .fold-cta-title{ font-size:26px; }
  .fold-cta-sub{ display:none; }
  .fold-cta-go{ margin-top:6px; }
}

/* ---------- geen vallende sterren op een telefoon ----------
   Op een klein scherm is de muur zelf al het hele beeld, en dan is een streep
   die er dwars doorheen schiet drukte in plaats van lucht. Het paneel hoort
   hier licht te blijven, dus vallen ze alleen nog op een breed scherm.

   js/main.js vuurt er onder deze breedte geen meer af; deze regel is het
   vangnet voor eentje die al onderweg was toen het venster smaller werd. In
   hetzelfde bereik zet build() alle tegels op één maat — zie `uniform`. */
@media (max-width:760px){
  .fold-meteor{ display:none; }
}

@media (prefers-reduced-motion: reduce){
  /* De muur staat stil en is verder precies dezelfde muur: alles wat er te
     zien en te klikken valt staat er nog. */
  .fold-col-track{ animation:none; transform:translate3d(0,0,0); }
  .fold-tile{ transition:none; }
  .fold-cta{ transition:none; }
  /* js/main.js laat er geen meer vallen; dit is het vangnet voor eentje die
     al onderweg was toen de voorkeur omging. */
  .fold-meteor{ display:none; }
}

/* ==========================================================================
   Book — five sourcing steps, turned by hand.
   Each page owns a --page-color; JS drives rotateY from the current step.
   One screen tall, like the panels either side of it: it used to be a 500vh
   pinned sequence driven by the scroll position, which cannot coexist with a
   page that moves a whole panel per gesture — one swipe skipped three steps.
   ========================================================================== */
.book{
  position:relative;
  height:100vh;
  background:var(--paper);
  border-top:1px solid var(--paper-line);
  border-bottom:1px solid var(--paper-line);
}
.book-sticky{
  position:relative;
  height:100%;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:clamp(20px,3.4vh,38px);
  overflow:hidden;
}
/* Heading and progress sit above the stage so a lifted, turning sheet
   passes behind them instead of clipping over the type. */
.book-head{ text-align:center; position:relative; z-index:70; }
/* Plain ink, on the owner's call: foil-stamping the heading was tried and put
   back. The section's shine lives in the sheets themselves — the holo backs
   and the specular that crosses a page as it turns — and the heading reads
   better as the quiet thing above them. */
.book-head h2{ font-size:clamp(26px,3.2vw,40px); }

.book-stage{
  position:relative;
  width:min(760px,88vw);
  height:min(430px,50vh);
  perspective:1900px;
  perspective-origin:56% 50%;
}
/* binding shadow down the spine edge */
.book-spine{
  position:absolute; top:-6px; bottom:-6px; left:-6px; width:30px;
  background:linear-gradient(to right, rgba(13,15,18,.26), rgba(13,15,18,0));
  border-radius:6px 0 0 6px;
  z-index:60; pointer-events:none;
}
.book-page{
  position:absolute; inset:0;
  transform-origin:left center;
  transform-style:preserve-3d;
  /* transform only. `will-change:opacity` counts as a grouping property, and
     a grouping property forces transform-style back to flat — which takes
     backface-visibility down with it, so a sheet turned past side-on showed
     its own front in mirror instead of its back. The fade at the end of a
     turn is applied to the two faces instead (§7a), where flattening a leaf
     costs nothing. */
  will-change:transform;
}
.page-face{
  position:absolute; inset:0;
  border-radius:3px 12px 12px 3px;
  backface-visibility:hidden;
  overflow:hidden;
  box-shadow:0 30px 70px -28px rgba(13,15,18,.55), 0 2px 6px rgba(13,15,18,.14);
}
.page-front{ background:var(--page-color); color:var(--ink); }
/* Specular sweep, driven from the turn itself: paint() (§7a) writes --gloss-x
   and --gloss-o from the same `t` that drives the rotation and the lift, so
   the light crosses the sheet exactly once per turn and is gone at rest.
   Both faces carry it — the front leads the turn, the back finishes it. */
.page-face::after{
  content:""; position:absolute; inset:0; z-index:4; pointer-events:none;
  background:linear-gradient(102deg,
    transparent 38%, rgba(255,255,255,.62) 50%, transparent 62%);
  background-size:260% 100%;
  background-position:var(--gloss-x,130%) 0;
  opacity:var(--gloss-o,0);
  /* Plain alpha, deliberately: a blend mode here flattens the 3D group and
     backface-visibility stops hiding the front of a sheet that has turned
     past side-on — you end up reading the page's own text in mirror. */
}
/* The reverse of the sheet is a holo card back. You only ever see it in the
   second half of a turn, which is the point: the panel is calm paper at rest
   and catches the light exactly when a page moves — the same trade the hero
   cards and the fold screen make, in the one material this section owns. */
.page-back{
  transform:rotateY(180deg);
  background:var(--page-color);
  border-radius:12px 3px 3px 12px;
}
.page-back::after{
  content:""; position:absolute; inset:0;
  background:
    /* Fine lines rather than stripes: at this pitch the rainbow reads as the
       diffraction on a holo card back, not as wrapping paper. */
    repeating-linear-gradient(112deg,
      rgba(201,162,39,.16) 0 1.6%,
      rgba(123,95,255,.14) 1.6% 3.2%,
      rgba(47,217,196,.12) 3.2% 4.8%,
      rgba(228,188,62,.15) 4.8% 6.4%),
    linear-gradient(118deg, transparent 34%, rgba(255,255,255,.22) 50%, transparent 66%),
    linear-gradient(to left, rgba(13,15,18,.55), rgba(13,15,18,.30));
}
.page-inner{
  position:relative; z-index:3;
  height:100%;
  padding:clamp(24px,3.6vw,44px);
  display:flex; flex-direction:column; justify-content:space-between; gap:16px;
}
.page-num, .page-tag{
  font-family:var(--font-mono); font-size:11px;
  letter-spacing:.16em; text-transform:uppercase;
  color:rgba(13,15,18,.62);
}
.page-inner h3{ font-size:clamp(24px,3.2vw,38px); max-width:15ch; }
.page-inner p{
  font-size:clamp(14px,1.25vw,17px);
  /* The 55% is the desktop half of the same text-safe-zone problem the
     max-width:900px block below solves for small screens, and it is needed
     for the same reason: the copy sizes in ch while .page-mon sizes off the
     stage height (--mon-h of min(430px,50vh)), so on a tall desktop window
     the art grows left while the text column does not move, and they meet.
     Measured at 1440x1000 (the worst case, where the stage height caps at
     430px): the art's left edge lands between 57.2% and 66.8% of the stage
     depending on the page, while 44ch of copy reached 67.9%. 55% of the
     content box puts the text's right edge at 54.5% of the stage, clearing
     the widest character by ~21px. The art files have no transparent margin
     — checked, every one is opaque edge to edge — so the box really is the
     visible extent and there is no hidden slack to lean on. */
  max-width:min(44ch, 55%);
  margin:12px 0 0; color:rgba(13,15,18,.78);
}
@media (max-width:900px){
  /* Below this width the ch-based text column and the height-based
     character art size independently and start to overlap (measured up to
     ~176px at 430px wide, on every page except Piplup). Capping both to
     fixed percentage lanes — paired with .page-mon's max-width below —
     means neither can ever grow into the other's space, regardless of
     line length or a character's silhouette. Desktop (>900px) is untouched. */
  .page-inner p{ max-width:min(44ch, 60%); }
}
/* oversized folio number, bled off the outer corner */
.page-big{
  position:absolute; right:-.06em; bottom:-.30em;
  font-family:var(--font-display); font-size:clamp(200px,26vw,330px);
  line-height:.8; color:rgba(13,15,18,.11);
  z-index:1; pointer-events:none; user-select:none;
}
/* spine shadow + outer-edge highlight; opacity is driven by the turn */
/* Character art, paired to each page's colour. Sits above the folio number
   but below the copy, so text always stays readable over it. Because it
   lives inside .page-front it inherits the 3D turn and backface-visibility,
   so it swings away and vanishes with its own page. */
.page-mon{
  position:absolute;
  right:var(--mon-right, 5%);
  bottom:var(--mon-bottom, 0%);
  height:var(--mon-h, 64%);
  width:auto;
  z-index:2;
  pointer-events:none;
  user-select:none;
  filter:drop-shadow(0 14px 20px rgba(13,15,18,.32));
}
@media (max-width:900px){
  /* The text-safe-zone counterpart to the .page-inner p rule above: capping
     max-width forces the browser to also shrink the used height to keep the
     artwork's own aspect ratio (object-fit:contain, not a plain resize) —
     without object-fit the box would still be 34% wide but the *art* inside
     it would squish, since height stays pinned by --mon-h while width gets
     clamped. bottom:16% lifts it clear of the .page-tag caption sitting in
     the same corner, which the old fixed per-page --mon-bottom values were
     never guaranteed to clear at this size. */
  .page-mon{ max-width:34%; bottom:16%; object-fit:contain; }
}
@media (max-width:640px){
  /* Smaller still and pushed to the corner on true phones. */
  .page-mon{ height:calc(var(--mon-h, 64%) * .72); right:2%; opacity:.92; }
}

.page-shade{
  position:absolute; inset:0; z-index:4; pointer-events:none; opacity:0;
  background:
    linear-gradient(to right, rgba(13,15,18,.46) 0%, rgba(13,15,18,0) 30%),
    linear-gradient(to left,  rgba(255,255,255,.34) 0%, rgba(255,255,255,0) 20%);
}

/* The controls: an arrow either side of the step indicator, which doubles as
   a row of jump-to-step buttons. */
.book-nav{
  display:flex; align-items:center; gap:clamp(14px,2.4vw,28px);
  position:relative; z-index:70;
}
.book-arrow{
  appearance:none; cursor:pointer;
  width:44px; height:44px; padding:0;
  display:grid; place-items:center;
  border:1px solid var(--paper-line);
  border-radius:50%;
  background:rgba(255,255,255,.7);
  color:var(--ink);
  transition:background .3s, border-color .3s, transform .3s var(--ease),
             opacity .3s, visibility 0s linear .3s;
}
.book-arrow svg{ width:20px; height:20px; fill:none; stroke:currentColor; stroke-width:1.8;
                 stroke-linecap:round; stroke-linejoin:round; }
.book-arrow:not(:disabled){ transition-delay:0s; }
.book-arrow:hover:not(:disabled){ background:var(--ink); color:var(--paper); border-color:var(--ink); }
.book-arrow:active:not(:disabled){ transform:scale(.94); }
/* Gone, not greyed: at the first page there is no back and at the last there
   is no forward, and a dead button is just something to try. It keeps its
   space so the step indicator stays put instead of sliding across. */
.book-arrow:disabled{
  opacity:0;
  visibility:hidden;
  pointer-events:none;
}
.book-arrow:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }

.book-progress{ display:flex; gap:7px; position:relative; z-index:70; }
.book-progress button{
  appearance:none; border:0; padding:0; cursor:pointer;
  width:30px; height:3px; border-radius:2px;
  background:rgba(13,15,18,.16);
  transition:background .35s var(--ease), transform .35s var(--ease);
}
.book-progress button[aria-current]{ background:var(--gold); transform:scaleX(1.12); }
.book-progress button:focus-visible{ outline:2px solid var(--gold); outline-offset:4px; }
/* The stage takes horizontal swipes, so the browser keeps the vertical axis
   and we keep the horizontal one. */
.book-stage{ touch-action:pan-y; }

@media (max-width:760px){
  /* Touch turns the pages by swiping the stage; the arrows stay as a hint and
     an accessible control, just smaller. */
  .book-arrow{ width:38px; height:38px; }
  .book-nav{ gap:16px; }
}

@media (max-width:760px){
  .book-stage{ width:92vw; height:min(400px,52vh); perspective:1200px; }
  .page-big{ font-size:clamp(150px,40vw,240px); }
}

/* --------------------------------------------------------------------------
   Card rain — atmospheric background layer for the book section.
   A fixed pool of card backs falls behind the turning pages for the whole
   five-step sequence. Purely decorative: no pointer events, and it sits
   below .book-stage in the stacking order (the stage comes later in the DOM
   and this layer is pinned to z-index 0), so a page never passes behind it.
   Elements are moved with transform only; JS never touches layout props.
   -------------------------------------------------------------------------- */
.card-rain{
  position:absolute; inset:0;
  z-index:0;
  pointer-events:none;
  overflow:hidden;
  contain:layout paint;               /* the falling cards can't reflow anything */
}
.card-rain img{
  position:absolute;
  top:0; left:0;
  width:var(--card-w, 70px);
  height:auto;
  opacity:0;                          /* JS fades each card in on its first fall */
  will-change:transform, opacity;
  transform:translate3d(-999px,-999px,0);
  user-select:none;
}
/* Depth: the furthest lane loses a touch of focus. Kept to a 1px radius and
   only on the back lane, so we're never compositing a heavy blur across many
   elements at once. */
.card-rain img[data-lane="back"]{ filter:blur(1px); }

/* The static (reduced-motion) book has no atmosphere to sit behind. */
.book--static .card-rain{ display:none; }

@media (prefers-reduced-motion: reduce){
  .card-rain{ display:none; }
}

/* No pinning without motion: the pages become a plain stack of colour cards. */
.book--static{ height:auto; }
.book--static .book-sticky{ position:static; height:auto; overflow:visible; padding:70px 0; }
.book--static .book-stage{
  width:min(760px,88vw); height:auto; perspective:none;
  display:flex; flex-direction:column; gap:18px;
}
.book--static .book-page{ position:relative; inset:auto; transform:none !important; opacity:1 !important; }
.book--static .page-face{ position:relative; inset:auto; min-height:240px; }
.book--static .page-back,
.book--static .book-spine,
.book--static .book-progress,
.book--static .book-nav{ display:none; }
.book--static .page-shade{ display:none; }

/* ==========================================================================
   Section headers
   ========================================================================== */
.sec-head{ display:flex; justify-content:space-between; align-items:flex-end; gap:30px; margin-bottom:52px; flex-wrap:wrap; }
.sec-head h2{ font-size:clamp(30px,3.6vw,44px); }
.sec-head p{ max-width:40ch; color:var(--paper-dim); margin:14px 0 0; }
.bg-ink .sec-head p,
.bg-ink-soft .sec-head p{ color:var(--ink-dim); }

/* ==========================================================================
   Category / product cards
   ========================================================================== */
.cat-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:22px; }
@media (max-width:980px){ .cat-grid{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:560px){ .cat-grid{ grid-template-columns:1fr; } }

.cat-card{
  background:var(--ink-soft);
  border:1px solid var(--ink-line);
  border-radius:var(--radius);
  padding:26px 22px 22px;
  position:relative;
  overflow:hidden;
  transition:border-color .3s, transform .3s var(--ease);
}
.cat-card:hover{ border-color:var(--gold); transform:translateY(-4px); }
.cat-card::before{
  content:"";
  position:absolute; top:0; left:0; width:0; height:2px;
  background:linear-gradient(90deg,var(--gold),var(--teal),var(--violet));
  transition:width .5s var(--ease);
}
.cat-card:hover::before{ width:100%; }
.cat-swatch{
  width:100%; aspect-ratio:4/3; border-radius:2px; margin-bottom:18px;
  position:relative; overflow:hidden;
  background:linear-gradient(150deg,var(--swatch-a,#26170a),var(--swatch-b,#0d0f12));
  display:flex; align-items:center; justify-content:center;
}
.cat-swatch::after{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(120deg,transparent 30%, rgba(255,255,255,.16) 46%, transparent 60%);
  background-size:250% 250%;
  animation:swatchSheen 7s ease-in-out infinite;
}
@keyframes swatchSheen{
  0%,100%{ background-position:0% 0%; }
  50%{ background-position:100% 100%; }
}
.cat-swatch .glyph{ font-family:var(--font-display); font-style:italic; font-size:34px; color:rgba(255,255,255,.75); position:relative; z-index:1; }
.cat-card h3{ font-size:19px; color:var(--ink-text); margin-bottom:6px; }
.cat-card p{ font-size:13px; color:var(--ink-dim); margin:0 0 14px; }
.cat-card .cat-link{ font-family:var(--font-mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--gold); }

/* ---------------------------------------------------------------- spotlight
   The catalogue's one announcement band, directly under the hero on /shop and
   nowhere else — tools/build-seo.mjs strips it out of the category pages that
   are rendered from the same shell.

   It takes the warm shelf tone the homepage showcase uses rather than a third
   sheet of paper: between an ink hero and a paper grid, a third paper block
   would read as the start of the list instead of as news about it. The
   artwork is the showcase cut-outs, not the catalogue photographs, for the
   reason those cut-outs exist at all — a white studio background on a dark
   ground reads as a rectangle floating behind the box.

   When the 30th line stops being news, delete the markers and this rule
   together; the band carries no product data of its own. */
.shop-spotlight{
  background:var(--shelf);
  border-top:1px solid var(--shelf-line);
  border-bottom:1px solid var(--shelf-line);
  padding:clamp(30px,4vw,54px) 0;
}
.shop-spotlight-inner{
  display:grid;
  grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  gap:clamp(22px,4vw,54px);
  align-items:center;
}
/* Three boxes standing on one line, the display box in front. The outer two
   are rotated away from it and pulled in over their neighbour, so the group
   reads as a shelf rather than as three separate pictures in a row. */
.ss-art{ display:flex; align-items:flex-end; justify-content:center; }
.ss-art img{
  height:auto;
  filter:drop-shadow(0 20px 30px rgba(0,0,0,.55));
}
.ss-art img:nth-child(1){ width:33%; transform:rotate(-7deg) translateX(18%); }
.ss-art img:nth-child(2){ width:46%; position:relative; z-index:2; }
.ss-art img:nth-child(3){ width:33%; transform:rotate(7deg) translateX(-18%); }
/* The heading is one phrase and has to read as one: a sentence with a colon
   and an exclamation mark, broken over two lines, delivers its second half as
   a separate thought. So it never wraps — and because it never wraps, the type
   has to be sized by the space it actually has.

   That space is the copy column, not the viewport, so it is measured in
   container units against .ss-copy. The number is measured rather than
   guessed: this heading renders about 11.9x its own font-size wide in
   Instrument Serif — but 15.8x in Georgia, the fallback that shows while the
   web font is still loading, and English is the longest of the four
   combinations. 6cqi is tuned to the widest of them, so the line cannot stick
   out of the band during the font swap, or if the web font never arrives.

   The vw declaration above it is for browsers without container units and is
   conservative for the same reason; anything that understands cqi discards
   it. */
.ss-copy{ container-type:inline-size; }
.ss-copy h2{
  color:var(--ink-text);
  white-space:nowrap;
  font-size:clamp(20px,3.2vw,32px);
  font-size:clamp(16px,6cqi,36px);
  margin:0 0 12px;
}
.ss-copy p{ color:var(--ink-dim); font-size:15px; max-width:58ch; }
@media (max-width:860px){
  .shop-spotlight-inner{ grid-template-columns:1fr; gap:20px; }
  .ss-art{ max-width:420px; margin-inline:auto; }
  .ss-copy p{ font-size:14px; }
}
@media (prefers-reduced-motion:no-preference){
  /* The fan-out is keyed to the artwork's own box, not to the band. Hovering
     the band meant hovering most of the screen width — the boxes moved while
     the cursor was somewhere off in the paragraph, which reads as the page
     twitching rather than as an answer to the pointer. .ss-art is exactly the
     three products' frame: the images fill its width and its height is the
     tallest of them, so the effect runs while the cursor is on them and
     unwinds the moment it leaves. */
  .ss-art img{ transition:transform .5s var(--ease); }
  .ss-art:hover img:nth-child(1){ transform:rotate(-9deg) translateX(14%); }
  .ss-art:hover img:nth-child(3){ transform:rotate(9deg) translateX(-14%); }
}

/* product grid (shop page) */
.shop-toolbar{
  display:flex; justify-content:flex-start; align-items:flex-start;
  gap:12px 16px; flex-wrap:wrap; margin-bottom:40px;
}

/* Group switch — the primary split between sealed product and singles. A
   segmented control rather than two more dropdown entries, because it is the
   one choice that changes what the sub-filter below it even means. */
.group-switch{
  display:inline-flex; align-items:stretch;
  border:1px solid var(--paper-line); border-radius:20px;
  background:#fff; padding:3px; gap:2px;
}
.group-btn{
  display:inline-flex; align-items:center; gap:7px;
  font-family:var(--font-mono); font-size:12px; text-transform:uppercase; letter-spacing:.05em;
  padding:7px 14px; border:none; border-radius:16px;
  background:none; color:var(--paper-dim); cursor:pointer;
  transition:background .25s, color .25s;
  white-space:nowrap;
}
.group-btn:hover{ color:var(--paper-text); }
.group-btn .count{ font-size:10px; opacity:.7; }
.group-btn.active{ background:var(--ink); color:var(--gold-bright); }
.group-btn.active .count{ opacity:.85; }

/* Category filter — a single dropdown rather than a chip per category, so
   the toolbar isn't a wide row of six competing buttons. */
.filter-dropdown{ position:relative; }
.filter-dropdown-btn{
  display:flex; align-items:center; gap:8px;
  font-family:var(--font-mono); font-size:12px; text-transform:uppercase; letter-spacing:.05em;
  padding:9px 14px; border:1px solid var(--paper-line); border-radius:20px; color:var(--paper-text);
  background:#fff; transition:border-color .25s;
}
.filter-dropdown-btn:hover, .filter-dropdown.open .filter-dropdown-btn{ border-color:var(--paper-text); }
.filter-dropdown-btn span:first-child{ color:var(--paper-dim); }
.filter-dropdown-value{ font-weight:600; }
.filter-dropdown-btn .chevron{ transition:transform .25s; color:var(--paper-dim); }
.filter-dropdown.open .filter-dropdown-btn .chevron{ transform:rotate(180deg); }

.filter-dropdown-menu{
  position:absolute; top:calc(100% + 8px); left:0; z-index:20;
  min-width:220px; background:#fff; border:1px solid var(--paper-line); border-radius:8px;
  box-shadow:var(--shadow-lg); padding:6px;
  opacity:0; visibility:hidden; transform:translateY(-6px);
  transition:opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
}
.filter-dropdown.open .filter-dropdown-menu{ opacity:1; visibility:visible; transform:translateY(0); }
.filter-option{
  display:block; width:100%; text-align:left;
  font-size:14px; color:var(--paper-text); background:none; border:none;
  padding:10px 12px; border-radius:5px; cursor:pointer;
}
.filter-option:hover{ background:var(--paper-soft); }
.filter-option.active{ color:var(--gold); font-weight:600; }
.filter-option .count{ color:var(--paper-dim); font-family:var(--font-mono); font-size:11px; margin-left:6px; }

/* Catalogue search — one box across sealed product and singles. Shares the
   pill shape and mono type of the category dropdown so the toolbar reads as
   one control cluster rather than two unrelated widgets. */
/* Basis is deliberately small: the group switch and the sub-dropdown take
   their natural width first, and the search absorbs whatever is left over.
   With a larger basis it grew past the row and pushed the result count onto
   a line of its own. */
.shop-search{
  position:relative; display:flex; align-items:center;
  flex:1 1 200px; max-width:380px;
}
.shop-search-icon{
  position:absolute; left:14px; color:var(--paper-dim); pointer-events:none;
}
.shop-search input{
  width:100%;
  font-family:var(--font-mono); font-size:12px; letter-spacing:.03em;
  padding:10px 34px 10px 36px;
  border:1px solid var(--paper-line); border-radius:20px;
  background:#fff; color:var(--paper-text);
  transition:border-color .25s, box-shadow .25s;
}
.shop-search input::placeholder{ color:var(--paper-dim); text-transform:uppercase; letter-spacing:.05em; }
.shop-search input:hover{ border-color:var(--paper-text); }
.shop-search input:focus{
  outline:none; border-color:var(--gold); box-shadow:0 0 0 3px rgba(201,162,39,.14);
}
/* Chrome adds its own clear affordance on type=search; we ship our own. */
.shop-search input::-webkit-search-decoration,
.shop-search input::-webkit-search-cancel-button{ -webkit-appearance:none; appearance:none; }
.shop-search-clear{
  position:absolute; right:8px;
  display:flex; align-items:center; justify-content:center;
  width:22px; height:22px; border:none; border-radius:50%;
  background:var(--paper-soft); color:var(--paper-dim);
  font-size:11px; cursor:pointer; transition:background .2s, color .2s;
}
.shop-search-clear:hover{ background:var(--ink); color:var(--gold-bright); }

.shop-count{
  margin-left:auto; align-self:center; flex-shrink:0;
  font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--paper-dim);
}

.shop-empty{
  grid-column:1 / -1; text-align:center; color:var(--paper-dim);
  padding:60px 20px; max-width:46ch; margin:0 auto;
}
.shop-more{ display:flex; justify-content:center; margin-top:44px; }

@media (max-width:640px){
  .shop-search{ flex-basis:100%; max-width:none; order:3; }
  .shop-count{ order:2; margin-left:auto; }
  /* The three group buttons plus their counts don't fit one phone row beside
     the dropdown, so the switch takes a row of its own and splits evenly. */
  .group-switch{ order:0; flex-basis:100%; }
  .group-btn{ flex:1; justify-content:center; padding:8px 6px; font-size:11px; letter-spacing:.02em; }
  .filter-dropdown{ order:1; }
}
@media (max-width:380px){
  .group-btn .count{ display:none; }
}

/* Fully fluid grid, no manual breakpoints and no view-mode toggle: a track
   is never asked to be narrower than min(45%, 260px), so at MOST 2 tracks
   ever fit below ~578px wide (2 x 45% = 90% of the row) — that's the "never
   fewer than 2 per row" floor. Above that, 260px is the per-card cap, so
   extra width just adds more columns automatically (3 around 780px+ in the
   1180px container, 4 on very wide screens) with no JS involved. */
.product-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(min(45%, 260px), 1fr));
  gap:26px;
}
@media (max-width:560px){
  /* At true phone widths two-up genuinely gets tight, so trim the card back
     to its essentials rather than shrinking everything proportionally. */
  .product-grid{ gap:14px; }
  .product-grid .product-body{ padding:12px 12px 14px; gap:5px; }
  .product-grid .product-body h3{ font-size:14px; }
  .product-grid .p-cn, .product-grid .p-meta{ display:none; }
  /* What made this look wrong was weight, not width: at 13px in a 40px block
     the button competed with the 14px title above it. It spans the tile
     again — a button hugging two words looked stunted — but at 11px in a
     33px block, so it sits clearly under the title instead of over it. The
     short label keeps 40px of air on either side at that width. */
  .product-grid .product-foot{ flex-direction:column; align-items:stretch; gap:8px; }
  .product-grid .add-btn{ font-size:11px; padding:6px 14px; text-align:center; }
}

.product-card{
  background:#fff; border:1px solid var(--paper-line); border-radius:var(--radius);
  overflow:hidden; display:flex; flex-direction:column;
  transition:box-shadow .3s, transform .3s var(--ease);
}
.product-card:hover{ box-shadow:var(--shadow-lg); transform:translateY(-4px); }
.product-media{
  aspect-ratio:4/3; position:relative; overflow:hidden;
  background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12));
  display:flex; align-items:center; justify-content:center;
}
.product-link{ color:inherit; }
.product-title-link{ color:inherit; transition:color .25s; }
.product-title-link:hover, .product-title-link:focus-visible{ color:var(--gold-dark); }
.product-media::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(120deg,transparent 30%, rgba(255,255,255,.18) 46%, transparent 60%);
  background-size:250% 250%; animation:swatchSheen 8s ease-in-out infinite;
}
.product-media .glyph{ font-family:var(--font-display); font-style:italic; font-size:30px; color:rgba(255,255,255,.8); z-index:1; }
/* Real product shots are on white, so the tile switches to a light ground
   and the sheen overlay is dropped — it only reads well over the gradients. */
.product-media.has-photo{ background:#fff; }
.product-media.has-photo::after{ display:none; }
.product-media img{
  width:100%; height:100%; object-fit:contain; padding:10px;
  transition:transform .5s var(--ease);
}
.product-card:hover .product-media img{ transform:scale(1.05); }
.product-tag{
  position:absolute; top:10px; left:10px; z-index:2;
  font-family:var(--font-mono); font-size:10px; letter-spacing:.06em; text-transform:uppercase;
  background:var(--ink); color:var(--gold-bright); padding:4px 8px; border-radius:2px;
}
.product-body{ padding:18px 18px 20px; display:flex; flex-direction:column; gap:8px; flex:1; }
.product-body .p-cat{ font-family:var(--font-mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--paper-dim); }
.product-body h3{ font-size:19px; }
.product-body .p-cn{ font-family:var(--font-mono); font-size:12px; color:var(--paper-dim); }
.product-body .p-meta{ font-size:13px; color:var(--paper-dim); margin:2px 0 8px; }
.product-foot{ margin-top:auto; display:flex; align-items:center; justify-content:space-between; gap:10px; }
.product-foot .p-price{ font-family:var(--font-mono); font-size:13px; color:var(--paper-text); }
.product-foot .p-price small{ display:block; color:var(--paper-dim); font-size:10px; text-transform:uppercase; letter-spacing:.05em; }
.add-btn{
  font-family:var(--font-body); font-size:13px; font-weight:500; letter-spacing:normal;
  /* "Toevoegen aan quote" is the longest label a tile carries and it broke
     over two lines on a phone, which made every card in the grid a different
     height. It stays on one line and the type gives way instead. */
  white-space:nowrap;
  background:var(--ink); color:var(--ink-text); border:none; padding:10px 16px;
  border-radius:8px; position:relative; overflow:hidden; isolation:isolate;
  transition:background .25s;
}
/* On a singles tile the button shares the row with the grade, and as a
   flex item it was giving up width to it and clipping its own label. The
   grade wraps instead — it is two short words and already did. Sealed tiles
   are unaffected: the :only-child rule below outranks this one. */
.add-btn{ flex:0 0 auto; }
/* The tile button carries both labels (see twoLabels() in main.js); the long
   one is the default and the short one only appears at the phone breakpoint. */
.add-btn .add-short{ display:none; }
/* Never narrower than its own words (13 Oct 2026). With min-width:0 the
   rarity block shrank below "Classic Collection" / "ZELDZAAMHEID" on a narrow
   tile and the text ran underneath the button. If the two do not fit side
   by side the row now wraps instead. */
.product-foot{ flex-wrap:wrap; }
.product-foot .p-price{ min-width:auto; flex:1 1 auto; }
/* A tile too narrow for rarity and button side by side stacks them, with
   the button across the full width, rather than waiting for the wrap. */
.product-card{ container-type:inline-size; }
@container (max-width: 400px){
  .product-foot{ flex-direction:column; align-items:stretch; gap:10px; }
  .product-foot .add-btn{ width:100%; text-align:center; }
}
.add-btn:hover{ background:var(--gold); color:#171204; }
.add-btn.added{ background:var(--gold); color:#171204; }
/* Sealed tiles no longer carry a MOQ, so the button is the whole footer row
   and takes the width the label used to sit in. A single keeps its rarity
   next to it, so there the button stays its own size. */
.product-foot .add-btn:only-child{ flex:1 1 auto; text-align:center; }
/* Out of stock: the tile still reads as a product, the button does not read
   as a thing to press. That dead button is the entire signal — no corner
   badge on top of it. */
.add-btn.is-soldout, .add-btn.is-soldout:hover{
  background:none; color:var(--paper-dim); border:1px solid var(--paper-line); cursor:not-allowed;
}
.btn.is-soldout, .btn.is-soldout:hover{
  background:none; color:var(--paper-dim); border:1px solid var(--paper-line);
  box-shadow:none; cursor:not-allowed; transform:none;
}
.product-soldout-note{
  margin:14px 0 0; max-width:52ch;
  font-size:12.5px; line-height:1.6; color:var(--paper-dim);
}

/* The loose-pack note (js/main.js §10b, shown for cat "pack" only).
   Deliberately quiet: a gold hairline and a tinted ground, no icon and no
   warning colour. It is not an alarm — it is the one thing about this product
   the buyer has to have read before ordering, and a box shouting at them
   reads as boilerplate to be skipped. Sits under the request button, so it
   keeps the same measure as the copy column above it. */
.product-note{
  display:block;
  margin:18px 0 0; max-width:56ch;
  padding:14px 16px;
  border-left:2px solid var(--gold);
  border-radius:0 4px 4px 0;
  background:var(--paper-soft);
}
.product-note b{
  display:block;
  font-size:13px; margin-bottom:4px;
  color:var(--paper-text);
}
.product-note p{
  margin:0;
  font-size:12.5px; line-height:1.65; color:var(--paper-dim);
}

/* Single cards ------------------------------------------------------------
   A single is a portrait card, not a boxy sealed product, so its tile is
   taller and keeps the dark gradient + sheen instead of the white ground used
   for sealed photography. `.is-card` therefore has to undo `.has-photo`
   (which the sync script also sets, since singles do carry an image): the card
   art is a full-bleed rectangle that reads far better floating on ink with a
   little light moving across it — the same foil language as the hero card. */
.product-media.is-card{ aspect-ratio:3/4; }
.product-media.has-photo.is-card{
  background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12));
}
.product-media.has-photo.is-card::after{ display:block; }
.product-media.is-card img{
  padding:16px;
  filter:drop-shadow(0 10px 18px rgba(0,0,0,.45));
}
/* The sheen sits above the artwork, so it must not swallow the click. */
.product-media.is-card::after{ pointer-events:none; }

@media (max-width:560px){
  .product-media.is-card img{ padding:9px; }
  /* Two tiles across leaves about 135px for the button. Rather than shrink
     the type and the padding to fit the long label — which is what made it
     look crammed — the label gives way and the button keeps the size and
     breathing room it has everywhere else. */
  .add-btn .add-long{ display:none; }
  .add-btn .add-short{ display:inline; }
  /* "Binnenkort terug" is the longest of the short labels and lands a pixel
     over at 375px, so that one state trades a little side padding. */
  .add-btn.is-soldout{ padding-left:8px; padding-right:8px; }
}

/* ==========================================================================
   Closing CTA card (homepage) — everything centred inside one white card.
   Padding, type size and measure all scale with the viewport so the card
   never gets cramped as the screen narrows.
   ========================================================================== */
/* The closing CTA sits on a positioned section so the mascot can be pinned
   to the bottom-right of the container without leaving the flow of the page
   or adding height to it.

   And it is a panel of the deck like any other: full width, exactly one
   screen tall, arrived at whole (js/main.js §6c, DECK_PANELS). It used to be
   a `section-pad` band of its own height, which made it the one full-bleed
   statement on the homepage that did not obey the deck's rule — the page
   could come to rest with the fold above it half on screen. Same treatment as
   .chain and .assure, and for the same reasons:
     · min-height, not height: a short window lets it grow rather than clip,
       and the guard in §8 reports it if it does (?panels=1);
     · every vertical measure in clamp(px, svh, px) — a bare vw reads the
       wrong axis and overflows a wide, short window;
     · the fixed header takes the top 77px of every panel, so a panel whose
       content can fill the screen reserves that height itself;
     · margin:auto on the inner box does the centring, which is what keeps
       the card in the middle of the screen at any height. */
.closing-section{
  --edge:#F6F4EE;
  position:relative;
  min-height:100svh;
  display:flex;
  padding:calc(77px + clamp(16px,3svh,48px)) 0 clamp(16px,3svh,48px);
}
.closing-section > .container{ margin:auto; width:100%; }
/* Gouden haarlijn op de naad met het foldscherm erboven — hetzelfde folie-
   accent dat de andere paneelnaden dragen. */
.closing-section::before{
  content:""; position:absolute; inset:0 0 auto; height:1px;
  background:linear-gradient(90deg,transparent,rgba(201,162,39,.45) 30%,rgba(201,162,39,.45) 70%,transparent);
}
.closing-mascot{
  position:absolute;
  /* Flush on the band's bottom edge. The sticker is cropped at the chest in
     the source art, so the only way it does not read as half a Pikachu is
     for that crop to coincide exactly with the edge of the section — then it
     is simply standing behind it. bottom:0, and nothing between.
     (It used to sit a browser-bar higher, var(--deck-bar), to clear Safari's
     toolbar while the band was pinned. Since 12 Oct 2026 the page scrolls
     freely below the last panel, so the band's real bottom edge is in view
     and the lift read as Pikachu floating above the seam.) */
  bottom:0;
  /* Lines up with the right edge of the container at every width without
     needing the container as its parent. */
  right:max(28px, calc((100% - var(--container)) / 2));
  width:clamp(170px, 19vw, 260px);
  height:auto;
  z-index:2;
  pointer-events:none;
  user-select:none;
  /* Grounded rather than floating: a short, tight shadow at the feet. */
  filter:drop-shadow(0 6px 10px rgba(0,0,0,.16));
}
@media (max-width:1080px){
  /* Below this the card fills the container, so the mascot steps out to the
     very edge of the screen and gives the copy its room back. */
  .closing-mascot{ width:clamp(150px, 17vw, 190px); right:8px; }
}
@media (max-width:760px){
  /* Phones: the card runs the full width, so there is no room *beside* it —
     the band gets a taller foot instead and the mascot stands in that strip,
     still flush on the bottom edge.
     The foot is derived from the mascot's own width rather than hard-coded:
     the sprite is 412x420, so its rendered height is width x 1.02, and the
     fixed 132px foot it used to have was shorter than that. The overflow
     went upwards, which put the mascot on top of the corner of the CTA card
     instead of beside it the way the desktop layout has it. Tying the two
     together keeps it clear of the card at every phone width — and it stays
     correct if the sprite is ever recut at a different aspect. */
  .closing-section{
    --mascot-w:clamp(120px, 36vw, 152px);
    padding-bottom:calc(var(--mascot-w) * 1.02 + 20px);
  }
  .closing-mascot{
    display:block;
    width:var(--mascot-w);
    /* Clear of the quote FAB's column rather than under it. The FAB is fixed
       in the bottom-right corner, and the bottom of this band is exactly
       where the two meet — at right:12px the button sat on Pikachu's face.
       Derived from the FAB's own geometry (18px inset + 52px button) so the
       two stay apart if either is resized. */
    right:calc(18px + 52px + 10px);
  }
}
@media (max-width:400px){
  /* Narrowest phones: smaller again, and the foot follows it down. */
  .closing-section{ --mascot-w:clamp(106px, 32vw, 128px); }
  .closing-mascot{ right:calc(18px + 52px + 4px); }
}

.closing-cta{
  background:#fff;
  border:1px solid var(--paper-line);
  border-radius:6px;
  /* Vertical padding reads both axes — see the panel rule on .closing-section:
     a bare vw would make this card grow on a wide, short window, which is the
     one shape where the panel has no room to give. */
  padding:clamp(30px, min(4.4vw,8svh), 72px) clamp(20px,4vw,52px);
  text-align:center;
}
.closing-cta h2{
  font-size:clamp(26px, min(3.2vw,6svh), 46px);
  /* Wide measure so the line breaks once, not twice — 24ch forced it onto
     three lines. `min(100%, …)` keeps it inside the padded card on narrow
     screens instead of overflowing. */
  max-width:min(100%, 30ch);
  margin:0 auto clamp(12px,2.2svh,22px);
  text-wrap:balance;          /* even line lengths where supported */
}
/* Second sentence of the margin statement — italic so the two halves read
   as a pair ("our margin" / "your margin"). The .shine variant keeps the
   gold sweep and must not have its clipped-text colour overridden here. */
.closing-cta h2 i{ font-style:italic; }
.closing-cta h2 i:not(.shine){ color:var(--gold); }
.closing-cta p{
  max-width:min(100%, 54ch);
  margin:0 auto clamp(16px,3svh,32px);
  color:var(--paper-dim);
}
/* Same grid as the hero actions: both buttons take the width of the widest
   label rather than each sizing to its own text. The card is text-align
   centred, so the inline-grid centres itself. */
.closing-cta-actions{
  display:inline-grid;
  grid-auto-flow:column;
  /* minmax(0,1fr), not 1fr: a bare 1fr floors each column at the button's
     min-content width, so on a narrow screen the pair kept its full size and
     the second button ("Ga naar catalogus") hung outside the card. */
  grid-auto-columns:minmax(0,1fr);
  /* Shrink-wrap the pair, but never past the card. */
  width:min(100%, max-content);
  gap:14px;
}
@media (max-width:560px){
  /* Side-by-side buttons get too tight to read below this — stack them
     rather than letting the labels squeeze. */
  .closing-cta-actions{ grid-auto-flow:row; width:100%; }
}

/* ==========================================================================
   Feature cards — three editorial doorways, revealed by a running Pokémon
   ==========================================================================
   Layer order inside a card (all siblings of the <a>, so nothing nests
   another interactive element):

     1  .fc-media   the photo, clipped away at rest and wiped open by JS
     2  .fc-veil    gradient that buys the copy its contrast
     3  .fc-mon…    the character — deliberately ABOVE the veil, so it reads
                    as running in front of the picture rather than inside it
     4  .fc-body    index / title / description
     5  .fc-cta     arrow, inside .fc-body

   Everything here is visible-by-default: the pre-reveal state (photo clipped,
   copy hidden, character parked off the card) only exists under .fc-ready,
   which JS adds once it knows it can animate. No JS, no GSAP or reduced
   motion → the cards render finished, and only the hover polish is lost.
   ========================================================================== */
.feature{
  background:var(--ink);
  color:var(--ink-text);
  /* Full width, full height — the same treatment the hero gets: the section
     is exactly the viewport, so coming out of the book's pinned sequence
     lands on a finished screen instead of on the top edge of one. It also
     makes the Poke Ball lid (inset:0 of this section) a full-screen shutter.
     Dynamic viewport units, unlike the hero's 100svh: this section is a snap
     target, and with a phone's toolbar hidden a static svh would leave a
     strip of the next (paper) section showing under a screen that is supposed
     to be filled. The cards below are stretched by the flex chain, so a
     toolbar sliding away resizes them rather than shifting anything.
     100svh first for browsers without dvh — never taller than the screen. */
  min-height:100svh;
  min-height:100dvh;
  display:flex;
  flex-direction:column;
  justify-content:center;
  /* The homepage header is position:fixed and 77px tall (see Header), and it
     takes no space in the flow — so a section that starts exactly at the top
     of the viewport starts *behind* it. The hero solves this with a padding-top
     of its own; this section, now that it is snapped flush to the top edge,
     has to do the same or its heading sits under the bar. */
  padding:calc(77px + clamp(20px,2.6vh,44px)) 0 clamp(40px,6vh,90px);
}
/* Heading first, cards take everything left over: the container grows into
   the section and hands the remaining height down to the grid, so the cards
   are sized by the viewport instead of the section being sized by them. */
.feature > .container{
  flex:1 1 auto;
  display:flex;
  flex-direction:column;
  justify-content:center;
  width:100%;
}
.feature-head{ margin-bottom:clamp(22px,3.4vh,52px); }
.feature-head h2{ font-size:clamp(26px,3.2vw,40px); max-width:20ch; }

/* Editorial, not a three-up grid: the catalogue card owns the left column at
   full height, the other two stack beside it, and the right column starts a
   step lower so the top edge never runs straight across. */
.feature-grid{
  display:grid;
  grid-template-columns:1.16fr 1fr;
  grid-template-rows:1fr 1fr;
  gap:clamp(14px,1.7vw,24px);
  align-items:stretch;
  /* Fills the height handed down by the container. The floor is what keeps a
     short laptop window readable: below it the section grows past the
     viewport rather than crushing the copy inside the cards. */
  flex:1 1 auto;
  min-height:min(430px,52vh);
}
.feature-card--lg{ grid-row:1 / span 2; min-height:0; }
.feature-card--sm{ min-height:0; }
/* The two columns run to exactly the same height: the left card spans both
   rows, the right column is two equal rows and the gap. The asymmetry in this
   composition comes from the column split itself — every card's copy starts at the same left
   edge, so the three read as one set rather than as a mirrored pair. */

.feature-card{
  position:relative;
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  padding:clamp(20px,2.3vw,34px);
  border:1px solid var(--ink-line);
  border-radius:6px;
  background:var(--ink-soft);
  color:var(--ink-text);
  isolation:isolate;
  /* The character is allowed to hang past the card edge — only the photo is
     clipped, by .fc-media below. */
  overflow:visible;
  transform:perspective(1400px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translate3d(0,var(--ty,0px),0);
  transform-style:preserve-3d;
  transition:transform .55s var(--ease), border-color .45s var(--ease),
             box-shadow .55s var(--ease), opacity .6s var(--ease);
}
.feature-card:hover{
  --ty:-5px;
  border-color:rgba(201,162,39,.42);
  box-shadow:0 34px 64px -34px rgba(0,0,0,.9);
}

/* ---------- 1. photo ---------- */
.fc-media{
  position:absolute; inset:0;
  z-index:1;
  border-radius:inherit;
  overflow:hidden;
  /* What sits behind the un-wiped part: a near-black plate with the faintest
     ledger rule, so a card waiting to be revealed still reads as a surface
     rather than a hole. */
  background:
    repeating-linear-gradient(115deg, rgba(201,162,39,.05) 0 1px, transparent 1px 26px),
    radial-gradient(120% 90% at 18% 8%, #171b22 0%, #0b0d11 62%);
}
.fc-img{
  width:100%; height:100%;
  object-fit:cover;
  /* Slight overscale at rest so the hover zoom has somewhere to go and never
     exposes an edge. */
  transform:scale(1.03);
  transition:transform .9s var(--ease);
}
.feature-card:hover .fc-img{ transform:scale(1.07); }

/* ---------- 2. veil ---------- */
.fc-veil{
  position:absolute; inset:0;
  z-index:2;
  border-radius:inherit;
  background:
    linear-gradient(to top, rgba(8,9,12,.95) 0%, rgba(8,9,12,.84) 24%, rgba(8,9,12,.44) 50%, rgba(8,9,12,.08) 78%),
    linear-gradient(to right, rgba(8,9,12,.60), rgba(8,9,12,0) 62%),
    /* A flat sit-down over the whole photo. The three shots are studio-lit on
       white and would otherwise read as three different brightnesses against
       the ink; this pulls them onto one level. */
    linear-gradient(rgba(8,9,12,.22), rgba(8,9,12,.22));
  transition:opacity .5s var(--ease);
}
.feature-card:hover .fc-veil{ opacity:.88; }

/* ---------- 3. character ---------- */
/* .fc-mon-track spans the whole card, so GSAP's xPercent/yPercent are
   percentages of the CARD, not of the sprite — the choreography stays the
   same at every breakpoint with no pixel maths anywhere.
   .fc-mon-anchor parks the sprite just off the left edge (right:100%), which
   is the zero point every travel value in FEATURE_MOTION is measured from,
   and carries the pointer parallax so it never fights the run.
   .fc-mon-bob carries the run cycle. Four elements, four jobs, no shared
   transform between them. */
/* The clip has to sit on a element that never moves: clip-path is resolved in
   the element's own coordinate space, so putting it on the track would drag
   the clip along with the character and clip nothing. Three sides are cut at
   the card edge, so a character can never end up stranded in the page gutter;
   the top is left open by 40px so a tall sprite or a leap still breaks the
   frame, which is what keeps the characters reading as a layer in front of
   the composition rather than something living inside it. */
.fc-mon-clip{
  position:absolute; inset:0;
  z-index:3;
  pointer-events:none;
  clip-path:inset(-40px 0 0 0 round 6px);
}
.fc-mon-track{ position:absolute; inset:0; }
.fc-mon-anchor{
  position:absolute;
  right:100%;
  bottom:var(--mon-bottom,10%);
  transform:translate3d(var(--mx,0px), var(--my,0px), 0);
  transition:transform .6s var(--ease);
}
.fc-mon-bob{ transform-origin:50% 100%; }
.fc-mon{
  height:var(--mon-h,140px);
  width:auto;
  max-width:none;
  transform:scaleX(var(--mon-flip,1));
  filter:drop-shadow(0 14px 18px rgba(0,0,0,.45));
}

/* ---------- 4/5. copy ---------- */
.fc-body{
  position:relative;
  z-index:4;
  max-width:34ch;
}
.fc-title{
  font-size:clamp(22px,2.4vw,34px);
  margin-bottom:10px;
  text-wrap:balance;
}
.feature-card--lg .fc-title{ font-size:clamp(26px,3vw,42px); }
.fc-desc{
  margin:0 0 18px;
  font-size:14px;
  line-height:1.5;
  color:rgba(236,234,227,.74);
  max-width:30ch;
}
.fc-cta{
  position:relative;
  z-index:5;
  display:inline-flex; align-items:center; gap:10px;
  font-family:var(--font-mono);
  font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-text);
  padding-bottom:4px;
  border-bottom:1px solid rgba(201,162,39,.45);
  transition:color .35s var(--ease), border-color .35s var(--ease);
}
.fc-cta svg{
  width:17px; height:17px; flex:0 0 auto;
  fill:none; stroke:currentColor; stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round;
  transition:transform .4s var(--ease);
}
.feature-card:hover .fc-cta{ color:var(--gold-bright); border-color:var(--gold-bright); }
.feature-card:hover .fc-cta svg{ transform:translateX(5px); }
.feature-card:focus-visible{ outline-offset:5px; }
.feature-card:focus-visible .fc-cta svg{ transform:translateX(5px); }

/* ---------- pre-reveal state (JS-gated) ---------- */
/* Only applied once .fc-ready is on the grid, i.e. once something is going to
   animate it back. The photo's own clip-path is set inline by JS from the
   same value that drives the character, so it is not repeated here. */
.fc-ready .feature-card{ --ty:26px; opacity:0; }
.fc-ready .feature-card.is-in{ --ty:0px; opacity:1; }
.fc-ready .fc-body > *{
  opacity:0;
  transform:translateY(14px);
  transition:opacity .6s var(--ease), transform .6s var(--ease);
}
.fc-ready .is-in .fc-body > *{ opacity:1; transform:none; }
/* Copy lands after the sprite has crossed, arrow last. */
.fc-ready .is-in .fc-body > *:nth-child(1){ transition-delay:.42s; }
.fc-ready .is-in .fc-body > *:nth-child(2){ transition-delay:.50s; }
.fc-ready .is-in .fc-body > *:nth-child(3){ transition-delay:.58s; }
.fc-ready .is-in .fc-body > *:nth-child(4){ transition-delay:.70s; }

/* ---------- responsive ---------- */
@media (max-width:1024px){
  /* Tablet: one column of three, still inside the one viewport — the three
     rows share the height the grid was handed instead of each card carrying
     its own. The reveal still plays, it just has less distance to cover. */
  .feature-grid{ grid-template-columns:1fr; grid-template-rows:repeat(3,1fr); gap:12px; }
  .feature-card--lg{ grid-row:auto; min-height:min(190px,23vh); }
  .feature-card--sm{ min-height:min(190px,23vh); }
  /* Three stacked cards plus a heading is the tightest fit of the three
     layouts, so the heading gives back the few pixels that decide whether
     the section still ends exactly at the bottom of the screen. */
  .feature-head{ margin-bottom:clamp(16px,2.2vh,40px); }
}

.feature-dots{ display:none; }

/* --------------------------------------------------------------------------
   Phones: one swipeable row instead of a stack.
   Three full-height cards stacked cost about 1150px of scrolling for what is
   essentially a three-item menu. Laid on one snapping row they cost ~380px,
   and each card keeps the full width of the screen — which the reveal needs,
   since the character has to have somewhere to run.

   Scroll-snap, not a JS carousel: the browser's own scrolling is smoother
   than anything scripted, works with a flick, and needs no state. The next
   card is deliberately left peeking past the right edge — that sliver is what
   tells a first-time visitor there is more here without a hint or an arrow.
   -------------------------------------------------------------------------- */
@media (max-width:640px){
  .feature-grid{
    display:flex;
    gap:12px;
    /* Full-bleed: the row scrolls edge to edge while the copy above it stays
       inside the container, which is what makes the peeking card read as "the
       row continues" rather than "the layout is broken". */
    margin-inline:calc(50% - 50vw);
    padding-inline:20px;
    overflow-x:auto;
    overflow-y:hidden;
    scroll-snap-type:x mandatory;
    scroll-padding-inline:20px;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
  }
  .feature-grid::-webkit-scrollbar{ display:none; }
  /* The three cards match each other (align-items:stretch), but the row
     itself does not grow into the leftover height — that is what keeps the
     cards card-shaped and lets the container centre the whole block. */
  .feature-grid{ align-items:stretch; flex:0 0 auto; }
  .feature-card{
    flex:0 0 86%;
    scroll-snap-align:start;
    /* A hidden card must not be reachable by Tab before its neighbours: the
       row is in DOM order, so this is already true — but the scroll container
       has to be told to bring a focused card into view. */
    min-height:0;
    padding:22px 20px 24px;
  }
  /* Card-sized, not screen-sized. The section is the full viewport, but the
     row is not: a card stretched to the whole height of a phone screen is a
     poster you have to scroll to read, and three of them in a row read as
     three long pages. It keeps roughly half the screen and the section
     centres it, which is what makes the framing read as deliberate.
     dvh so it tracks the toolbar the section is already sized against; the
     vh line above it is the fallback for browsers without dvh. */
  .feature-card--lg, .feature-card--sm{
    min-height:clamp(300px,56vh,520px);
    min-height:clamp(300px,56dvh,520px);
  }
  /* No entrance offset on a horizontal row: a card sliding up as it snaps in
     fights the scroll direction. */
  .fc-ready .feature-card{ --ty:0px; }
  .fc-body{ max-width:none; }
  .fc-desc{ max-width:34ch; }
  .feature-dots{
    display:flex; justify-content:center; gap:7px;
    margin-top:20px;
  }
  .feature-dots span{
    width:6px; height:6px; border-radius:50%;
    background:var(--ink-line);
    transition:background .3s var(--ease), width .3s var(--ease);
  }
  .feature-dots span.on{ width:20px; border-radius:3px; background:var(--gold); }
  /* Sprites shrink on phones so they stay a foreground detail rather than
     covering the copy they run past, and they run across the upper half of
     the card instead of along its floor: with one column the copy is full
     width down there, and a character resting on top of it is the one thing
     this section must never do. (!important because the height is an inline
     per-card variable — this is the breakpoint overriding the instance.) */
  .feature-card{ --mon-h:clamp(78px,22vw,112px) !important; }
  .feature-card--lg, .feature-card--sm{ --mon-bottom:51% !important; }
  .fc-mon{ filter:drop-shadow(0 8px 12px rgba(0,0,0,.45)); }
}

@media (prefers-reduced-motion: reduce){
  /* Nothing moves and nothing is hidden: no run, no wipe, no lift, no zoom,
     no 3D. The section is fully functional, just still. */
  .feature-card,
  .fc-img, .fc-veil, .fc-cta, .fc-cta svg, .fc-mon-anchor,
  .fc-ready .fc-body > *{ transition:none !important; }
  .feature-card{ --ty:0px !important; --rx:0deg !important; --ry:0deg !important; opacity:1 !important; }
  .fc-ready .fc-body > *{ opacity:1 !important; transform:none !important; }
  .feature-card:hover .fc-img{ transform:scale(1.03); }
}

/* ==========================================================================
   Forms — customs/ledger style
   ========================================================================== */
.form-shell{
  background:#fff; border:1px solid var(--paper-line); border-radius:6px;
  padding:44px; box-shadow:var(--shadow-lg);
}
@media (max-width:640px){ .form-shell{ padding:28px 22px; } }
.form-shell .form-title{ font-size:15px; }
.form-legend{
  font-family:var(--font-mono); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--gold); display:block; margin:30px 0 16px; padding-top:20px; border-top:1px solid var(--paper-line);
}
/* .is-first blijft bestaan voor een legenda die niet als eerste kind in zijn
   formulier staat maar er wel bovenaan uitkomt, bijvoorbeeld omdat het blok
   erboven verborgen is. Op de offertepagina is dat sinds de accountpoort niet
   meer nodig: het bedrijfsblok is daar weg. */
.form-legend:first-child, .form-legend.is-first{ margin-top:0; padding-top:0; border-top:none; }
.form-row{ display:grid; grid-template-columns:1fr 1fr; gap:18px; margin-bottom:18px; }
.form-row.single{ grid-template-columns:1fr; }
@media (max-width:600px){ .form-row{ grid-template-columns:1fr; } }
.field label{
  display:block; font-family:var(--font-mono); font-size:11px; text-transform:uppercase;
  letter-spacing:.06em; color:var(--paper-dim); margin-bottom:7px;
}
.field .hint{ font-size:12px; color:var(--paper-dim); margin-top:5px; }
.field input, .field select, .field textarea{
  width:100%; border:1px solid var(--paper-line); border-radius:3px;
  padding:12px 13px; background:var(--paper); color:var(--paper-text);
  transition:border-color .25s;
}
.field input:focus, .field select:focus, .field textarea:focus{ border-color:var(--paper-text); outline:none; }
.field textarea{ resize:vertical; min-height:96px; }

/* ----------------------------------------------------------------------
   Telefoonveld met landenkiezer
   ----------------------------------------------------------------------
   De knop en het invoerveld vormen samen één vak, zodat het er niet uitziet
   als twee losse velden die toevallig naast elkaar staan. Daarvoor krijgt de
   rand de wrapper in plaats van het <input>, en verliest het <input> de zijne.
   ---------------------------------------------------------------------- */
.phone-field{
  position:relative; display:flex; align-items:stretch;
  border:1px solid var(--paper-line); border-radius:3px;
  background:var(--paper); transition:border-color .25s;
}
.phone-field:focus-within{ border-color:var(--paper-text); }
.field .phone-field input[name="phone_local"]{
  border:0; border-radius:0; flex:1 1 auto; min-width:0; background:transparent;
}
.field .phone-field input:focus{ outline:none; }

.phone-country{
  display:flex; align-items:center; gap:7px; flex:0 0 auto;
  border:0; border-right:1px solid var(--paper-line); border-radius:3px 0 0 3px;
  background:var(--paper-soft); color:var(--paper-text);
  padding:0 12px; font:inherit; cursor:pointer; white-space:nowrap;
}
.phone-country::after{
  content:""; width:7px; height:7px; margin-left:1px;
  border-right:1.5px solid var(--paper-dim); border-bottom:1.5px solid var(--paper-dim);
  transform:translateY(-2px) rotate(45deg);
}
.phone-country:hover{ background:var(--paper); }
.phone-country:focus-visible{ outline:2px solid var(--gold); outline-offset:-2px; }
.phone-flag{ font-size:17px; line-height:1; }
.phone-dial{ font-variant-numeric:tabular-nums; }
/* Zolang er niets gekozen is leest het woord "Land" als een opdracht, niet als
   een waarde — vandaar de kleur van een placeholder. */
.phone-leeg{ color:var(--paper-dim); }

/* Nog geen land, maar wel al een nummer: dat moet te zien zijn vóór iemand op
   verzenden drukt. De browsermelding komt er bij het verzenden overheen. */
.phone-field.needs-country{ border-color:#B4451F; }
.phone-field.needs-country .phone-country{ background:rgba(180,69,31,.08); }

/* --- het uitklapvenster --- */
.phone-pop{
  /* Ruim onder de plakkende koptekst (200), ruim boven alles wat er verder
     in een formulier staat. Stond eerst op 40, wat te laag bleek zodra er iets
     zwevends naast kwam te liggen. */
  position:absolute; z-index:70; top:calc(100% + 5px); left:0;
  width:min(340px, 100%); max-width:100%;
  background:var(--paper); border:1px solid var(--paper-line); border-radius:4px;
  box-shadow:0 14px 34px rgba(13,15,18,.16);
  padding:8px; display:flex; flex-direction:column; gap:7px;
}
/* ZONDER DEZE REGEL STAAT HET PANEEL ALTIJD OPEN.
   De browser verbergt een element met het hidden-attribuut via zijn eigen
   stijlblad, en dat verliest het van élke stijlregel die hier staat — ook van
   `display:flex` hierboven. JavaScript zette `hidden` dus keurig, en er
   veranderde niets op het scherm. Vandaar dat het verbergen hier expliciet
   herhaald moet worden, ná de regel die display zet. */
.phone-pop[hidden]{ display:none; }
.field .phone-pop .phone-search{
  width:100%; border:1px solid var(--paper-line); border-radius:3px;
  padding:9px 11px; background:var(--paper); color:var(--paper-text);
}
.phone-list{
  list-style:none; margin:0; padding:0; max-height:252px; overflow-y:auto;
  overscroll-behavior:contain;
}
.phone-list li{
  display:flex; align-items:center; gap:10px;
  padding:8px 10px; border-radius:3px; cursor:pointer; font-size:14px;
}
.phone-list li.on{ background:var(--paper-soft); }
.phone-list li[aria-selected="true"] .phone-name{ font-weight:600; }
.phone-name{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.phone-list .phone-dial{ color:var(--paper-dim); font-size:13px; }
/* De streep onder de acht landen waar de klanten vandaan komen. Verdwijnt
   zodra er gezocht wordt, want dan is de indeling weg. */
.phone-list li[data-grens="1"]{
  border-bottom:1px solid var(--paper-line); border-radius:3px 3px 0 0;
  margin-bottom:5px; padding-bottom:11px;
}
.phone-list .phone-none{ padding:14px 10px; color:var(--paper-dim); font-size:13px; cursor:default; }

/* Op een telefoon is 340px al bijna de hele breedte; laat hem dan meelopen met
   het veld in plaats van eroverheen te steken. */
@media (max-width:520px){
  .phone-pop{ width:100%; }
  .phone-list{ max-height:46vh; }
}

.checkbox-row{ display:flex; gap:10px; align-items:flex-start; font-size:13px; color:var(--paper-dim); margin:22px 0; }
.checkbox-row input{ margin-top:3px; }
.form-note{
  font-size:12px; color:var(--paper-dim); background:var(--paper-soft); border-radius:3px;
  padding:14px 16px; margin-bottom:26px; border:1px dashed var(--paper-line);
}
.form-success{ text-align:center; padding:40px 10px; display:none; }
.form-success.show{ display:block; }

/* two-step account wizard */
.form-narrow{ max-width:580px; margin:0 auto; }
.wizard-steps{ display:flex; align-items:center; gap:14px; margin-bottom:32px; }
.wstep{ display:flex; align-items:center; gap:10px; font-size:14px; color:var(--paper-dim); white-space:nowrap; }
.wstep .num{
  width:28px; height:28px; border-radius:50%;
  background:var(--paper-soft); color:var(--paper-dim);
  display:flex; align-items:center; justify-content:center;
  font-family:var(--font-mono); font-size:13px;
  transition:background .3s, color .3s;
}
.wstep.active{ color:var(--paper-text); font-weight:600; }
.wstep.active .num{ background:var(--ink); color:var(--gold); }
.wizard-line{ flex:1; height:1px; background:var(--paper-line); }
.wpanel{ display:none; }
.wpanel.active{ display:block; }
.wizard-back{
  display:block; width:100%; margin-top:14px; padding:10px;
  background:none; border:none; color:var(--paper-dim);
  font-family:var(--font-mono); font-size:12px; letter-spacing:.06em; text-transform:uppercase;
}
.wizard-back:hover{ color:var(--paper-text); }
.form-success .stamp{
  display:inline-flex; align-items:center; justify-content:center;
  border:2px solid var(--red-stamp); color:var(--red-stamp);
  font-family:var(--font-mono); font-size:13px; letter-spacing:.1em; text-transform:uppercase;
  padding:10px 22px; border-radius:4px; transform:rotate(-4deg); margin-bottom:20px;
}

/* ==========================================================================
   Table (price list) — mono ledger
   ========================================================================== */
.ledger{ width:100%; border-collapse:collapse; font-size:14px; }
.ledger th{
  text-align:left; font-family:var(--font-mono); font-size:11px; text-transform:uppercase;
  letter-spacing:.08em; color:var(--paper-dim); padding:12px 14px; border-bottom:1px solid var(--paper-text);
}
.ledger td{ padding:16px 14px; border-bottom:1px solid var(--paper-line); vertical-align:top; }
.ledger tr:hover td{ background:var(--paper-soft); }
.ledger .num{ font-family:var(--font-mono); }
.ledger-wrap{ overflow-x:auto; border:1px solid var(--paper-line); border-radius:4px; }
.ledger-wrap .ledger{ min-width:720px; }
.price-product-link{ color:var(--paper-text); text-decoration:underline; text-decoration-color:rgba(184,137,43,.45); text-underline-offset:4px; transition:color .2s, text-decoration-color .2s; }
.price-product-link:hover, .price-product-link:focus-visible{ color:var(--gold-dark); text-decoration-color:currentColor; }

/* Product detail — the same catalogue information, given a calmer product-led canvas. */
.product-detail{ padding:54px 0 88px; min-height:calc(100vh - 76px); }
.product-back{ display:inline-flex; margin-bottom:30px; color:var(--paper-dim); font-family:var(--font-mono); font-size:11px; letter-spacing:.07em; text-transform:uppercase; }
.product-back:hover{ color:var(--gold-dark); }
.product-detail-grid{ display:grid; grid-template-columns:minmax(0,1.08fr) minmax(300px,.92fr); gap:clamp(34px,6vw,88px); align-items:start; }
.product-detail-media{ position:relative; aspect-ratio:1/1; border:1px solid var(--paper-line); border-radius:var(--radius); overflow:hidden; display:flex; align-items:center; justify-content:center; background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12)); box-shadow:var(--shadow-lg); }
.product-detail-media.has-photo{ background:#fff; }
/* A single keeps the ink ground rather than the white photo ground, so the
   detail hero matches the card tiles in the grid it was clicked from. */
.product-detail-media.has-photo.is-card{
  background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12));
}
.product-detail-media.is-card img{ filter:drop-shadow(0 14px 26px rgba(0,0,0,.5)); }
.product-detail-media img{ display:block; width:100%; height:100%; object-fit:contain; padding:28px; }
/* Losse kaart: kantelt in 3D naar de muis (js: initCardTilt). De wrapper
   krimpt om de kaartfoto heen, zodat glans en folie precies op de kaart
   liggen en niet op het lege vak eromheen. */
.product-detail-media.is-card{ perspective:1100px; }
.card-tilt{
  --rx:0deg; --ry:0deg; --px:50%; --py:50%; --sx:0px; --sy:16px; --lift:0;
  position:relative; display:block; height:calc(100% - 56px);
  border-radius:4.6% / 3.3%;
  transform:rotateX(var(--rx)) rotateY(var(--ry)) scale(calc(1 + var(--lift) * .045));
  will-change:transform;
}
.product-detail-media .card-tilt img{
  width:auto; height:100%; padding:0;
  filter:drop-shadow(var(--sx) calc(var(--sy) + var(--lift) * 10px) calc(26px + var(--lift) * 14px) rgba(0,0,0,.55));
}
.card-tilt-shine,
.card-tilt-glare{ position:absolute; inset:0; border-radius:inherit; pointer-events:none; opacity:0; }
/* Regenboogfolie: schuift diagonaal mee met de cursor, zoals holo onder een lamp. */
.card-tilt-shine{
  background:repeating-linear-gradient(115deg,
    hsl(0 90% 70%) 0%, hsl(50 95% 68%) 4%, hsl(140 80% 66%) 8%,
    hsl(195 90% 68%) 12%, hsl(275 85% 72%) 16%, hsl(0 90% 70%) 20%);
  background-size:260% 260%;
  background-position:var(--px) var(--py);
  mix-blend-mode:color-dodge;
  opacity:calc(var(--lift) * .2);
}
/* Lichtvlek: de lamp zelf, waar de cursor staat. */
.card-tilt-glare{
  background:radial-gradient(farthest-corner circle at var(--px) var(--py),
    rgba(255,255,255,.7) 0%, rgba(255,255,255,.18) 28%, rgba(0,0,0,.28) 90%);
  mix-blend-mode:overlay;
  opacity:calc(var(--lift) * .85);
}
.product-detail-media .glyph{ font-family:var(--font-display); font-size:clamp(44px,7vw,84px); color:rgba(255,255,255,.85); font-style:italic; text-align:center; padding:28px; }
.product-detail-copy{ display:flex; flex-direction:column; min-height:100%; }
.product-detail-copy h1{ margin:8px 0; font-size:clamp(38px,4.2vw,58px); line-height:.98; }
.product-detail-copy .p-cat, .product-detail-copy .p-cn{ font-family:var(--font-mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--paper-dim); }
.product-detail-copy .p-cn{ display:block; margin-top:8px; }
.product-detail-set{ color:var(--paper-dim); font-size:16px; line-height:1.6; margin:28px 0 0; max-width:42ch; }
.variant-switch{
  display:flex; gap:10px; margin:20px 0 28px; flex-wrap:wrap;
}
.variant-option{
  background:none; border:1px solid var(--paper-line); color:var(--paper-dim);
  font-family:var(--font-mono); font-size:11px; text-transform:uppercase; letter-spacing:.06em;
  padding:10px 22px; border-radius:999px; transition:color .2s, border-color .2s, background .2s;
}
/* The design switch can hold nine options, so it sits tighter than the
   two-option format switch above it. */
.design-switch{ gap:8px; margin:20px 0 12px; }
.design-switch .variant-option{ padding:8px 14px; }
.variant-option:hover{ color:var(--paper-text); border-color:var(--paper-text); }
.variant-option.active{ color:var(--gold); border-color:var(--gold); background:rgba(201,162,39,.1); }
.product-specs{ border-top:1px solid var(--paper-line); margin:0 0 30px; }
.product-specs div{ display:flex; justify-content:space-between; gap:20px; padding:14px 0; border-bottom:1px solid var(--paper-line); }
.product-specs dt{ color:var(--paper-dim); font-size:13px; }
.product-specs dd{ margin:0; font-family:var(--font-mono); font-size:12px; text-align:right; }
.product-detail-add.added{ background:var(--ink); color:var(--gold-bright); }
.product-not-found{ text-align:center; padding:84px 0; }
.product-not-found p{ color:var(--paper-dim); margin:14px auto 24px; }
.product-related{ margin-top:88px; padding-top:48px; border-top:1px solid var(--paper-line); }
.product-related h2{ font-size:clamp(22px,2.6vw,30px); margin-bottom:24px; }
@media (max-width:760px){
  .product-detail{ padding:34px 0 60px; }
  .product-detail-grid{ grid-template-columns:1fr; gap:30px; }
  .product-detail-media{ aspect-ratio:4/3; }
  .product-detail-media img{ padding:18px; }
  .card-tilt{ height:calc(100% - 36px); }
  .product-related{ margin-top:56px; padding-top:34px; }
}

/* ==========================================================================
   Contact page — ink/paper split
   --------------------------------------------------------------------------
   Twee gelijke helften die van de header tot de footer doorlopen. Links inkt,
   rechts papier; de naad is de kleurwissel en dus geen lijn.

   Alles wat hieronder een maat heeft, heeft er twee: een ondergrens voor een
   telefoon en een bovengrens waarboven verder rekken niets meer toevoegt.
   De breedtes lopen mee met vw, de verticale ruimte met vh. De eerste versie
   had vaste 400 en 460 px in een helft die op een breed scherm 720 px is:
   dat zette de inhoud als een smalle sliert tegen de middennaad met een gat
   ernaast, en dat leest als opgepropt. Nu vult de kolom zijn helft en is de
   marge eromheen een marge in plaats van een restje.
   ========================================================================== */
.contact-split{
  display:grid; grid-template-columns:1fr 1fr; align-items:stretch;
}
.contact-ink,
.contact-paper{
  display:flex; justify-content:center;
  padding:clamp(52px,7.5vh,108px) clamp(22px,4vw,72px);
}
/* Het inktvlak is een oppervlak en geen vlakke zwarte rechthoek. Twee zachte
   gouden lichtbronnen, linksboven de sterkste, over een verloop dat van
   grafiet naar bijna zwart zakt. Het goud in de hoeken is hetzelfde goud als
   de foil op de kaarten: het vlak krijgt er diepte van zonder dat er een
   plaatje aan te pas komt.

   Als variabele, want boven de 900px schuift het gordijn en schildert niet dit
   blok maar zijn ::after-laag. Die moet exact hetzelfde oppervlak hebben,
   anders zie je de naad bewegen. */
.contact-ink{
  --ink-surface:
    radial-gradient(110% 80% at 6% -4%, rgba(201,162,39,.16), transparent 58%),
    radial-gradient(85% 65% at 104% 104%, rgba(201,162,39,.09), transparent 62%),
    linear-gradient(158deg, #15171B 0%, #0D0E11 52%, #08090B 100%);
  background:var(--ink-surface); color:var(--ink-text);
}
.contact-paper{ background:var(--paper); }

/* --------------------------------------------------------------------------
   Het gordijn
   --------------------------------------------------------------------------
   Eén doek, niet twee. Het papier is een vel dat over een vaste zwarte
   ondergrond heen en weer schuift: ga je met de muis over het zwart, dan trekt
   het vel naar rechts weg en komt er meer zwart onderuit; ga je over het
   papier, dan schuift het vel naar links het zwart in. De inhoud van beide
   helften reist mee, die van de achterste helft ruim een derde zo ver. Dat
   verschil in afstand is wat het twee lagen maakt.

   Het was eerst één doek per helft die elkaar overlapten, met een z-index die
   omklapte zodra je de naad overstak. Dat klappen was de schok: het uitgeschoven
   zwart verdween in één beeld in plaats van terug te trekken. Nu is er maar één
   bewegend vlak en één vaste stapelvolgorde, dus valt er niets meer om te
   klappen en gaat de beweging altijd dezelfde kant op: rustig weg, rustig terug.

   De schaduw op de linkerrand van het vel staat er altijd en beweegt gewoon mee.
   Een schaduw die per beweging opkomt en weer wegvalt is duur om te tekenen en
   stottert; deze wordt één keer gezet en verder alleen verschoven.

   Alles wat beweegt is een transform, dus er wordt geen opmaak herberekend: de
   kop breekt niet anders, de velden verspringen niet. De curve start zacht en
   eindigt zacht (geen snelle uithaal aan het begin) en duurt bijna een seconde,
   want een gordijn heeft gewicht.

   Alleen voor een echte muis op een breed scherm. Op een aanraakscherm blijft
   :hover hangen na een tik, en gestapeld onder 900px heeft zijwaarts schuiven
   geen betekenis.
   -------------------------------------------------------------------------- */
@media (hover:hover) and (pointer:fine) and (min-width:901px){
  .contact-split{
    --curtain:clamp(28px,4vw,68px);
    --curtain-back:calc(var(--curtain) * .36);
    --curtain-ease:cubic-bezier(.4,0,.2,1);
    --curtain-time:.95s;
    /* clip, niet hidden: hidden zet overflow-y stilletjes op auto en zou
       daarmee de landenkiezer in het formulier afknippen. */
    overflow-x:clip;
  }
  /* Vaste stapelvolgorde: het papier ligt altijd op het zwart.

     De helften geven hun eigen achtergrondkleur hier op. Buiten deze media
     query dragen ze die wel, als terugval voor een browser zonder :has() —
     maar hierbinnen schilderden ze mee: de rechterhelft zette zijn beige over
     precies de strook waar het zwart naartoe schuift, en dan zie je het vel
     wegtrekken met een beige baan die blijft staan. Alleen de twee vellen
     hieronder schilderen nog. */
  .contact-ink{ position:relative; z-index:0; background:none; }
  .contact-paper{ position:relative; z-index:1; background:none; }

  /* De zwarte ondergrond. Staat stil en loopt twee gordijnbreedtes door onder
     het papier: één breedte is wat het vel maximaal wegtrekt, de tweede is
     speling zodat er op geen enkele zoomstand een haarlijn tussen valt. Het
     overschot ligt onder het vel en is dus nooit te zien. */
  .contact-ink::after{
    content:""; position:absolute; top:0; bottom:0;
    left:0; right:calc(var(--curtain) * -2);
    background:var(--ink-surface); z-index:0;
  }

  /* Het vel dat schuift. Het enige dat beweegt aan de achtergrond. */
  .contact-paper::before{
    content:""; position:absolute; top:0; bottom:0;
    left:calc(var(--curtain) * -1); right:calc(var(--curtain) * -1);
    background:var(--paper); z-index:0;
    box-shadow:-18px 0 48px -14px rgba(0,0,0,.5);
    transform:translateX(var(--curtain));
    transition:transform var(--curtain-time) var(--curtain-ease);
    will-change:transform;
  }

  /* Bewust géén will-change hier. Dat zet deze blokken permanent op een eigen
     laag, en daar valt subpixel-antialiasing weg: de lichte tekst op het zwart
     werd er in rust zichtbaar vaal van. Het vel hieronder mag het wel hebben,
     dat is een vlak zonder letters. */
  .contact-ink-inner, .contact-paper-inner{
    position:relative; z-index:1;
    transition:transform var(--curtain-time) var(--curtain-ease);
  }

  /* Muis op zwart: het vel trekt naar rechts weg, beide helften schuiven mee. */
  .contact-split:has(.contact-ink:hover) .contact-paper::before{
    transform:translateX(calc(var(--curtain) * 2));
  }
  .contact-split:has(.contact-ink:hover) .contact-ink-inner{
    transform:translateX(var(--curtain));
  }
  .contact-split:has(.contact-ink:hover) .contact-paper-inner{
    transform:translateX(var(--curtain-back));
  }

  /* Muis op papier: het vel schuift het zwart in, weer beide helften mee. */
  .contact-split:has(.contact-paper:hover) .contact-paper::before{
    transform:translateX(0);
  }
  .contact-split:has(.contact-paper:hover) .contact-paper-inner{
    transform:translateX(calc(var(--curtain) * -1));
  }
  .contact-split:has(.contact-paper:hover) .contact-ink-inner{
    transform:translateX(calc(var(--curtain-back) * -1));
  }
}
@media (prefers-reduced-motion:reduce){
  .contact-ink::after, .contact-paper::before{ display:none; }
  .contact-ink-inner, .contact-paper-inner{ transform:none !important; }
}
.contact-ink-inner,
.contact-paper-inner{ width:100%; max-width:clamp(340px,44vw,660px); }

/* De binnenkant van het inktvlak is een kolom met de kop bovenaan en de
   kanalen onderaan. Op een breed scherm staat het paneel strak tussen header
   en footer en bleef er anders een halve schermhoogte zwart onder de gegevens
   hangen; nu zakken die naar de onderrand en is het vlak een compositie in
   plaats van een stapel met een gat. */
.contact-ink-inner{
  display:flex; flex-direction:column; justify-content:space-between;
  gap:clamp(36px,5vh,72px);
}
.contact-ink h1{
  font-size:clamp(34px,4vw,58px); line-height:1.04; margin:0;
  letter-spacing:-.01em; text-wrap:balance;
}
.contact-lede{
  color:var(--ink-dim); font-size:clamp(15px,1.15vw,17px); line-height:1.6;
  margin:clamp(14px,1.8vh,22px) 0 0; max-width:38ch;
}

/* Onderin de kanalen, de code en de registratie. Één kolom zolang de helft
   smal is: bij 901px is dat nog geen 400px en dan knijpt een code ernaast de
   regels tot losse letters. Pas als er echt ruimte is schuift de code naar
   rechts, en dan staan de twee blokken met hun onderrand gelijk. */
.contact-ink-foot{
  display:grid; grid-template-columns:minmax(0,1fr);
  column-gap:clamp(26px,3vw,56px); row-gap:clamp(20px,2.6vh,30px);
  align-items:end;
}
.contact-channels-block, .contact-qr-block, .contact-reg{ grid-column:1; }
.contact-qr-block{ display:flex; flex-direction:column; gap:12px; align-items:flex-start; }
@media (min-width:1200px){
  .contact-ink-foot{ grid-template-columns:minmax(0,1fr) auto; }
  .contact-qr-block{ grid-column:2; grid-row:1; }
  .contact-reg{ grid-column:1 / -1; }
}

.contact-ink-label{
  display:block; font-family:var(--font-mono); font-size:11px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--gold); margin-bottom:clamp(10px,1.4vh,16px);
}

/* --------------------------------------------------------------------------
   Kanaalregels
   --------------------------------------------------------------------------
   De ledgerregels van het dashboard, maar dan als tikdoel: haarlijn boven en
   onder, een merkje links, het kanaal met zijn reactietijd in het midden en
   een pijl rechts. De <a> vult de hele regel, dus het doel is de regel en niet
   het adres erin. Minimaal 60px hoog, want dit is op een telefoon het eerste
   wat iemand doet die niet wil typen.
   -------------------------------------------------------------------------- */
.contact-channels{
  list-style:none; margin:0; padding:0;
  display:grid; gap:10px;
}
.contact-channel > a{
  display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center;
  gap:clamp(12px,1.6vw,16px); min-height:62px; padding:12px 14px;
  color:var(--ink-text); text-decoration:none;
  background:rgba(255,255,255,.04);
  border:1px solid rgba(255,255,255,.07);
  border-radius:14px;
  -webkit-tap-highlight-color:transparent;
  transition:background-color .22s var(--ease), border-color .22s var(--ease),
             transform .22s var(--ease), box-shadow .22s var(--ease);
}
.contact-channel > a:hover{
  background:rgba(255,255,255,.065); border-color:rgba(201,162,39,.45);
  transform:translateY(-1px); box-shadow:0 12px 28px -18px rgba(0,0,0,.9);
}
.contact-channel > a:active{ transform:translateY(0); background:rgba(255,255,255,.08); }
.contact-channel > a:focus-visible{ outline:2px solid var(--gold); outline-offset:2px; }
.channel-mark{
  display:flex; align-items:center; justify-content:center;
  width:38px; height:38px; flex:0 0 auto;
  border:1px solid rgba(201,162,39,.25); border-radius:11px;
  background:rgba(201,162,39,.1); color:var(--gold);
  transition:border-color .25s, background-color .25s;
}
.contact-channel > a:hover .channel-mark{ border-color:rgba(201,162,39,.6); background:rgba(201,162,39,.18); }
.channel-text{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.channel-name{
  font-size:15px; line-height:1.3; overflow-wrap:break-word;
}
.channel-meta{ font-family:var(--font-mono); font-size:11.5px; color:var(--ink-dim); }
.channel-go{
  flex:0 0 auto; color:var(--ink-dim);
  transition:transform .25s var(--ease), color .25s;
}
.contact-channel > a:hover .channel-go{ color:var(--gold); transform:translateX(3px); }

/* Registratie: één regel onder de streep, geen eigen hoofdstuk. */
.contact-reg{
  display:flex; flex-wrap:wrap; gap:6px 18px; margin:0;
  font-family:var(--font-mono); font-size:11.5px; color:var(--ink-dim);
}

/* De code houdt zijn witte vlak: een scanner leest een omgekeerde QR niet
   betrouwbaar, dus wordt hij hier een kaartje op het donker in plaats van
   een uitgesneden vorm. */
.contact-qr{
  display:block; width:clamp(142px,11vw,176px); padding:11px; background:#fff;
  border-radius:4px; box-shadow:0 18px 40px -22px rgba(0,0,0,.6);
  transition:transform .3s var(--ease);
}
.contact-qr img{ width:100%; height:auto; display:block; }
.contact-qr:hover{ transform:translateY(-2px); }
.contact-fact-note{ font-family:var(--font-mono); font-size:12px; color:var(--ink-dim); max-width:30ch; }


/* --------------------------------------------------------------------------
   Velden als sleuven in het papier
   --------------------------------------------------------------------------
   Een rand én een vulling tegelijk is de oude formulierlook: dan ligt er een
   doosje op het vel. Hier is de rand weg en doet het materiaal het werk. De
   vulling is duidelijk dieper dan het papier eronder en krijgt bovenin een
   haarfijne binnenschaduw, zodat de balk een uitgespaarde regel in het vel
   lijkt in plaats van een vorm die erop getekend is.

   De ronding is 10px, dezelfde als .btn. Daarmee horen de velden en de
   gouden knop eronder bij elkaar in plaats van bij twee verschillende jaren.

   Drie toestanden, alle drie via box-shadow zodat er niets verspringt:
     rust      binnenschaduw bovenin
     ingevuld  een gedempte gouden streep links, zodat je in één oogopslag
               ziet wat af is (:valid pakt hier alleen omdat elk veld
               required is)
     focus     wit papier, een gouden haarlijn rondom en diezelfde streep op
               vol goud. De cursor op de ledgerregel.

   Alleen binnen .contact-form: /quote en /account houden hun eigen velden.
   -------------------------------------------------------------------------- */
.contact-form .field input,
.contact-form .field textarea{
  border:0; border-radius:10px;
  background:#EAE5D7; color:var(--paper-text);
  padding:17px 16px; font-size:16px;
  box-shadow:inset 0 0 0 0 var(--gold), inset 0 1px 2px rgba(23,24,27,.08);
  transition:background-color .25s var(--ease), box-shadow .3s var(--ease);
}
.contact-form .field input::placeholder,
.contact-form .field textarea::placeholder{ color:var(--paper-dim); opacity:.9; }
.contact-form .field input:hover,
.contact-form .field textarea:hover{ background:#E5DFCF; }

/* Ingevuld en goedgekeurd: de streep blijft staan als afvinkteken. */
.contact-form .field input:required:valid,
.contact-form .field textarea:required:valid{
  box-shadow:inset 3px 0 0 0 rgba(201,162,39,.45), inset 0 1px 2px rgba(23,24,27,.08);
}

.contact-form .field input:focus,
.contact-form .field textarea:focus,
.contact-form .field input:required:valid:focus,
.contact-form .field textarea:required:valid:focus{
  background:#FFFDF7; outline:none;
  box-shadow:inset 3px 0 0 0 var(--gold), inset 0 0 0 1px rgba(201,162,39,.55),
             0 0 0 3px rgba(201,162,39,.12);
}

/* Chrome zet zijn eigen blauwe vulling op een automatisch ingevuld veld en
   luistert alleen naar een box-shadow van 100px. Die neemt de plek in van de
   schaduw hierboven, dus staat de gouden streep er hier opnieuw in. */
.contact-form .field input:-webkit-autofill,
.contact-form .field input:-webkit-autofill:hover,
.contact-form .field input:-webkit-autofill:focus{
  -webkit-box-shadow:inset 3px 0 0 0 rgba(201,162,39,.45), inset 0 0 0 100px #FFFDF7;
  -webkit-text-fill-color:var(--paper-text);
}

.contact-form .field textarea{ min-height:clamp(120px,18vh,190px); line-height:1.6; }
.contact-form .form-row{ margin-bottom:clamp(16px,2.2vh,26px); gap:clamp(16px,2vw,26px); }

/* Het telefoonveld is één balk waarin de landkeuze en de cijfers samen zitten.
   De opmaak zit daarom op de wrapper en het <input> erbinnen is doorzichtig,
   anders liggen er twee vullingen over elkaar. */
.contact-form .phone-field{
  border:0; border-radius:10px; background:#EAE5D7;
  box-shadow:inset 0 0 0 0 var(--gold), inset 0 1px 2px rgba(23,24,27,.08);
  transition:background-color .25s var(--ease), box-shadow .3s var(--ease);
}
.contact-form .phone-field:hover{ background:#E5DFCF; }
.contact-form .phone-field:focus-within{
  background:#FFFDF7;
  box-shadow:inset 3px 0 0 0 var(--gold), inset 0 0 0 1px rgba(201,162,39,.55),
             0 0 0 3px rgba(201,162,39,.12);
}
.contact-form .field .phone-field input,
.contact-form .field .phone-field input:hover,
.contact-form .field .phone-field input:focus,
.contact-form .field .phone-field input:required:valid{
  background:transparent; box-shadow:none; border-radius:0; padding-left:14px;
}
/* De scheiding met de landkeuze is een haarlijn in de sleuf zelf, geen tweede
   rand: bij focus zou een vaste beige lijn dwars door het goud heen lopen. */
.contact-form .phone-country{
  background:none; border-right:1px solid rgba(23,24,27,.1); border-radius:10px 0 0 10px;
  padding-left:16px;
}
.contact-form .form-legend{ margin-top:clamp(26px,3.6vh,46px); }

/* De knop loopt over dezelfde breedte als de balken erboven, met dezelfde
   ronding. Onder een rij sleuven die tot de marge doorlopen is een knop op
   woordbreedte een losse rest. */
.contact-form .btn-block{ display:flex; width:100%; margin-top:16px; padding:17px 34px; }

/* Windows-contrastmodus gooit elke box-shadow weg, en daar hangt hier alles
   aan: zonder deze regels zouden de velden daar helemaal geen omtrek meer
   hebben. Dan komt de rand terug en wordt de focus een echte outline. */
@media (forced-colors: active){
  .contact-form .field input,
  .contact-form .field textarea,
  .contact-form .phone-field{ border:1px solid; }
  .contact-form .field input:focus,
  .contact-form .field textarea:focus,
  .contact-form .phone-field:focus-within{ outline:2px solid Highlight; outline-offset:1px; }
}

/* Zodra het formulier weg is en de bevestiging ervoor in de plaats komt, is
   er niets meer waarmee die bovenaan uitgelijnd hoeft te staan: het formulier
   begon op de hoogte van de kop links, de bevestiging heeft daar geen partner
   meer. Dan hangt hij midden in zijn eigen helft. */
.contact-paper:has(.form-success.show){ align-items:center; }
.contact-paper-inner .form-success{ padding:0 10px; }

/* --------------------------------------------------------------------------
   Telefoon: één kolom
   --------------------------------------------------------------------------
   Hier stonden de twee helften achter elkaar in een pager die je met je vinger
   verschoof, met een tabbalk erboven. Dat kostte meer dan het opleverde: de
   <h1> zat op het tweede paneel, dus je landde op een formulier zonder kop en
   de enige aanwijzing dat er meer was, was een knop met het woord Gegevens.
   Het inktblok werd daar 879px hoog op een scherm van 812 voor vier regels
   informatie, en de QR nam er 142px in beslag voor een code die je op de
   telefoon zelf niet kunt scannen.

   Nu is het gewoon een kolom die je scrollt: kop, kanalen, formulier. Het
   inktblok is nog een derde van wat het was en de kop staat weer op de plek
   waar je binnenkomt. Het gordijn blijft boven de 900px, want daar heeft een
   naad tussen twee helften nog betekenis.
   -------------------------------------------------------------------------- */
@media (max-width:900px){
  .contact-split{ display:block; }
  .contact-ink-inner, .contact-paper-inner{ max-width:min(100%,600px); }

  /* Strakker dan op een breed scherm. De verticale lucht van het paneel is
     daar bedoeld om een halve schermhoogte te vullen; hier staat alles onder
     elkaar en is diezelfde marge gewoon een gat. */
  .contact-ink{ padding:clamp(30px,4.6vh,42px) 22px clamp(26px,3.6vh,34px); }
  .contact-paper{ padding:clamp(26px,3.4vh,34px) 22px clamp(46px,6vh,66px); }
  .contact-ink-inner{ gap:clamp(26px,3.4vh,34px); }

  /* Geen twee kolommen meer: de code is hier weg, dus de kanalen krijgen de
     volle breedte en de registratie blijft eronder staan. */
  .contact-ink-foot{ grid-template-columns:minmax(0,1fr); }
  .contact-channels-block, .contact-reg{ grid-column:1; }
  .contact-qr-block{ display:none; }

  /* Een tik is geen muisaanwijzer: de regel moet hoger zijn dan op een
     scherm waar je precies kunt richten. */
  .contact-channel > a{ min-height:64px; }
}

/* ==========================================================================
   FAQ
   --------------------------------------------------------------------------
   Vijftien vragen onder elkaar met een dunne streep ertussen is een lijst;
   dit is een naslagwerk. Vandaar de drie lagen: een balk met onderwerpen en
   een zoekveld, kaarten in plaats van regels, en een eigen adres per vraag.

   De opmaak is die van de catalogus en niet iets nieuws: dezelfde pillen van
   20px met mono in kapitalen, hetzelfde zoekveld, dezelfde witte kaart met
   --radius en --paper-line, dezelfde telling rechts. Een klant die van /shop
   hierheen loopt moet niet het gevoel hebben dat hij op een andere site is
   beland. De donkere band bovenaan is onveranderd .page-hero.bg-ink, precies
   zoals "Huidig groothandelsaanbod".

   Alles staat altijd in de broncode. Het filteren gebeurt met een class op de
   kaart en niet door hem te verwijderen, zodat een crawler elke vraag en elk
   antwoord blijft zien en het FAQPage-schema klopt met wat er staat.
   ========================================================================== */
.faq-body{ padding-top:0; }

/* De balk is de shop-toolbar met andere inhoud: onderwerpen, zoekveld,
   telling. Niet plakkend, want de catalogusbalk is dat ook niet. */
.faq-bar{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:12px 16px; padding:36px 0 0; margin-bottom:34px;
}
.faq-chips{
  display:inline-flex; align-items:stretch; flex-wrap:wrap;
  border:1px solid var(--paper-line); border-radius:20px;
  background:#fff; padding:3px; gap:2px;
}
.faq-chip{
  display:inline-flex; align-items:center; gap:7px;
  font-family:var(--font-mono); font-size:12px; text-transform:uppercase; letter-spacing:.05em;
  padding:7px 14px; border:none; border-radius:16px;
  background:none; color:var(--paper-dim); cursor:pointer; white-space:nowrap;
  transition:background .25s, color .25s;
}
.faq-chip:hover{ color:var(--paper-text); }
.faq-chip-n{ font-size:10px; opacity:.7; }
.faq-chip.is-active{ background:var(--ink); color:var(--gold-bright); }
.faq-chip.is-active .faq-chip-n{ opacity:.85; }
.faq-chip:focus-visible{ outline:2px solid var(--gold); outline-offset:2px; }
/* Een onderwerp waar het zoekwoord niets in achterlaat, is een doodlopende
   knop. Hij blijft staan zodat de balk niet verspringt, maar hij is dof. */
.faq-chip.is-empty{ opacity:.38; }

.faq-search{
  position:relative; display:flex; align-items:center;
  flex:1 1 200px; max-width:380px;
}
.faq-search-icon{ position:absolute; left:14px; color:var(--paper-dim); pointer-events:none; }
.faq-search input{
  width:100%;
  font-family:var(--font-mono); font-size:12px; letter-spacing:.03em;
  padding:10px 34px 10px 36px;
  border:1px solid var(--paper-line); border-radius:20px;
  background:#fff; color:var(--paper-text);
  transition:border-color .25s, box-shadow .25s;
}
.faq-search input:focus{
  outline:none; border-color:var(--gold); box-shadow:0 0 0 3px rgba(201,162,39,.14);
}
.faq-search input::-webkit-search-decoration,
.faq-search input::-webkit-search-cancel-button{ -webkit-appearance:none; appearance:none; }
/* Een display uit een class wint van het hidden-attribuut, dus staat die
   regel er expliciet bij. Zonder dat staat het kruisje er al voordat er iets
   getypt is. */
.faq-search-clear[hidden]{ display:none; }
.faq-search-clear{
  position:absolute; right:8px;
  display:flex; align-items:center; justify-content:center;
  width:22px; height:22px; border:none; border-radius:50%;
  background:var(--paper-soft); color:var(--paper-dim);
  font-size:13px; line-height:1; cursor:pointer;
  transition:background .2s, color .2s;
}
.faq-search-clear:hover{ background:var(--ink); color:var(--gold-bright); }

.faq-tools{ display:flex; align-items:center; gap:18px; margin-left:auto; flex-shrink:0; }
.faq-count{
  font-size:11px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--paper-dim); white-space:nowrap;
}
.faq-toggle-all{
  background:none; border:0; padding:0; cursor:pointer;
  font-size:11px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--paper-text);
  border-bottom:1px solid var(--paper-line); transition:border-color .2s, color .2s;
}
.faq-toggle-all:hover{ color:var(--gold-dark); border-color:var(--gold); }

.faq-group{ margin-bottom:clamp(30px,4vh,48px); }
.faq-group:last-child{ margin-bottom:0; }
.faq-group-h{
  display:flex; align-items:baseline; gap:10px;
  font-family:var(--font-mono); font-size:11px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--paper-dim);
  margin:0 0 12px; font-weight:400;
}
.faq-group-n{ color:var(--gold); }

/* --------------------------------------------------------------------------
   De kaart
   --------------------------------------------------------------------------
   Dezelfde kaart als een catalogustegel: wit, --radius, een haarlijn van
   --paper-line, en bij aanwijzen --shadow-lg. Het optillen van de tegel zit
   er niet in: een productkaart tilt op omdat je hem aanklikt om ergens heen te
   gaan, deze klapt open op zijn plek.

   Open krijgt hij een gouden rand, zodat je in een lijst van vijftien ziet
   welke je openzette.
   -------------------------------------------------------------------------- */
.qa{
  background:#fff; border:1px solid var(--paper-line); border-radius:var(--radius);
  margin-bottom:8px; overflow:hidden;
  /* Ruimte voor de header, zodat een vraag die via #faq-q9 wordt aangesproken
     er niet onder eindigt. */
  scroll-margin-top:calc(var(--header-h,64px) + 20px);
  transition:border-color .25s, box-shadow .3s;
}
.qa:last-child{ margin-bottom:0; }
/* Aanwijzen zet een gouden streep op de linkerkant, hetzelfde teken als een
   veld in focus op /contact: dit is de regel waar je nu bent. Een inset-
   schaduw en geen border, want een border verandert de breedte van de kaart
   en zou de vraag een paar pixels laten verspringen; deze ligt binnen de rand
   en volgt de ronding mee. De nul-versie staat in de ruststand zodat de
   streep in- en uitschuift in plaats van aan- en uitspringen. */
.qa{ box-shadow:inset 0 0 0 0 var(--gold); }
.qa:hover, .qa:focus-within{
  box-shadow:inset 3px 0 0 0 var(--gold), var(--shadow-lg);
}
.qa.open{ border-color:rgba(201,162,39,.55); }
.qa-h{ margin:0; font-size:inherit; font-weight:inherit; }
.qa-q{
  width:100%; display:flex; align-items:flex-start; justify-content:space-between; gap:18px;
  padding:19px 20px; background:none; border:0; cursor:pointer; text-align:left;
  font-family:var(--font-display); font-size:clamp(16px,1.35vw,18.5px); line-height:1.35;
  color:var(--paper-text); -webkit-tap-highlight-color:transparent;
}
.qa-q:focus-visible{ outline:2px solid var(--gold); outline-offset:-2px; }
.qa-sign{
  flex:0 0 auto; margin-top:2px; color:var(--gold);
  transition:transform .35s var(--ease);
}
.qa.open .qa-sign{ transform:rotate(135deg); }

/* De hoogte komt van het script (--qa-h = gemeten inhoud) en niet van een
   vast maximum: het langste antwoord hier is de vijf stappen van het
   bestelproces, en dat liep op een telefoon tegen de oude limiet aan. Een
   antwoord dat half afgekapt opent leest als een bug.

   visibility doet het tabwerk: dicht staat het paneel niet alleen op nul maar
   is het ook onbereikbaar, anders tabt een toetsenbord door links in
   antwoorden die niemand ziet. */
.qa-a{
  max-height:0; overflow:hidden; visibility:hidden;
  transition:max-height .38s var(--ease), visibility 0s linear .38s;
}
.qa.open .qa-a{
  max-height:var(--qa-h,1200px); visibility:visible;
  transition:max-height .38s var(--ease), visibility 0s;
}
.qa-a-in{ padding:0 20px 20px; }
.qa-a-in p{ color:var(--paper-dim); line-height:1.7; margin:0; max-width:70ch; }
.qa-a-in p mark{ background:rgba(201,162,39,.28); color:inherit; border-radius:2px; padding:0 1px; }
.qa-text mark{ background:rgba(201,162,39,.32); color:inherit; border-radius:2px; padding:0 1px; }

/* Een eigen adres per vraag: sales stuurt een klant het antwoord door in
   plaats van "staat in de FAQ, scroll maar". Dezelfde pil als de balk. */
.qa-copy{
  display:inline-flex; align-items:center; gap:7px; margin-top:16px;
  padding:7px 12px; border:1px solid var(--paper-line); border-radius:20px;
  background:#fff; color:var(--paper-dim); cursor:pointer;
  font-family:var(--font-mono); font-size:11px; letter-spacing:.05em;
  text-transform:uppercase;
  transition:border-color .25s, color .25s, background-color .25s;
}
.qa-copy:hover{ border-color:var(--paper-text); color:var(--paper-text); }
.qa-copy.is-done{ background:var(--ink); border-color:var(--ink); color:var(--gold-bright); }
.qa-copy:focus-visible{ outline:2px solid var(--gold); outline-offset:2px; }

/* Aangesprongen via een link uit een mailtje: even oplichten, dan weer rustig,
   zodat je ziet wélke van de vijftien bedoeld werd. */
@keyframes qaFlash{
  0%   { box-shadow:0 0 0 0 rgba(201,162,39,.45); }
  100% { box-shadow:0 0 0 14px rgba(201,162,39,0); }
}
.qa.is-targeted{ animation:qaFlash 1.1s var(--ease) 1; }
@media (prefers-reduced-motion:reduce){
  .qa.is-targeted{ animation:none; }
  .qa-a, .qa.open .qa-a{ transition:none; }
}

.qa.is-hidden, .faq-group.is-hidden{ display:none; }

/* Dezelfde lege staat als een catalogus zonder treffers. */
.faq-empty{ text-align:center; color:var(--paper-dim); padding:60px 20px; }
.faq-empty-h{ font-family:var(--font-display); font-size:20px; color:var(--paper-text); margin:0 0 8px; }
.faq-empty-term{ color:var(--gold-dark); }
.faq-empty-p{ margin:0 auto 22px; max-width:46ch; }

/* Het slotblok is een inktkaart en geen alinea met een knop eronder: na een
   lijst van witte kaarten is een donker vlak het einde van de pagina. Vlak
   inkt, dezelfde kleur als de band bovenaan. */
.faq-cta{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:22px 32px; margin-top:clamp(40px,6vh,72px);
  padding:clamp(26px,3.6vh,40px) clamp(24px,3vw,44px); border-radius:var(--radius);
  background:var(--ink); color:var(--ink-text);
}
.faq-cta h2{ font-size:clamp(20px,2.2vw,27px); margin:0 0 7px; line-height:1.2; }
.faq-cta p{ color:var(--ink-dim); margin:0; max-width:46ch; font-size:15px; }
/* Twee knoppen van gelijke maat. De tweede stond op .btn-outline-light, en dat
   is de variant voor papier: donkere rand, donkere letter. Op inkt was hij
   daardoor pas te zien als je eroverheen ging. */
.faq-cta-btns{ display:flex; flex-wrap:wrap; gap:12px; }
.faq-cta-btns .btn{ min-width:190px; justify-content:center; padding:16px 28px; }

@media (max-width:760px){
  .faq-bar{ gap:12px; padding-top:28px; }
  /* Op een telefoon passen vijf onderwerpen niet op een rij. Ze schuiven
     zijwaarts in plaats van over drie regels te vallen. */
  .faq-chips{
    flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
    max-width:100%; justify-content:flex-start;
  }
  .faq-chips::-webkit-scrollbar{ display:none; }
  .faq-chip{ flex:0 0 auto; }
  .faq-search{ flex-basis:100%; max-width:none; }
  .faq-tools{ width:100%; margin-left:0; justify-content:space-between; }
  .qa-q{ padding:17px 16px; }
  .qa-a-in{ padding:0 16px 18px; }
  .faq-cta{ flex-direction:column; align-items:flex-start; }
  .faq-cta-btns{ width:100%; }
  .faq-cta-btns .btn{ flex:1 1 100%; min-width:0; }
}

/* WhatsApp is het afsprakenkanaal voor afhalen, dus die link moet in een
   alinea meteen te vinden zijn. Huisstijlgoud met een dikkere onderstreping,
   zwaarder dan de gewone tekstlink eromheen; de selectorlijst is er om de
   specifiekere paginaregels (.legal-copy p a) te overstemmen. */
a.wa-link,
.legal-copy p a.wa-link,
.faq-a p a.wa-link{
  color:var(--gold);
  font-weight:700;
  text-decoration:underline;
  text-decoration-thickness:2px;
  text-underline-offset:3px;
  text-decoration-color:rgba(201,162,39,.5);
  white-space:nowrap;
  transition:color .18s ease, text-decoration-color .18s ease;
}
a.wa-link:hover,
.legal-copy p a.wa-link:hover,
.faq-a p a.wa-link:hover{ color:var(--gold-bright); text-decoration-color:var(--gold-bright); }
a.wa-link:focus-visible{ outline:2px solid var(--gold-bright); outline-offset:3px; border-radius:2px; }

/* Afhalen of verzenden — twee keuzekaarten in plaats van kale radio's, omdat
   het verschil zit in wat eronder staat (adres, betaalmoment) en niet in het
   woord ernaast. De invoer zelf blijft een echte radio: toetsenbord, screen
   reader en het formulier zien gewoon een radiogroep. */
.choice-row{ display:grid; gap:12px; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); }
/* Compact gehouden: dit is een keuze van twee regels tekst, geen kaart om in
   te lezen. Elke pixel hier staat tussen de klant en de verstuurknop. */
.choice{
  display:flex; gap:10px; align-items:flex-start;
  padding:12px 14px; cursor:pointer;
  border:1px solid var(--paper-line); border-radius:12px;
  background:#fff;
  transition:border-color .18s ease, box-shadow .18s ease, background .18s ease;
}
.choice:hover{ border-color:var(--paper-text); }
.choice input[type=radio]{
  appearance:none; -webkit-appearance:none;
  flex:0 0 auto; width:18px; height:18px; margin:2px 0 0; border-radius:50%;
  border:1.5px solid var(--paper-line); background:#fff; cursor:pointer;
  transition:border-color .18s ease, box-shadow .18s ease;
}
.choice input[type=radio]:checked{ border-color:var(--gold); box-shadow:inset 0 0 0 4px var(--gold); }
.choice input[type=radio]:focus-visible{ outline:2px solid var(--gold-bright); outline-offset:2px; }
.choice:has(input[type=radio]:checked){ border-color:var(--gold); background:rgba(201,162,39,.06); }
.choice-body{ display:flex; flex-direction:column; gap:2px; }
.choice-title{ font-weight:700; font-size:14px; color:var(--paper-text); }
.choice-sub{ font-size:12px; line-height:1.45; color:var(--paper-dim); }

/* ==========================================================================
   Wishlist
   --------------------------------------------------------------------------
   Eén hartje, drie plekken. Op de catalogustegel ligt het als ronde knop op
   de foto (.wish-tile), op de productpagina staat het als vierkant naast de
   offerteknop (.wish-solo), en op de wishlistpagina zelf is het weer de
   tegelknop — daar haalt een klik het product eraf in plaats van erop.

   Gevuld hart = op de lijst. De omtrek blijft in beide standen even zwaar,
   dus de tegel verspringt niet bij het aan- en uitzetten.
   ========================================================================== */
.wish-btn{
  background:none; border:0; padding:0; cursor:pointer; color:var(--paper-dim);
  display:inline-flex; align-items:center; justify-content:center;
  transition:color .25s, background .25s, border-color .25s, transform .15s var(--ease);
}
.wish-btn svg{ fill:none; transition:fill .25s; }
.wish-btn:hover, .wish-btn:focus-visible{ color:var(--gold); }
.wish-btn.on{ color:var(--gold); }
.wish-btn.on svg{ fill:currentColor; }
.wish-btn:active{ transform:scale(.88); }

/* De tegel is een <article> zonder eigen positionering; het hartje hangt
   erin en niet in de <a> eromheen — een knop in een link is ongeldige HTML
   en op een telefoon een gok welke van de twee je raakt. */
.product-card{ position:relative; }
.wish-tile{
  position:absolute; top:10px; right:10px; z-index:2;
  width:32px; height:32px; border-radius:50%;
  background:rgba(255,255,255,.92); box-shadow:0 1px 5px rgba(0,0,0,.18);
}
.wish-tile:hover, .wish-tile:focus-visible{ background:#fff; }

/* De rij onder de specs: de gouden offerteknop plus de bewaarknop. Die twee
   moeten als paar lezen, dus de bewaarknop houdt geen eigen vaste hoogte maar
   rekt mee met de gouden knop (align-items:stretch). Verandert de padding van
   .btn ooit, dan volgt deze mee in plaats van er 4px naast te gaan staan.

   De offerteknop mag groeien, de bewaarknop niet: anders duwt het lange
   Nederlandse label de twee op een telefoon op aparte regels. */
.product-detail-actions{ display:flex; align-items:stretch; gap:12px; flex-wrap:nowrap; container-type:inline-size; }
/* min-width:max-content is hier geen luxe: .btn heeft overflow:hidden voor zijn
   foil-sweep, en een flexitem met overflen anders dan visible krijgt van de
   flexbox min-width:0 mee. Zonder deze regel krimpt de knop onder zijn eigen
   label en knipt hij "Toevoegen aan quote-lij" af. */
.product-detail-add{ flex:1 1 auto; min-width:max-content; }
.wish-solo{
  flex:0 0 auto;
  width:auto; height:auto; padding:0 20px; gap:9px;
  border:1px solid var(--paper-line); border-radius:10px; background:#fff;
  /* Dezelfde letter als .btn — een hartje met een woord erin is een knop,
     geen icoon, en hoort in de knoptypografie te staan. */
  color:var(--paper-text);
  font-family:var(--font-body); font-size:15px; font-weight:500; white-space:nowrap;
  /* Dezelfde lift en dezelfde timing als .btn: twee knoppen naast elkaar die
     anders reageren lezen weer als twee losse dingen. De foil-sweep krijgt hij
     bewust niet — dat is het gebaar van de primaire knop. */
  transition:color .25s, background .3s, border-color .3s, transform .35s var(--ease);
}
.wish-solo svg{ width:20px; height:20px; }
.wish-solo:hover, .wish-solo:focus-visible{ transform:translateY(-2px); }
.wish-solo:hover, .wish-solo:focus-visible, .wish-solo.on{ border-color:var(--gold); }
.wish-solo.on{ background:rgba(201,162,39,.06); }
/* Een knop met een woord erin die indrukt hoort terug te zakken; de .88-krimp
   van het tegelhartje is daarvoor te veel. */
.wish-solo:active{ transform:translateY(0); }
/* Geen ruimte meer voor het woord: terug naar alleen het hartje, nog steeds op
   knophoogte. De 18px links en rechts maken het vierkant — samen met het icoon
   van 20px komt dat op 58px uit, precies de hoogte die .btn hier haalt. Een
   aspect-ratio doet hier niets: de breedte van een flexitem komt uit zijn
   inhoud, niet uit de uitgerekte hoogte.

   Bewust een @container en geen @media: of het woord past hangt af van de
   breedte van de kolom, niet van die van het venster. Op een telefoon is de
   kolom het hele scherm en past het net niet; op een tablet in twee kolommen
   is de kolom smaller dán op die telefoon. Eén vensterbreedte kan dus beide
   kanten op vallen — vandaar dat de knoprij zichzelf meet. */
@container (max-width:400px){
  .product-detail-actions{ gap:10px; }
  .wish-solo{ padding:0 18px; }
  .wish-solo-label{ display:none; }
}

/* Het hartje in de header, naast het mandje. Eigen badgeklasse en niet
   .quote-badge: updateCartBadges() schrijft in álle .quote-badge en zou er
   anders het aantal quote-regels in zetten. */
.header-wish .wish-badge{ position:absolute; top:-3px; right:-7px; width:15px; height:15px; font-size:9px; }
.wish-badge{
  background:var(--gold); color:#171204; font-family:var(--font-mono);
  font-size:10px; font-weight:700; border-radius:50%;
  width:17px; height:17px; display:inline-flex; align-items:center; justify-content:center;
}
@media (max-width:940px){
  .main-nav .nav-mobile-action .wish-badge{ margin-left:auto; }
}

/* Wishlistpagina */
.wishlist-bar{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  flex-wrap:wrap; margin-bottom:26px;
}
.wishlist-count{
  font-family:var(--font-mono); font-size:11px; letter-spacing:.06em;
  text-transform:uppercase; color:var(--paper-dim);
}

/* ==========================================================================
   Quote/cart page
   ========================================================================== */
.cart-table{ width:100%; border-collapse:collapse; margin-bottom:10px; }
.cart-table th{ text-align:left; font-family:var(--font-mono); font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--paper-dim); padding:10px 12px; border-bottom:1px solid var(--paper-text); }
.cart-table td{ padding:16px 12px; border-bottom:1px solid var(--paper-line); }
.cart-item{ display:flex; align-items:center; gap:14px; }
.cart-item .swatch{
  width:52px; height:52px; border-radius:3px; flex-shrink:0; overflow:hidden;
  background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12));
}
.cart-item .swatch:has(img){ background:#fff; border:1px solid var(--paper-line); }
.cart-item .swatch img{ width:100%; height:100%; object-fit:contain; padding:3px; }
.cart-item .name{ font-size:14px; }
.cart-item .cat{ font-family:var(--font-mono); font-size:11px; color:var(--paper-dim); }
.qty-input{ width:60px; padding:8px; border:1px solid var(--paper-line); border-radius:3px; font-family:var(--font-mono); text-align:center; }
/* De − en + horen bij de telefoon: daar is de native spinner van een
   number-input een paar pixels hoog en niet te raken. Op desktop staan ze er
   wel in de markup maar niet op het scherm, zodat de spinner het werk doet en
   de kolom smal blijft. */
.qty-field{ display:flex; align-items:center; gap:0; }
.qty-step{ display:none; }
.remove-btn{ font-family:var(--font-mono); font-size:11px; text-transform:uppercase; color:var(--paper-dim); background:none; border:none; }
.remove-btn:hover{ color:var(--red-stamp); }
@media (max-width:640px){
  /* MOQ is secondary info (it's shown per-product in the shop already) —
     drop that column so item name + qty + remove all fit on a phone. */
  .cart-table .col-moq{ display:none; }
  .cart-table td, .cart-table th{ padding:14px 6px; }
  .cart-item{ gap:10px; }
  .cart-item .name{ font-size:13px; }
  .qty-field{
    border:1px solid var(--paper-line); border-radius:4px;
    background:#fff; overflow:hidden; width:max-content;
  }
  .qty-step{
    display:block; width:32px; height:34px; padding:0;
    border:none; background:none; color:var(--paper-dim);
    font-family:var(--font-mono); font-size:16px; line-height:34px;
    text-align:center; cursor:pointer; -webkit-tap-highlight-color:transparent;
  }
  .qty-step:active{ background:var(--paper-line); color:var(--paper-text); }
  .qty-step[disabled]{ opacity:.3; cursor:default; }
  .qty-input{
    width:34px; padding:6px 0; height:34px;
    border:none; border-left:1px solid var(--paper-line); border-right:1px solid var(--paper-line);
    border-radius:0; background:none; -moz-appearance:textfield;
  }
  /* Anders staat de native spinner náást de eigen knoppen. */
  .qty-input::-webkit-outer-spin-button,
  .qty-input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
  .remove-btn{ font-size:15px; padding:6px; }
}
.cart-empty{ text-align:center; padding:70px 20px; }
.cart-empty p{ color:var(--paper-dim); margin:14px 0 26px; }
.gate-note{
  border:1px dashed var(--gold); background:rgba(201,162,39,.07); border-radius:4px;
  padding:22px 24px; margin-bottom:34px; display:flex; gap:16px; align-items:flex-start;
}
.gate-note .g-ico{ font-family:var(--font-mono); color:var(--gold); font-size:20px; }
.gate-note h4{ font-family:var(--font-body); font-weight:700; font-size:14px; margin:0 0 6px; }
.gate-note p{ margin:0; font-size:13px; color:var(--paper-dim); }
.gate-note a{ color:var(--gold); text-decoration:underline; }

/* Both ways out of this notice are buttons, but deliberately not .btn: a
   second filled gold block on a page that already has one ("naar catalogus")
   makes the page shout twice. These are outlined and smaller — same mono
   lettering, so they still belong to the site, but they sit inside the note
   instead of competing with the page's own call to action. Hierarchy between
   the two is carried by the border: gold for the one we want taken, a plain
   paper rule for the one that is only for people who already registered. */
.gate-note .gate-actions{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px 12px; margin-top:16px;
}
.gate-note .gate-btn{
  display:inline-flex; align-items:center; justify-content:center;
  padding:10px 18px; border-radius:8px;
  position:relative; overflow:hidden; isolation:isolate;
  font-family:var(--font-body); font-size:13px; font-weight:500; text-decoration:none;
  /* A touch darker than --paper-dim: at 11px uppercase that grey sits right on
     the edge of AA against the note's tinted background. */
  border:1px solid var(--paper-line); background:transparent; color:#4A4943;
  transition:background .3s, border-color .3s, color .3s;
}
.gate-note .gate-btn:hover{ border-color:var(--paper-text); color:var(--paper-text); }
.gate-note .gate-btn-primary{
  border-color:var(--gold); background:rgba(201,162,39,.12); color:var(--paper-text);
  font-weight:500;
}
.gate-note .gate-btn-primary:hover{ background:rgba(201,162,39,.22); border-color:var(--gold); }
@media (max-width:520px){
  .gate-note .gate-actions{ gap:8px; }
  .gate-note .gate-btn{ flex:1 1 100%; }
}

/* ==========================================================================
   Customer dashboard
   ==========================================================================
   Built almost entirely out of what the rest of the site already has: the
   quote lines reuse .ledger-wrap/.ledger from the price list, the notices
   reuse .gate-note from the quote page, the empty state reuses .cart-empty.
   What is genuinely new is the status pill, the quote list, and the
   confirmation dialog in front of accepting an offer.
   ========================================================================== */
.dash-loading{ color:var(--paper-dim); font-family:var(--font-mono); font-size:13px; padding:40px 0; }

/* --------------------------------------------------------------------------
   The shell — written for the phone first
   --------------------------------------------------------------------------
   Around nine in ten customers open this page on a phone, so the phone layout
   is the one written without a media query and the desktop sidebar is the
   variant. That ordering is not decoration: it is what keeps the mobile rules
   short and the desktop overrides few, instead of a wide layout being undone
   piece by piece at every breakpoint.

   Three consequences run through everything below.

   1. Navigation is a bottom tab bar, inside thumb reach, not a sidebar folded
      into a hamburger. The sidebar markup exists too — both are rendered —
      and which one is on screen is one media query.
   2. Anything tappable is at least 44px on its shortest side, and the two
      controls that get hit most (buy-again's "+", the tab bar) are round or
      full-height targets with the tap highlight taken off.
   3. Nothing scrolls sideways except where a sideways scroll is the point
      (new arrivals). The quote-line table, which is a genuine table on a
      desktop, becomes a stack of cards below 640px rather than a 720px-wide
      thing in a scroller.

   `env(safe-area-inset-bottom)` appears wherever something is pinned to the
   bottom: on an iPhone the home indicator sits in that strip and a tab bar
   without it has its labels half-covered.
   -------------------------------------------------------------------------- */
.dash-main{ background:var(--paper); }
/* padding-block, not the padding shorthand: this element is also .container,
   whose 28px of horizontal padding is what keeps the dashboard lined up with
   the header and the footer. A shorthand here would silently zero it. */
.dash-shell{ padding-block:20px calc(78px + env(safe-area-inset-bottom)); }
.dash-body{ min-width:0; }

@media (min-width:900px){
  .dash-shell{ padding-block:38px 90px; }

  /* De twee kolommen alleen als er ook werkelijk een zijbalk staat.
     `dash-has-nav` wordt door renderDashboard() op de body gezet en er weer
     afgehaald: een account dat nog beoordeeld wordt of geblokkeerd is krijgt
     geen menu, want er zit nog niets achter.

     Zonder deze voorwaarde bleef de kolom van 238px plus het gat van 44px
     staan als er niets in stond, en schoof de melding 141px naar rechts uit
     het midden. Op het scherm zag dat eruit als een pagina die scheef hangt —
     en het was precies de eerste pagina die een nieuwe klant te zien krijgt.
     De klasse bestond al en werd alleen nergens gelezen behalve door één
     regel voor de footer. */
  body.dash-has-nav .dash-shell{
    display:grid; grid-template-columns:238px minmax(0,1fr);
    gap:44px; align-items:start;
  }
}

/* --- the sidebar (desktop) --- */
.dash-nav{ display:none; }
@media (min-width:900px){
  .dash-nav{ display:block; position:sticky; top:88px; }
}
.dash-nav-group{
  font-family:var(--font-mono); font-size:10px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--paper-dim);
  margin:24px 0 8px; padding-left:12px;
}
.dash-nav-group:first-child{ margin-top:0; }

/* Every item carries a transparent border so the active one can take a real
   border without moving its neighbours by a pixel. */
.dash-nav-item{
  display:flex; align-items:center; gap:11px;
  padding:9px 12px; border:1px solid transparent; border-radius:5px;
  font-size:14px; color:var(--paper-text);
  transition:background .18s ease, border-color .18s ease;
}
.dash-nav-item svg{ width:18px; height:18px; flex:0 0 18px; color:var(--paper-dim); }
.dash-nav-item.is-active{ background:#fff; border-color:var(--paper-line); font-weight:600; }
.dash-nav-item.is-active svg{ color:var(--gold); }
@media (hover:hover){ .dash-nav-item:hover{ background:var(--paper-soft); } }
.dash-nav-badge{
  margin-left:auto; font-family:var(--font-mono); font-size:10px;
  color:var(--paper-dim); background:var(--paper-soft);
  border-radius:999px; padding:2px 8px; line-height:1.5;
}
.dash-nav-item .dash-nav-out{ width:13px; height:13px; margin-left:auto; opacity:.45; }

/* --- the tab bar (phone) ---
   Fixed to the bottom, five columns, and deliberately opaque: a translucent
   bar means a blur pass on every scroll frame, and this bar is on screen for
   the whole session. */
.dash-tabs{
  position:fixed; left:0; right:0; bottom:0; z-index:90;
  display:grid; grid-template-columns:repeat(5,1fr);
  background:#fff; border-top:1px solid var(--paper-line);
  padding-bottom:env(safe-area-inset-bottom);
}
@media (min-width:900px){ .dash-tabs{ display:none; } }
.dash-tab{
  position:relative;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  min-height:58px; padding:9px 3px 7px;
  background:none; border:0;
  font-family:var(--font-mono); font-size:9px; letter-spacing:.05em;
  text-transform:uppercase; color:var(--paper-dim);
  touch-action:manipulation; -webkit-tap-highlight-color:transparent;
}
.dash-tab svg{ width:22px; height:22px; }
.dash-tab.is-active{ color:var(--paper-text); }
.dash-tab.is-active svg{ color:var(--gold); }
.dash-tab.is-active::before{
  content:""; position:absolute; top:0; left:50%; transform:translateX(-50%);
  width:26px; height:2px; background:var(--gold);
}
.dash-tab:active{ background:var(--paper-soft); }
/* A dot, not a count: at this size a two-digit badge is a smudge, and what a
   glance at the bar needs to say is "something is waiting", not how much. */
.dash-tab-dot{
  position:absolute; top:8px; left:calc(50% + 7px);
  width:8px; height:8px; border-radius:50%;
  background:var(--gold); border:1.5px solid #fff;
}

/* --- the "More" sheet (phone) --- */
.dash-sheet-backdrop{
  position:fixed; inset:0; z-index:95;
  background:rgba(13,15,18,.5);
  opacity:0; transition:opacity .25s ease;
}
.dash-sheet-backdrop.is-open{ opacity:1; }
.dash-sheet{
  position:fixed; left:0; right:0; bottom:0; z-index:96;
  background:#fff; border-radius:16px 16px 0 0;
  padding:8px 14px calc(16px + env(safe-area-inset-bottom));
  box-shadow:0 -22px 50px -24px rgba(13,15,18,.5);
  transform:translateY(100%);
  transition:transform .3s var(--ease);
}
.dash-sheet.is-open{ transform:translateY(0); }
.dash-sheet[hidden], .dash-sheet-backdrop[hidden]{ display:none; }
.dash-sheet-grip{
  display:block; width:38px; height:4px; border-radius:999px;
  background:var(--paper-line); margin:4px auto 12px;
}
.dash-sheet .dash-nav-item{ min-height:52px; font-size:15px; }
.dash-sheet-close{
  width:100%; min-height:48px; margin-top:8px;
  background:var(--paper-soft); border:1px solid var(--paper-line); border-radius:6px;
  font-family:var(--font-mono); font-size:11px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--paper-dim);
  touch-action:manipulation;
}
@media (min-width:900px){ .dash-sheet, .dash-sheet-backdrop{ display:none !important; } }

/* The footer would otherwise end underneath the tab bar. Lifted only while the
   dashboard's navigation is actually on screen. (This used to lift the floating
   basket button too; that button is gone — a wholesale catalogue is not a
   webshop and did not need one hovering over every page.) */
@media (max-width:899px){
  body.dash-has-nav .site-footer{ padding-bottom:calc(74px + env(safe-area-inset-bottom)); }
}

/* --- greeting --- */
.dash-greet{ margin-bottom:20px; }
.dash-greet h1{
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(27px,7vw,38px); line-height:1.1; letter-spacing:-.01em;
  margin:0 0 6px;
}
.dash-greet p{ margin:0; font-size:14px; color:var(--paper-dim); }
.dash-wave{ font-size:.72em; }

.dash-h1{ font-family:var(--font-display); font-weight:400; font-size:clamp(25px,6.5vw,34px); line-height:1.12; margin:0 0 6px; }
.dash-lead{ margin:0 0 18px; font-size:14px; color:var(--paper-dim); max-width:62ch; }

/* --- KPI tiles ---
   Two by two on a phone, because four across at 375px gives each tile 84px
   and a number that has to shrink to fit is not a number anyone reads. Each
   tile is also the shortest route into the section it summarises. */
.kpi-row{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:10px; margin-bottom:16px;
}
@media (min-width:760px){ .kpi-row{ grid-template-columns:repeat(4,minmax(0,1fr)); gap:14px; } }
.kpi{
  position:relative; overflow:hidden;
  display:flex; flex-direction:column; gap:2px;
  padding:15px 14px; background:#fff;
  border:1px solid var(--paper-line); border-radius:7px;
  color:var(--paper-text);
}
a.kpi{ transition:border-color .2s ease, transform .16s var(--ease); touch-action:manipulation; -webkit-tap-highlight-color:transparent; }
a.kpi:active{ transform:scale(.985); }
@media (hover:hover){ a.kpi:hover{ border-color:var(--paper-text); } }
.kpi-value{ font-size:clamp(24px,7.5vw,30px); line-height:1.05; }
.kpi-label{ font-size:12.5px; font-weight:600; line-height:1.3; margin-top:2px; }
.kpi-sub{ font-size:11px; line-height:1.35; color:var(--paper-dim); }
/* The one tile that means "do something" is the only one with colour on it. */
.kpi-alert{ border-color:rgba(201,162,39,.55); }
.kpi-alert::before{ content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--gold); }
.kpi-alert .kpi-value{ color:#8a6d10; }

/* --- the banner for a quote waiting on a decision --- */
.dash-action{
  display:flex; flex-direction:column; gap:14px;
  padding:20px; margin-bottom:16px;
  background:var(--ink); color:var(--ink-text); border-radius:9px;
}
.dash-action h2{ font-family:var(--font-body); font-size:16px; font-weight:700; margin:0 0 6px; }
.dash-action p{ margin:0; font-size:13.5px; line-height:1.5; color:var(--ink-dim); }
.dash-action .btn{ align-self:stretch; text-align:center; }
.dash-action-more{ color:var(--gold); }
@media (min-width:760px){
  .dash-action{ flex-direction:row; align-items:center; justify-content:space-between; gap:26px; padding:22px 26px; }
  .dash-action .btn{ align-self:auto; flex:0 0 auto; }
}

/* --- panels --- */
.dash-panel{
  background:#fff; border:1px solid var(--paper-line); border-radius:9px;
  padding:18px 16px; margin-bottom:14px;
}
@media (min-width:760px){ .dash-panel{ padding:24px 26px; margin-bottom:18px; } }
.dash-panel-head{ display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:10px 14px; margin-bottom:14px; }
.dash-panel-head h2{ font-family:var(--font-body); font-size:15px; font-weight:700; margin:0; }
.dash-panel-link{
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.07em;
  text-transform:uppercase; color:var(--gold); white-space:nowrap;
}
.dash-panel-sub{ margin:-8px 0 14px; font-size:12.5px; color:var(--paper-dim); }
.dash-panel-note{ margin:14px 0 0; font-size:11.5px; line-height:1.55; color:var(--paper-dim); }
.dash-panel-foot{
  display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap;
  margin-top:16px; padding-top:14px; border-top:1px solid var(--paper-line);
}
.dash-panel-foot .btn{ flex:1 1 auto; text-align:center; }
@media (min-width:520px){ .dash-panel-foot .btn{ flex:0 0 auto; } }
.dash-count{ font-family:var(--font-mono); font-size:11px; color:var(--paper-dim); }
/* The right-hand end of a panel head, when there is a stamp as well as a link.
   Wraps under the title on a phone rather than squeezing both onto the rule. */
.dash-head-end{ display:inline-flex; align-items:center; gap:12px; margin-left:auto; }

/* The counter-stamp on a quote that has ended. Same frame and the same slant
   as the "Received" stamp on the contact form — the tilt is the whole point:
   it reads as something pressed onto the page by hand rather than another
   status chip printed with it. Green when the order ran its course —
   collected_at, nothing earlier — and red when it did not: declined or
   cancelled. */
.stamp-mark{
  display:inline-flex; align-items:center; justify-content:center;
  border:2px solid currentColor;
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.1em; text-transform:uppercase;
  padding:4px 11px; border-radius:4px; transform:rotate(3deg);
  white-space:nowrap;
}
.stamp-mark.is-done{ color:var(--green-stamp); }
.stamp-mark.is-void{ color:var(--red-stamp); }
.q-head .stamp-mark{ font-size:11.5px; padding:5px 13px; }

.dash-empty{ text-align:center; padding:32px 10px; }
.dash-empty h3{ font-size:16px; margin:0 0 8px; }
.dash-empty p{ margin:0 auto 18px; font-size:13px; line-height:1.55; color:var(--paper-dim); max-width:42ch; }
.dash-empty .btn:last-child{ margin:0; }

/* A quote row inside a white panel cannot also be a white card, so it drops
   its own frame and becomes a ruled row. Outside a panel it stays a card. */
.dash-panel .q-list{ gap:0; }
.dash-panel .q-card{
  background:none; border:0; border-radius:0;
  border-bottom:1px solid var(--paper-line);
  padding:14px 0; transform:none;
}
.dash-panel .q-card:first-child{ padding-top:0; }
.dash-panel .q-card:last-child{ border-bottom:0; padding-bottom:0; }
.dash-panel .q-card:hover{ transform:none; border-color:transparent; border-bottom-color:var(--paper-line); }
.dash-panel .q-card.needs-action{ position:relative; padding-left:13px; border-left:0; }
.dash-panel .q-card.needs-action::before{
  content:""; position:absolute; left:0; top:12px; bottom:12px; width:2px; background:var(--gold);
}

/* --- the order track ---
   Vertical on a phone (five labels never fit across 375px), horizontal from
   the tablet up. The last step is drawn hollow and unreachable on purpose:
   collection is what happens next, not a state the database records. */
.q-track{ list-style:none; margin:16px 0 0; padding:0; display:flex; flex-direction:column; }
.q-track li{ position:relative; display:flex; align-items:flex-start; gap:12px; padding-bottom:15px; }
.q-track li:last-child{ padding-bottom:0; }
.q-track li::after{
  content:""; position:absolute; left:6px; top:17px; bottom:2px; width:1px; background:var(--paper-line);
}
.q-track li:last-child::after{ display:none; }
.track-dot{
  position:relative; z-index:1; flex:0 0 13px;
  width:13px; height:13px; margin-top:3px; border-radius:50%;
  background:#fff; border:1.5px solid var(--paper-line);
}
.track-label{ font-size:13.5px; line-height:1.35; color:var(--paper-dim); }
.q-track .is-done .track-dot{ background:var(--paper-text); border-color:var(--paper-text); }
.q-track .is-done .track-label{ color:var(--paper-text); }
.q-track .is-current .track-dot{ background:var(--gold); border-color:var(--gold); box-shadow:0 0 0 4px rgba(201,162,39,.18); }
.q-track .is-current .track-label{ color:var(--paper-text); font-weight:600; }
@media (min-width:700px){
  .q-track{ flex-direction:row; margin-top:20px; }
  .q-track li{ flex:1; flex-direction:column; align-items:flex-start; gap:9px; padding:0 14px 0 0; }
  .q-track li::after{ left:19px; right:10px; top:6px; bottom:auto; width:auto; height:1px; }
  .q-track li:last-child{ flex:0 0 auto; padding-right:0; }
  .track-dot{ margin-top:0; }
  .track-label{ font-size:12px; }
}
.q-track-detail{ margin:0 0 20px; }
.dash-track-ref{ margin:0; font-size:12px; color:var(--paper-dim); }
.dash-track-note{ margin:16px 0 0; font-size:11.5px; line-height:1.55; color:var(--paper-dim); }

/* --- buy again ---
   The "+" is 44px round: it is the most-tapped control on the page and it
   sits next to a link, so it has to be unmissable by a thumb. */
.ba-list{ list-style:none; margin:0; padding:0; }
.ba-row{ display:flex; align-items:center; gap:12px; padding:12px 0; border-bottom:1px solid var(--paper-line); }
.ba-row:first-child{ padding-top:0; }
.ba-row:last-child{ border-bottom:0; padding-bottom:0; }
.ba-row .swatch{
  flex:0 0 52px; width:52px; height:52px; border-radius:5px; overflow:hidden;
  background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12));
}
.ba-row .swatch:has(img){ background:#fff; border:1px solid var(--paper-line); }
.ba-row .swatch img{ width:100%; height:100%; object-fit:contain; padding:3px; }
.ba-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px; }
.ba-name{
  font-size:13.5px; line-height:1.3;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.ba-variant{ font-size:10.5px; color:var(--paper-dim); }
.ba-last{ font-size:11px; color:var(--paper-dim); }
.ba-qty{ flex:0 0 auto; font-size:12px; color:var(--paper-dim); }
.ba-add{
  flex:0 0 44px; width:44px; height:44px; border-radius:50%;
  background:#fff; border:1px solid var(--paper-line);
  font-size:22px; line-height:1; color:var(--paper-text);
  touch-action:manipulation; -webkit-tap-highlight-color:transparent;
  transition:background .18s ease, border-color .18s ease, color .18s ease, transform .14s var(--ease);
}
.ba-add:active{ transform:scale(.9); }
.ba-add.is-added{ background:var(--gold); border-color:var(--gold); color:#171204; font-size:17px; }
.ba-gone{
  flex:0 0 auto; max-width:96px; text-align:right; line-height:1.3;
  font-family:var(--font-mono); font-size:9.5px; text-transform:uppercase; color:var(--paper-dim);
}

/* --- new arrivals ---
   A snap carousel on a phone rather than a four-column grid squeezed to
   85px-wide cards. It used to bleed past the panel (margin:0 -28px, written
   for a .container) — inside a .dash-panel padded 16px that put the cards
   12px outside the white box on both sides. The carousel now stays within
   the panel's own padding; the half-visible next card is what tells a thumb
   there is more to the right. */
.na-grid{
  display:grid; grid-auto-flow:column; grid-auto-columns:minmax(132px,43%);
  gap:12px; overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;
  margin:0; padding:0 0 4px;
  scrollbar-width:none;
}
.na-grid::-webkit-scrollbar{ display:none; }
.na-card{ scroll-snap-align:start; display:flex; flex-direction:column; gap:7px; min-width:0; }
@media (min-width:760px){
  .na-grid{
    grid-auto-flow:row; grid-template-columns:repeat(4,minmax(0,1fr));
    overflow:visible; margin:0; padding:0;
  }
}
.na-shot{
  position:relative; aspect-ratio:4/3; border-radius:6px; overflow:hidden;
  background:linear-gradient(150deg,var(--swatch-a,#1c1408),var(--swatch-b,#0d0f12));
}
.na-shot img{ width:100%; height:100%; object-fit:cover; }
.na-badge{
  position:absolute; top:8px; left:8px;
  font-family:var(--font-mono); font-size:9px; letter-spacing:.12em;
  background:var(--gold); color:#171204; padding:3px 7px; border-radius:2px;
}
.na-name{ font-size:12.5px; line-height:1.3; color:var(--paper-text); }
.na-cat{ font-size:9.5px; letter-spacing:.07em; text-transform:uppercase; color:var(--paper-dim); }

/* --- purchasing activity, company details --- */
.act-grid, .acc-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:16px 20px; margin:0; }
@media (min-width:760px){ .act-grid{ grid-template-columns:repeat(4,minmax(0,1fr)); } }
@media (min-width:600px){ .acc-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); } }
.act-grid dt, .acc-grid dt{
  font-family:var(--font-mono); font-size:10px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--paper-dim); margin-bottom:4px;
}
.act-grid dd{ margin:0; font-size:17px; }
.acc-grid dd{ margin:0; font-size:14px; overflow-wrap:anywhere; }

.acc-form{ display:grid; gap:16px; }
@media (min-width:700px){
  .acc-form{ grid-template-columns:repeat(3,minmax(0,1fr)); }
  .acc-form-foot{ grid-column:1 / -1; }
}
.acc-form-foot{ display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
.acc-form-foot .btn{ flex:1 1 auto; text-align:center; }
@media (min-width:520px){ .acc-form-foot .btn{ flex:0 0 auto; } }
.acc-form-msg{ font-family:var(--font-mono); font-size:11px; }
.acc-form-msg.is-ok{ color:#149c8b; }
.acc-form-msg.is-error{ color:var(--red-stamp); }

/* --- documents --- */
.doc-list{ list-style:none; margin:0; padding:0; }
.doc-row{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:13px 0; border-bottom:1px solid var(--paper-line); min-height:56px;
}
.doc-row:first-child{ padding-top:0; }
.doc-row:last-child{ border-bottom:0; padding-bottom:0; }
.doc-text{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.doc-name{ font-size:13px; overflow-wrap:anywhere; }
.doc-meta{ font-size:11px; color:var(--paper-dim); }
.btn-sm{ padding:10px 18px; font-size:13px; min-height:42px; flex:0 0 auto; border-radius:8px; }

/* --- support --- */
.sup-list{ list-style:none; margin:0; padding:0; }
.sup-list li{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:13px 0; border-bottom:1px solid var(--paper-line); min-height:54px;
}
.sup-list li:first-child{ padding-top:0; }
.sup-list li:last-child{ border-bottom:0; padding-bottom:0; }
.sup-label{
  font-family:var(--font-mono); font-size:10px; letter-spacing:.09em;
  text-transform:uppercase; color:var(--paper-dim);
}
.sup-list a{ font-size:13.5px; color:var(--gold); text-align:right; overflow-wrap:anywhere; }
.sup-steps{ margin:0; padding-left:20px; }
.sup-steps li{ font-size:13.5px; line-height:1.6; margin-bottom:11px; }
.sup-steps li:last-child{ margin-bottom:0; }
.sup-steps::marker{ color:var(--gold); }

/* The off-canvas menu slides over the whole screen and has its own links in
   it, so the tab bar underneath would be a second, contradictory navigation.
   Same treatment the basket button and the back-to-top control already get. */
@media (max-width:899px){
  body.nav-open .dash-tabs{ opacity:0; pointer-events:none; }
}

/* Reduced motion: the sheet still opens and closes, it just stops travelling.
   closeDashSheet() does not depend on the transition firing — it has a timer
   behind it for exactly this case. */
@media (prefers-reduced-motion:reduce){
  .dash-sheet, .dash-sheet-backdrop, a.kpi, .ba-add, .dash-nav-item{ transition:none; }
}
.dash-h2{ font-size:26px; margin-bottom:22px; }

/* A quote list is two facts and a state per row, and on a phone those want to
   stack rather than scroll sideways — which is why this is not a table like
   the lines inside it are. */
.q-list{ display:flex; flex-direction:column; gap:10px; }
.q-card{
  display:flex; align-items:center; justify-content:space-between; gap:20px;
  padding:18px 20px; background:#fff;
  border:1px solid var(--paper-line); border-radius:4px;
  transition:border-color .25s, transform .25s var(--ease);
}
.q-card:hover{ border-color:var(--paper-text); transform:translateY(-1px); }
/* The one row the customer has to do something about earns a gold edge. */
.q-card.needs-action{ border-left:3px solid var(--gold); }
.q-card-main{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.q-card-side{ display:flex; align-items:center; gap:18px; flex-shrink:0; }
.q-number{ font-size:14px; color:var(--paper-text); }
.q-date{ font-size:12px; color:var(--paper-dim); }
.q-total{ font-size:13px; color:var(--paper-text); }

.q-status{
  display:inline-flex; align-items:center;
  font-family:var(--font-mono); font-size:10px; letter-spacing:.09em; text-transform:uppercase;
  padding:5px 11px; border-radius:999px; border:1px solid var(--paper-line); color:var(--paper-dim);
  white-space:nowrap;
}
.q-status.is-in_behandeling{ border-color:rgba(201,162,39,.5); color:var(--gold); }
/* Quoted is the state that wants a decision, so it is the only filled one. */
.q-status.is-geoffreerd{ background:var(--gold); border-color:var(--gold); color:#171204; }
.q-status.is-geaccepteerd{ border-color:rgba(47,217,196,.55); color:#149c8b; }
.q-status.is-geannuleerd{ border-color:rgba(180,59,47,.45); color:var(--red-stamp); }

@media (max-width:640px){
  .q-card{ flex-direction:column; align-items:flex-start; gap:12px; padding:16px; }
  .q-card-side{ width:100%; justify-content:space-between; }
}

/* --- detail ---
   The header used to be a stack: heading, then four full-width rows of
   label/value, then two notes each behind a bordered legend. That pushed the
   actual quote most of a screen down, which is backwards — the lines are what
   the customer opened the page for. Everything above them is now one strip
   that wraps, so it costs about a third of the height it did. */
/* Looking at one quote: the hero shrinks to a title bar and gives its height
   to the thing being looked at. Same page, same markup — see renderDashboard(). */
body.dash-detail .page-hero p{ display:none; }
.dash-section{ padding-top:56px; }
body.dash-detail .dash-section{ padding-top:34px; }

.q-detail-back{ margin-bottom:16px; }

.q-head{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:16px; }
.q-head h1{ font-family:var(--font-mono); font-size:clamp(20px,2.6vw,26px); letter-spacing:.02em; }

/* Label over value, in as many columns as fit — three of these across is one
   row where it used to be three. */
.q-meta{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr));
  gap:12px 26px; margin:0 0 18px; padding:13px 0;
  border-top:1px solid var(--paper-line); border-bottom:1px solid var(--paper-line);
}
.q-meta > div{ min-width:0; }
.q-meta dt{
  font-family:var(--font-mono); font-size:10px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--paper-dim); margin-bottom:3px;
}
.q-meta dd{ margin:0; font-size:14px; }
.q-expired{ font-family:var(--font-mono); font-size:10px; text-transform:uppercase; color:var(--red-stamp); }

.q-notes{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:14px 26px; margin-bottom:22px;
}
.q-note{ min-width:0; }
.q-note p{ margin:0; font-size:13px; line-height:1.5; color:var(--paper-text); white-space:pre-line; }

/* The same small gold caption .form-legend gives, without its 30px margins,
   20px padding and rule — three of those were most of the wasted height. */
.q-note-label{
  display:block; font-family:var(--font-mono); font-size:10px;
  letter-spacing:.08em; text-transform:uppercase; color:var(--gold); margin-bottom:4px;
}
span.q-note-label{ margin-bottom:8px; }

.ledger .num{ text-align:right; font-family:var(--font-mono); white-space:nowrap; }
.ledger .name{ font-size:14px; }
.ledger .cat{ font-size:11px; color:var(--paper-dim); margin-top:3px; }

/* The quote lines are a real table on a desktop and a stack of cards on a
   phone. The alternative the stylesheet had — a 720px-wide table inside a
   horizontal scroller — meant the customer had to swipe sideways to see what
   a line cost, on the page whose whole purpose is what a line costs.
   `data-th` is written by renderQuoteDetail(), so the labels come from the
   same dictionary as the table head they replace. */
@media (max-width:640px){
  .ledger-wrap{ border:0; border-radius:0; overflow:visible; }
  .ledger-wrap .ledger{ min-width:0; font-size:13px; }
  .ledger thead{ position:absolute; width:1px; height:1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
  .ledger tr{
    display:block; margin-bottom:10px; padding:14px;
    background:#fff; border:1px solid var(--paper-line); border-radius:7px;
  }
  .ledger tr:last-child{ margin-bottom:0; }
  .ledger tr:hover td{ background:none; }
  .ledger td{ display:flex; align-items:center; justify-content:space-between; gap:14px; padding:5px 0; border:0; }
  .ledger td:first-child{ display:block; margin-bottom:7px; padding-bottom:11px; border-bottom:1px solid var(--paper-line); }
  .ledger td[data-th]::before{
    content:attr(data-th);
    font-family:var(--font-mono); font-size:9.5px; letter-spacing:.08em;
    text-transform:uppercase; color:var(--paper-dim);
  }
  .q-totals{ max-width:none; }
}

.q-totals{ margin:24px 0 6px; margin-left:auto; max-width:340px; }
.q-totals > div{ display:flex; justify-content:space-between; gap:24px; padding:9px 0; }
.q-totals dt{ color:var(--paper-dim); font-size:13px; }
.q-totals dd{ margin:0; font-size:14px; }
.q-totals .grand{ border-top:1px solid var(--paper-text); margin-top:6px; padding-top:13px; }
.q-totals .grand dt{ color:var(--paper-text); font-weight:600; }
.q-totals .grand dd{ font-size:17px; }
.q-vat{ text-align:right; font-size:12px; color:var(--paper-dim); margin:0 0 8px; }
.q-awaiting{ margin:24px 0 0; font-size:14px; color:var(--paper-dim); }

.q-actions{ display:flex; flex-wrap:wrap; gap:12px; align-items:center; margin-top:34px; padding-top:26px; border-top:1px solid var(--paper-line); }
.q-accepted{ font-size:12px; letter-spacing:.06em; text-transform:uppercase; color:#149c8b; }
.q-declined{ font-size:12px; letter-spacing:.06em; text-transform:uppercase; color:var(--red-stamp); }
.q-status.is-afgewezen{ border-color:rgba(180,59,47,.45); color:var(--red-stamp); }

/* --- the accept dialog ---
   Accepting is the only irreversible thing a customer can do on this site, so
   it gets a real modal: the page behind it is unusable while the question is
   open, Escape and the backdrop both cancel, and the confirm button does not
   light up until the authority checkbox is ticked. */
.modal-backdrop{
  position:fixed; inset:0; z-index:120;
  background:rgba(13,15,18,.72);
  display:none; align-items:center; justify-content:center; padding:24px;
}
.modal-backdrop.open{ display:flex; }
.modal-card{
  background:#fff; border:1px solid var(--paper-line); border-radius:6px;
  box-shadow:var(--shadow-lg); padding:34px;
  width:100%; max-width:520px; max-height:88vh; overflow-y:auto;
}
.modal-card h2{ font-size:26px; margin-bottom:12px; }
.modal-lead{ margin:0 0 20px; font-size:14px; color:var(--paper-text); }
.modal-figures{
  display:flex; justify-content:space-between; gap:20px;
  padding:14px 16px; margin-bottom:20px;
  background:var(--paper-soft); border:1px solid var(--paper-line); border-radius:3px;
  font-size:14px;
}
.modal-warn{
  margin:0 0 6px; padding:14px 16px;
  background:rgba(180,59,47,.06); border:1px solid rgba(180,59,47,.28); border-radius:3px;
  font-size:13px; color:var(--paper-text);
}
.modal-card .checkbox-row a{ color:var(--gold); text-decoration:underline; }
@media (max-width:640px){ .modal-card{ padding:24px 20px; } }

/* A warning notice is the same object as .gate-note with a different colour;
   the gold is for "here is something useful", the red for "this affects you". */
.gate-note.warn{ border-color:rgba(180,59,47,.5); background:rgba(180,59,47,.05); }
.gate-note.warn .g-ico{ color:var(--red-stamp); }

/* --- de MOQ-balk op de naad (quote.html) ------------------------------
   Wat er is gebleven van de teller: één lijn, over de volle breedte, op de
   overgang van de zwarte banner naar het papier. Geen pil, geen label, geen
   getal ernaast — die lazen als een kader om een waarschuwing, terwijl dit
   alleen maar hoeft te laten zien hoe ver de aanvraag is. De uitleg staat als
   gewone zin in de container eronder.

   Absoluut geplaatst in de papiersectie (die daarvoor `position:relative`
   inline meekrijgt), dus de breedte is die van de sectie — vanzelf de volle
   schermbreedte, op elk formaat, zonder 100vw en dus zonder het horizontale
   schuifbalkje dat 100vw naast een verticale scrollbar oplevert. Hij neemt
   geen hoogte in de pagina in: de lijst begint waar hij altijd al begon. */
.sealed-seam{
  position:absolute; left:0; right:0; top:0; height:4px; z-index:3;
}
.sealed-track{
  position:relative; display:block; height:100%; overflow:hidden;
  background-color:rgba(23,24,27,.13);
}
/* Eén doorlopende lijn: geen naden tussen de segmenten. De verdeling per
   sealed regel zit nog wel in de DOM — daar hangen de tooltips aan — maar je
   ziet hem niet meer. */
.sealed-fill{
  position:relative; display:flex; gap:0; height:100%; width:0;
  transition:width .55s cubic-bezier(.2,.75,.3,1);
}
/* Het foil dat over de vulling blijft lopen: één lichtstreep die van links
   naar rechts door het groen trekt en opnieuw begint. Eén laag over de
   segmenten heen, zodat de streep niet bij elke regelgrens herstart. */
.sealed-fill::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(100deg,
    rgba(255,255,255,0) 36%, rgba(255,255,255,.2) 44%,
    rgba(255,255,255,.9) 50%, rgba(255,255,255,.2) 56%, rgba(255,255,255,0) 64%);
  background-size:260% 100%; background-repeat:no-repeat;
  animation:sealedFoil 2.2s linear infinite;
}
/* De richting zit andersom dan je zou denken: de laag is breder dan de balk,
   en bij een achtergrond die groter is dan zijn element schuift een hogere
   `background-position` het beeld juist naar links. Van 180% naar -80% loopt
   de lichtstreep dus van links naar rechts. */
@keyframes sealedFoil{
  from{ background-position:180% 0; }
  to{   background-position:-80% 0; }
}
/* Stoplicht, in de systeemkleuren van iOS — gekozen naast Shopify (#008060, te
   donker en te veel teal), GitHub, Tailwind en Spotify.

   Geel is de basis en niet het midden van een reeks: onder de 30% komt rood
   erover, en groen komt er pas op als de tien er staan. Zo is er geen stand
   zonder kleur, ook niet tijdens de overgang — en juist die overgang moet
   geel blijven totdat het groen eroverheen is gewassen. */
.sealed-seg{ min-width:2px; flex:1 1 auto; background:#FFCC00; }
.sealed-track.is-low  .sealed-seg{ background:#FF3B30; }
.sealed-track.is-done .sealed-seg{ background:#34C759; }

/* De groene golf. Ligt over de volle balk en staat opgerold tot links; bij het
   halen van de tien rolt hij in één haal open, en daarna neemt de vulling zelf
   het groen over (`is-done`) en gaat deze laag weer dicht. `transform` en niet
   `width`, want dat scheelt de layout-stap per frame. */
.sealed-flood{
  position:absolute; inset:0; pointer-events:none;
  background:#34C759; transform:scaleX(0); transform-origin:left center;
}

/* Waar de confetti uit komt: een hoogteloze strook op de lijn, zodat de
   scherfjes eruit lijken te springen zonder dat er iets verschuift of iets
   aanklikbaars wordt bedekt. js/main.js §fireSealedBurst vult hem. */
.sealed-burst{
  position:absolute; left:0; right:0; top:100%; height:0;
  display:block; pointer-events:none;
}
.sealed-shard{
  position:absolute; bottom:0; width:5px; height:9px; border-radius:1px;
  opacity:0; will-change:transform, opacity;
}

/* De zin onder de balk: gewone tekst, geen kader. Een kleine negatieve marge
   haalt hem dichter naar de naad toe, zodat de lijn en zijn uitleg bij elkaar
   horen zonder dat de lijst verder naar beneden schuift. */
.sealed-note{ display:block; margin:-26px 0 22px; }
.sealed-note > p{
  margin:0; font-size:13px; line-height:1.6; color:var(--paper-dim);
}
/* De link loopt mee in de regel en staat er niet als knop naast: een spatie
   ervoor via de marge, en donkerder dan de zin zodat hij als het aanklikbare
   deel leest zonder de zin te overstemmen. Breekt niet af op een smal scherm. */
.sealed-more{
  margin-left:.55em; white-space:nowrap;
  color:var(--paper-text); text-decoration:underline; text-underline-offset:2px;
  text-decoration-color:var(--paper-line);
}
.sealed-more:hover{ text-decoration-color:var(--gold); }

@media (prefers-reduced-motion: reduce){
  .sealed-fill{ transition:none; }
  .sealed-fill::after{ animation:none; display:none; }
  .sealed-track.is-done::after{ display:none; }
}

/* Waarom drukken veilig is, op de plek waar gedrukt wordt. Eén regel onder de
   verstuurknop, in de maat van een voetnoot: hij hoort gelezen te worden door
   wie twijfelt, en genegeerd door wie dat niet doet. */
.submit-reassure{
  margin:14px 0 0; font-size:12.5px; line-height:1.6; color:var(--paper-dim);
}

/* --- offline preview bar (demo.html only) ---
   Styled to look like tooling rather than like the site, so a screenshot of
   the preview can never be mistaken for the real dashboard. */
.demo-bar{
  position:fixed; left:0; right:0; bottom:0; z-index:200;
  display:flex; flex-wrap:wrap; align-items:center; gap:8px;
  padding:10px 14px; background:#111418; border-top:2px solid var(--gold);
  font-family:var(--font-mono); font-size:11px; color:var(--ink-dim);
}
.demo-bar strong{ color:var(--gold); letter-spacing:.14em; }
.demo-bar span{ margin-right:10px; }
.demo-bar button{
  background:none; border:1px solid var(--ink-line); color:var(--ink-dim);
  padding:5px 10px; border-radius:2px; font-family:inherit; font-size:11px;
}
.demo-bar button:hover{ color:var(--ink-text); border-color:var(--ink-text); }
.demo-bar button.on{ color:#171204; background:var(--gold); border-color:var(--gold); }
body:has(.demo-bar) .site-footer{ padding-bottom:90px; }

/* ==========================================================================
   Reveal on scroll — visible-by-default failsafe pattern
   ========================================================================== */
.reveal, .reveal-stagger > *{
  opacity:1;
  transform:none;
  transition:opacity .7s var(--ease), transform .7s var(--ease);
}
/* The clip exists to stop the 20px rise poking out of the section above, so
   it only belongs to the pre-reveal state. Leaving it on permanently gave
   every revealed element a clipped raster layer for the rest of the page's
   life, which is dead weight on a phone. */
.reveal.pre, .reveal-stagger.pre > *{
  clip-path:inset(0 0 -6% 0);
  opacity:0;
  transform:translateY(20px);
}
.reveal-stagger.pre > *:nth-child(2){ transition-delay:.08s; }
.reveal-stagger.pre > *:nth-child(3){ transition-delay:.16s; }
.reveal-stagger.pre > *:nth-child(4){ transition-delay:.24s; }
@media (prefers-reduced-motion: reduce){
  .reveal, .reveal-stagger > *{ transition:none !important; opacity:1 !important; transform:none !important; }
}

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer{ --edge:#0D0F12; background:var(--ink); color:var(--ink-dim); padding:70px 0 30px; }
.footer-grid{ display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr 1fr; gap:40px; margin-bottom:56px; }
@media (max-width:980px){ .footer-grid{ grid-template-columns:1fr 1fr 1fr; } }
@media (max-width:820px){ .footer-grid{ grid-template-columns:1fr 1fr; } }
@media (max-width:520px){ .footer-grid{ grid-template-columns:1fr; } }
.footer-grid h4{ font-family:var(--font-body); font-size:14px; font-weight:600; text-transform:none; letter-spacing:normal; color:var(--gold); margin-bottom:16px; }
.footer-grid p{ font-size:13px; max-width:32ch; }
.footer-tagline{ color:var(--ink-dim); line-height:1.5; }
/* The language switch, moved out of the header (sep 2026): the service is
   Dutch-only wholesale and foreign traffic is already met by the region gate,
   so a permanent toggle beside the primary CTA was paying header rent for a
   setting most visitors never touch. It keeps the label it had in the
   slide-out menu, since on its own in a footer column "NL EN" is a riddle. */
.footer-lang{
  display:flex; align-items:center; gap:12px; margin-top:20px;
  color:var(--ink-dim); font-family:var(--font-body);
  font-size:13px; font-weight:400; letter-spacing:normal; text-transform:none;
}
.footer-grid li{ margin-bottom:10px; font-size:13px; }
/* The footer speaks in the buttons' voice too (Inter), including the NL/EN
   switch and the KvK/BTW lines that carry .mono elsewhere. */
.site-footer .mono,
.site-footer .lang-switch{ font-family:var(--font-body); font-weight:400; }
.footer-grid a:hover{ color:var(--gold); }
.footer-social{ display:flex; gap:10px; margin-top:14px; list-style:none; }
.footer-social-icon{ display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px; border:1px solid var(--ink-line); border-radius:50%; color:var(--ink-dim); transition:color .25s, border-color .25s; }
.footer-social-icon:hover{ color:var(--gold); border-color:var(--gold); }
/* Discord heeft nog geen uitnodigingslink: het icoon staat er wel, maar doet
   niets en zegt dat ook, in plaats van naar de bovenkant van de pagina te
   springen. Weghalen zodra de link er is. */
.footer-social-icon.is-placeholder{ opacity:.45; cursor:default; pointer-events:none; }
.footer-bottom{
  display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:12px;
  border-top:1px solid var(--ink-line); padding-top:26px; font-size:12.5px; font-family:var(--font-body);
}

/* generic content pages */
/* The dark band at the top of every page but the homepage.

   Deliberately short. It used to open with 64px of padding above a 52px
   heading, which on a page whose point is the content below it — a quote, a
   returns policy, a list of items — was most of a phone screen spent saying
   where you already knew you were. These are the proportions the dashboard's
   detail view was cut down to, applied everywhere.

   The homepage is untouched: that hero is .hero, a different element with a
   different job. */
.page-hero{ padding:30px 0 24px; }
.page-hero h1{ font-size:clamp(22px,2.8vw,30px); }
.page-hero p{ color:var(--ink-dim); max-width:62ch; margin-top:9px; font-size:15px; }
.two-col{ display:grid; grid-template-columns:1fr 1fr; gap:60px; align-items:start; }
@media (max-width:860px){ .two-col{ grid-template-columns:1fr; } }
/* Stacks unconditionally, at every width — for the one .two-col instance
   (homepage closing CTA) the owner wants vertical always, not just on
   narrow screens. Higher specificity than the media query above, so no
   !important needed. */
.two-col.stack-always{ grid-template-columns:1fr; gap:32px; }

.legal-copy h2{ font-size:22px; margin-top:44px; margin-bottom:14px; }
.legal-copy h2:first-child{ margin-top:0; }
.legal-copy p{ color:var(--paper-dim); margin-bottom:14px; }
.legal-copy p a{ text-decoration:underline; text-underline-offset:2px; color:var(--paper-text); }
.legal-copy p a:hover{ color:var(--gold); }

/* Product gallery — thumbnail strip under the main photo on the detail page. */
.product-detail-figure{ min-width:0; }
.product-thumbs{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
.product-thumb{ flex:0 0 auto; width:74px; aspect-ratio:1/1; padding:0; background:#fff; border:1px solid var(--paper-line); border-radius:10px; overflow:hidden; cursor:pointer; transition:border-color .18s ease, transform .18s ease; }
.product-thumb img{ display:block; width:100%; height:100%; object-fit:contain; padding:6px; }
.product-thumb:hover{ transform:translateY(-2px); }
.product-thumb.active{ border-color:var(--gold); }
@media (max-width:640px){ .product-thumb{ width:62px; } }
/* --------------------------------------------------------------------------
   Back to top — fixed at the top right, under the header, every breakpoint.
   Injected by initBackToTop(); that function owns .is-visible.
   -------------------------------------------------------------------------- */
.to-top{
  position:fixed;
  /* Clear of the 76px sticky header, and below it in the stack: the two never
     overlap, but the header must win if a zoomed viewport ever makes them. */
  top:92px;
  right:max(20px, env(safe-area-inset-right));
  z-index:190;
  display:inline-flex; align-items:center; justify-content:center;
  width:46px; height:46px;
  border-radius:50%;
  background:rgba(13,15,18,.88);
  color:var(--ink-text);
  border:1px solid var(--ink-line);
  backdrop-filter:blur(8px);
  box-shadow:0 8px 22px rgba(13,15,18,.3);
  /* Hidden until there is somewhere to go back to. visibility rides along so
     the button leaves the tab order while it is invisible. */
  opacity:0;
  visibility:hidden;
  transform:translateY(-8px) scale(.94);
  transition:opacity .3s var(--ease), transform .3s var(--ease),
             visibility .3s, background .25s var(--ease), color .25s var(--ease);
}
.to-top.is-visible{ opacity:1; visibility:visible; transform:none; }
.to-top svg{
  width:20px; height:20px;
  fill:none; stroke:currentColor;
  stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;
}
.to-top:hover{ background:var(--gold); color:var(--ink); border-color:var(--gold); }
.to-top:active{ transform:scale(.93); }
/* The off-canvas menu covers the screen — a button floating on top of it is
   just clutter over the nav's own controls. */
body.nav-open .to-top{ opacity:0; visibility:hidden; pointer-events:none; }
@media (max-width:640px){
  .to-top{
    top:88px;
    right:max(14px, env(safe-area-inset-right));
    width:42px; height:42px;
  }
  .to-top svg{ width:18px; height:18px; }
}
@media (prefers-reduced-motion:reduce){
  .to-top{ transition:opacity .01s, visibility .01s; }
}

/* ---------------------------------------------------------------------
   14. Entrance intro — bouncing Poké Ball (index.html only)
   ---------------------------------------------------------------------
   A ~1.5s black overlay that is pure theatre. It deliberately measures
   nothing: no image, font, CDN script or fetch is waited on. The page boots
   underneath it at full speed and the overlay leaves on its own schedule
   whether the rest arrived or not — see the controller in index.html's
   <head>, which only listens for `introReveal` starting and ending.

   Everything here runs on transform, opacity and one masked radius, so the
   whole sequence stays on the compositor. All timing lives in this file;
   the JS holds no durations of its own.

   Timeline (from first paint):
     0.06s  ball fades in, held one ball-height above the ground
     0.12s  drop begins            0.34s  first impact + squash
     0.59s  top of the bounce      0.90s  second, smaller impact
     0.04s  progress bar starts    1.20s  bar reaches full
     1.14s  circular reveal opens from the ball's centre
     1.54s  overlay gone
   --------------------------------------------------------------------- */

/* The reveal is a hole punched through the black with a radial-gradient
   mask. Only a *registered* custom property interpolates, hence @property;
   where that is unsupported the radius simply stays at 0 and the opacity
   fade built into the same keyframes carries the transition instead. */
@property --intro-r{
  syntax:"<length>";
  inherits:false;
  initial-value:0px;
}

/* Inert unless the <head> controller opted this load in — a returning visit
   inside the same session never renders it at all. */
.intro{ display:none; }

html.intro-active .intro{
  --ball:clamp(64px, 15vw, 84px);
  --drop:calc(var(--ball) * -1);   /* the ball starts one diameter up */
  display:block;
  position:fixed; inset:0; z-index:9000;
  background:#000;
  /* The overlay has nothing to scroll and the page underneath shouldn't move
     while it is up; the controller blocks wheel/touch for the same reason. */
  touch-action:none;
  overscroll-behavior:contain;
  /* The base layer of the dissolve — the slowest and softest of the five,
     so it is the last thing to clear and its animationend is what the
     controller in index.html treats as "the intro is over". */
  -webkit-mask-image:radial-gradient(circle at 50% 50%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2.6 + 1px));
          mask-image:radial-gradient(circle at 50% 50%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2.6 + 1px));
  animation:introReveal 1.75s cubic-bezier(.5,.02,.4,1) 1.18s forwards;
}
/* 95vmax clears the half-diagonal of any aspect ratio (worst case ~71vmax)
   even after the feather is subtracted, so the opening always finishes
   larger than the viewport. The feather is a *multiple* of the radius, not a
   fixed length: at r=0 both gradient stops sit at 0, so the screen starts
   completely black and the edge only softens as the hole grows. */
@keyframes introReveal{
  0%   { --intro-r:0px;     opacity:1; }
  55%  {                    opacity:1; }
  100% { --intro-r:95vmax;  opacity:0; }
}

/* --- The other four veils. Same idea, stacked, each a beat later and a
   little lumpier than the one under it. Two extra off-centre gradients per
   layer break the perfect circle, and a real blur turns each edge into a
   wisp; the stagger is what makes the black come apart layer by layer
   instead of all at once. inset:-14% keeps the blurred edge of the layer
   itself off screen. --- */
html.intro-active .intro-veil{
  position:absolute; inset:-14%;
  display:block;
  background:#000;
  /* opacity only. mask-image cannot go to the compositor, so naming it here
     bought nothing and cost a layer that had to be re-rastered every frame
     regardless — on four stacked full-screen layers that each also carry a
     real blur, at the one moment the hero is being built underneath them.
     opacity is compositable and is what the keyframes actually fade. */
  will-change:opacity;
  animation:introReveal 1.5s cubic-bezier(.5,.02,.42,1) 1.14s forwards;
}
html.intro-active .intro-veil.v1{
  filter:blur(10px);
  animation-delay:1.02s; animation-duration:1.20s;
  -webkit-mask-image:radial-gradient(circle at 50% 50%, transparent var(--intro-r), #000 calc(var(--intro-r) * 1.7 + 1px));
          mask-image:radial-gradient(circle at 50% 50%, transparent var(--intro-r), #000 calc(var(--intro-r) * 1.7 + 1px));
}
html.intro-active .intro-veil.v2{
  filter:blur(16px);
  animation-delay:1.10s; animation-duration:1.32s;
  -webkit-mask-image:
    radial-gradient(circle at 46% 53%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2 + 1px)),
    radial-gradient(circle at 56% 44%, transparent calc(var(--intro-r) * .8), #000 calc(var(--intro-r) * 1.9 + 1px));
          mask-image:
    radial-gradient(circle at 46% 53%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2 + 1px)),
    radial-gradient(circle at 56% 44%, transparent calc(var(--intro-r) * .8), #000 calc(var(--intro-r) * 1.9 + 1px));
  -webkit-mask-composite:source-over;
          mask-composite:add;
}
html.intro-active .intro-veil.v3{
  filter:blur(24px);
  animation-delay:1.22s; animation-duration:1.45s;
  -webkit-mask-image:
    radial-gradient(circle at 54% 47%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2.2 + 1px)),
    radial-gradient(circle at 43% 58%, transparent calc(var(--intro-r) * .7), #000 calc(var(--intro-r) * 2.1 + 1px));
          mask-image:
    radial-gradient(circle at 54% 47%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2.2 + 1px)),
    radial-gradient(circle at 43% 58%, transparent calc(var(--intro-r) * .7), #000 calc(var(--intro-r) * 2.1 + 1px));
  -webkit-mask-composite:source-over;
          mask-composite:add;
}
html.intro-active .intro-veil.v4{
  filter:blur(34px);
  animation-delay:1.34s; animation-duration:1.58s;
  -webkit-mask-image:
    radial-gradient(circle at 50% 50%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2.4 + 1px)),
    radial-gradient(circle at 60% 60%, transparent calc(var(--intro-r) * .6), #000 calc(var(--intro-r) * 2.3 + 1px));
          mask-image:
    radial-gradient(circle at 50% 50%, transparent var(--intro-r), #000 calc(var(--intro-r) * 2.4 + 1px)),
    radial-gradient(circle at 60% 60%, transparent calc(var(--intro-r) * .6), #000 calc(var(--intro-r) * 2.3 + 1px));
  -webkit-mask-composite:source-over;
          mask-composite:add;
}

/* --- Nothing on the page shows through the dissolve. Everything above the
   fold is held at zero until the black is completely gone, and then arrives
   as a cascade: logo, nav, header tools, headline, lead, each button, the
   card scene, the scroll cue — reading order, top to bottom. Each step is
   short (.5s) and they overlap, so it lands as one quick sweep down the
   screen rather than eight separate entrances.

   The delays are derived from one number, --introStep, so the whole cascade
   can be tightened or loosened in a single edit.

   Without the .intro-in half of this pair the page is simply itself — a
   browser that never ran the overlay, or reduce-motion, never sees a
   hidden header. --- */
html.intro-hold .logo,
html.intro-hold .main-nav > a,
html.intro-hold .header-tools,
html.intro-hold .hero h1,
html.intro-hold .hero-lead,
html.intro-hold .hero-actions > *,
html.intro-hold .hero-card-stage,
html.intro-hold .hero-scroll-wrap{
  opacity:0;
}
html.intro-in{ --introStep:.07s; }
html.intro-in .logo,
html.intro-in .main-nav > a,
html.intro-in .header-tools,
html.intro-in .hero h1,
html.intro-in .hero-lead,
html.intro-in .hero-actions > *,
html.intro-in .hero-card-stage,
html.intro-in .hero-scroll-wrap{
  animation:introFadeUp .5s ease-out both;
  /* Each of these gets its own compositor layer for the length of the
     cascade, so the fades are composited rather than repainting text and the
     card scene on the main thread frame by frame. The boot work happening
     alongside them (§13) can then no longer stutter them. It is scoped to
     .intro-in, so the layers exist only while the cascade is playing and are
     dropped with the class — a permanent will-change here would pin a layer
     per element for the rest of the page's life. */
  will-change:opacity;
}
html.intro-in .logo             { animation-delay:0s; }
html.intro-in .main-nav > a:nth-child(1){ animation-delay:calc(var(--introStep) * 1); }
html.intro-in .main-nav > a:nth-child(2){ animation-delay:calc(var(--introStep) * 1.6); }
html.intro-in .main-nav > a:nth-child(3){ animation-delay:calc(var(--introStep) * 2.2); }
html.intro-in .main-nav > a:nth-child(4){ animation-delay:calc(var(--introStep) * 2.8); }
html.intro-in .header-tools     { animation-delay:calc(var(--introStep) * 3.4); }
html.intro-in .hero h1          { animation-delay:calc(var(--introStep) * 4.4); }
html.intro-in .hero-lead        { animation-delay:calc(var(--introStep) * 5.6); }
html.intro-in .hero-actions > *:nth-child(1){ animation-delay:calc(var(--introStep) * 6.6); }
html.intro-in .hero-actions > *:nth-child(2){ animation-delay:calc(var(--introStep) * 7.2); }
/* The card scene's own cards are dealt in by GSAP (js/main.js §5b), which is
   held back by the same amount — this only fades the stage they sit on. */
html.intro-in .hero-card-stage  { animation-delay:calc(var(--introStep) * 7.8); }
html.intro-in .hero-scroll-wrap { animation-delay:calc(var(--introStep) * 9); }
/* The gold sweep is a background-position animation on background-clip:text,
   which means it re-rasterises the glyphs on the main thread every single
   frame. Running that inside the word that is itself fading up is what made
   the cascade stutter on a real first load. It waits for the cascade to
   finish instead — .intro-in is dropped by releaseIntroCascade() (main.js
   §13), and the sweep starts then, on an idle page, where it costs nothing
   anyone can see.

   .intro-active matters just as much and is easy to miss: that is the
   dissolve, and the dissolve is four stacked veils each opening a blurred
   radial hole over the whole viewport. Everything underneath is recomposited
   behind them every frame, so anything that also repaints per frame is paid
   for twice. On a refresh the browser restores the scroll position, so the
   word under the overlay is whichever one the visitor was last looking at —
   which is why this showed on the closing CTA's "Jouw marge blijft retail"
   but never on the hero headline, where intro-hold's opacity:0 means it is
   not painted at all. */
html.intro-active .shine,
html.intro-in .shine{ animation:none; }

/* Same reasoning, other mechanism: the closing mascot is a 12-frame animated
   WebP, so it decodes and repaints on its own clock for as long as it is
   rendered — no CSS animation to switch off. Taking it out of rendering for
   the length of the dissolve is what stops it. It is absolutely positioned
   and decorative, so nothing moves when it goes. */
html.intro-active .closing-mascot{ display:none; }

/* And the third one, which is about the compositor rather than about paint.
   .book-page and .card-rain img both carry a permanent will-change, so the
   book section alone is five viewport-sized layers (each with a 70px-blur
   box-shadow) plus sixteen card layers. The dissolve is itself five masked,
   blurred, full-viewport layers — stack the two and the compositor is moving
   twenty-odd viewport-scale surfaces per frame, which is why a refresh with
   the book on screen stuttered while the same refresh on the hero or the
   feature cards (where those layers are off screen) did not.
   Nothing underneath is animating during the dissolve — initBook and the rain
   are both deferred past it — so the promotions buy nothing here and are
   handed back for its duration. */
html.intro-active .book-page,
html.intro-active .card-rain img{ will-change:auto; }

/* Opacity only. Nothing travels: with eight elements coming in one after
   the other, any shared vertical offset reads as a roller blind coming down
   the page rather than as eight things appearing. The sequence alone carries
   the top-to-bottom direction. */
@keyframes introFadeUp{
  from{ opacity:0; }
  to  { opacity:1; }
}
@media (max-width:940px){
  /* The nav is collapsed behind the toggle here, so its links are not part
     of the cascade and the copy should not wait for them. */
  html.intro-in{ --introStep:.06s; }
  html.intro-in .main-nav > a{ animation:none; opacity:1; transform:none; }
  html.intro-in .header-tools{ animation-delay:calc(var(--introStep) * 1); }
  html.intro-in .hero h1     { animation-delay:calc(var(--introStep) * 2); }
  html.intro-in .hero-lead   { animation-delay:calc(var(--introStep) * 3.2); }
  html.intro-in .hero-actions > *:nth-child(1){ animation-delay:calc(var(--introStep) * 4.2); }
  html.intro-in .hero-actions > *:nth-child(2){ animation-delay:calc(var(--introStep) * 4.8); }
  html.intro-in .hero-card-stage { animation-delay:calc(var(--introStep) * 5.4); }
  html.intro-in .hero-scroll-wrap{ animation-delay:calc(var(--introStep) * 6.2); }
}

/* The ball sits on the exact centre of the viewport, so the reveal opens
   from the ball rather than from the middle of ball + bar as a group. */
.intro-stage{
  position:absolute; left:50%; top:50%;
  width:var(--ball); height:var(--ball);
  margin:calc(var(--ball) / -2) 0 0 calc(var(--ball) / -2);
  animation:
    introStageIn  .16s linear .06s both,
    introStageOut .55s cubic-bezier(.5,0,.75,1) 1.14s forwards;
}
@keyframes introStageIn{ from{ opacity:0; } to{ opacity:1; } }
/* Pushes through the ball as the hole opens; the mask, not this fade, is
   what actually removes it — the opacity is only there so the fallback
   path (no @property) still has something to leave on. */
@keyframes introStageOut{
  0%   { transform:scale(1);   opacity:1; }
  70%  {                       opacity:1; }
  100% { transform:scale(1.9); opacity:0; }
}

/* Three jobs, three elements, so no two animations fight over one transform:
   .intro-drop = the bounce, .intro-ball = the squash, .intro-shadow = the
   contact patch (which stays on the ground and never travels with the ball). */
.intro-drop{
  position:absolute; inset:0; display:block;
  will-change:transform;
  animation:introDrop .90s .12s both;
}
@keyframes introDrop{
  0%   { transform:translateY(var(--drop));               animation-timing-function:cubic-bezier(.45,0,.85,.6); }
  24%  { transform:translateY(0);                         animation-timing-function:cubic-bezier(.2,.6,.4,1); }
  52%  { transform:translateY(calc(var(--drop) * .40));   animation-timing-function:cubic-bezier(.5,0,.85,.55); }
  78%  { transform:translateY(0);                         animation-timing-function:cubic-bezier(.2,.6,.4,1); }
  90%  { transform:translateY(calc(var(--drop) * .12));   animation-timing-function:cubic-bezier(.5,0,.9,.6); }
  100% { transform:translateY(0); }
}

.intro-ball{
  display:block; width:100%; height:100%;
  transform-origin:50% 100%;   /* squash from the contact point, not the middle */
  will-change:transform;
  animation:introSquash .90s .12s both;
}
@keyframes introSquash{
  0%,20%   { transform:scale(1,1); }
  25%      { transform:scale(1.16,.84); }
  33%      { transform:scale(.95,1.05); }
  44%,74%  { transform:scale(1,1); }
  79%      { transform:scale(1.09,.91); }
  86%      { transform:scale(.98,1.02); }
  94%,100% { transform:scale(1,1); }
}

/* On a black field a shadow can only read as a faint light contact patch —
   kept well under a tenth of full white so it never becomes a glow. */
.intro-shadow{
  position:absolute; left:50%; top:calc(100% + 7px);
  width:var(--ball); height:calc(var(--ball) * .17);
  margin-left:calc(var(--ball) / -2);
  border-radius:50%;
  background:rgba(255,255,255,.55);
  filter:blur(5px);
  opacity:.12;
  will-change:transform, opacity;
  animation:introShadow .90s linear .12s both;
}
@keyframes introShadow{
  0%   { transform:scale(.5,.5);   opacity:.05; }
  24%  { transform:scale(1.1,1);   opacity:.15; }
  33%  { transform:scale(1,1);     opacity:.12; }
  52%  { transform:scale(.68,.9);  opacity:.07; }
  78%  { transform:scale(1.06,1);  opacity:.14; }
  90%  { transform:scale(.86,.95); opacity:.10; }
  100% { transform:scale(1,1);     opacity:.12; }
}

/* Deliberately no percentage, no counter and no "Loading…" — it measures
   nothing, so it must not claim to. */
.intro-bar{
  position:absolute; left:50%; top:50%;
  transform:translateX(-50%);
  margin-top:calc(var(--ball) * .5 + clamp(34px, 7vw, 44px));
  width:clamp(140px, 46vw, 200px); height:3px;
  border-radius:999px;
  background:rgba(255,255,255,.13);
  overflow:hidden;
  animation:introFadeOut .22s ease 1.06s forwards;
}
.intro-bar-fill{
  display:block; height:100%; width:100%;
  border-radius:inherit;
  background:rgba(255,255,255,.92);
  transform:scaleX(0); transform-origin:0 50%;
  will-change:transform;
  animation:introBar 1.16s cubic-bezier(.45,.15,.2,1) .04s forwards;
}
@keyframes introBar{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }
@keyframes introFadeOut{ to{ opacity:0; } }

/* ======================================================================
   30th Celebration mark on the loading screen · TEMPORARY
   ----------------------------------------------------------------------
   Ball on the left, anniversary mark on the right, the same height, bouncing
   together. Delete this block and the <span class="intro-30"> in index.html
   to end the campaign.

   Nothing here rewrites a rule the ball uses, which is the point: the mark
   runs introDrop, introSquash and introShadow — the ball's own keyframes, at
   the ball's own duration and delay — instead of an imitation of them. So the
   two cannot drift apart while both are on screen, and when this comes out the
   ball is left with exactly the entrance it has now.

   That is also why the mark is built in three parts like the ball is: one
   element to fall, one to squash on landing, one to stay on the ground as the
   contact patch. Squash and travel on a single element would fight over the
   same transform.
   ====================================================================== */
.intro{
  --mark-gap:clamp(14px, 3vw, 22px);
  /* Same height as the ball; the width follows from the file being 240x177. */
  --mark-w:calc(var(--ball) * 1.356);
}
.intro-30{
  position:absolute;
  /* The fallback matters more than it looks: if this ever computes to auto the
     mark drops to its static position and lands on top of the ball. */
  left:calc(100% + var(--mark-gap, 18px));
  top:0;
  width:var(--mark-w); height:var(--ball);
}
.i30-drop{
  position:absolute; inset:0; display:block;
  will-change:transform;
  animation:introDrop .90s .12s both;
}
.intro-30 img{
  display:block; width:100%; height:100%;
  /* The global img{max-width:100%} measures against .intro-stage, which is one
     ball wide — without this the mark is squashed to the ball's width. */
  max-width:none;
  transform-origin:50% 100%;
  will-change:transform;
  animation:introSquash .90s .12s both;
}
.i30-shadow{
  position:absolute; left:50%; top:calc(100% + 7px);
  width:var(--mark-w); height:calc(var(--ball) * .17);
  margin-left:calc(var(--mark-w) / -2);
  border-radius:50%;
  background:rgba(255,255,255,.55);
  filter:blur(5px);
  opacity:.12;
  will-change:transform, opacity;
  animation:introShadow .90s linear .12s both;
}
/* Centring the pair, not the ball. The stage's own margin-left is what puts
   the ball on the viewport's centre line; pulling it half the mark's width
   plus the gap further left puts the middle of the two there instead, and the
   bar — centred on the viewport in its own right — then sits under the middle
   of the group with no rule of its own. The origin moves with it, so the exit
   pushes both outward evenly instead of throwing the mark off to the right.

   What this costs: the dissolve's hole opens on the viewport centre, which is
   now the gap between ball and mark rather than the ball itself. That was a
   deliberate choice in the original (see .intro-stage above) and it is traded
   knowingly — the hole is soft, it starts as the group is already scaling out,
   and it covers both within a few frames, whereas a pair visibly parked right
   of centre is the first thing anyone sees. Both lines go with this block and
   the original framing returns with them. */
.intro-stage{
  margin-left:calc(var(--ball) / -2 - (var(--mark-w) + var(--mark-gap)) / 2);
  transform-origin:calc(50% + (var(--mark-w) + var(--mark-gap)) / 2) 50%;
}

/* ======================================================================
   14b. Language pick on the loading screen
   ----------------------------------------------------------------------
   NL / EN under the ball while the intro plays. The intro is over in about
   three seconds, far too short to read and click, so the pick outlives it:
   once the black is gone (.intro-active dropped) it docks as a small glass
   pill over the hero, and the controller in index.html's <head> removes
   .lang-ask after 7 s, on the first scroll or on a click. It never holds
   the intro up.

   Only on a visitor's first homepage load: .lang-ask is set by that same
   controller and only while nothing is remembered under stc-lang-picked.
   The scroll cue sits in the same spot, so it waits until the pick is gone.
   ====================================================================== */
.intro-lang{ display:none; }
html.lang-ask .intro-lang{
  position:fixed; left:50%; bottom:clamp(28px, 7vh, 56px); z-index:9001;
  display:flex; align-items:center; gap:4px;
  padding:5px; border-radius:12px;
  border:1px solid transparent;
  /* The buttons' voice (.btn): Inter, sentence case. */
  font-family:var(--font-body); font-size:13px; font-weight:600; letter-spacing:normal;
  white-space:nowrap;
  transform:translateX(-50%);
  opacity:0;
  animation:introLangIn .4s ease .3s forwards;
  transition:background-color .5s, border-color .5s;
}
.intro-lang-q{ color:var(--ink-dim); padding:0 10px 0 8px; font-weight:500; text-transform:none; }
.intro-lang button{
  font:inherit; letter-spacing:inherit;
  background:none; color:var(--ink-dim);
  border:1px solid var(--ink-line); border-radius:8px;
  padding:8px 14px; cursor:pointer;
  transition:color .2s, border-color .2s;
}
.intro-lang button:hover,
.intro-lang button:focus-visible{ color:var(--ink-text); border-color:var(--ink-dim); }
html[lang="nl"] .intro-lang button[data-lang="nl"],
html[lang="en"] .intro-lang button[data-lang="en"]{ color:var(--gold); border-color:var(--gold); }
/* Docked: the overlay is gone and the pick now sits over the hero. */
html.lang-ask:not(.intro-active) .intro-lang{
  background:rgba(13,15,18,.82);
  border-color:var(--ink-line);
  -webkit-backdrop-filter:blur(10px);
          backdrop-filter:blur(10px);
}
/* !important because the intro cascade animates this element's opacity, and
   an animation outranks every normal declaration. */
html.lang-ask .hero-scroll-wrap{ opacity:0 !important; }
@keyframes introLangIn{ to{ opacity:1; } }
/* Leaving: a quick fade instead of vanishing (the controller removes
   .lang-ask once this has played). Replaces the fade-in animation, so it also
   works when the pick is dismissed before it has fully appeared. */
html.lang-ask.lang-out .intro-lang{ animation:introLangOut .25s ease forwards; pointer-events:none; }
@keyframes introLangOut{ from{ opacity:1; } to{ opacity:0; } }
@media (prefers-reduced-motion: reduce){
  html.lang-ask .intro-lang{ animation-delay:0s; animation-duration:.2s; }
}

@media (prefers-reduced-motion: reduce){
  /* Same animation *name* on the overlay — the controller keys off it, so
     renaming it here would strand the overlay on screen until the safety
     timeout. Only its shape changes: a short fade, no travelling mask. */
  @keyframes introReveal{ 0%{ opacity:1; } 100%{ opacity:0; } }
  html.intro-active .intro{
    -webkit-mask-image:none; mask-image:none;
    animation-duration:.26s; animation-delay:.30s; animation-timing-function:linear;
  }
  html.intro-active .intro-drop,
  html.intro-active .intro-ball,
  html.intro-active .intro-shadow,
  html.intro-active .intro-bar{ animation:none; }
  /* TEMPORARY, with the block above: the mark stands still exactly as the
     ball does, and needs nothing handing back — it has no resting state of
     its own to undo. */
  html.intro-active .i30-drop,
  html.intro-active .intro-30 img,
  html.intro-active .i30-shadow{ animation:none; }
  html.intro-active .intro-stage{ animation:introStageIn .12s linear both; }
  html.intro-active .intro-bar-fill{ animation-duration:.30s; animation-delay:0s; }
  /* The layered dissolve is the one thing that cannot be toned down — it IS
     motion — so the extra veils are simply not drawn. */
  html.intro-active .intro-veil{ display:none; }
  /* The page still waits for the overlay, but arrives all at once and
     without travel — a cascade is motion, which is the thing being opted
     out of here. */
  html.intro-in .logo,
  html.intro-in .main-nav > a,
  html.intro-in .header-tools,
  html.intro-in .hero h1,
  html.intro-in .hero-lead,
  html.intro-in .hero-actions > *,
  html.intro-in .hero-card-stage,
  html.intro-in .hero-scroll-wrap{
    animation:introFade .25s linear both;
    animation-delay:0s !important;
  }
  @keyframes introFade{ from{ opacity:0; } to{ opacity:1; } }
}

/* ---------------------------------------------------------------------
   15. Hero scroll-down button
   ---------------------------------------------------------------------
   Sits in normal flow at the foot of the hero rather than being pinned to
   the viewport, so it keeps the hero's own rhythm at every breakpoint.
   Only the arrow ever moves: the capsule, its border and the label are
   fixed, which is what keeps the invitation subtle instead of restless.
   --------------------------------------------------------------------- */
/* Pinned to the very bottom edge of the hero (matching the reference): the
   capsule sits flush on the hero's lower boundary, centred, so it reads as
   the doorway out of the hero rather than as one more element in the stack.
   The hero itself carries extra bottom padding so nothing collides with it. */
.hero-scroll-wrap{
  display:flex; justify-content:center;
  position:absolute;
  left:0; right:0;
  bottom:var(--deck-bar);
  z-index:3;
  pointer-events:none;              /* only the button itself is clickable */
}
.hero-scroll-wrap > *{ pointer-events:auto; }
.hero-scroll{
  display:inline-flex; align-items:center; justify-content:center;
  gap:9px;
  min-height:42px;
  padding:11px 22px 12px;
  /* Sits flush on the hero's bottom edge and rises out of it: only the top
     corners are rounded, at the dynamic island's radius. Rounding the bottom
     as well would leave two visible notches against the edge. */
  border-radius:21px 21px 0 0;
  border:1px solid rgba(236,234,227,.10);
  border-bottom:none;
  background:rgba(10,12,15,.55);
  -webkit-backdrop-filter:blur(10px) saturate(1.2);
          backdrop-filter:blur(10px) saturate(1.2);
  /* Deliberately quiet: it is a cue, not a call to action. */
  color:rgba(236,234,227,.72);
  font-family:var(--font-mono);
  font-size:10.5px;
  letter-spacing:.12em;
  text-transform:uppercase;
  text-indent:.12em;                /* optical centring, as on .btn */
  cursor:pointer;
  /* Enters with the hero (see initHeroScroll); the colour transitions are
     the hover state and are unaffected by the entrance. */
  opacity:0; transform:translateY(10px);
  transition:opacity .6s var(--ease), transform .6s var(--ease),
             background .3s var(--ease), border-color .3s var(--ease),
             color .3s var(--ease);
}
.hero-scroll.is-in{ opacity:1; transform:none; }
/* Scrolling has started: drop it straight down through the hero's bottom
   edge rather than fading in place — it reads as the cue getting out of the
   way. Wins over .is-in because it comes later at equal specificity. */
.hero-scroll.is-in.is-gone{
  opacity:0;
  transform:translateY(100%);
  pointer-events:none;
  /* Safari (iOS especially) keeps compositing a backdrop-filter's blurred
     backdrop even at opacity:0, so on a phone the capsule stayed on screen
     as a ghost after scrolling started while desktop hid it correctly.
     Dropping the filter and taking the element out of rendering once the
     transition has finished is what actually removes it. `visibility` is
     transitionable, so the fade still plays before it goes. */
  -webkit-backdrop-filter:none;
          backdrop-filter:none;
  visibility:hidden;
  transition:opacity .25s var(--ease), transform .35s var(--ease),
             visibility 0s linear .35s;
}
.hero-scroll-arrow{
  display:inline-flex;
  order:-1;                         /* arrow leads the label, as in the island */
  transition:transform .3s var(--ease);
}
.hero-scroll-arrow svg{
  display:block; width:12px; height:12px;
  fill:none; stroke:currentColor; stroke-width:1.8;
  stroke-linecap:round; stroke-linejoin:round;
}
/* One bounce, then a long rest — a continuously moving arrow reads as a
   loading state rather than an invitation. */
@keyframes heroScrollArrow{
  0%,16%   { transform:translateY(0);   animation-timing-function:cubic-bezier(.4,0,.2,1); }
  30%      { transform:translateY(5px); animation-timing-function:cubic-bezier(.3,1.3,.5,1); }
  46%,100% { transform:translateY(0); }
}
.hero-scroll.is-in .hero-scroll-arrow svg{
  animation:heroScrollArrow 1.5s infinite;
}
@media (hover:hover){
  .hero-scroll:hover{
    background:rgba(10,12,15,.72);
    border-color:rgba(236,234,227,.22);
    color:var(--ink-text);
  }
  .hero-scroll:hover .hero-scroll-arrow{ transform:translateY(3px); }
}
.hero-scroll:active{ background:rgba(10,12,15,.85); }
@media (max-width:760px){
  /* A touch tighter so it clears the quote FAB in the bottom-right corner
     on narrow phones. */
  .hero-scroll{ padding:10px 18px 11px; min-height:38px; font-size:10px; letter-spacing:.1em; border-radius:19px 19px 0 0; }
}
@media (max-width:640px){
  .hero-scroll{ min-height:38px; padding:11px 18px; font-size:10.5px; gap:10px; }
}
@media (prefers-reduced-motion: reduce){
  .hero-scroll{ transition:background .3s, border-color .3s; opacity:1; transform:none; }
  .hero-scroll.is-in .hero-scroll-arrow svg{ animation:none; }
  .hero-scroll-arrow{ transition:none; }
}

/* ==========================================================================
   16. Poké Ball shutter — "Wat bieden we?" is opened by the visitor.

   Not a ball on top of the page: a full-width Poké Ball lid lying over the
   section and nothing else — red panel above, paper panel below, one dark
   seam between them and the button sitting on that seam. It is absolutely
   positioned inside the section rather than stuck to the viewport, so it
   covers exactly this one hero and scrolls away with it; a sticky lid stayed
   glued to the screen and read as if it had taken over the whole site. Deliberately flat: solid fills, no
   gradients, no highlights, no shadows. The only light in the whole thing is
   the button, because the button is the one thing the visitor has to notice.

   Built by initPokeballShutter() (main.js §7d) and only when GSAP is there
   and motion is allowed — without it nothing is inserted and the section is
   the plain one it always was.
   ========================================================================== */
.feature{ position:relative; }

.feature-head{ text-align:center; position:relative; }
.feature-head h2{ margin-inline:auto; }
.feature > .container{ position:relative; z-index:1; }

.pokeball-reveal{
  position:absolute;
  inset:0;                /* exactly the section — no more, no less */
  z-index:3;
  overflow:hidden;        /* the panels leave through the viewport edges */
  pointer-events:none;    /* only the button is clickable — see .pb-eye */
  /* Declared here rather than on the button: the label positions itself
     against the button's size too, and a custom property set on the button
     would not reach a sibling. */
  --pb-eye-size:clamp(58px, 6.6vw, 92px);
}

/* The two panels. Full-bleed, flat colour, and the seam is a border on each
   of them, so when they part each takes its own half of it with it — the
   seam can never be left hanging off one panel. */
.pokeball-half{
  position:absolute;
  left:0; width:100%; height:50%;
  will-change:transform;
}
.pokeball-top{
  top:0;
  background:var(--pb-red);
  border-bottom:var(--pb-seam) solid var(--ink);
}
.pokeball-bottom{
  bottom:0;
  background:var(--pb-shell);
  border-top:var(--pb-seam) solid var(--ink);
}
:root{
  --pb-red:   #D0342C;   /* flat, no gradient — the shell is one colour */
  --pb-shell: #F2F0EA;   /* paper-white, so the lid belongs to this palette */
  --pb-seam:  clamp(7px, 0.9vw, 12px);
  /* The round seam reads thinner than the straight one at the same width —
     it is a ring, so only its outer edge is ever against the shell. A touch
     more weight puts the two back in balance. */
  --pb-ring:  clamp(9px, 1.15vw, 15px);
}

/* --- The button. The only lit element, and the only clickable one. --- */
.pb-eye{
  position:absolute;
  left:50%; top:50%;
  width:var(--pb-eye-size); height:var(--pb-eye-size);
  margin:0; padding:0;
  transform:translate3d(-50%,-50%,0);
  border:var(--pb-ring) solid var(--ink);
  border-radius:50%;
  background:var(--pb-shell);
  cursor:pointer;
  pointer-events:auto;
  -webkit-tap-highlight-color:transparent;
  /* The glow lives on the button and nowhere else. */
  animation:pbEyeBreathe 2.6s ease-in-out infinite;
}
@keyframes pbEyeBreathe{
  0%,100%{ background:var(--pb-shell); box-shadow:0 0 0 0 rgba(228,188,62,0); }
  50%    { background:#FFF6D8;         box-shadow:0 0 clamp(22px,3vw,40px) clamp(4px,.6vw,9px) rgba(228,188,62,.55); }
}

/* The radar ring: one calm pulse outward, not a constant halo. */
.pb-eye::after{
  content:"";
  position:absolute;
  inset:calc(-1 * var(--pb-ring));
  border:2px solid rgba(228,188,62,.85);
  border-radius:50%;
  animation:pbEyeRing 2.6s ease-out infinite;
  pointer-events:none;
}
@keyframes pbEyeRing{
  0%  { transform:scale(1);   opacity:.75; }
  70% { transform:scale(2.1); opacity:0; }
  100%{ transform:scale(2.1); opacity:0; }
}

/* Label, in the site's own mono voice. Sits below the button, on the paper
   panel, so it is dark-on-light and needs no plate behind it. */
.pb-hint{
  position:absolute;
  left:50%; top:calc(50% + var(--pb-eye-size) * .5 + clamp(18px, 2.4vw, 30px));
  transform:translate3d(-50%,0,0);
  /* Same voice as the buttons (.btn): Inter, sentence case. */
  font-family:var(--font-body);
  font-size:clamp(13px, 1.2vw, 15px);
  font-weight:600;
  letter-spacing:normal;
  text-transform:none;
  color:var(--paper-text);
  white-space:nowrap;
  pointer-events:none;
  /* Bounces on the button's own beat: same 2.6s period, same easing, same
     start — so the label lifts exactly as the eye lights up and the ring
     goes out. The two read as one heartbeat rather than two loops that
     happen to be nearby. */
  animation:pbHintBounce 2.6s ease-in-out infinite;
}
@keyframes pbHintBounce{
  0%,100%{ transform:translate3d(-50%,0,0)            scale(1);    }
  38%    { transform:translate3d(-50%,-7px,0)         scale(1.04); }
  50%    { transform:translate3d(-50%,-9px,0)         scale(1.05); }
  66%    { transform:translate3d(-50%,0,0)            scale(1);    }
  74%    { transform:translate3d(-50%,-2.5px,0)       scale(1.01); }
}

@media (hover:hover){
  /* Hover confirms rather than decorates: the pulse runs faster and the ring
     is brighter, and nothing moves. */
  .pb-eye:hover{ animation-duration:1.3s; }
  .pb-eye:hover::after{ animation-duration:1.3s; border-color:rgba(228,188,62,1); }
  /* The label keeps time with it — the hint is a later sibling of the
     button, so it can follow the hover without any JS. */
  .pb-eye:hover ~ .pb-hint{ animation-duration:1.3s; }
}
.pb-eye:focus-visible{ outline:3px solid var(--gold-bright); outline-offset:6px; }
.pb-eye:active{ transform:translate3d(-50%,-50%,0) scale(.94); }

@media (max-width:640px){
  .pokeball-reveal{ --pb-eye-size:clamp(56px, 15vw, 74px); }
}
@media (prefers-reduced-motion: reduce){
  /* Nothing is inserted in this case, but if the layer is ever present the
     lid must not blink or pulse. */
  .pokeball-reveal{ display:none; }
}

/* --- The heading's rolling Poké Ball (main.js §7e). Flat, like the lid: two
   solid bands and a hard seam, no gradient shading and no shadow. It is
   positioned by JS in pixels measured from the heading's own text, so it is
   correct at any type size and in either language. --- */
.wwo-ball{
  position:absolute;
  left:0; top:0;
  width:var(--wwo-size); height:var(--wwo-size);
  --wwo-size:clamp(19px, 2vw, 28px);
  border-radius:50%;
  /* Hard colour stops — a two-tone shell, not a gradient. */
  background:linear-gradient(180deg,
    var(--pb-red)   0 45%,
    var(--ink)      45% 55%,
    var(--pb-shell) 55% 100%);
  box-shadow:inset 0 0 0 2px var(--ink);   /* flat outline, not a glow */
  opacity:0;
  pointer-events:none;
  will-change:transform;
}
.wwo-ball.is-rolling{ opacity:1; }
.wwo-ball::after{
  content:"";
  position:absolute;
  left:50%; top:50%;
  width:30%; height:30%;
  margin:-15% 0 0 -15%;
  border-radius:50%;
  background:var(--pb-shell);
  box-shadow:0 0 0 2px var(--ink);
}
@media (prefers-reduced-motion: reduce){
  .wwo-ball{ display:none; }
}


/* ==========================================================================
   Touch devices — what the deck costs a phone
   Everything here is an override, so it has to come last: a media query
   carries no specificity of its own and these all restate a property some
   earlier rule already set.

   The theme is the same throughout. A desktop composites a full-screen
   blend layer or a backdrop blur once and forgets about it; a phone GPU
   re-does it for every frame of a scroll, and the frames it is asked to
   re-do it in are exactly the frames where the next full-height panel is
   being composited for the first time. None of what is switched off below
   is reachable by a finger anyway — it is all pointer light and hover
   polish, paid for on a device with no pointer.
   ========================================================================== */
@media (pointer:coarse){

  /* Eén viewport-eenheid voor de hele deck — en dat is sinds 2026-09-24
     een vaste: vh voor de dóós, dvh alleen voor wat erin staat.

     De geschiedenis hoort erbij, anders draait iemand dit terug. De panelen
     stonden eerst in drie verschillende eenheden (hero en fold in svh, book
     in vh, feature in dvh), wat op een desktop onzichtbaar is en op een
     telefoon drie verschillende secties oplevert: die eenheden liggen tot
     ~90px uit elkaar en dat gat opent en sluit terwijl de adresbalk schuift.

     Toen werd het overal svh, met deze afweging er expliciet bij: svh
     verandert nooit, dus de deck wordt één keer opgemeten en de browser snapt
     tegen vaste posities — en de prijs was dat met een ingeschoven adresbalk
     het venster hóger is dan svh, zodat er een strook van het volgende paneel
     onderaan meekijkt. Dat werd geaccepteerd als "hint dat er meer is".

     Toen werd het overal dvh, om die strook weg te halen. Dat werkte, en het
     kostte precies wat er in de oude noot als risico stond: dvh volgt de balk
     continu, dus elk gebaar dat hem in- of uitschuift hertekent álle panelen
     midden in dat gebaar. En omdat elk paneel even hoog is, schuift de fout
     op met het paneelnummer: de bovenkant van paneel 3 verspringt drie
     balkhoogtes. De browser snapt daarna netjes opnieuw — maar ná het gebaar,
     en dat is wat je ziet: het paneel komt op ~85% binnen en gaat daarna
     alsnog naar 100%. Terugswipen laat het het duidelijkst zien, want dát is
     het gebaar dat de balk terughaalt.

     Nu zijn de twee dingen uit elkaar getrokken die niet hetzelfde hoeven te
     zijn:

       · de dóós van een paneel meet 100vh — de stand mét weggeschoven balk,
         en die verandert nooit. Alle snap-posities in het document liggen dus
         vast, hoe de balk ook beweegt. Geen herberekening tijdens een gebaar,
         dus ook geen correctie erna. En omdat een paneel nooit kórter is dan
         het scherm, is er geen enkele stand waarin je twee panelen tegelijk
         ziet: de regel "of paneel A, of paneel B" geldt nu in alle drie de
         standen van de balk in plaats van in één.

       · wat erin staat richt zich op het zíchtbare venster. Dat gaat via
         --deck-bar (= 100vh - 100dvh: 0 zolang de balk weg is, zijn eigen
         hoogte zodra hij er staat), op twee manieren:

           - alles wat absoluut aan de onderrand hangt krijgt hem bij zijn
             `bottom` opgeteld — de scrollknop van de hero, de twee ingangen
             van de muur, de onderfade van de muur, Pikachu op de slotband.
             Die staan daardoor op de onderrand van het schérm in plaats van
             onder de vouw. Absoluut geplaatst, dus dit kost geen hoogte.

           - de gecentreerde inhoud van een paneel schuift een halve
             balkhoogte omhoog met een transform, want gecentreerd in een doos
             van 100vh staat een halve balk te laag in een venster van
             100dvh. Nadrukkelijk een transform en geen ondermarge: marge telt
             mee in de hoogte, en op een kort scherm — .assure is daar de
             krapste — zou het paneel er juist door gaan groeien, wat precies
             de beweging terugbrengt die we kwijt willen.

     Wat er dan nog beweegt is alleen de inhoud bínnen een paneel, tijdens de
     balkanimatie. Dat is een herschikking van één scherm en geen verschuiving
     van het document eronder — en het is het enige stuk waar iets aan te
     merken valt als het op een echte telefoon onrustig voelt. De uitweg is
     dan: de transform hieronder eruit (de inhoud staat dan een halve balk te
     laag zolang de balk in beeld staat). De doosmaat niet aanraken — die is
     het hele punt. */
  :root{ --deck-bar:calc(100vh - 100dvh); }
  .hero, .chain, .assure, .closing-section{ min-height:100vh; }
  .fold{ height:100vh; min-height:100vh; }
  .book{ height:100vh; }

  /* En de keerzijde van een doos die hoger mág zijn dan het scherm, want die
     is er: de covering-regel van scroll-snap. Staat het snapgebied van een
     paneel hoger dan de scrollport, dan is *elke* stand waarin dat gebied de
     scrollport bedekt een geldige rustplaats — de browser trekt je dan dus
     nérgens naartoe en je blijft staan waar je ophield. Gemeten op 430x762
     met panelen van 874: loslaten op 112px voorbij de bovenkant van .fold
     blijft op 112px staan, en dan kijk je naar 762px muur met 112px van de
     slotband eronder. Dat is exact de melding "de banner is in werkelijkheid
     ietsjes groter". Dezelfde regel laat de footer met opzet vrij scrollen.

     Het snapgebied is niet de doos: `scroll-margin` verschuift de randen
     ervan, en een negatieve waarde trekt ze naar binnen — zonder de layout
     aan te raken, dus zonder ook maar iets aan de vaste hoogtes hierboven te
     veranderen. De onderrand gaat een balkhoogte plus één pixel omhoog, dus
     het gebied is altijd nét kleiner dan de scrollport en covering kan niet
     aanslaan, in geen van de balkstanden. Die ene pixel vangt de gebroken
     schermhoogtes af: op een venster van 874,3px is een paneel van 874,3px
     precies groot genoeg om alsnog te bedekken, en dan zie je een haarlijn
     van het volgende paneel onderaan — de "miniscule lijn die niet goed
     gedicht is".

     De bovenrand blijft waar hij is, en `scroll-snap-align:start` mikt op de
     bovenrand, dus de snappositie zelf is en blijft de bovenkant van het
     paneel. Gecontroleerd (430x762, panelen 874): 0/874/1748/2622/3496 exact,
     footer bereikbaar, bodem exact de bodem. */
  .hero, .c30-banner, .chain, .assure, .fold, .book, .closing-section{
    scroll-margin-bottom:calc(-1px - var(--deck-bar));
  }
  /* De inhoudsblokken zelf, niet de panelen: deze vier dragen de gecentreerde
     inhoud van hun paneel. Geen van de vier is een .reveal — die zitten
     erbinnen en animeren hun eigen transform, dus die zouden deze overschrijven. */
  .hero-grid,
  .chain-inner,
  .assure-inner,
  .closing-section > .container{
    transform:translateY(calc(var(--deck-bar) / -2));
  }

  /* De muur is het zwaarste dat deze pagina een telefoon te composeren geeft,
     dus daar geeft hij op wat hij missen kan: de vlek eigen kleur achter elke
     tegel is een verloop per tegel, en dat is het eerste dat ophoudt zichzelf
     terug te verdienen bij een kolombreedte van honderd pixels. */
  .fold-tile::before{ display:none; }

  /* The header blurs the full width of the viewport, and it is pinned over
     content that is moving — so on a phone it is re-blurred every frame of
     every scroll, from the first panel to the last. Opaque instead: at 86%
     over ink the blur was barely doing anything visible in the first place.
     The homepage header keeps its transition, so it still fades in over the
     hero rather than snapping on. */
  .site-header,
  body.home .site-header.is-solid{
    -webkit-backdrop-filter:none;
            backdrop-filter:none;
    background:rgba(13,15,18,.97);
  }
  body.home .site-header{ transition:background .35s ease, border-color .35s ease; }

  /* --- The three loops that never stop -------------------------------
     A CSS animation does not pause because its element scrolled off
     screen. Everything below was running on a phone from the first paint
     to the last, including through every frame of every swipe, and none
     of the three can be handed to the compositor: they animate paint,
     not transform. Between them they were re-painting the hero and the
     feature panel continuously while the browser was trying to raster
     the next panel of the deck.

     None of them is switched off on a desktop, where they cost nothing
     visible and are half the character of the page. */

  /* 1. The gold sweep across "betrouwbare" in the headline. A moving
        background-position on background-clip:text repaints the glyphs
        every frame, forever, and text painting is the slowest paint
        there is. Both ends of the keyframe land on flat gold anyway
        (see the comment on @keyframes shineSweep), so on touch the word
        is simply that gold — same colour, no gradient machinery at all. */
  .shine{
    background-image:none;
    -webkit-background-clip:border-box;
            background-clip:border-box;
    color:var(--gold-bright);
    animation:none;
  }

  /* 2. The foil band travelling over each hero card: a moving gradient
        underneath mix-blend-mode:overlay, three times over. Blending
        cannot be cached, so an animated one is a standing order to
        recomposite the card stage every frame. Parked mid-sweep the band
        still reads as light lying across the card — it just stops
        travelling, and a static blend layer is rastered once. */
  .hero-card .card-foil{
    animation:none;
    background-position:46% 46%;
  }

  /* 3. The Poké Ball's breathing glow, two panels down and running the
        whole time you are anywhere on the page. It animates box-shadow
        with a blur radius, which is recomputed from scratch on every
        frame and is among the most expensive properties there is to
        animate. Lit steadily instead — the radar ring on ::after and the
        bouncing label both animate transform and opacity only, so they
        are free, and they are what actually says "tap me". */
  .pb-eye{
    animation:none;
    background:#FFF6D8;
    box-shadow:0 0 clamp(22px,3vw,40px) clamp(4px,.6vw,9px) rgba(228,188,62,.42);
  }
}

/* ==========================================================================
   Over ons / About — about.html
   --------------------------------------------------------------------------
   The page has two jobs that pull in opposite directions: it has to read like
   a letter from one person, and it has to carry the plain, searchable
   description of what the company sells that no other page states in full.
   So it is built as two registers rather than one. The letter is set in the
   display serif on a card that behaves like paper; everything below it drops
   back to the same body type and card grid the rest of the site uses, so the
   personal half stays personal by contrast instead of by decoration.
   ========================================================================== */

/* One column. There was a second one holding a sprite until 01-09-2026; it
   was taken out, and the letter reads better with the full measure. */
.about-letter{ display:block; }

.about-note{
  background:#fff;
  border:1px solid var(--paper-line);
  border-radius:6px;
  padding:clamp(26px,3.6vw,52px);
  position:relative;
}
/* A single gold rule along the top edge, inside the radius. */
.about-note::before{
  content:"";
  position:absolute; left:0; right:0; top:0; height:3px;
  border-radius:6px 6px 0 0;
  background:linear-gradient(90deg, var(--gold), var(--gold-bright) 55%, rgba(201,162,39,0));
}

.about-eyebrow{
  font-family:var(--font-mono);
  font-size:11px;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--gold);
  margin:0 0 14px;
}
.about-hello{
  font-family:var(--font-display);
  font-size:clamp(32px,4.4vw,52px);
  line-height:1.05;
  margin:0 0 22px;
}
.about-note p{
  color:var(--paper-dim);
  font-size:16px;
  line-height:1.72;
  margin:0 0 17px;
  max-width:62ch;
}
.about-note p:last-of-type{ margin-bottom:0; }
/* The one sentence the whole letter turns on gets the ink colour back. */
.about-note p.about-lead{
  color:var(--paper-text);
  font-size:clamp(17px,1.6vw,19px);
}
.about-note a{
  color:var(--paper-text);
  text-decoration:underline;
  text-underline-offset:2px;
}
.about-note a:hover{ color:var(--gold); }
.about-note strong{ color:var(--paper-text); font-weight:600; }

.about-sign{
  margin-top:30px;
  padding-top:22px;
  border-top:1px solid var(--paper-line);
}
.about-sign-name{
  display:block;
  font-family:var(--font-display);
  font-style:italic;
  font-size:clamp(26px,3vw,34px);
  line-height:1.1;
  color:var(--paper-text);
}
.about-sign-role{
  display:block;
  margin-top:7px;
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--paper-dim);
}

/* --- The rail --------------------------------------------------------------
   Not a meter in the margin. The line lives *inside* the chapters, down their
   left edge, and it scrolls with them — which is the whole difference between
   an indicator that reports progress and one that appears to be doing the
   revealing. Its filled end is pinned to a fixed height in the viewport (see
   READ_LINE in initScrollRail), so as the page moves the bright edge travels
   down over the copy, and every chapter's words arrive as the edge reaches
   the marker beside them.

   Positioned to sit twenty pixels left of the container's text edge at every
   width: .container is max-width 1180 with 28px of padding, so its content
   starts at calc(50% - 590px + 28px) once there is room to centre, and at
   28px before that. The max() below tracks both without a media query.

   The markers are anchors rather than decoration. They look like the plain
   dots they are modelled on, but the chapter list stays reachable by keyboard
   and is announced as navigation; the label each one needs to say that is
   present and unseen. */
.story{ position:relative; }

.scroll-rail{
  position:absolute;
  z-index:5;
  top:60px;
  bottom:0;
  left:max(10px, calc(50% - 590px + 8px));
  width:2px;
  pointer-events:none;   /* the markers switch it back on for themselves */
}
.scroll-rail-track{
  position:absolute; inset:0;
  width:1px;
  background:rgba(236,234,227,.22);
}
/* Scaled rather than resized: transform is the one property that can be
   changed on every scroll frame without asking for layout back. */
.scroll-rail-fill{
  position:absolute; inset:0;
  width:2px;
  background:linear-gradient(180deg, var(--gold), var(--gold-bright));
  transform:scaleY(0);
  transform-origin:50% 0;
  will-change:transform;
}

.scroll-rail-dot{
  position:absolute;
  left:50%;
  width:24px; height:24px;
  margin:-12px 0 0 -12px;
  display:grid;
  place-items:center;
  pointer-events:auto;
}
.scroll-rail-dot::before{
  content:"";
  width:7px; height:7px;
  border-radius:50%;
  border:1px solid rgba(236,234,227,.55);
  background:var(--ink);
  opacity:.6;
  transform:scale(.8);
  transition:opacity .25s var(--ease), transform .25s var(--ease),
             background .25s var(--ease), border-color .25s var(--ease);
}
.scroll-rail-dot:hover::before{ border-color:var(--gold-bright); transform:scale(1.2); opacity:1; }
.scroll-rail-dot.is-done::before{
  background:var(--gold);
  border-color:var(--gold);
  opacity:1;
  transform:scale(1);
}
.scroll-rail-dot.is-here::before{
  background:var(--gold-bright);
  border-color:var(--gold-bright);
  transform:scale(1.55);
  box-shadow:0 0 0 4px rgba(201,162,39,.16);
}
/* The number and the chapter name are what make the marker a link worth
   following for anyone not looking at it. Nothing is drawn: the big number in
   the column beside it already says which chapter this is, and a second one
   on the line would only collide with it. */
.scroll-rail-num,
.scroll-rail-name{
  position:absolute;
  width:1px; height:1px;
  padding:0; margin:-1px;
  overflow:hidden;
  clip:rect(0 0 0 0);
  white-space:nowrap;
}
@media (prefers-reduced-motion: reduce){
  .scroll-rail-fill{ transition:none; }
  .scroll-rail-dot::before{ transition:none; }
}

/* The sections the rail points at now carry the anchors, so they are the ones
   that have to clear the sticky header on a jump. */
.chapter{ scroll-margin-top:76px; }

/* The legal and policy pages (terms, shipping, returns, privacy, b2b-terms)
   set their sub-headings one step under the page's h2. about.html used this
   too until 02-09-2026; it has no running body copy left. */
.legal-copy h3{
  font-size:20px;
  margin-top:40px;
  margin-bottom:13px;
  letter-spacing:-.01em;
}
.legal-copy h3:first-child{ margin-top:0; }

@media (prefers-reduced-motion: reduce){
  .scroll-rail-fill{ transition:none; }
}

/* ==========================================================================
   Story banners — the chapter openers on about.html
   ==========================================================================
   Every chapter now opens with a full-bleed dark band: a narrow column of
   copy on the left, and beside it five photographs dropped on the page the
   way you would drop prints on a table — overlapping, each a couple of
   degrees off square, a strip of tape holding one corner down.

   Three rules make the scatter work, and all three are worth stating because
   breaking any one of them turns it back into a grid:

     1. The collage is a positioning context with a *percentage* height, and
        every photo is placed against it in percentages. So the arrangement
        survives every width without a single media query rewriting it — the
        whole thing simply scales.
     2. No two photos share an arrangement. Each chapter gets its own set of
        five positions (--set-a … --set-d below) so the page never repeats a
        composition the eye has already learned.
     3. Rotation lives in a custom property, not in the transform shorthand.
        The scroll parallax writes --py and nothing else, so the two effects
        compose instead of overwriting one another.

   TO ADD THE REAL PHOTOGRAPHS
   ---------------------------
   Each slot is a <div class="story-photo … is-empty"> holding a placeholder.
   For every photo: drop the file in img/about/, then in about.html AND
   en/about.html replace

       <span class="story-ph" aria-hidden="true"><b>01</b></span>
   with
       <img src="/img/about/h1-01.webp" alt="…" loading="lazy" decoding="async">

   and remove `is-empty` from the wrapping div. Nothing else changes: the
   frame, the crop, the tape and the parallax are all already in place. The
   naming convention is h<chapter>-<slot>, so h3-04.webp is the fourth photo
   of chapter three. Landscape crops suit slots 1, 2 and 4; slots 3 and 5 are
   the taller ones.
   ========================================================================== */

.story-banner{
  position:relative;
  isolation:isolate;
  overflow:hidden;
  background-color:var(--ink);
  border-bottom:1px solid var(--ink-line);
}
/* --- The scan behind each band --------------------------------------------
   Every dark band on this page carries a different page of manga, and none of
   them the one the homepage opens with — four bands that all shared a scan
   would read as one long band with the copy repeating over it.

   The scans are flattened to grey and inverted at build time (see
   img/about/README.md), so this layer never needs a CSS filter: filtering a
   full-bleed element re-runs on every scrolled frame, and there are four of
   them on the page. Named on the section as --hero-img so a scan can be
   swapped in the markup without touching the stylesheet, exactly like the
   opening band above.

   Stacking is load-bearing here, not decoration: ::before paints before an
   element's children, so art 0, scrim 1, copy 2, gold seam 4. Leave any of
   them implicit and the scan lands on top of the veil meant to tame it. */
.story-banner-art{
  position:absolute; inset:0; z-index:0;
  pointer-events:none;
  background-image:var(--hero-img, none);
  background-position:var(--hero-pos, 50% 40%);
  background-size:cover;
  background-repeat:no-repeat;
  /* Low enough that it is texture rather than an image: the photographs are
     the picture on this band, and the empty slots are see-through. */
  opacity:.20;
  -webkit-mask-image:linear-gradient(to bottom, transparent 0%, #000 14%, #000 76%, transparent 100%);
          mask-image:linear-gradient(to bottom, transparent 0%, #000 14%, #000 76%, transparent 100%);
}
/* Dark under the copy, open under the collage — the same left-to-right read
   the band itself has. */
.story-banner::after{
  content:"";
  position:absolute; inset:0; z-index:1;
  pointer-events:none;
  background:
    linear-gradient(to right,
      rgba(13,15,18,.94) 0%,
      rgba(13,15,18,.86) 26%,
      rgba(13,15,18,.52) 58%,
      rgba(13,15,18,.42) 100%),
    linear-gradient(to bottom,
      rgba(13,15,18,.62) 0%,
      rgba(13,15,18,.14) 34%,
      rgba(13,15,18,.72) 100%);
}
@media (max-width:820px){
  /* No lit right-hand side to protect on a phone, and the scan is standing
     behind a column of prints rather than beside it. */
  .story-banner-art{
    background-image:var(--hero-img-sm, var(--hero-img, none));
    opacity:.16;
  }
  .story-banner::after{
    background:linear-gradient(to bottom,
      rgba(13,15,18,.60) 0%,
      rgba(13,15,18,.46) 30%,
      rgba(13,15,18,.86) 100%);
  }
}
/* A single wash of gold along the top edge, so the band reads as the start of
   something rather than as a gap between two paper sections. */
.story-banner::before{
  content:"";
  position:absolute; left:0; right:0; top:0; height:1px; z-index:4;
  background:linear-gradient(90deg, rgba(201,162,39,.55), rgba(201,162,39,.06) 58%, rgba(201,162,39,0));
  pointer-events:none;
}
.story-banner-inner{
  position:relative;
  z-index:2;
  display:grid;
  grid-template-columns:minmax(250px,330px) minmax(0,1fr);
  gap:clamp(34px,4.4vw,72px);
  align-items:center;
  min-height:660px;
  padding-top:66px;
  padding-bottom:72px;
}

/* --- The copy column ------------------------------------------------------
   Deliberately narrow. It is a caption for the photographs beside it, not a
   column of body text — the argument is made further down the page, on paper,
   where a long measure belongs. */
.story-copy{ position:relative; }
.story-num{
  font-family:var(--font-mono);
  font-size:clamp(24px,2.2vw,32px);
  line-height:1;
  letter-spacing:.02em;
  color:var(--gold);
  margin:0 0 24px;
}
/* The chapter the rail is standing in. The number takes the brighter gold, so
   the marker in the gutter and the number in the column say the same thing at
   the same moment — which is the point of driving both from one measurement. */
.story-banner.is-active .story-num{ color:var(--gold-bright); }
.story-num{ transition:color .35s var(--ease); }

.story-eyebrow{
  font-family:var(--font-mono);
  font-size:11px;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:rgba(236,234,227,.62);
  margin:0 0 16px;
}
.story-banner h2{
  font-family:var(--font-display);
  font-size:clamp(34px,3.6vw,52px);
  line-height:1.02;
  letter-spacing:-.02em;
  margin:0 0 20px;
  /* The measure is set here rather than on the column: the heading is allowed
     to overhang into the gutter, the paragraph under it is not. */
  max-width:14ch;
}
/* The body copy in the column, set to the same specification as the reference
   the page is modelled on: 14px on a 1.46 leading, in a 270px measure inside a
   330px column. Small and tight on purpose — it is a caption standing next to
   the photographs, so it has to stay visibly subordinate to the heading above
   it rather than turning the column into an article. */
.story-body{
  margin:0;
  max-width:270px;
}
.story-body p{
  margin:0 0 13px;
  font-size:14px;
  line-height:1.46;
  color:var(--ink-dim);
}
.story-body p:last-child{ margin-bottom:0; }

/* The button beside the photographs. Same shape as the reference: a box the
   width of its own label with the arrow pushed to the far edge, and the fill
   inverting on hover rather than the border changing colour — the box is what
   moves, not its outline. The fill is gold rather than white so it lands in
   the same family as the buttons at the bottom of the page. */
.story-cta{
  width:fit-content;
  min-height:50px;
  margin-top:26px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:28px;
  padding:10px 17px;
  border:1px solid rgba(236,234,227,.72);
  color:var(--ink-text);
  background:transparent;
  font-family:var(--font-mono);
  font-size:11.5px;
  line-height:1.3;
  letter-spacing:.1em;
  text-transform:uppercase;
  text-decoration:none;
  transition:color .22s var(--ease), background .22s var(--ease), border-color .22s var(--ease);
}
.story-cta > span:first-child{ max-width:170px; }
.story-cta-arrow{
  font-size:20px;
  letter-spacing:0;
  transition:transform .2s var(--ease);
}
.story-cta:hover,
.story-cta:focus-visible{
  color:#171204;
  background:var(--gold);
  border-color:var(--gold);
}
.story-cta:hover .story-cta-arrow,
.story-cta:focus-visible .story-cta-arrow{ transform:translateX(6px); }
@media (prefers-reduced-motion: reduce){
  .story-cta, .story-cta-arrow{ transition:none; }
  .story-cta:hover .story-cta-arrow{ transform:none; }
}
.story-lead{
  margin:0;
  max-width:34ch;
  font-size:14.5px;
  line-height:1.62;
  color:var(--ink-dim);
}

/* --- The collage ---------------------------------------------------------- */
.story-collage{
  position:relative;
  isolation:isolate;
  /* min-height and nothing else, deliberately. Every child is absolutely
     positioned, so the box has no content height of its own and resolves to
     exactly this — which is what keeps the five slots at the proportions they
     were designed at however long the copy beside them runs. Give it
     height:100% instead and a long column stretches the composition.

     Shortened from 590 on 02-09-2026 to close the gaps between the prints.
     The prints themselves did not change size: their heights are percentages
     of this box, so they were scaled up by exactly the amount the box came
     down and every one of them still measures what it did before. What
     changed is the air between them — measured coverage of the box went from
     67-79% to 78-91% across the four arrangements. */
  min-height:470px;
  align-self:center;
}
.story-photo{
  position:absolute;
  display:block;
  transform-origin:center center;
  /* --py is written by initStoryCollage() on scroll; --rot is set per slot
     below and never changes. Order matters: the drift is applied in the
     collage's own axes, then the print is turned. */
  transform:translate3d(0, var(--py,0px), 0) rotate(var(--rot,0deg));
  will-change:transform;
}
.story-photo-frame{
  display:block;
  width:100%; height:100%;
  overflow:hidden;
  border:1px solid rgba(236,234,227,.42);
  background:rgba(236,234,227,.028);
  box-shadow:0 22px 44px -22px rgba(0,0,0,.85);
}
/* An empty slot is a mark on the page, not an object resting on it, so it
   does not get the shadow that lifts a photograph off the band. */
.story-photo.is-empty .story-photo-frame{ box-shadow:none; }
.story-photo img{
  display:block;
  width:100%; height:100%;
  object-fit:cover;
}

/* The empty state. Two hairlines crossing corner to corner — the same mark a
   layout program leaves where a picture is going to go, which is exactly what
   this is. The slot number sits in the corner so the person filling them in
   can tell at a glance which file belongs where. */
.story-ph{
  position:relative;
  display:block;
  width:100%; height:100%;
  overflow:hidden;
}
.story-ph::before,
.story-ph::after{
  content:"";
  position:absolute; left:50%; top:50%;
  width:150%; height:1px;
  background:rgba(236,234,227,.16);
  transform-origin:center center;
}
.story-ph::before{ transform:translate(-50%,-50%) rotate(31deg); }
.story-ph::after { transform:translate(-50%,-50%) rotate(-31deg); }
.story-ph b{
  position:absolute; left:10px; top:8px;
  font-family:var(--font-mono);
  font-size:10px;
  font-weight:400;
  letter-spacing:.14em;
  color:rgba(236,234,227,.42);
}

/* --- The tape -------------------------------------------------------------
   Washi, not sellotape: a warm, slightly cloudy strip that lets the band show
   through. The torn edge is a clip-path rather than an image, so it costs
   nothing and stays crisp at any size. Only three of the five prints are
   taped — taping all of them looks like a pattern instead of an accident. */
.story-tape{
  position:absolute;
  z-index:7;
  top:-11px;
  display:block;
  background-color:rgba(233,226,205,.58);
  box-shadow:0 2px 6px rgba(0,0,0,.28);
  transform-origin:center center;
  pointer-events:none;
}
.story-tape--1{
  width:74px; height:23px; left:50%;
  background-image:repeating-linear-gradient(84deg, rgba(255,255,255,.14) 0 3px, rgba(120,112,92,.08) 3px 5px);
  clip-path:polygon(3% 10%, 96% 0, 100% 78%, 91% 100%, 5% 91%, 0 34%);
  transform:translateX(-50%) rotate(-4deg);
}
.story-tape--2{
  width:62px; height:27px; right:9%;
  background-image:linear-gradient(102deg, rgba(255,255,255,.2), transparent 38%, rgba(80,77,68,.1));
  clip-path:polygon(0 5%, 92% 0, 100% 27%, 95% 100%, 8% 91%, 2% 68%);
  transform:rotate(8deg);
}
.story-tape--3{
  width:84px; height:20px; left:11%;
  background-image:repeating-linear-gradient(8deg, transparent 0 5px, rgba(255,255,255,.12) 5px 7px);
  clip-path:polygon(7% 0, 100% 13%, 94% 91%, 73% 100%, 0 82%, 3% 19%);
  transform:rotate(-10deg);
}

/* --- The four arrangements ------------------------------------------------
   Two prints to a chapter since 02-09-2026, down from five. A pair is a
   different problem from a scatter: with five you are filling a field, with
   two you are placing one against the other, and the only thing holding the
   composition together is the diagonal between them and the corner where they
   overlap. So each set is an anchor print with a second falling across it from
   the opposite corner, and the diagonal runs a different way in each — a,
   c and d lead from the left, b from the right, so no two chapters open with
   the same gesture.

   Percentages against the collage box, as before, so each set scales rather
   than reflows. Both slots are wider than they are tall in every set: all
   eight photographs are landscape, and a tall slot would crop them to a
   sliver. */

/* 01 — the letter: anchor left, the second falling in from bottom right. */
[data-set="a"] .story-photo--1{ z-index:1; left:0;   top:3%;    width:58%; height:72%; --rot:-2.3deg; }
[data-set="a"] .story-photo--2{ z-index:3; right:0;  bottom:2%; width:50%; height:64%; --rot:2.6deg;  }

/* 02 — how it began: the diagonal reversed, anchor to the right. */
[data-set="b"] .story-photo--1{ z-index:1; right:1%; top:0;     width:57%; height:70%; --rot:2.1deg;  }
[data-set="b"] .story-photo--2{ z-index:3; left:0;   bottom:2%; width:52%; height:66%; --rot:-2.8deg; }

/* 03 — the range: flatter and wider, two bands rather than two squares. */
[data-set="c"] .story-photo--1{ z-index:1; left:0;   top:2%;    width:68%; height:62%; --rot:-1.2deg; }
[data-set="c"] .story-photo--2{ z-index:3; right:1%; bottom:0;  width:62%; height:58%; --rot:1.6deg;  }

/* 04 — the method: one large, one small, so the last chapter does not echo
   the first even though the diagonal runs the same way. */
[data-set="d"] .story-photo--1{ z-index:1; left:1%;  top:5%;    width:60%; height:74%; --rot:-1.7deg; }
[data-set="d"] .story-photo--2{ z-index:3; right:1%; bottom:3%; width:47%; height:56%; --rot:3.4deg;  }

/* --- Entrance -------------------------------------------------------------
   The chapter arrives as one event, released by initScrollRail() the moment
   the rail reaches it — the same number that positions the dot and decides
   when it lights. That is the whole point of doing it from the rail rather
   than from an observer of its own: the marker moving down the gutter and the
   words appearing are not two effects that happen to coincide, they are one.

   Two rules worth stating because both are easy to get wrong:

     1. The delays live on the *settled* state, not on .story-pre. A
        transition reads its delay from the state it is moving TO, so a delay
        declared only on the hidden state staggers the disappearance and does
        nothing on the way in — which is the opposite of what is wanted.
     2. The photograph's reveal is on .story-photo-frame, never on
        .story-photo: that element owns the rotation and the scroll drift, and
        an entrance transform there would fight both.

   Slot order sets the stagger for the prints, DOM order for the copy, so each
   lands roughly in the order the eye takes it. */
.story-copy > *,
.story-photo-frame{
  transition:opacity .62s var(--ease), transform .62s var(--ease);
}
/* The copy first, top to bottom. */
.story-copy > *:nth-child(2){ transition-delay:.06s; }
.story-copy > *:nth-child(3){ transition-delay:.12s; }
.story-copy > *:nth-child(4){ transition-delay:.19s; }
.story-copy > *:nth-child(5){ transition-delay:.26s; }
/* Then the prints, overlapping the tail of the copy rather than queueing
   behind it — a chapter that takes a full second to assemble reads as slow. */
.story-photo--1 .story-photo-frame{ transition-delay:.10s; }
.story-photo--2 .story-photo-frame{ transition-delay:.20s; }

.story-banner.story-pre .story-copy > *{
  opacity:0;
  transform:translateY(14px);
}
.story-banner.story-pre .story-photo-frame{
  opacity:0;
  transform:translateY(16px) scale(.985);
}
/* Leaving is never staggered — nothing ever transitions back into this state,
   and a delay here would only slow the very first paint. */
.story-banner.story-pre .story-copy > *,
.story-banner.story-pre .story-photo-frame{ transition-delay:0s; }

@media (max-width:1080px){
  .story-banner-inner{ grid-template-columns:270px minmax(0,1fr); gap:34px; min-height:600px; }
  .story-collage{ min-height:410px; }
}

/* --- Phones ---------------------------------------------------------------
   The two columns become one, and the collage turns from a wall into a
   column: five prints down the page, still overlapping and still off-square,
   but each one now wide enough to actually read. The arrangements collapse
   into a single scatter — four different ones at this width would only be
   four different ways of being cramped. */
@media (max-width:820px){
  .story-banner-inner{
    grid-template-columns:1fr;
    gap:38px;
    min-height:0;
    padding-top:58px;
    padding-bottom:64px;
    /* Room for the rail. The container's own padding shrinks on a phone, which
       would leave the line about ten pixels off the copy and its markers
       touching the first letter; the column steps right instead of the line
       stepping left, because the line wants to stay near the screen edge. */
    padding-left:36px;
  }
  .story-banner h2{ max-width:18ch; }
  /* Same step the reference takes at this width: a wider measure and a size
     down, because the column is now the full width of the screen. */
  .story-body{ max-width:390px; }
  .story-body p{ font-size:13px; line-height:1.5; margin-bottom:12px; }
  .story-lead{ max-width:46ch; }
  .story-collage{ min-height:clamp(330px,88vw,430px); }

  /* Roughly 14% smaller than they were, scaled about each print's own centre
     rather than its anchored corner: shrinking towards a corner would open a
     gap on one side of every print and quietly redraw the arrangement, and
     the arrangement is the part that was right. The centres are held, so the
     scatter is identical and only the prints are smaller. Slots 1, 3 and 5
     keep a percent of bleed past the edge — losing that would tidy the
     composition into something that stops at a margin. */
  /* Stacked rather than scattered, overlapping across the middle eight
     percent so the pair still reads as two prints dropped on each other. */
  [data-set] .story-photo--1{ z-index:1; left:-2%; right:auto; top:0;    bottom:auto; width:78%; height:54%; --rot:-1.7deg; }
  [data-set] .story-photo--2{ z-index:3; left:auto; right:-3%; top:auto; bottom:0;    width:72%; height:54%; --rot:2.2deg;  }

  /* Half the width, so the strips have to come down with it. */
  /* Fixed pixels against prints that just lost a seventh of their size, so
     the strips come down with them or they start looking like parcel tape. */
  .story-tape{ top:-7px; }
  .story-tape--1{ width:48px; height:16px; }
  .story-tape--2{ width:41px; height:17px; }
  .story-tape--3{ width:53px; height:14px; }
}

@media (prefers-reduced-motion: reduce){
  /* No drift and no entrance: the composition is the point, and it is fully
     legible standing still. */
  .story-photo{ transform:rotate(var(--rot,0deg)); will-change:auto; }
  .story-copy > *, .story-photo-frame{ transition:none; }
  .story-banner.story-pre .story-copy > *,
  .story-banner.story-pre .story-photo-frame{ opacity:1; transform:none; }
}


/* ==========================================================================
   CCIC grading explainer (/ccic-grading)
   --------------------------------------------------------------------------
   One long read, built from six blocks that alternate ink and paper like the
   rest of the site. Everything here is scoped to .ccic- so it cannot leak
   into the shop or the legal pages, and every block degrades to one column
   before it gets cramped rather than at a single breakpoint chosen for the
   widest of them.

   De twee labels in §3 zijn foto's van echte slabs uit eigen bezit: een gouden
   CCIC 10 (皮卡丘ex, CSV9C) en een PSA 10 (Gengar & Mimikyu GX, Tag Bolt).
   Ze zaten hier eerder als natekening; foto's overtuigen meer, en omdat het
   onze eigen slabs zijn zetten we niemand anders zijn productfotografie op een
   commerciële pagina.

   De genummerde call-outs liggen er als SVG overheen, in hetzelfde
   coördinatenstelsel als de foto (één viewBox van 460x200, de foto op
   x=20/y=38). Daardoor blijven de markeringen op elke breedte op hun plek en
   legt de legenda eronder ze in beide talen uit. Vervang je een foto, meet dan
   de vier posities opnieuw in — ze horen bij dit exemplaar, niet bij het
   formaat.
   ========================================================================== */

/* The read itself: wider than .legal-copy because the blocks below carry
   two-column grids, but still a comfortable measure for the prose. */
.ccic-wrap{ max-width:960px; }
.ccic-block h2{
  font-family:var(--font-display);
  font-size:clamp(24px,3vw,34px);
  line-height:1.15;
  margin:0 0 6px;
}
.ccic-block > p{ max-width:68ch; margin:0 0 14px; }
.bg-paper .ccic-block > p{ color:var(--paper-dim); }
.bg-ink   .ccic-block > p{ color:var(--ink-dim); }
.ccic-block > p:last-child{ margin-bottom:0; }
.ccic-block p a{ color:inherit; text-decoration:underline; text-underline-offset:2px; }
.ccic-block p a:hover{ color:var(--gold); }
/* A short label above a heading, in the ledger voice the rest of the site
   uses for section markers. */
.ccic-kicker{
  display:block;
  font-family:var(--font-mono);
  font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--gold);
  margin-bottom:10px;
}

/* ---------- three facts about the organisation ---------- */
.ccic-facts{
  display:grid; gap:14px;
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  margin-top:26px;
}
.ccic-fact{
  position:relative;
  overflow:hidden;
  background:#fff;
  border:1px solid var(--paper-line);
  border-radius:4px;
  padding:20px 20px 22px;
}
.ccic-fact h3{ font-size:15px; margin:0 0 6px; }
.ccic-fact p{ font-size:14px; line-height:1.55; color:var(--paper-dim); margin:0; }
.ccic-fact .ccic-cn{
  display:block; font-size:12px; color:var(--gold);
  font-family:"Songti SC","STSong","Noto Serif SC",serif;
  margin-top:8px; letter-spacing:.04em;
}
/* De folieveeg, dezelfde als op de prijsreferenties in de hero op de homepage
   (.assure-case). Ook hier staan de kaarten wit op papier, dus opnieuw de drie
   huiskleuren op lage sterkte met `multiply` in plaats van wit met `overlay`:
   wit op wit doet niets, en overlay zou de donkere kop uitwassen precies op het
   moment dat de band eroverheen loopt.

   `infinite` op swatchSheen laat het effect zich herhalen; de drie blokken
   lopen met een negatieve delay uit de pas, zodat het leest als drie losse
   kaarten die elk hun eigen licht vangen in plaats van als één knippering. */
.ccic-fact::after{
  content:"";
  position:absolute; inset:0;
  z-index:2;
  pointer-events:none;
  border-radius:inherit;
  background:linear-gradient(115deg,
    transparent 32%,
    rgba(201,162,39,.30) 43%,
    rgba(123,95,255,.16) 50%,
    rgba(47,217,196,.20) 57%,
    transparent 68%);
  background-size:250% 250%;
  mix-blend-mode:multiply;
  animation:swatchSheen 9s ease-in-out infinite;
  animation-delay:var(--sheen-delay,0s);
}
.ccic-facts .ccic-fact:nth-child(2){ --sheen-delay:-3s; }
.ccic-facts .ccic-fact:nth-child(3){ --sheen-delay:-6s; }
/* Versiering, geen informatie: wie minder beweging vraagt krijgt de folie als
   een stille tint in plaats van als een lopende band. */
@media (prefers-reduced-motion: reduce){
  .ccic-fact::after{ animation:none; background-position:38% 38%; }
}

/* ---------- the scale strip ---------- */
/* Ten cells, the tenth lit. It is a picture of one sentence — "a ten-point
   scale, and the ten is the only grade we list" — and it says it faster than
   the sentence does. */
.ccic-scale{
  display:grid; grid-template-columns:repeat(10,1fr);
  gap:4px; margin:24px 0 10px;
}
.ccic-scale span{
  display:flex; align-items:center; justify-content:center;
  height:38px; border-radius:3px;
  font-family:var(--font-mono); font-size:12px;
  background:rgba(236,234,227,.05);
  border:1px solid var(--ink-line);
  color:var(--ink-dim);
}
.ccic-scale span.is-top{
  background:linear-gradient(180deg,var(--gold-bright),var(--gold));
  border-color:var(--gold-bright);
  color:#1a1508; font-weight:700;
}
.ccic-scale-note{ font-size:13px; color:var(--ink-dim); margin:0 0 26px; }
@media (max-width:560px){
  .ccic-scale{ grid-template-columns:repeat(5,1fr); }
}

/* ---------- gold vs silver ---------- */
.ccic-tiers{
  display:grid; gap:16px;
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  margin-top:8px;
}
.ccic-tier{
  border:1px solid var(--ink-line);
  border-radius:4px;
  padding:22px;
  background:rgba(236,234,227,.03);
  position:relative;
  overflow:hidden;
}
/* The tier's own colour as a band along the top edge — the same thing the
   physical label does, which is the whole point of the section. */
.ccic-tier::before{
  content:""; position:absolute; inset:0 0 auto; height:3px;
  background:var(--tier, var(--gold));
}
.ccic-tier--gold{  --tier:linear-gradient(90deg,#8A6A12,#E4BC3E 45%,#F3DA8A 60%,#8A6A12); }
.ccic-tier--silver{ --tier:linear-gradient(90deg,#6E7480,#C9CFD8 45%,#EDF1F6 60%,#6E7480); }
.ccic-tier h3{ font-size:16px; margin:0 0 4px; }
.ccic-tier .ccic-tier-sub{
  font-family:var(--font-mono); font-size:11px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--ink-dim); margin:0 0 12px;
}
.ccic-tier p{ font-size:14px; line-height:1.6; color:var(--ink-dim); margin:0; }

/* ---------- the two label mock-ups ---------- */
.ccic-labels{
  display:grid; gap:20px;
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
  margin:26px 0 8px;
}
.ccic-label-fig{ margin:0; }
.ccic-label-fig svg{ display:block; width:100%; height:auto; border-radius:4px; }
.ccic-label-fig figcaption{
  font-family:var(--font-mono); font-size:11px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--paper-dim);
  margin-top:10px;
}
/* The call-out numbers, shared by the diagrams and the legend below them, so
   the reader can look from one to the other without hunting. */
.ccic-legend{ list-style:none; margin:24px 0 0; padding:0; display:grid; gap:14px; }
.ccic-legend li{ display:grid; grid-template-columns:26px 1fr; gap:12px; align-items:start; }
.ccic-legend b{ display:block; font-size:14px; margin-bottom:2px; }
.ccic-legend p{ font-size:14px; line-height:1.55; color:var(--paper-dim); margin:0; }
.ccic-num{
  display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; border-radius:50%;
  background:var(--gold); color:#1a1508;
  font-family:var(--font-mono); font-size:12px; font-weight:700;
}

/* ---------- verification steps ---------- */
.ccic-steps{ list-style:none; counter-reset:ccic; margin:26px 0 0; padding:0; display:grid; gap:2px; }
.ccic-steps li{
  counter-increment:ccic;
  display:grid; grid-template-columns:34px 1fr; gap:16px; align-items:start;
  padding:18px 0;
  border-top:1px solid var(--ink-line);
}
.ccic-steps li:last-child{ border-bottom:1px solid var(--ink-line); }
.ccic-steps li::before{
  content:counter(ccic);
  display:flex; align-items:center; justify-content:center;
  width:34px; height:34px; border-radius:50%;
  border:1px solid var(--gold);
  color:var(--gold); font-family:var(--font-mono); font-size:13px;
}
.ccic-steps b{ display:block; font-size:15px; margin-bottom:3px; }
.ccic-steps p{ font-size:14px; line-height:1.6; color:var(--ink-dim); margin:0; }
/* The sprite beside the steps. Decorative, hidden from screen readers in the
   markup, and the first thing to go when the column gets narrow. */
.ccic-verify{ display:grid; grid-template-columns:1fr 180px; gap:40px; align-items:end; }
.ccic-verify img{ width:100%; height:auto; opacity:.9; }
@media (max-width:820px){
  .ccic-verify{ grid-template-columns:1fr; }
  .ccic-verify img{ display:none; }
}

/* ---------- the comparison table ---------- */
/* .ledger, borrowed from the price list, with two changes: the first column
   is a row label rather than data, and the CCIC column is tinted so the eye
   can follow it down without reading the header again. */
.ccic-compare th:first-child,
.ccic-compare td:first-child{ width:26%; font-weight:600; color:var(--paper-text); }
.ccic-compare td{ font-size:14px; line-height:1.5; }
.ccic-compare .is-ccic{ background:rgba(201,162,39,.06); }
.ccic-compare thead .is-ccic{ background:rgba(201,162,39,.14); }
/* Under 640px .ledger turns every row into a card and hides the head — the
   treatment the quote page uses — so each cell needs to say which column it
   came from. data-th does that (the labels are brand names, identical in both
   languages, so they stay in the markup rather than the dictionary). The one
   change from the quote page: there a cell holds a number and sits on the
   right of its label; here it holds a sentence, so the label goes above it and
   both align left. */
@media (max-width:640px){
  .ccic-compare td{ flex-direction:column; align-items:flex-start; gap:3px; padding:9px 0; }
  /* The 26% that makes the label column a column on a desktop makes it a
     narrow strip once the row is a card — "Erkenning in China" broke over
     three lines. */
  .ccic-compare td:first-child{ width:auto; font-size:15px; }
  .ccic-compare .is-ccic{ background:none; }
  .ccic-compare td[data-th]::before{ color:var(--gold); }
  .ccic-compare td[data-th="PSA"]::before{ color:var(--paper-dim); }
}

/* ---------- the verdict ---------- */
.ccic-verdict{ list-style:none; margin:26px 0 0; padding:0; display:grid; gap:12px; }
.ccic-verdict li{
  display:grid; grid-template-columns:30px 1fr; gap:14px; align-items:start;
  background:#fff; border:1px solid var(--paper-line); border-radius:4px;
  padding:16px 18px;
}
.ccic-verdict b{ display:block; font-size:14px; margin-bottom:2px; }
.ccic-verdict p{ font-size:14px; line-height:1.55; color:var(--paper-dim); margin:0; }
.ccic-mark{
  display:flex; align-items:center; justify-content:center;
  width:30px; height:30px; border-radius:50%;
  font-size:15px; font-weight:700; line-height:1;
}
.ccic-mark--yes{   background:rgba(46,122,78,.12);  color:var(--green-stamp); }
.ccic-mark--no{    background:rgba(180,59,47,.12);  color:var(--red-stamp); }
.ccic-mark--maybe{ background:rgba(201,162,39,.16); color:#8A6A12; }

/* ---------- call-out box ---------- */
.ccic-note{
  border-left:3px solid var(--gold);
  padding:16px 18px;
  margin:26px 0 0;
  border-radius:0 4px 4px 0;
}
.bg-paper .ccic-note{ background:var(--paper-soft); }
.bg-ink   .ccic-note{ background:rgba(236,234,227,.04); }
.ccic-note b{ display:block; font-size:14px; margin-bottom:4px; }
.ccic-note p{ font-size:14px; line-height:1.6; margin:0; }
.bg-paper .ccic-note p{ color:var(--paper-dim); }
.bg-ink   .ccic-note p{ color:var(--ink-dim); }

/* ---------- closing call to action ---------- */
.ccic-cta{ display:flex; flex-wrap:wrap; gap:12px; margin-top:22px; }

/* ---------- the link from a single card's product page ---------- */
/* Sits directly under the grade switcher, because that is the moment the
   question is actually asked: four buttons, two of which say a word the
   visitor may never have seen. */
.grade-help{
  display:inline-flex; align-items:center; gap:7px;
  margin:-4px 0 16px;
  font-size:13px;
  color:var(--paper-dim);
  text-decoration:none;
  border-bottom:1px solid transparent;
  transition:color .18s ease, border-color .18s ease;
}
.grade-help svg{ flex:0 0 auto; }
.grade-help:hover{ color:var(--gold); border-bottom-color:rgba(201,162,39,.5); }
.grade-help:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; border-radius:2px; }

/* Aanbetaling ---------------------------------------------------------------
   Eén chip voor tegel, offertelijst en dashboard, en één kader voor de uitleg
   op de productpagina en onder de offertelijst. Goud en niet rood: het is een
   voorwaarde, geen waarschuwing. De chip staat op papier (licht), dus de
   tekst is --paper-text en niet het heldere goud van de donkere secties. */
.p-deposit{
  align-self:flex-start;
  display:inline-block;
  font-family:var(--font-mono); font-size:10px; letter-spacing:.06em; text-transform:uppercase;
  line-height:1; padding:5px 8px; border-radius:3px;
  color:var(--paper-text);
  background:rgba(201,162,39,.16);
  box-shadow:inset 0 0 0 1px rgba(201,162,39,.55);
}
.cart-item .p-deposit, .q-lines .p-deposit{ margin-top:6px; }
.product-note-deposit{ border-left-color:var(--gold); }
.deposit-note{
  margin:18px 0 0;
  padding:14px 16px;
  border-left:2px solid var(--gold);
  border-radius:0 4px 4px 0;
  background:var(--paper-soft);
}
.deposit-note b{ display:block; font-size:13px; margin-bottom:4px; color:var(--paper-text); }
.deposit-note p{ margin:0; font-size:13px; line-height:1.65; color:var(--paper-dim); }
.q-totals .deposit-due{ border-top:1px dashed var(--paper-line); margin-top:6px; padding-top:11px; }
.q-totals .deposit-due dt{ color:var(--paper-text); }
.shop-deposit-note{
  margin:0 0 18px; padding:10px 14px;
  border-left:2px solid var(--gold); border-radius:0 4px 4px 0;
  background:var(--paper-soft);
  font-size:13px; line-height:1.6; color:var(--paper-dim);
}
.shop-deposit-note[hidden]{ display:none; }

/* ======================================================================
   Hero, 30th style (12 Oct 2026)
   ----------------------------------------------------------------------
   Replaces the manga backdrop + three-card scene on the homepage with the
   look of /30th-celebration: its WebGL background (.c30-bg, started by
   js/celebration.js off [data-hero-bg]), centred copy, the .c30-pill
   buttons from css/celebration.css and four glowing category tiles.

   Sits at the very end of the file on purpose: it overrides the older
   .hero rules above (the h1 size, .hero-actions{width:max-content},
   the phone layout) by coming later, not by fighting specificity.
   The box is exactly one screen tall because the homepage is a deck of
   panels; every size in here is clamped against vh for that reason.
   Do not put .hero-card-stage on the tiles: that class has a fixed 520px
   height and pushes the panel past the viewport.
   ====================================================================== */
.hero-x{ padding:0; height:100vh; height:100svh; display:grid; place-items:center; background:#000; overflow:hidden; }
.hero-x::before, .hero-x::after{ content:none; display:none; }
/* .hero-x in front on purpose: css/celebration.css loads after this file and
   its .c30-bg{position:fixed} won at equal specificity, which pinned the hero
   background to the viewport and painted it over the footer. */
.hero-x .hx-bg{ position:absolute; inset:0; height:auto; z-index:0; }
.hx-inner{
  position:relative; z-index:2; text-align:center;
  width:min(1100px, 90vw); padding:clamp(72px,11vh,110px) 0 clamp(56px,9vh,90px);
}
.hx-eyebrow{
  margin:0 0 16px; font-family:var(--font-body); font-size:15px; font-weight:600;
  letter-spacing:0; color:rgba(255,255,255,.72); text-wrap:balance;
}
.hero-x .hx-title{
  margin:0 auto; max-width:22ch;
  font:700 clamp(30px, min(4.2vw, 5.4vh), 54px)/1.12 var(--font-body); letter-spacing:-.02em;
  color:transparent;
  background-image:linear-gradient(143deg,#fff 2.8%,#fff 23.9%,#cbfff5 30.9%,#ffe3f7 36.8%,#fff 45.9%,#fff 78%,#e9ffe3 83.7%,#fff2c5 86.5%,#fff 96.7%);
  -webkit-background-clip:text; background-clip:text;
  text-wrap:balance;
}
.hx-lead{
  margin:18px auto 0; max-width:52ch;
  color:rgba(255,255,255,.62); font-size:clamp(15px,1.3vw,17px); line-height:1.55; text-wrap:balance;
}
.hero-x .hx-actions{ display:flex; justify-content:center; flex-wrap:wrap; gap:14px; width:auto; margin:clamp(20px,3.6vh,34px) 0 0; }
.hx-actions .c30-pill{ font-size:16px; height:50px; padding:0 26px; }
.hx-actions .c30-pill:not(.c30-pill--primary){ padding:0 26px; }

.hx-tiles{ display:flex; justify-content:center; gap:clamp(14px,2vw,26px); margin:clamp(28px,5vh,56px) auto 0; }
.hx-tile{ --tilt:0deg; display:block; text-decoration:none; color:#fff; }
.hx-tile:nth-child(1){ --tilt:-6deg; } .hx-tile:nth-child(2){ --tilt:2deg; }
.hx-tile:nth-child(3){ --tilt:-3deg; } .hx-tile:nth-child(4){ --tilt:4deg; }
.hx-tile[data-glow="violet"]{ --glow:#ff38e4; } .hx-tile[data-glow="cyan"]{ --glow:#00e5ff; }
.hx-tile[data-glow="yellow"]{ --glow:#e8ff39; } .hx-tile[data-glow="orange"]{ --glow:#ff6444; }
.hx-frame{
  display:block; height:clamp(120px, 24vh, 250px); aspect-ratio:252/320; padding:14px; border-radius:18px;
  background:rgba(255,255,255,.02);
  box-shadow:inset 0 0 14px var(--glow), 0 0 18px color-mix(in srgb, var(--glow) 45%, transparent);
  transform:rotate(var(--tilt)); transition:transform .3s, filter .3s;
}
.hx-frame img{ display:block; width:100%; height:100%; object-fit:contain; }
.hx-tile:hover .hx-frame, .hx-tile:focus-visible .hx-frame{ transform:rotate(var(--tilt)) translateY(-8px) scale(1.025); filter:brightness(1.15); }
.hx-label{ display:block; margin-top:16px; font-weight:700; font-size:16px; letter-spacing:-.01em; }

.hx-proof{
  list-style:none; padding:0; margin:clamp(22px,4vh,44px) 0 0; display:flex; justify-content:center; flex-wrap:wrap; gap:8px 0;
  font-family:var(--font-body); font-size:14px; letter-spacing:0; color:rgba(255,255,255,.62);
}
.hx-proof li{ padding:0 16px; border-left:1px solid rgba(255,255,255,.18); }
.hx-proof li:first-child{ border-left:0; }

@media (max-width:760px){

  .hx-inner{ padding:84px 0 70px; }
  .hx-title br{ display:none; }
  .hero-x .hx-actions{ flex-direction:row; flex-wrap:nowrap; align-items:stretch; gap:10px; }
  .hero-x .hx-actions .c30-pill{ font-size:14px; height:52px; min-height:52px; padding:0 10px; flex:1 1 0; min-width:0; justify-content:center; white-space:nowrap; }
  .hx-actions .c30-pill svg{ display:none; }
  body.home .hero-x .hx-tiles{ display:flex; gap:10px; }
  .hx-frame{ height:auto; width:calc((90vw - 30px) / 4); padding:6px; border-radius:12px; }
  .hx-label{ font-size:10.5px; margin-top:10px; letter-spacing:-.02em; }
  .hx-proof{ margin-top:32px; font-size:12.5px; }
  .hx-eyebrow{ font-size:13px; margin-bottom:12px; }
  .hx-proof li{ padding:0 8px; }
  .hx-proof li:last-child{ display:none; }
}

/* Intro cascade for the parts the old hero did not have. Same keyframes
   and step as the list in §14; h1 and the buttons are already covered
   there through .hero h1 and .hero-actions > *. */
html.intro-hold .hx-eyebrow,
html.intro-hold .hx-lead,
html.intro-hold .hx-tiles,
html.intro-hold .hx-proof{ opacity:0; }
html.intro-in .hx-eyebrow,
html.intro-in .hx-lead,
html.intro-in .hx-tiles,
html.intro-in .hx-proof{ animation:introFadeUp .5s ease-out both; will-change:opacity; }
html.intro-in .hx-eyebrow{ animation-delay:calc(var(--introStep) * 3.8); }
html.intro-in .hx-lead   { animation-delay:calc(var(--introStep) * 5.6); }
html.intro-in .hx-tiles  { animation-delay:calc(var(--introStep) * 7.8); }
html.intro-in .hx-proof  { animation-delay:calc(var(--introStep) * 8.6); }

/* rev 2026-10-12b: new ?v= after the hero deploy cached stale bytes under the previous one */


/* ======================================================================
   Homepage, calm version (12 Oct 2026)
   ----------------------------------------------------------------------
   Second pass on the hero above plus the panels below it. The homepage is
   now: hero, 5 reasons, the 30th banner, closing. Sealed/Singles (.fold)
   and "Minder schakels" (.chain) are no longer in index.html; their CSS
   and JS stay and do nothing without the markup.
   - hero: the manga scan returns on top of the 30th shader (.hx-manga,
     overlay blend, so the glow tints the linework) under a dark .hx-calm
     veil; the headline is Instrument Serif and always exactly two lines
   - "Scroll omlaag" in Inter, flush with the hero's bottom edge
   - glass on the cards of "5 redenen" and the closing panel only; both
     panels keep their own backgrounds, the pokeball lid is untouched
   - buttons stay the gold .btn-gold everywhere except the 30th banner
   ====================================================================== */
/* ---------- Hero: manga terug, met de gloed eroverheen als tint ---------- */
.hx-manga{
  position:absolute; inset:0; z-index:3;
  background:url("/img/hero-manga.webp") center 12% / cover no-repeat;
  mix-blend-mode:overlay;
  opacity:.55;
}
@media (max-width:820px){
  .hx-manga{ background-image:url("/img/hero-manga-900.webp"); background-position:76% top; }
}
/* Rust: een donkere sluier die de gloed en de lijnen samen terugduwt, zodat
   alleen de kop eruit springt (de golfclub-referentie). */
.hx-calm{
  position:absolute; inset:0; z-index:4;
  background:
    radial-gradient(60% 55% at 50% 45%, rgba(0,0,0,.18), rgba(0,0,0,.5) 100%),
    linear-gradient(to bottom, rgba(0,0,0,.35), rgba(0,0,0,.1) 30%, rgba(0,0,0,.45));
}

/* Kop: altijd precies twee regels. De breedte is 12,5em, de tekst is ruim
   twee keer zo lang als de helft daarvan niet past en korter dan twee volle
   regels, dus hij breekt altijd één keer. De lettergrootte schaalt mee met
   de beschikbare breedte zodat die 12,5em altijd in het scherm past. */
.hero-x .hx-title{
  max-width:12.5em;
  font-size:min(72px, calc(min(1100px, 90vw) / 12.8), 7.4vh);
  line-height:1.08;
  text-wrap:balance;
}
.hero-x .hx-eyebrow{ margin-bottom:20px; }
.hero-x .hx-actions{ margin-top:clamp(26px,4.4vh,40px); }

/* 4 + 5. "Scroll omlaag" in de huisstijl en precies op de onderrand. */
.hero-x .hero-scroll-wrap{ bottom:0; }
.hero-x .hero-scroll{
  font-family:var(--font-body);
  font-size:13px; font-weight:600;
  letter-spacing:0; text-transform:none; text-indent:0;
  color:rgba(255,255,255,.8);
  background:rgba(8,9,14,.55);
  border-color:rgba(255,255,255,.14);
}


/* Gouden knoppen terug (oude huisstijl). In de hero staan ze op mobiel naast
   elkaar, even breed. */
.hero-x .hx-title i.shine{ font-style:italic; }
/* Lettertype terug naar de oude hero-kop: Instrument Serif, normale dikte,
   effen wit, met de gouden glans op "betrouwbare". De serif is smaller dan
   Inter, dus de breedte (en daarmee de grootte) is herberekend voor precies
   twee regels. */
.hero-x .hx-title{
  font-family:var(--font-display); font-weight:400; letter-spacing:0;
  color:var(--ink-text); background:none; -webkit-background-clip:border-box; background-clip:border-box;
  max-width:10.5em; line-height:1.05;
  font-size:min(86px, calc(min(1100px, 90vw) / 10.8), 8.8vh);
}
@media (max-width:760px){
  .hero-x .hx-actions{ flex-direction:row; flex-wrap:nowrap; gap:10px; }
  .hero-x .hx-actions .btn{ flex:1 1 0; min-width:0; justify-content:center; white-space:nowrap; padding-left:8px; padding-right:8px; font-size:clamp(12px, 3.75vw, 14px); }
}

/* ---------- Glas (7), achtergronden blijven zoals ze zijn ---------- */
body.home{
  --glass-blur:blur(16px) saturate(1.2);
  --grad-text:linear-gradient(143deg,#fff 2.8%,#fff 23.9%,#cbfff5 30.9%,#ffe3f7 36.8%,#fff 45.9%,#fff 78%,#e9ffe3 83.7%,#fff2c5 86.5%,#fff 96.7%);
  --grad-accent:linear-gradient(100deg,#8ec5ff,#b49bff 55%,#ff9be4);
}
/* 5 redenen: inkt blijft, de lijst en de uitdaging worden glas. */
body.home .assure-list{
  background:rgba(255,255,255,.045);
  border:1px solid rgba(255,255,255,.12); border-radius:22px;
  /* no backdrop-filter: flat backdrop, and a live blur costs every scroll frame */
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 24px 60px -30px rgba(0,0,0,.8);
  padding:6px 22px;
}
body.home .assure-list > li{ border-top:1px solid rgba(255,255,255,.07); }
body.home .assure-list > li:first-child{ border-top:0; }
body.home .assure-list > li:last-child{ border-bottom:0; }
body.home .chain-dare{
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.12); border-radius:var(--radius);
  /* no backdrop-filter: flat backdrop, and a live blur costs every scroll frame */
  box-shadow:none;
}
/* The gold rule along the band's top edge (.assure .chain-dare::before) is
   the one bit of shine this band has; on glass it needs a touch more weight
   and must sit above the frosted layer to still read. */
body.home .assure .chain-dare::before{ z-index:1; height:1.5px; }

/* Onze marge is groothandel: papier blijft, de kaart wordt matglas. */
body.home .closing-cta{
  background:rgba(255,255,255,.55);
  border:1px solid rgba(255,255,255,.9); border-radius:26px;
  /* no backdrop-filter: flat backdrop, and a live blur costs every scroll frame */
  box-shadow:inset 0 1px 0 #fff, 0 30px 70px -40px rgba(23,24,27,.45);
}


@media (max-width:760px){
  body.home .assure-list{ padding:0 16px; border-radius:18px; }
  /* The shorter copy (12 Oct 2026) gives back ~20px on a phone; it goes
     to the rows so the list breathes. */
  body.home .assure-list > li{ padding-top:7px; padding-bottom:7px; }
}


/* ======================================================================
   Hero background: black and white again, with a cursor light
   (12 Oct 2026, after a side-by-side comparison with the tinted version)
   ----------------------------------------------------------------------
   The original backdrop comes back as it was: .hero-bg (the manga scan at
   low opacity), the grey wash on .hero::after and the faint 闪亮贸易 mark
   on .hero::before. Those rules are still in "Manga backdrop" near the top;
   this only switches them on again for .hero-x, which had turned them off.

   .hx-lens is a second copy of the same scan at a much higher opacity,
   masked to a soft circle at --lx/--ly (set by initHeroLight() in main.js),
   with a faint white glow on top. It must track .hero-bg exactly, so it
   copies its image, position and size at every breakpoint.
   ====================================================================== */
.hero-x{ background:var(--ink); }
.hero-x::before{ content:"闪 亮 贸 易"; display:block; }
.hero-x::after{ content:""; display:block; }
/* The lens is a fixed-size round window that moves with the cursor; inside
   it a full-hero copy of the scan (.hx-lens-img, created by initHeroLight())
   moves the opposite way, so it stays registered with .hero-bg. Both are
   transforms and nothing else, so following the cursor repaints nothing.
   (The first version masked a full-hero layer with a gradient whose centre
   was a custom property: every cursor step repainted the whole hero, and
   Chrome sends a move for every frame the page scrolls under a still mouse,
   which is what made scrolling between panels lag on desktop.) */
.hx-lens{
  position:absolute; left:0; top:0; z-index:1; pointer-events:none;
  width:520px; height:520px; border-radius:50%; overflow:hidden;
  opacity:0; transition:opacity .6s ease;
  will-change:transform;
  -webkit-mask-image:radial-gradient(closest-side, #000 0%, rgba(0,0,0,.55) 38%, transparent 100%);
          mask-image:radial-gradient(closest-side, #000 0%, rgba(0,0,0,.55) 38%, transparent 100%);
}
.hx-lens-img{
  position:absolute; left:0; top:0;
  background:url("../img/hero-manga.webp") center 12% / cover no-repeat;
  will-change:transform;
}
.hx-lens::after{
  content:""; position:absolute; inset:0;
  background:radial-gradient(closest-side, rgba(255,255,255,.08), transparent 70%);
}
.hero-x.is-lit .hx-lens{ opacity:.6; }
@media (max-width:820px){
  .hx-lens-img{ background-image:url("../img/hero-manga-900.webp"); }
}
@media (max-width:760px){
  .hx-lens-img{ background-position:76% top; }
}

/* rev 2026-10-12c: new ?v= after the lens deploy cached stale bytes under the previous one */


/* ======================================================================
   "5 redenen", roomier (12 Oct 2026)
   ----------------------------------------------------------------------
   The list read cramped inside its glass card. The copy is shorter now
   (assure.n1-5 / d1-5 in main.js) and on desktop the five reasons are five
   separate glass tiles side by side: icon on top, heading, one short line.
   On phones it stays the list from the block above, with roomier rows.
   The dare band underneath is as wide as the row of tiles, drops its sub
   line everywhere, and its one line is set large enough to fill most of
   the space beside the button (about 70-78%) without touching it.
   ====================================================================== */
@media (min-width:761px){
  body.home .assure .assure-list{
    display:grid; grid-template-columns:repeat(5, minmax(0,1fr)); gap:14px;
    max-width:1120px; padding:0;
    background:none; border:0; box-shadow:none; border-radius:0;
    backdrop-filter:none; -webkit-backdrop-filter:none;
  }
  body.home .assure .assure-list > li,
  body.home .assure .assure-list > li:first-child,
  body.home .assure .assure-list > li:last-child{
    display:flex; flex-direction:column; align-items:flex-start; gap:10px;
    padding:22px 18px;
    background:rgba(255,255,255,.045);
    border:1px solid rgba(255,255,255,.12); border-radius:18px;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.06);
  }
  body.home .assure .assure-ico{ width:26px; height:26px; transform:none; margin-bottom:4px; }
  body.home .assure .assure-list > li b{ font-size:clamp(14px,1.1vw,16px); line-height:1.25; }
  body.home .assure .assure-list > li span{ font-size:clamp(12.5px,.98vw,14px); line-height:1.5; }
  body.home .assure .chain-dare{ width:100%; max-width:1120px; }
  body.home .assure .chain-dare-line{ font-size:clamp(20px, min(2.8vw, 4.4svh), 34px); }
}
body.home .assure .chain-dare-sub{ display:none; }
body.home .assure .chain-dare-line{ margin-bottom:0; }
@media (max-width:760px){
  body.home .assure .chain-dare-line{ text-align:center; }
}

/* rev 2026-10-12h: new ?v= after a stale-cache deploy */

/* rev 2026-10-12i: new ?v= after a stale-cache deploy */


/* ======================================================================
   Closing panel: category tiles (12 Oct 2026)
   ----------------------------------------------------------------------
   "Onze marge is groothandel" no longer ends in two buttons but in the four
   category tiles that briefly sat in the hero (same .hx-tile markup and
   glow colours, styled in "Hero, 30th style" above). On this light card the
   tile has no fill of its own: it takes the card's colour, so only the
   coloured glow edge frames the product. Labels in the paper text colour.
   ====================================================================== */
.closing-tiles{ margin:clamp(22px,4svh,40px) auto 0; }
.closing-tiles .hx-frame{
  background:transparent;
  height:clamp(110px, 21svh, 220px);
}
.closing-tiles .hx-label{ color:var(--paper-text); margin-top:14px; }
/* One quiet way on to the main action under the tiles: a text link in the
   buttons' voice, not a third button, so the card keeps its calm. */
.closing-link{
  display:inline-flex; align-items:center; gap:8px;
  margin-top:clamp(18px,3svh,28px);
  font-family:var(--font-body); font-size:15px; font-weight:600;
  color:var(--paper-text); text-decoration:none;
  border-bottom:1px solid var(--gold); padding-bottom:3px;
  transition:color .2s, border-color .2s, gap .2s;
}
.closing-link svg{ width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.closing-link:hover, .closing-link:focus-visible{ color:var(--gold); gap:11px; }
@media (max-width:760px){
  .closing-tiles{ display:flex; gap:8px; }
  .closing-tiles .hx-frame{ height:auto; width:calc((100vw - 32px - 2 * 20px - 24px) / 4); padding:6px; border-radius:12px; }
  .closing-tiles .hx-label{ font-size:10.5px; margin-top:10px; letter-spacing:-.02em; }
}

/* rev 2026-10-12j: new ?v= after a stale-cache deploy */

/* rev 2026-10-12k: new ?v= after a stale-cache deploy */

/* ==========================================================================
   Catalogus: quotebalk, plakkende filterbalk, compacte kop op mobiel
   ========================================================================== */
.shop-section{ padding-top:64px; }

/* --- de quotebalk onderin (js/main.js §quotebalk) ------------------------
   Donker glas zoals de header, één regel inhoud, de gouden knop als enige
   actie. Staat onderin het midden, dus niet in de weg van de naar-boven-knop
   rechtsboven. Uit beeld = omlaag geschoven en niet aanklikbaar. */
.quote-bar{
  position:fixed; z-index:180;
  left:50%; bottom:max(16px, calc(env(safe-area-inset-bottom) + 10px));
  width:min(580px, calc(100% - 24px));
  display:flex; align-items:center; gap:12px;
  padding:10px 10px 10px 10px;
  background:rgba(13,15,18,.92);
  backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
  border:1px solid var(--ink-line); border-radius:14px;
  box-shadow:0 18px 40px rgba(13,15,18,.35), 0 2px 6px rgba(13,15,18,.2);
  color:var(--ink-text);
  overflow:hidden;
  transform:translate(-50%, calc(100% + 40px));
  opacity:0; visibility:hidden;
  transition:transform .45s cubic-bezier(.2,.8,.2,1), opacity .3s ease, visibility .45s;
}
.quote-bar.is-visible{ transform:translate(-50%, 0); opacity:1; visibility:visible; }
body.nav-open .quote-bar{ opacity:0; visibility:hidden; pointer-events:none; }

/* De voortgang naar de tien sealed items: dezelfde lijn en kleuren als op
   /quote, hier langs de bovenrand van de balk. */
.qb-track{
  position:absolute; left:0; right:0; top:0; height:3px;
  background:rgba(255,255,255,.08);
}
.qb-track[hidden]{ display:none; }
.qb-fill{
  display:block; height:100%; width:0; background:#FFCC00;
  transition:width .55s cubic-bezier(.2,.75,.3,1), background .3s;
}
.qb-track.is-low  .qb-fill{ background:#FF3B30; }
.qb-track.is-done .qb-fill{ background:#34C759; }

.qb-thumb{
  flex:0 0 auto; width:44px; height:44px; border-radius:9px;
  background:#fff; display:grid; place-items:center; overflow:hidden;
}
.qb-thumb[hidden]{ display:none; }
.qb-thumb img{ width:100%; height:100%; object-fit:contain; padding:3px; }

.qb-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px; }
.qb-count{ font:600 14.5px/1.25 var(--font-body); }
.qb-sub{
  font-size:12.5px; line-height:1.35; color:var(--ink-dim);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.quote-bar.is-done .qb-sub{ color:#6FDC8C; }

.qb-cta{
  flex:0 0 auto; display:inline-flex; align-items:center; gap:8px;
  height:44px; padding:0 18px; border-radius:10px;
  background:var(--gold); color:#171204;
  font:600 14px/1 var(--font-body); text-decoration:none; white-space:nowrap;
  transition:background .2s, transform .15s;
}
.qb-cta:hover{ background:var(--gold-bright); }
.qb-cta:active{ transform:scale(.97); }
.qb-cta svg{ width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.qb-cta-short{ display:none; }

/* Elke toevoeging: de balk tikt even omhoog en het plaatje springt, zodat
   je ziet dat het gelukt is zonder dat er een melding over de tegels valt. */
.quote-bar.is-bump .qb-thumb{ animation:qbPop .5s cubic-bezier(.3,1.6,.5,1); }
.quote-bar.is-bump{ animation:qbNudge .45s cubic-bezier(.3,1.4,.5,1); }
@keyframes qbPop{ 0%{ transform:scale(.6); } 100%{ transform:none; } }
@keyframes qbNudge{
  0%{ transform:translate(-50%, 0); }
  35%{ transform:translate(-50%, -6px); }
  100%{ transform:translate(-50%, 0); }
}

/* Ruimte onderaan, zodat "Toon meer" en de footer niet onder de balk vallen. */
body.has-quote-bar .site-footer{ padding-bottom:calc(var(--qb-space, 96px)); }
body.has-quote-bar .shop-more{ margin-bottom:24px; }

@media (max-width:560px){
  .quote-bar{ gap:10px; padding:8px; border-radius:13px; width:calc(100% - 16px); bottom:max(8px, calc(env(safe-area-inset-bottom) + 4px)); }
  .qb-thumb{ width:40px; height:40px; }
  .qb-count{ font-size:13.5px; }
  .qb-sub{ font-size:11.5px; }
  .qb-cta{ height:42px; padding:0 14px; }
  .qb-cta-long{ display:none; }
  .qb-cta-short{ display:inline; }
}
@media (prefers-reduced-motion:reduce){
  .quote-bar{ transition:opacity .01s, visibility .01s; transform:translate(-50%, 0); }
  .quote-bar.is-bump, .quote-bar.is-bump .qb-thumb{ animation:none; }
}

/* --- mobiel: de producten eerder in beeld --------------------------------
   Kop en 30th-band namen samen het hele eerste scherm; de eerste tegel
   stond pas op scherm twee. De kop krimpt, de band wordt een strook (zie
   celebration.css) en de filterbalk plakt onder de header zolang je omhoog
   scrollt (js/main.js initShopToolbarTuck). */
/* Op /shop zelf geen zichtbare kop meer: "Huidig groothandelsaanbod" met de
   voorbehouden eronder was het eerste wat je las, en het zei vooral wat er
   níet kon. De h1 blijft voor zoekmachines en schermlezers, alleen niet in
   beeld. Alleen waar de 30th-band er direct op volgt, en die staat alleen op
   /shop: de categoriepagina's uit dezelfde shell (tools/build-seo.mjs) houden
   hun kop met de categorietekst. */
.page-hero:has(+ .shop-spotlight){
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0;
}

@media (max-width:760px){
  .shop-section{ padding-top:16px; }

  .shop-toolbar{
    position:sticky; top:var(--header-h, 64px); z-index:50;
    margin:0 -28px 16px; padding:10px 28px;
    background:rgba(246,244,238,.94);
    backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
    gap:8px 10px;
    transition:transform .3s cubic-bezier(.2,.8,.2,1), box-shadow .3s;
  }
  .shop-toolbar.is-stuck{ box-shadow:0 1px 0 var(--paper-line), 0 10px 20px rgba(23,24,27,.06); }
  @media (max-width:640px){ .shop-toolbar{ margin-inline:-20px; padding-inline:20px; } }
  .shop-toolbar.is-tucked{ transform:translateY(calc(-100% - var(--header-h, 64px))); box-shadow:none; }
}

/* ==========================================================================
   Prijsindicaties (js/main.js §prijsindicaties, migratie 0021)
   ========================================================================== */
:root{ --trend-up:#1E9E57; --trend-down:#D23B2F; }

/* --- op de tegel: net boven de knop, op dezelfde hoogte in elke rij ----- */
.p-ind{ margin-top:auto; min-height:38px; display:flex; flex-direction:column; justify-content:flex-end; gap:2px; }
.p-ind + .product-foot{ margin-top:0; }
.p-ind-row{ display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
.p-ind-row strong{ font:600 19px/1.1 var(--font-body); color:var(--paper-text); letter-spacing:-.01em; font-variant-numeric:tabular-nums; }
.p-ind small{ font-family:var(--font-mono); font-size:10px; letter-spacing:.05em; text-transform:uppercase; color:var(--paper-dim); }
.p-trend{ display:inline-flex; align-items:center; gap:3px; font:600 12px/1 var(--font-body); font-variant-numeric:tabular-nums; white-space:nowrap; }
.p-trend span{ font-size:9px; }
.p-trend.is-up{ color:var(--trend-up); }
.p-trend.is-down{ color:var(--trend-down); }
.p-ind-lock, .p-ind-ask{
  display:inline-flex; align-items:center; gap:6px; align-self:flex-start;
  font-size:12.5px; color:var(--paper-dim); text-decoration:none;
}
a.p-ind-lock:hover{ color:var(--paper-text); }
a.p-ind-lock:hover span{ text-decoration:underline; text-underline-offset:3px; }
.p-ind-skel{
  display:block; width:46%; height:20px; border-radius:6px;
  background:linear-gradient(90deg, var(--paper-soft) 25%, #fff 50%, var(--paper-soft) 75%);
  background-size:200% 100%; animation:pIndSkel 1.2s linear infinite;
}
.p-ind-skel.is-lg{ width:min(260px, 70%); height:44px; }
@keyframes pIndSkel{ from{ background-position:100% 0; } to{ background-position:-100% 0; } }
@media (prefers-reduced-motion:reduce){ .p-ind-skel{ animation:none; } }
@media (max-width:560px){
  .p-ind-row strong{ font-size:17px; }
  .p-ind small{ font-size:9px; }
}

/* --- op de productpagina ------------------------------------------------ */
.pp-price{ margin:22px 0 4px; }
.pp-price-head{ display:flex; flex-direction:column; gap:4px; }
.pp-price-label{ font-family:var(--font-mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--paper-dim); }
.pp-price-row{ display:flex; align-items:baseline; gap:12px; }
.pp-price-row strong{ font:600 36px/1.05 var(--font-body); letter-spacing:-.02em; color:var(--paper-text); font-variant-numeric:tabular-nums; }
.pp-price-row .p-trend{ font-size:15px; }
.pp-price-row .p-trend span{ font-size:11px; }
.pp-price-band{ font-size:13.5px; color:var(--paper-text); }
.pp-price-since{ font-size:12.5px; color:var(--paper-dim); }
.pp-ask{ font-size:15px; color:var(--paper-dim); }

.pp-lock{
  display:flex; gap:12px; align-items:flex-start;
  padding:16px 18px; border:1px solid var(--paper-line); border-radius:12px; background:#fff;
}
.pp-lock > svg{ width:18px; height:18px; margin-top:2px; flex:0 0 auto; color:var(--gold); }
.pp-lock b{ display:block; font-size:15px; }
.pp-lock p{ margin:4px 0 0; font-size:13.5px; color:var(--paper-dim); max-width:46ch; }
.pp-lock-actions{ display:flex; align-items:center; gap:16px; margin-top:12px; }
.pp-lock-btn{
  display:inline-flex; align-items:center; height:38px; padding:0 16px; border-radius:10px;
  background:var(--ink); color:var(--gold-bright); font:600 13.5px/1 var(--font-body); text-decoration:none;
}
.pp-lock-btn:hover{ background:#000; }
.pp-lock-link{ font-size:13.5px; color:var(--paper-text); text-underline-offset:3px; }

/* De grafiek */
.pp-chart{ margin-top:18px; padding:14px 14px 8px; border:1px solid var(--paper-line); border-radius:12px; background:#fff; }
.pc-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:6px; }
.pc-title{ font-family:var(--font-mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--paper-dim); }
.pc-ranges{ display:inline-flex; gap:2px; padding:2px; border:1px solid var(--paper-line); border-radius:9px; }
.pc-range{
  border:0; background:none; cursor:pointer; padding:5px 10px; border-radius:7px;
  font:600 11.5px/1 var(--font-body); color:var(--paper-dim);
}
.pc-range:hover{ color:var(--paper-text); }
.pc-range.active{ background:var(--ink); color:var(--gold-bright); }
.pc-wrap{ position:relative; }
.pc-svg{ display:block; width:100%; height:auto; overflow:visible; touch-action:pan-y; }
.pc-tick line{ stroke:var(--paper-line); stroke-width:1; stroke-dasharray:2 4; }
.pc-tick text, .pc-x{ font:14px var(--font-body); fill:var(--paper-dim); font-variant-numeric:tabular-nums; }
.pc-area{ fill:rgba(201,162,39,.12); stroke:none; }
.pc-line{ fill:none; stroke:var(--gold); stroke-width:2.2; stroke-linejoin:round; }
.pc-dot{ fill:#fff; stroke:var(--gold); stroke-width:2; }
.pc-cursor{ stroke:var(--paper-text); stroke-width:1; stroke-dasharray:3 3; opacity:.5; }
.pc-tip{
  position:absolute; top:-6px; transform:translateX(-50%); pointer-events:none;
  display:flex; flex-direction:column; align-items:center; gap:1px;
  padding:6px 10px; border-radius:8px; background:var(--ink); color:var(--ink-text);
  font-size:11.5px; white-space:nowrap; box-shadow:0 6px 16px rgba(13,15,18,.2);
}
.pc-tip b{ font-size:13.5px; color:#fff; }
.pc-tip span{ color:var(--ink-dim); }
.pp-chart-few{ margin:4px 0 8px; font-size:13px; color:var(--paper-dim); }
.pp-chart-skel{ height:180px; border-radius:8px; background:var(--paper-soft); }

/* --- productpagina: de knop boven de vouw (13 okt 2026) -------------------
   Omschrijving op twee regels met "Lees meer" (js/main.js initDetailClamp),
   en de grafiek en specificaties pas ónder de knop. */
.pd-set{ margin-top:16px; max-width:46ch; }
.pd-set .product-detail-set{
  margin:0; max-width:none;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; overflow:hidden;
}
.pd-set.is-open .product-detail-set{ display:block; -webkit-line-clamp:unset; line-clamp:none; overflow:visible; }
.pd-more{
  margin-top:4px; padding:0; border:0; background:none; cursor:pointer;
  font:600 13.5px/1.4 var(--font-body); color:var(--paper-text);
  text-decoration:underline; text-underline-offset:3px; text-decoration-color:var(--paper-line);
}
.pd-more:hover{ text-decoration-color:currentColor; }
.pd-more[hidden]{ display:none; }
.pp-price{ margin:20px 0 0; }
.pp-price + .product-detail-actions{ margin-top:18px; }
.pp-chart{ margin-top:28px; }
.pp-chart:empty{ display:none; }
.pp-chart + .product-specs{ margin-top:28px; }

/* Mobiel: vaste knop onderin zolang de echte knop buiten beeld is
   (js/main.js initStickyAdd). Breder dan 760px bestaat hij niet: daar staat
   de knop naast de foto en dus boven de vouw. */
.pd-sticky{ display:none; }
@media (max-width:760px){
  .pd-sticky{
    position:fixed; z-index:180; left:8px; right:8px;
    bottom:max(8px, calc(env(safe-area-inset-bottom) + 4px));
    display:flex; align-items:center; gap:12px;
    padding:8px 8px 8px 14px; border-radius:13px;
    background:rgba(13,15,18,.92); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
    border:1px solid var(--ink-line); box-shadow:0 18px 40px rgba(13,15,18,.35);
    color:var(--ink-text);
    transform:translateY(calc(100% + 24px)); opacity:0; visibility:hidden;
    transition:transform .4s cubic-bezier(.2,.8,.2,1), opacity .25s ease, visibility .4s;
  }
  .pd-sticky.is-visible{ transform:none; opacity:1; visibility:visible; }
  body.nav-open .pd-sticky{ opacity:0; visibility:hidden; }
  .pd-sticky-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px; }
  .pd-sticky-text b{ font:600 13.5px/1.25 var(--font-body); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .pd-sticky-price{ font-size:12px; color:var(--ink-dim); font-variant-numeric:tabular-nums; }
  .pd-sticky-price:empty{ display:none; }
  .pd-sticky-btn{
    flex:0 0 auto; height:42px; padding:0 18px; border:0; border-radius:10px; cursor:pointer;
    background:var(--gold); color:#171204; font:600 14px/1 var(--font-body);
  }
  .pd-sticky-btn.added{ background:#34C759; color:#04140a; }
  body:has(.pd-sticky) .site-footer{ padding-bottom:96px; }
}
@media (prefers-reduced-motion:reduce){ .pd-sticky{ transition:opacity .01s, visibility .01s; transform:none; } }

/* De kolom is een flexbox, dus marges vallen niet samen: de 28px onder de
   formaatknoppen plus de 20px boven de prijs gaven 48px, twee keer de ruimte
   tussen de andere blokken. Eén ritme van 24px. */
.product-detail-copy .pp-price{ margin-top:24px; }
.product-detail-copy .variant-switch:has(+ .pp-price){ margin-bottom:0; }

/* --- account aanvragen: wat je krijgt, naast het formulier ----------------- */
.acc-layout{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,480px); gap:64px; align-items:start; max-width:1080px; margin:0 auto; }
.acc-kicker{ font-family:var(--font-mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--gold); }
.acc-pitch h1{ font-size:clamp(34px,4.4vw,54px); line-height:1; margin:10px 0 14px; }
.acc-lead{ color:var(--paper-dim); font-size:16px; line-height:1.55; max-width:44ch; margin:0; }
.acc-preview{
  position:relative; margin:28px 0 26px; padding:16px 18px 0; border:1px solid var(--paper-line);
  border-radius:14px; background:#fff; overflow:hidden; max-width:420px;
}
.acc-preview-top{ display:flex; flex-direction:column; gap:4px; }
.acc-preview-label{ font-family:var(--font-mono); font-size:10.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--paper-dim); }
.acc-preview-row{ display:flex; align-items:baseline; gap:10px; filter:blur(5px); user-select:none; }
.acc-preview-row strong{ font:600 30px/1.05 var(--font-body); letter-spacing:-.02em; }
.acc-preview-chart{ display:block; width:calc(100% + 36px); margin:10px -18px 0; height:70px; filter:blur(2.5px); }
.acc-pc-area{ fill:rgba(201,162,39,.14); }
.acc-pc-line{ fill:none; stroke:var(--gold); stroke-width:2.2; }
.acc-preview-lock{
  position:absolute; right:14px; top:14px; display:inline-flex; align-items:center; gap:6px;
  padding:6px 10px; border-radius:999px; background:var(--ink); color:var(--gold-bright);
  font:600 11.5px/1 var(--font-body);
}
.acc-benefits{ list-style:none; margin:0; padding:0; display:grid; gap:14px; max-width:46ch; }
.acc-benefits li{ position:relative; padding-left:28px; display:flex; flex-direction:column; gap:2px; }
.acc-benefits li::before{
  content:""; position:absolute; left:0; top:3px; width:16px; height:16px; border-radius:50%;
  background:var(--gold) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m4.5 8.2 2.2 2.2 4.8-4.8' fill='none' stroke='%23171204' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/16px no-repeat;
}
.acc-benefits b{ font-size:15px; }
.acc-benefits span{ font-size:13.5px; color:var(--paper-dim); line-height:1.45; }
.acc-formcol{ position:sticky; top:calc(var(--header-h,76px) + 24px); }
@media (max-width:900px){
  .acc-layout{ grid-template-columns:1fr; gap:28px; }
  .acc-formcol{ position:static; }
  .acc-preview{ max-width:none; }
  /* Op een telefoon: eerst de belofte en het formulier, de uitleg erna. */
  .acc-pitch{ display:contents; }
  .acc-kicker{ order:1; } .acc-pitch h1{ order:2; margin-top:0; } .acc-lead{ order:3; margin-top:-14px; }
  .acc-preview{ order:4; margin:0; } .acc-formcol{ order:5; } .acc-benefits{ order:6; } }

/* Beide kolommen even hoog: het formulierkader en de uitleg rekken mee tot
   dezelfde hoogte, de inlogregel staat onderin het kader en de voordelen
   onderaan de linkerkolom. Bij de langere stap 2 groeit de linkerkant mee. */
@media (min-width:901px){
  .acc-layout{ align-items:stretch; }
  .acc-pitch{ display:flex; flex-direction:column; }
    .acc-preview{ margin-block:auto; }
  .acc-benefits{ margin-top:0; }
  .acc-formcol{ position:static; display:flex; flex-direction:column; }
  .acc-formcol .form-shell{ flex:1 1 auto; display:flex; flex-direction:column; }
}
.acc-already{
  margin:auto 0 0; padding-top:20px; border-top:1px solid var(--paper-line);
  font-size:12.5px; line-height:1.5; color:var(--paper-dim); text-align:left;
}
.acc-already b{ color:var(--paper-text); }
.acc-already a{ color:var(--gold); text-decoration:underline; }
.acc-formcol form{ margin-bottom:24px; }

/* Op een telefoon geen voorproef: die duwde het formulier naar beneden. De
   vier voordelen blijven, onder het formulier. */
@media (max-width:900px){
  .acc-preview, }

/* Labels in het contact- en accountformulier: Inter in zinsstijl, zoals de
   knoppen, in plaats van het monolettertype in hoofdletters. */
#contactForm .field label, #accountForm .field label{
  font-family:var(--font-body); font-size:13.5px; font-weight:600;
  text-transform:none; letter-spacing:0; color:var(--paper-text);
}
/* De kopjes in het contactformulier ("Jouw gegevens", "Jouw bericht") ook in
   Inter. Iets groter dan de labels en in goud, zodat ze als kop blijven lezen. */
#contactForm .form-legend{
  font-family:var(--font-body); font-size:15px; font-weight:600;
  letter-spacing:0; text-transform:none;
}
/* Ook het zwarte deel van de contactpagina in Inter: het kopje "Direct", de
   regels onder WhatsApp/e-mail/telefoon, de QR-uitleg, KvK en BTW-ID en de
   bevestiging na verzenden. De paginatitel blijft in de serif, zoals overal. */
.contact-ink-label, .channel-meta, .contact-fact-note, .contact-reg, .contact-reg span,
#contactSuccess .stamp{
  font-family:var(--font-body); letter-spacing:0; font-variant-numeric:tabular-nums;
}
.contact-ink-label{ font-size:13px; font-weight:600; text-transform:none; }
#contactSuccess .stamp{ text-transform:none; font-weight:600; }
