/* toiletvergelijken.nl — "Frisse Winkel"
 *
 * Design direction chosen from design/moodboards/board-10-frisse-winkel.html.
 * One stylesheet, no framework, self-hosted fonts. Everything hangs off the token
 * block, so a future direction change is a token swap plus component tweaks.
 *
 * The one rule that carries the whole system: **amber means "you are leaving to
 * a shop to buy"**. Nothing else on the site is amber. Blue is for our own
 * actions (navigate, compare, subscribe). A reader learns that distinction in
 * two pages and then never has to think about it again.
 *
 * Light only. The brief was clean, fresh, bright; a dark variant would need its
 * own validated palette rather than an inversion, so it is deliberately absent.
 */

/* ---------- fonts (self-hosted: the CSP is font-src 'self', so a font never
     comes from someone else's server) ---------- */
@font-face {
  font-family: "Plus Jakarta Sans";
  src: url("/static/fonts/jakarta-var-latin.woff2") format("woff2");
  font-weight: 600 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  src: url("/static/fonts/inter-var-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light;

  /* surfaces */
  --white: #ffffff;
  --tint: #f2f6fd;        /* page plane, table heads, quiet bands */
  --tint-2: #e9f0fb;
  --line: #cbd8ec;
  --line-soft: #e2eaf7;

  /* ink */
  --ink: #0b1b33;         /* headings and prices */
  --ink-2: #46566e;       /* body */
  --muted: #5f6e85;       /* captions — 4.9:1 on white, still AA */

  /* blue — our own actions */
  --blue: #0b57e3;        /* 5.6:1 on white */
  --blue-deep: #0a47b8;
  --blue-soft: #e8f0fe;

  /* amber — the outgoing buy button, and nothing else.
     White on amber is 1.83:1, which is why the label is near-black ink (9.3:1)
     and the ring makes the control shape itself clear 3:1 against white. */
  --amber: #ffb020;
  --amber-hover: #f5a000;
  --amber-ink: #2a1800;
  --amber-ring: #8a4b00;
  --amber-soft: #fff4de;

  /* status */
  --green: #067647;
  --green-soft: #e6f6ee;
  --red: #b3261e;
  --red-soft: #fdecea;

  /* shape — squarer and tighter than the calm directions */
  --r: 12px;
  --r-btn: 10px;
  --r-badge: 6px;
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 36px; --sp-7: 56px;
  --sh-1: 0 1px 2px rgba(11,27,51,.07);
  --sh-2: 0 10px 24px -16px rgba(11,27,51,.45);
  --dur: 140ms;
  --ease: cubic-bezier(.2,.7,.3,1);
  --wrap: 1200px;

  --font-display: "Plus Jakarta Sans", "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;
  --font-text: "Inter", "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* test-environment banner only — deliberately its own colour. Not amber
     (that means "buy"), not red (that means "error"/"geen voorraad"), not blue
     (that means "our own action"): nothing else on the site may use this. */
  --test: #6d28d9;
  --test-soft: #ede9fe;
}

* { box-sizing: border-box; }
/* The `hidden` attribute has to beat any `display` we set ourselves.
   The browser's own rule is `[hidden] { display: none }`, which loses to any
   author rule of equal specificity — and `.card { display: flex }` is exactly
   that. Result: the /douchewc filter set `card.hidden = true` on every card it
   wanted gone and nothing moved, so filtering looked broken while working
   perfectly. Fix it once, here, rather than per component. */
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--tint);
  color: var(--ink-2);
  font: 16px/1.62 var(--font-text);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 20px; }
.narrow-col { max-width: 760px; }
img, svg { max-width: 100%; height: auto; }

a { color: var(--blue); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--blue-deep); text-decoration-thickness: 2px; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--ink);
  line-height: 1.14;
  letter-spacing: -.022em;
  margin: 0 0 .45em;
  font-weight: 800;
}
h1 { font-size: clamp(1.9rem, 1.25rem + 2.6vw, 2.85rem); }
h2 { font-size: clamp(1.35rem, 1.1rem + 1.1vw, 1.85rem); }
h3 { font-size: 1.12rem; font-weight: 700; }
h4 { font-size: 1rem; font-weight: 700; }
p { margin: 0 0 1em; }

.lede { font-size: 1.12rem; line-height: 1.6; color: var(--ink-2); max-width: 66ch; }
.muted { color: var(--muted); }
.small { font-size: .875rem; }
.strong { font-weight: 700; }
.block { display: block; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.eyebrow {
  font-family: var(--font-display);
  font-size: .78rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .09em; color: var(--blue); margin: 0 0 .5em;
}

.sr-only, .skip:not(:focus) {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip:focus {
  position: fixed; top: 8px; left: 8px; z-index: 100; background: var(--blue);
  color: #fff; padding: 10px 16px; border-radius: var(--r-btn); font-weight: 700;
}
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--blue); outline-offset: 2px; border-radius: 4px;
}

/* ---------- test-environment banner ----------
   Shown on every environment that is not production (preview deploys, local
   dev). Scrolls away with the page on purpose — the sticky header is what a
   reader expects to stay put, not a warning stripe. */
.env-bar { background: var(--test); color: #fff; }
.env-bar-inner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--sp-3); padding: var(--sp-2) 20px; text-align: center;
}
.env-bar-msg { margin: 0; font-size: .9rem; }
.env-bar-btn { background: #fff; color: var(--test); border-color: #fff; }
.env-bar-btn:hover { background: var(--test-soft); color: var(--test); }

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 40; background: rgba(255,255,255,.92);
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--line-soft);
}
.header-inner { display: flex; align-items: center; gap: var(--sp-4); min-height: 64px; }
.brand {
  display: inline-flex; align-items: center; gap: 9px; font-family: var(--font-display);
  font-size: 1.06rem; font-weight: 700; color: var(--ink); text-decoration: none;
}
.brand:hover { color: var(--ink); }
.brand strong { font-weight: 800; }
.brand-mark { color: var(--blue); flex: none; }
.tld { color: var(--muted); font-weight: 600; }
.nav { margin-left: auto; display: flex; gap: 2px; align-items: center; flex-wrap: wrap; }
.nav a {
  color: var(--ink-2); text-decoration: none; padding: 9px 13px;
  border-radius: var(--r-btn); font-size: .94rem; font-weight: 600;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav a:hover { background: var(--blue-soft); color: var(--blue-deep); }
.nav .nav-cta { background: var(--blue); color: #fff; }
.nav .nav-cta:hover { background: var(--blue-deep); color: #fff; }
.nav-toggle {
  display: none; margin-left: auto; background: var(--white); border: 1px solid var(--line);
  color: var(--ink); border-radius: var(--r-btn); padding: 8px; cursor: pointer;
  min-width: 44px; min-height: 44px; align-items: center; justify-content: center;
}
@media (max-width: 880px) {
  .nav-toggle { display: inline-flex; }
  .nav { display: none; width: 100%; flex-direction: column; align-items: stretch; padding-bottom: var(--sp-3); }
  .nav.open { display: flex; }
  .nav a { padding: 13px; }
}

/* ---------- buttons ----------
   Hierarchy: buy (amber, outgoing) > primary (blue, ours) > ghost > link. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 20px; min-height: 44px; border-radius: var(--r-btn);
  border: 1px solid transparent; font: inherit; font-family: var(--font-display);
  font-weight: 700; text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
    transform 60ms var(--ease), box-shadow var(--dur) var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--blue); color: #fff; }
.btn-primary:hover { background: var(--blue-deep); color: #fff; }
.btn-buy {
  background: var(--amber); color: var(--amber-ink);
  border-color: var(--amber-ring); box-shadow: var(--sh-1);
}
.btn-buy:hover { background: var(--amber-hover); color: var(--amber-ink); }
.btn-buy::after {
  content: ""; width: 13px; height: 13px; flex: none;
  background: currentColor; opacity: .75;
  -webkit-mask: var(--icon-external) center/contain no-repeat;
  mask: var(--icon-external) center/contain no-repeat;
}
:root {
  --icon-external: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cline x1='10' y1='14' x2='21' y2='3'/%3E%3C/svg%3E");
}
.btn-ghost { background: var(--white); border-color: var(--line); color: var(--ink); }
.btn-ghost:hover { background: var(--tint); border-color: var(--muted); color: var(--ink); }
.btn-sm { padding: 8px 14px; min-height: 38px; font-size: .875rem; }
.btn-lg { padding: 15px 26px; min-height: 52px; font-size: 1.06rem; }
.linklike {
  background: none; border: 0; color: var(--blue); text-decoration: underline;
  cursor: pointer; font: inherit; padding: 0;
}

/* ---------- badges ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-display);
  font-size: .72rem; font-weight: 800; letter-spacing: .02em; padding: 4px 9px;
  border-radius: var(--r-badge); line-height: 1.35;
}
.badge-deal { background: var(--amber-soft); color: #6b3a00; border: 1px solid #e8c88b; }
.badge-stock { background: var(--green-soft); color: var(--green); }
.badge-nostock { background: var(--red-soft); color: var(--red); }
.badge-score { background: var(--blue); color: #fff; }
.badge-new { background: var(--blue-soft); color: var(--blue-deep); }

/* ---------- hero ---------- */
.hero {
  background: linear-gradient(180deg, var(--blue-soft) 0%, rgba(232,240,254,0) 100%);
  border-bottom: 1px solid var(--line-soft); padding: var(--sp-7) 0 var(--sp-6);
}
.hero-grid {
  display: grid; grid-template-columns: minmax(0,1.35fr) minmax(300px,.85fr);
  gap: var(--sp-6); align-items: start;
}
@media (max-width: 940px) { .hero-grid { grid-template-columns: 1fr; gap: var(--sp-5); } }
.hero h1 { margin-bottom: .3em; }
.hero-actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin: var(--sp-5) 0 var(--sp-4); }
.trust-row {
  list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
  padding: 0; margin: 0; color: var(--ink-2); font-size: .9rem; font-weight: 500;
}
.hero-panel {
  background: var(--white); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--sp-4); box-shadow: var(--sh-2);
}
.panel-h {
  font-family: var(--font-display); font-size: .78rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
  margin-bottom: var(--sp-3);
}
.pick {
  display: block; padding: 13px 15px; border-radius: var(--r-btn);
  border: 1px solid var(--line-soft); margin-bottom: var(--sp-2);
  text-decoration: none; color: var(--ink); background: var(--white);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.pick:hover { border-color: var(--blue); background: var(--blue-soft); color: var(--ink); }
.pick-title { display: block; font-family: var(--font-display); font-weight: 700; }
.pick-sub { display: block; font-size: .875rem; color: var(--muted); margin-top: 2px; }

/* ---------- sections ---------- */
.section { padding: var(--sp-6) 0; }
.page-head { padding: var(--sp-5) 0 var(--sp-2); }
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.section-link { font-size: .94rem; font-weight: 700; text-decoration: none; }
.band { background: var(--white); border-block: 1px solid var(--line-soft); padding: var(--sp-7) 0; margin: var(--sp-6) 0; }
.band-quiet { background: var(--blue-soft); border-color: transparent; }
.crumbs { font-size: .85rem; color: var(--muted); margin-bottom: var(--sp-2); }
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--blue); }

.grid { display: grid; gap: var(--sp-4); }
.cards { grid-template-columns: repeat(auto-fill, minmax(255px, 1fr)); }
.brand-grid { grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); }

/* ---------- product card ---------- */
.card {
  background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r);
  overflow: hidden; position: relative; display: flex; flex-direction: column;
  transition: border-color var(--dur) var(--ease);
}
/* Hover: rand + titel, geen schaduw en geen lift. De kaart hoort geen knop te
   worden — de amber koopknop erin is de knop, en die draagt al --sh-1. Zet je
   --sh-2 onder de hele kaart, dan wint de kaart het van zijn eigen knop.
   Toetsenbordfocus krijgt hetzelfde antwoord als de muis. */
.card:is(:hover, :focus-within) { border-color: var(--line); }
.card:is(:hover, :focus-within) .card-title { color: var(--blue-deep); }
.brand-card:is(:hover, :focus-within) h2 { color: var(--blue-deep); }
.card-link { text-decoration: none; color: inherit; display: block; }
.card-link:hover { color: inherit; }
.card-media {
  position: relative; background: var(--white); aspect-ratio: 4 / 3;
  display: grid; place-items: center; color: var(--ink); border-bottom: 1px solid var(--line-soft);
  overflow: hidden;
}
.card-media img { width: 100%; height: 100%; object-fit: contain; padding: 10px; }
/* De twee kaartfoto's liggen op elkaar: dicht in rust, open (of een ander
   aanzicht) op hover. Crossfade, want de packshots staan al op dezelfde plek in
   beeld — beweging zou alleen maar suggereren dat het toilet zelf beweegt.
   De rustfoto gaat pas een tel later weg, zodat je nooit door beide heen kijkt. */
.card-swap .card-img { position: absolute; inset: 0; transition: opacity .22s var(--ease); }
.card-img-hover { opacity: 0; }
.card-swap:is(:hover, :focus-within) .card-img-hover { opacity: 1; }
.card-swap:is(:hover, :focus-within) .card-img:not(.card-img-hover) {
  opacity: 0; transition-delay: .06s;
}
/* Eén bruikbare foto en dus niets om naar te wisselen: dan een trage zoom van 4%.
   De kaart moet antwoorden als je erop staat — een kaart die niets doet leest als
   kapot — maar zonder een tweede stand te suggereren die we niet hebben. Een
   product zonder foto krijgt ook dit niet: daar valt niets in te zoomen. */
.product-card:not(.card-swap) .card-img { transition: transform .4s var(--ease); }
.product-card:not(.card-swap):is(:hover, :focus-within) .card-img { transform: scale(1.04); }
/* Zonder muis is er geen hover: dan blijft de rustfoto staan en hoeft de tweede
   foto niet eens gedecodeerd te worden. */
@media (hover: none) {
  .card-img-hover { display: none; }
}
/* De globale reduced-motion-regel maakt elke transitie 0,01 ms; een zoom die
   erin springt is dan erger dan geen zoom. */
@media (prefers-reduced-motion: reduce) {
  .product-card:not(.card-swap):is(:hover, :focus-within) .card-img { transform: none; }
}
.card-media .ph { width: 100%; height: 100%; color: #8fa8cc; }
.card-media .badge-score { position: absolute; top: 10px; left: 10px; }
.card-body { padding: var(--sp-4) var(--sp-4) var(--sp-2); }
.card-brand {
  font-family: var(--font-display); font-size: .74rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0 0 3px;
}
.card-title {
  font-size: 1rem; margin: 0 0 6px; line-height: 1.3;
  transition: color var(--dur) var(--ease);
}
.card-sum { font-size: .875rem; color: var(--ink-2); margin: 0 0 var(--sp-2); }
.chips { display: flex; flex-wrap: wrap; gap: 5px; list-style: none; padding: 0; margin: 0 0 var(--sp-2); }
.chip {
  font-size: .72rem; font-weight: 600; padding: 3px 8px; border-radius: var(--r-badge);
  background: var(--tint); color: var(--ink-2);
}
.chip-alt { background: var(--green-soft); color: var(--green); }
/* Price above, full-width buy button below. Side by side the price wrapped over
   three lines in a 255px card, which destroyed the price hierarchy the whole
   direction rests on. A full-width button is also a bigger tap target. */
.card-foot {
  margin-top: auto; padding: var(--sp-2) var(--sp-4) var(--sp-4);
  display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-3);
}
.card-foot .btn { width: 100%; }
.price-block { display: flex; flex-direction: column; min-width: 0; }
.price {
  font-family: var(--font-display); font-size: 1.42rem; font-weight: 800;
  color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -.02em;
  line-height: 1.1; white-space: nowrap;
}
.price-note { font-size: .78rem; color: var(--muted); margin-top: 3px; white-space: nowrap; }
.shop { font-size: .82rem; color: var(--muted); margin-top: 2px; }
/* Ticking this does nothing without JS, so do not offer it then. app.js sets
   `.js` on <html> as its first act. */
html:not(.js) .cmp-toggle { display: none; }
.cmp-toggle {
  position: absolute; top: 10px; right: 10px; font-size: .72rem; font-weight: 600;
  background: rgba(255,255,255,.94); border: 1px solid var(--line); border-radius: var(--r-badge);
  padding: 4px 8px; display: inline-flex; gap: 5px; align-items: center; cursor: pointer;
  min-height: 28px;
}
.brand-card { padding: var(--sp-4); text-decoration: none; color: inherit; }
.brand-card h2 { font-size: 1.05rem; margin-bottom: .2em; }

/* ---------- listing ---------- */
.listing { display: grid; grid-template-columns: 265px minmax(0,1fr); gap: var(--sp-6); align-items: start; }
@media (max-width: 940px) { .listing { grid-template-columns: 1fr; } }
.filters {
  background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r);
  padding: var(--sp-4); position: sticky; top: 80px;
  max-height: calc(100vh - 100px); overflow-y: auto;
}
/* The default OS scrollbar is a thick grey bar that reads as broken against a
   card this narrow — thin it down and match it to the line color instead. */
.filters, .brand-filter { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.filters::-webkit-scrollbar, .brand-filter::-webkit-scrollbar { width: 8px; }
.filters::-webkit-scrollbar-track, .brand-filter::-webkit-scrollbar-track { background: transparent; }
.filters::-webkit-scrollbar-thumb, .brand-filter::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: 4px;
}
.filters::-webkit-scrollbar-thumb:hover, .brand-filter::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.filters fieldset { border: 0; padding: 0; margin: 0 0 var(--sp-4); }
.filter-hint { margin: 4px 0 0; }
/* Enough filters now that the long tail has to fold away. */
.filter-more { margin: 0 0 var(--sp-4); }
.filter-more summary {
  font-family: var(--font-display); font-weight: 800; font-size: .78rem;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
  cursor: pointer; padding: 5px 0; min-height: 32px; display: flex; align-items: center;
  /* `display: flex` drops the browser's disclosure triangle, so draw our own —
     without it the section reads as a dead heading. */
  list-style: none; gap: 6px;
}
.filter-more summary::-webkit-details-marker { display: none; }
.filter-more summary::after {
  content: ""; width: 7px; height: 7px; margin-left: auto;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur) var(--ease);
}
.filter-more[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.filter-more summary:hover { color: var(--ink); }
.brand-filter { max-height: 210px; overflow-y: auto; padding-right: 4px; }
.brand-filter label { font-size: .88rem; }
/* The honest half of the filter: models we cannot judge, offered back rather
   than silently binned. Set apart so it does not read as another feature. */
.unknown-filter {
  border-top: 1px solid var(--line-soft) !important; padding-top: var(--sp-3) !important;
}
.unknown-filter label { font-size: .88rem; color: var(--ink-2); align-items: flex-start; }
.filter-actions { margin: 0; display: flex; gap: var(--sp-2); }
.filters legend {
  font-family: var(--font-display); font-weight: 800; font-size: .78rem;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin-bottom: 6px;
}
.filters label { display: flex; gap: 9px; align-items: center; padding: 5px 0; font-size: .94rem; cursor: pointer; min-height: 32px; }
.filters select, .filters input[type=range] { width: 100%; }
.filter-status { font-size: .875rem; color: var(--muted); margin: 0; font-weight: 600; }
.empty { padding: var(--sp-6); text-align: center; color: var(--muted); background: var(--white); border-radius: var(--r); }

/* ---------- listing on a phone ----------
   Below 940px the two-column grid collapses and the filter panel stops being a
   sidebar: it becomes a 712px block standing in front of the results, which put
   the first product 1234px down the page on a 375px screen. Shrinking it does
   not help — eight fieldsets are eight fieldsets — so the panel leaves the flow
   and comes back as a sheet, opened from a sticky bar.

   Everything here is guarded on `html.js`. Without JavaScript there is nothing
   to open a sheet with, so the panel stays exactly where the server put it and
   the page behaves as it does today.

   The sheet body does scroll inside itself, which is the pattern the desktop
   panel was just cured of — the difference is that a sheet is a modal over a
   locked page, so there is no second scrollbar competing with the page's own.
   For the same reason the brand list gives up its own inner scroll here. */
.listing-bar { display: none; }
.filter-head { display: none; }
.sheet-apply { display: none; }
.sheet-scrim { display: none; }

@media (max-width: 940px) {
  html.js .listing-bar {
    display: block; position: sticky; top: 64px; z-index: 35;
    background: rgba(255,255,255,.97); backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--line-soft); padding: 8px 0 9px;
    margin-bottom: var(--sp-4);
  }
  .lbar-row { display: flex; gap: 8px; padding-bottom: 8px; }
  .lbar-btn {
    flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    min-height: 44px; border-radius: var(--r-btn); border: 1px solid var(--line);
    background: var(--white); color: var(--ink); cursor: pointer;
    font: 700 .9rem/1 var(--font-display);
  }
  .lbar-btn:active { transform: translateY(1px); }
  .lbar-btn:has(.lbar-n:not([hidden])) { border-color: var(--blue); background: var(--blue-soft); color: var(--blue-deep); }
  .lbar-n {
    display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px;
    border-radius: 999px; background: var(--blue); color: #fff; font-size: .72rem; font-weight: 800;
  }
  /* Bleeds to both screen edges so it is visible that the row scrolls on. */
  .lbar-quick {
    display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none;
    margin: 0 -20px; padding: 2px 20px;
  }
  .lbar-quick::-webkit-scrollbar { display: none; }
  .quick-chip {
    flex: none; min-height: 40px; padding: 0 14px; border-radius: 999px;
    border: 1px solid var(--line); background: var(--white); color: var(--ink);
    font: 650 .875rem/1 var(--font-text); white-space: nowrap; cursor: pointer;
  }
  .quick-chip[aria-pressed="true"] { background: var(--blue); border-color: var(--blue); color: #fff; }

  html.js .listing .filters {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 56;
    max-height: 88dvh; padding: 0; overflow: visible;
    display: flex; flex-direction: column;
    border: 1px solid var(--line); border-bottom: 0; border-radius: 18px 18px 0 0;
    box-shadow: 0 -12px 40px -18px rgba(11,27,51,.6);
    /* `visibility` rather than only a transform: an off-screen panel that is
       still in the tab order is a keyboard trap. */
    transform: translateY(100%); visibility: hidden;
  }
  /* The transition arrives a frame later, from script. Put it in the rule above
     and the *first* application of the transform — the moment app.js adds
     `html.js` — is itself an animation, so every page load opens with the panel
     filling the screen and then sliding away. */
  html.js .listing .filters.sheet-ready {
    transition: transform 240ms var(--ease), visibility 0s linear 240ms;
  }
  html.js .listing .filters.is-open {
    transform: none; visibility: visible; transition-delay: 0s;
  }
  html.js .filter-head {
    display: flex; align-items: center; gap: var(--sp-3); flex: none;
    padding: 6px 8px 6px var(--sp-4); border-bottom: 1px solid var(--line-soft);
  }
  html.js .filter-head h2 { margin: 0; font-size: 1.05rem; }
  html.js .filter-close {
    margin-left: auto; width: 44px; height: 44px; display: grid; place-items: center;
    background: none; border: 0; color: var(--ink-2); cursor: pointer; border-radius: var(--r-btn);
  }
  html.js .filter-body { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-4) var(--sp-4) 0; }
  html.js .listing .brand-filter { max-height: none; overflow: visible; padding-right: 0; }
  /* A row you can hit with a thumb, not a 32px label next to a 13px box. */
  html.js .listing .filters label { min-height: 44px; font-size: .95rem; }
  html.js .listing .filters input[type=checkbox],
  html.js .listing .filters input[type=radio] { width: 20px; height: 20px; flex: none; accent-color: var(--blue); }
  html.js .filter-status { flex: none; padding: 0 var(--sp-4) 10px; }
  html.js .listing .filter-actions {
    flex: none; background: var(--white); border-top: 1px solid var(--line-soft);
    padding: 10px var(--sp-4) calc(10px + env(safe-area-inset-bottom, 0px));
  }
  html.js .sheet-apply { display: inline-flex; flex: 1; }

  html.js .sheet-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(11,27,51,.45); opacity: 0; pointer-events: none;
    transition: opacity var(--dur) var(--ease);
  }
  html.js .sheet-scrim.is-open { opacity: 1; pointer-events: auto; }
  body.sheet-open { overflow: hidden; }
}

/* Two cards abreast instead of one. 71 single-column cards is 46 000px of
   scroll; the same list two-up with the summary dropped is under a third of
   that, and a phone reader is scanning brand + price, not reading blurbs. */
@media (max-width: 560px) {
  .listing .cards { grid-template-columns: 1fr 1fr; gap: 10px; }
  .listing .cards .card-body { padding: 10px 10px 2px; }
  .listing .cards .card-brand { font-size: .68rem; }
  .listing .cards .card-sum { display: none; }
  .listing .cards .card-title {
    font-size: .875rem; line-height: 1.25;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  .listing .cards .chip { font-size: .66rem; padding: 2px 6px; }
  .listing .cards .card-foot { padding: 0 10px 10px; gap: 8px; }
  .listing .cards .price { font-size: 1.12rem; }
  .listing .cards .price-note, .listing .cards .shop { font-size: .74rem; }
  .listing .cards .card-foot .btn { padding: 8px 10px; min-height: 40px; font-size: .82rem; }
  .listing .cards .cmp-toggle { top: 6px; right: 6px; font-size: .68rem; padding: 3px 6px; }
}

/* ---------- product page ---------- */
.product-top { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.05fr); gap: var(--sp-6); align-items: start; padding-top: var(--sp-2); }
@media (max-width: 940px) { .product-top { grid-template-columns: 1fr; } }
.product-media { background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); padding: var(--sp-4); color: var(--ink); }
.product-media .media-main { display: block; }
.product-media img { width: 100%; height: auto; object-fit: contain; background: #fff; border-radius: var(--r-btn); }
.media-gallery { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.media-gallery li { width: 80px; }
.media-gallery img { width: 80px; height: 60px; object-fit: contain; border: 1px solid var(--line-soft); border-radius: var(--r-badge); background: #fff; }
.media-gallery a:hover img, .media-gallery a:focus-visible img { border-color: var(--blue); }
.media-credit { margin: var(--sp-2) 0 0; }
.spec-table caption { text-align: left; font-weight: 700; padding: 14px 16px 6px; color: var(--ink); }
.spec-table + .spec-table { margin-top: 14px; }
.spec-count { margin: 0 0 12px; }
.spec-unknown th[scope=row], .spec-unknown td { color: var(--ink-2); }
.spec-gaps { margin: 6px 0 16px; }
.cmp-differs th[scope=row] { position: relative; }
.cmp-differs th[scope=row]::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; background: var(--accent, currentColor); border-radius: 2px; }
.cmp-differs td { font-weight: 600; }
.empty-brand { max-width: 62ch; }
.spec-src { margin-left: 8px; font-size: 0.78em; text-decoration: none; border-bottom: 1px dotted currentColor; opacity: .65; }
.spec-src:hover, .spec-src:focus-visible { opacity: 1; }
.variant-table th[scope=col] { text-align: left; font-weight: 600; }
.rating-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.rating { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; padding: 10px 14px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
.rating-score b { font-size: 1.15em; }
.notice-discontinued { margin: 0 0 12px; padding: 10px 14px; border-radius: var(--radius); background: var(--surface-2); border: 1px solid var(--line); }
.doc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.doc { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; padding: 11px 15px; background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r-btn); }
.doc > a { font-weight: 700; }

.downside {
  background: var(--red-soft); border-left: 3px solid var(--red);
  padding: 11px 15px; border-radius: 0 var(--r-btn) var(--r-btn) 0; color: var(--ink);
}
.buybox {
  background: var(--white); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--sp-5); margin-top: var(--sp-5); box-shadow: var(--sh-2);
}
.buy-price {
  font-family: var(--font-display); font-size: clamp(2.3rem, 1.7rem + 2.6vw, 3.4rem);
  font-weight: 800; color: var(--ink); margin: 0 0 4px;
  font-variant-numeric: tabular-nums; letter-spacing: -.03em; line-height: 1.02;
}
.buy-meta { display: flex; flex-wrap: wrap; gap: 6px var(--sp-4); font-size: .875rem; color: var(--muted); margin: 0 0 var(--sp-3); }
.buy-meta b { color: var(--ink-2); font-weight: 650; }
.deal-note { color: #6b3a00; background: var(--amber-soft); border-radius: var(--r-badge); padding: 7px 11px; font-weight: 650; font-size: .9rem; margin: 0 0 var(--sp-3); display: inline-block; }
/* No shop to send the reader to. Deliberately NOT amber: amber on this site
   means "you are leaving to a shop to buy", and here there is nowhere to go. */
.buy-note { background: var(--blue-soft); color: var(--ink-2); border-radius: var(--r-btn);
  padding: 10px 13px; font-size: .9rem; line-height: 1.45; margin: var(--sp-4) 0 var(--sp-3); }
.buy-actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin: var(--sp-4) 0 var(--sp-3); }
.buy-trust { display: flex; flex-wrap: wrap; gap: 6px var(--sp-4); font-size: .82rem; color: var(--muted); margin: 0; }
.buy-trust span { display: inline-flex; align-items: center; gap: 5px; }

.offers { list-style: none; padding: 0; margin: var(--sp-4) 0; display: grid; gap: var(--sp-2); }
.offer {
  display: grid; grid-template-columns: 1fr auto auto auto; gap: var(--sp-3);
  align-items: center; padding: 11px 14px; border: 1px solid var(--line-soft);
  border-radius: var(--r-btn); background: var(--white);
}
.offer:hover { border-color: var(--line); }
.offer-shop { font-weight: 650; color: var(--ink); }
.offer-price { font-family: var(--font-display); font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
@media (max-width: 560px) { .offer { grid-template-columns: 1fr auto; } .offer-stock { display: none; } }

.alert-form { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--line-soft); }
.alert-form input[type=email] { flex: 1 1 200px; }
.spark-figure { background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); padding: var(--sp-4); }
.spark { width: 100%; height: 96px; color: var(--blue); }

/* Sticky mobile buy bar: on phones the price and the buy button must never
   scroll out of reach. Hidden on desktop, where the buybox stays visible. */
.buybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45; display: none;
  /* Fully opaque: at .97 the page heading showed through the bar on a phone. */
  background: var(--white); border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px -18px rgba(11,27,51,.6);
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
}
.buybar-inner { display: flex; align-items: center; gap: var(--sp-3); max-width: var(--wrap); margin: 0 auto; }
.buybar .price { font-size: 1.3rem; }
.buybar .btn { margin-left: auto; }
@media (max-width: 940px) { .buybar { display: block; } body:has(.buybar) { padding-bottom: 76px; } }

/* ---------- tables ---------- */
.table-scroll { overflow-x: auto; border: 1px solid var(--line-soft); border-radius: var(--r); background: var(--white); }
table { border-collapse: collapse; width: 100%; font-size: .94rem; }
.spec-table, .fit-table, .cmp-table, .calc-table { background: var(--white); }
.spec-table, .fit-table { border: 1px solid var(--line-soft); border-radius: var(--r); overflow: hidden; }
th, td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
thead th {
  background: var(--tint); font-family: var(--font-display); font-size: .74rem;
  font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--tint); }
.spec-table th[scope=row] { width: 40%; font-weight: 650; color: var(--ink-2); }
/* ---------- comparison ----------
   Photo columns on top, the spec sheet underneath in one <details> per section.
   The header row and every table share one width formula, so a column heading and
   the values below it stay in the same place at any width:
   `--cmp-label` for the row labels, the rest split evenly over `--cmp-n` models. */
.cmp-grid { --cmp-label: 190px; --cmp-n: 2; }
.cmp-grid[data-n="1"] { --cmp-n: 1; }
.cmp-grid[data-n="3"] { --cmp-n: 3; }
.cmp-grid[data-n="4"] { --cmp-n: 4; }
.cmp-tools { display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.cmp-only-diff { display: inline-flex; align-items: center; gap: 9px; font-weight: 650; color: var(--ink); cursor: pointer; }
.cmp-only-diff input { width: 18px; height: 18px; accent-color: var(--blue); }
.cmp-legend { margin: 0; max-width: 60ch; }
.cmp-scroll { border: 1px solid var(--line-soft); border-radius: var(--r); background: var(--white); }
.cmp-cols, .cmp-table { min-width: calc(var(--cmp-label) + var(--cmp-n) * 150px); }

.cmp-cols {
  display: grid; grid-template-columns: var(--cmp-label) repeat(var(--cmp-n), 1fr);
  position: sticky; top: 64px; z-index: 3; padding: 14px 0;
  background: var(--white); border-bottom: 1px solid var(--line);
}
.cmp-cols-spacer { position: sticky; left: 0; background: var(--white); }
.cmp-col { position: relative; display: flex; flex-direction: column; gap: 5px; padding: 0 14px; min-width: 0; }
.cmp-col > * { max-width: 100%; }
.cmp-col-media { display: block; align-self: center; }
.cmp-col-media img, .cmp-col-media .ph { width: 132px; height: 100px; object-fit: contain; background: #fff; }
.cmp-col-media .ph { color: #8fa8cc; }
.cmp-col-brand {
  margin: 0; font-family: var(--font-display); font-size: .7rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.cmp-col-name { font-family: var(--font-display); font-weight: 700; font-size: .92rem; line-height: 1.3; color: var(--ink); text-decoration: none; }
.cmp-col-name:hover, .cmp-col-name:focus-visible { color: var(--blue-deep); text-decoration: underline; }
.cmp-col-price { margin: 2px 0 0; display: flex; flex-direction: column; }
.cmp-col-price .price { font-size: 1.1rem; }
.cmp-col .btn { margin-top: auto; width: 100%; white-space: normal; padding-inline: 10px; }
.cmp-col-drop {
  position: absolute; top: -6px; right: 4px; width: 26px; height: 26px; border-radius: 8px;
  display: grid; place-items: center; color: var(--muted); text-decoration: none; font-size: 1rem; line-height: 1;
  border: 1px solid transparent;
}
.cmp-col-drop:hover, .cmp-col-drop:focus-visible { color: var(--red); border-color: var(--line); background: var(--white); }

.cmp-table { table-layout: fixed; width: 100%; }
/* Sticky so the row label survives scrolling the columns sideways on a phone:
   "ja / nee / onbekend" three columns to the right means nothing without it. */
.cmp-table th[scope=row] {
  width: var(--cmp-label); font-weight: 650; color: var(--ink-2);
  position: sticky; left: 0; z-index: 1; background: var(--white);
}
.cmp-table tbody tr:hover th[scope=row] { background: var(--tint); }
.cmp-summary { border-bottom: 1px solid var(--line-soft); }

/* One section per spec group. Sections where nothing differs start shut: there is
   nothing in them that can decide anything. `<details>` needs no JavaScript. */
.cmp-sec + .cmp-sec { border-top: 1px solid var(--line-soft); }
.cmp-sec > summary {
  display: flex; align-items: center; gap: 10px; cursor: pointer; list-style: none;
  padding: 12px 14px; background: var(--tint); color: var(--ink);
  font-family: var(--font-display); font-weight: 800; font-size: .95rem;
}
.cmp-sec > summary::-webkit-details-marker { display: none; }
.cmp-sec > summary::before { content: "▸"; color: var(--muted); font-size: .8rem; flex: none; transition: transform var(--dur) var(--ease); }
.cmp-sec[open] > summary::before { transform: rotate(90deg); }
.cmp-sec > summary:hover { background: var(--tint-2); }
.cmp-sec > summary:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
.cmp-sec-hint { margin-left: auto; font-family: var(--font-text); font-weight: 600; }
.cmp-badge {
  display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 7px;
  border-radius: 999px; background: var(--blue-soft); color: var(--blue-deep);
  font-size: .74rem; font-weight: 800; flex: none;
}
.cmp-badge.is-zero { background: var(--tint-2); color: var(--muted); }
.cmp-foot { margin-top: var(--sp-3); }

/* "Toon alleen verschillen" is a checkbox, not a script: the rows that differ are
   already marked server-side, so hiding the rest is a selector. The two summary
   rows stay — they are the verdict, not a specification you look up. */
.cmp-grid:has(#cmp-diff-only:checked) .cmp-sec .cmp-table tbody tr:not(.cmp-differs) { display: none; }
.cmp-grid:has(#cmp-diff-only:checked) .cmp-sec[data-differing="0"] { display: none; }

@media (max-width: 900px) {
  /* Narrow screens scroll the comparison sideways; that scroll container is also
     what stops the header row from sticking, which is fine at this size. */
  .cmp-scroll { overflow-x: auto; }
  .cmp-cols { position: static; }
  .cmp-grid { --cmp-label: 128px; }
  .cmp-col-media img, .cmp-col-media .ph { width: 96px; height: 74px; }
}

.fit-tag { display: inline-block; padding: 3px 10px; border-radius: var(--r-badge); font-size: .78rem; font-weight: 700; background: var(--tint); color: var(--ink-2); }
.fit-yes .fit-tag { background: var(--green-soft); color: var(--green); }
.fit-with_adapter .fit-tag { background: var(--amber-soft); color: #6b3a00; }
.fit-no .fit-tag { background: var(--red-soft); color: var(--red); }
.fit-unknown .fit-tag { color: var(--muted); }

/* ---------- fit finder ---------- */
.finder { display: grid; grid-template-columns: 350px minmax(0,1fr); gap: var(--sp-5); align-items: start; padding-bottom: var(--sp-6); }
@media (max-width: 940px) { .finder { grid-template-columns: 1fr; } }
.finder-panel { background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); padding: var(--sp-4); position: sticky; top: 80px; }
.finder-label { display: block; font-family: var(--font-display); font-weight: 700; margin-bottom: 6px; color: var(--ink); }
#bowl-search { width: 100%; }
.bowl-list { list-style: none; padding: 0; margin: var(--sp-3) 0 0; max-height: 52vh; overflow: auto; }
.bowl-list li a { display: block; padding: 10px 11px; border-radius: var(--r-btn); text-decoration: none; color: var(--ink); }
.bowl-list li a:hover, .bowl-list li[aria-selected=true] a { background: var(--blue-soft); }
.bowl-list strong { display: block; font-weight: 650; font-size: .94rem; }
.finder-results { min-height: 300px; }
.finder-empty { background: var(--white); border: 1px dashed var(--line); border-radius: var(--r); padding: var(--sp-6); }
.fit-list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-2); }
.fit-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; padding: 11px 14px; border: 1px solid var(--line-soft); border-radius: var(--r-btn); background: var(--white); }
.fit-row .src { margin-left: auto; }

/* ---------- calculator ---------- */
.calc-layout { display: grid; grid-template-columns: minmax(0,1.1fr) minmax(300px,.9fr); gap: var(--sp-5); align-items: start; padding-bottom: var(--sp-5); }
@media (max-width: 940px) { .calc-layout { grid-template-columns: 1fr; } }
.calc, .calc-out { background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); padding: var(--sp-5); }
.calc fieldset { border: 0; padding: 0; margin: 0 0 var(--sp-5); }
.calc legend { font-family: var(--font-display); font-weight: 800; margin-bottom: var(--sp-2); color: var(--ink); }
.radio, .check { display: flex; gap: 10px; align-items: flex-start; padding: 8px 0; cursor: pointer; min-height: 40px; }
.calc-out { position: sticky; top: 80px; }
.calc-total { font-family: var(--font-display); font-size: 1.95rem; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; margin: 0 0 var(--sp-3); letter-spacing: -.02em; }
.calc-table th { font-weight: 500; color: var(--ink-2); }

/* ---------- forms ---------- */
input[type=email], input[type=text], input[type=tel], input[type=search], textarea, select {
  padding: 12px 14px; min-height: 46px; border-radius: var(--r-btn);
  border: 1px solid var(--line); background: var(--white); color: var(--ink);
  font: inherit; font-family: var(--font-text); width: 100%;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input:hover, textarea:hover, select:hover { border-color: var(--muted); }
input:focus, textarea:focus, select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); outline: none; }
input:user-invalid { border-color: var(--red); }
textarea { resize: vertical; min-height: 96px; }
.card-form { background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); padding: var(--sp-5); display: grid; gap: var(--sp-4); }
.card-form label { font-weight: 650; color: var(--ink); display: grid; gap: 6px; }
.card-form fieldset { border: 0; padding: 0; margin: 0; }
.card-form legend { font-family: var(--font-display); font-weight: 800; margin-bottom: 4px; color: var(--ink); }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
@media (max-width: 560px) { .two-col { grid-template-columns: 1fr; } }
.stack { display: grid; gap: var(--sp-3); }
.inline-form { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }
.inline-form input[type=email] { flex: 1 1 240px; width: auto; }
.form-note { flex-basis: 100%; margin: 0; }
.form-msg { margin: 6px 0 0; font-weight: 650; }
.form-msg.ok { color: var(--green); }
.form-msg.err { color: var(--red); }
.news-cta, .fit-cta { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: var(--sp-6); align-items: center; }
@media (max-width: 860px) { .news-cta, .fit-cta { grid-template-columns: 1fr; } }
.fit-visual { color: var(--blue); }

/* ---------- prose ---------- */
.article-layout { display: grid; grid-template-columns: minmax(0,1fr) 310px; gap: var(--sp-6); align-items: start; padding-bottom: var(--sp-5); }
@media (max-width: 940px) { .article-layout { grid-template-columns: 1fr; } }
.prose { max-width: 70ch; font-size: 1.03rem; line-height: 1.72; }
.prose h2 { margin-top: 1.9em; padding-top: .35em; border-top: 1px solid var(--line-soft); }
.prose h3 { margin-top: 1.5em; }
.prose ul, .prose ol { padding-left: 1.3em; }
.prose li { margin: .35em 0; }
.prose blockquote {
  margin: 1.5em 0; padding: var(--sp-4) var(--sp-5); background: var(--blue-soft);
  border-left: 3px solid var(--blue); border-radius: 0 var(--r-btn) var(--r-btn) 0; color: var(--ink);
}
.prose blockquote p:last-child { margin-bottom: 0; }
.prose table { margin: 1.5em 0; border: 1px solid var(--line-soft); border-radius: var(--r); overflow: hidden; background: var(--white); display: block; overflow-x: auto; }
.prose code { background: var(--tint); padding: 2px 6px; border-radius: 5px; font-size: .9em; font-family: var(--font-mono); }
.prose img { border-radius: var(--r); }
.aside { display: grid; gap: var(--sp-4); position: sticky; top: 80px; }
.aside-box { background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); padding: var(--sp-4); }
.aside-box h2 { font-size: 1.02rem; }
.faq dt { font-family: var(--font-display); font-weight: 700; color: var(--ink); margin-top: 1.1em; }
.faq dd { margin: .3em 0 0; color: var(--ink-2); }
.ticks { list-style: none; padding: 0; }
.ticks li { padding-left: 28px; position: relative; margin: .55em 0; }
.ticks li::before { content: "✓"; position: absolute; left: 0; color: var(--green); font-weight: 800; }
.steps { padding-left: 1.2em; }
.steps li { margin: .6em 0; }
.kv-inline { display: flex; flex-wrap: wrap; gap: 6px var(--sp-5); margin: var(--sp-3) 0 0; font-size: .92rem; }
.kv-inline dt { color: var(--muted); }
.kv-inline dd { margin: 0 var(--sp-4) 0 0; font-weight: 650; color: var(--ink); }
.guide-list { list-style: none; padding: 0; display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fill, minmax(285px,1fr)); }
.guide-list.big { grid-template-columns: 1fr; }
.guide-list a { display: block; padding: var(--sp-4); background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--r); text-decoration: none; color: inherit; transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.guide-list a:hover { border-color: var(--blue); box-shadow: var(--sh-2); color: inherit; }
.guide-list h2, .guide-list h3 { margin-bottom: .3em; color: var(--ink); }
.guide-list a:hover h2, .guide-list a:hover h3 { color: var(--blue-deep); }
.guide-list p { margin-bottom: .4em; font-size: .92rem; }
.explain-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px,1fr)); gap: var(--sp-5); }
.pill-list { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.pill { display: inline-block; padding: 7px 13px; border-radius: var(--r-badge); background: var(--white); border: 1px solid var(--line-soft); text-decoration: none; color: var(--ink-2); font-size: .875rem; }
.pill:hover { border-color: var(--blue); color: var(--blue-deep); }
.src-list { list-style: none; padding: 0; word-break: break-word; }
.report summary { cursor: pointer; font-weight: 700; padding: 11px 0; color: var(--blue); }
.comfort-headline { font-size: 1.15rem; }
.comfort-headline b { font-size: 1.6rem; }
/* ---------- comfort score: het cijfer op de productpagina ----------
   Het blauwe cijfer linksboven op elke thumb is waar een lezer het cijfer voor
   het eerst ziet; op de detailpagina komt datzelfde chip groot terug (op de
   foto, en als badge boven de uitleg) zodat het herkenbaar hetzelfde getal is.

   Kleur draagt hier geen oordeel dat woorden niet ook dragen: amber betekent op
   deze site "je gaat naar een winkel" en rood betekent "fout", dus een laag
   cijfer krijgt grijs en het woord ernaast doet het werk. */
.badge-score-lg {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  display: inline-flex; flex-direction: column; align-items: center; line-height: 1.05;
  padding: 9px 13px; font-size: 1.45rem; font-weight: 800; font-family: var(--font-display);
  border-radius: var(--r-btn); text-decoration: none; box-shadow: var(--sh-2);
}
.badge-score-lg:hover { background: var(--blue-deep); color: #fff; }
.badge-score-sub { font-size: .58rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; opacity: .85; }
.product-media { position: relative; }

.cs h2 { margin-bottom: .25em; }
.cs-hero { display: flex; align-items: flex-start; gap: var(--sp-4); margin-bottom: var(--sp-5); }
.cs-hero-text { min-width: 0; }
.cs-badge {
  flex: none; margin: 0; display: inline-flex; flex-direction: column; align-items: center;
  justify-content: center; min-width: 84px; padding: 10px 14px; border-radius: var(--r);
  background: var(--blue); color: #fff; font-family: var(--font-display); font-weight: 800;
  font-size: 2rem; line-height: 1.05;
}
.cs-badge span { font-size: .72rem; font-weight: 700; letter-spacing: .06em; opacity: .85; }
.cs-badge-empty { background: var(--tint-2); color: var(--muted); }
.cs-line { font-size: 1.06rem; color: var(--ink-2); max-width: 62ch; margin-bottom: .5em; }

/* de balk: <progress> in plaats van een div, want de CSP verbiedt de inline
   width die een div-balk nodig heeft, en dit leest een screenreader vanzelf */
.cs-bar {
  -webkit-appearance: none; appearance: none; display: block; width: 100%; height: 8px;
  border: 0; border-radius: 5px; background: var(--tint-2); color: var(--blue);
}
.cs-bar::-webkit-progress-bar { background: var(--tint-2); border-radius: 5px; }
.cs-bar::-webkit-progress-value { background: var(--blue); border-radius: 5px; }
.cs-bar::-moz-progress-bar { background: var(--blue); border-radius: 5px; }
.cs-bar.is-top { color: var(--green); }
.cs-bar.is-top::-webkit-progress-value { background: var(--green); }
.cs-bar.is-top::-moz-progress-bar { background: var(--green); }
.cs-bar.is-matig, .cs-bar.is-zwak { color: var(--muted); }
.cs-bar.is-matig::-webkit-progress-value,
.cs-bar.is-zwak::-webkit-progress-value { background: var(--muted); }
.cs-bar.is-matig::-moz-progress-bar,
.cs-bar.is-zwak::-moz-progress-bar { background: var(--muted); }

.cs-chip {
  display: inline-block; padding: 3px 10px; border-radius: var(--r-badge); font-size: .8rem;
  font-weight: 700; background: var(--blue-soft); color: var(--blue-deep); white-space: nowrap;
}
.cs-chip.is-top { background: var(--green-soft); color: var(--green); }
.cs-chip.is-matig, .cs-chip.is-zwak { background: var(--tint-2); color: var(--ink-2); }

/* de vier onderdelen, elk een regel: naam, balk, oordeel in woorden, cijfer */
.cs-rows { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-3); }
.cs-row {
  display: grid; grid-template-columns: minmax(120px, 1.1fr) minmax(90px, 2fr) auto auto;
  align-items: center; gap: var(--sp-3); padding: 10px 0; border-top: 1px solid var(--line-soft);
}
.cs-row:first-child { border-top: 0; }
.cs-row-label { font-weight: 650; color: var(--ink); }
.cs-row-verdict { font-size: .9rem; color: var(--ink-2); }
.cs-row-mark { font-family: var(--font-display); font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  .cs-row { grid-template-columns: 1fr auto; }
  .cs-row .cs-bar { grid-column: 1 / -1; order: 3; }
  .cs-row-verdict { order: 4; }
}

.cs-more { margin-top: var(--sp-5); }
.cs-more summary { cursor: pointer; font-weight: 700; padding: 11px 0; color: var(--blue); }
.cs-more .spec-table { margin-top: var(--sp-2); }
.cs-block { margin-top: var(--sp-4); }
.cs-block h4 { margin-bottom: .35em; }

/* tabellen binnen de comfortsectie en op /comfortscore */
.cg-pts, .cg-wt { white-space: nowrap; }
/* een voetnoot onder een eigenschapsnaam is een voetnoot, geen kop: de rijkop
   is 650-vet, waardoor de "leeg is niet nee"-notities stonden te schreeuwen */
.spec-table th[scope=row] .small { font-weight: 400; }
.comfort-sum { margin-top: var(--sp-4); font-variant-numeric: tabular-nums; color: var(--ink-2); }
.comfort-sum b { color: var(--ink); font-size: 1.1rem; }
.score-group { margin-top: var(--sp-6); }
.score-weight { font-size: .82rem; font-weight: 650; color: var(--blue); background: var(--blue-soft); border-radius: var(--r-badge); padding: 3px 10px; margin-left: 6px; white-space: nowrap; vertical-align: middle; }
.spec-table td.thin { color: var(--amber-ring); font-weight: 650; }
.nowrap { white-space: nowrap; }
.crosses { list-style: none; padding: 0; }
.crosses li { padding-left: 28px; position: relative; margin: .55em 0; }
.crosses li::before { content: "✗"; position: absolute; left: 0; color: var(--muted); font-weight: 800; }
/* ---------- compare picker ----------
   Was a bare <select multiple>: unstyled, impossible to use on a phone (Ctrl-click)
   and wired to the wrong parameter. A checkbox list matches the "vergelijk"-toggle
   used everywhere else, and submits on its own without JavaScript. */
.picker-block { margin-top: var(--sp-6); }
.picker-block h2 { margin-bottom: .2em; }
.picker { margin-top: var(--sp-4); max-width: 620px; }
.picker-search { margin: 0 0 var(--sp-3); }
.picker-search input {
  width: 100%; padding: 11px 14px; border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--white); font: inherit; color: var(--ink); min-height: 44px;
}
.picker-list {
  list-style: none; margin: 0; padding: 0; max-height: 420px; overflow-y: auto;
  border: 1px solid var(--line-soft); border-radius: var(--r); background: var(--white);
  scrollbar-width: thin;
}
.picker-list li + li { border-top: 1px solid var(--line-soft); }
.picker-list label {
  display: flex; align-items: center; gap: var(--sp-3); padding: 10px 14px;
  cursor: pointer; min-height: 46px;
}
.picker-list label:hover { background: var(--tint); }
.picker-label { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.picker-brand {
  font-family: var(--font-display); font-size: .7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.picker-name { font-size: .95rem; color: var(--ink); }
.picker-price { font-size: .92rem; color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.picker-empty { color: var(--muted); padding: var(--sp-3) 0 0; }
.picker-actions { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-3) 0 0; flex-wrap: wrap; }
.meta { color: var(--muted); }

/* ---------- compare tray ---------- */
.compare-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; background: var(--white);
  border-top: 1px solid var(--line); box-shadow: 0 -8px 24px -18px rgba(11,27,51,.6);
  padding: 11px 0 calc(11px + env(safe-area-inset-bottom, 0px));
}
.compare-inner { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.compare-label { font-weight: 650; color: var(--ink); margin: 0; white-space: nowrap; }
.compare-items { list-style: none; display: flex; gap: 8px; padding: 0; margin: 0; flex: 1 1 auto; overflow-x: auto; }
/* The photo, not just the name: you recognise these models by their shape long
   before you recognise "AquaClean Tuma Comfort wandcloset". */
.compare-items li {
  font-size: .8rem; padding: 4px 4px 4px 4px; border-radius: var(--r-badge);
  background: var(--tint); white-space: nowrap; color: var(--ink-2);
  display: inline-flex; align-items: center; gap: 8px;
}
.compare-thumb {
  width: 40px; height: 34px; object-fit: contain; flex: none;
  background: #fff; border: 1px solid var(--line-soft); border-radius: var(--r-badge);
}
.compare-name { max-width: 15ch; overflow: hidden; text-overflow: ellipsis; }
/* The card you ticked is usually on another page by the time you want it gone. */
.compare-drop {
  border: 0; background: none; color: var(--muted); cursor: pointer; line-height: 1;
  font-size: 1rem; padding: 0 6px; border-radius: var(--r-badge); min-height: 24px;
}
.compare-drop:hover, .compare-drop:focus-visible { color: var(--ink); background: rgba(11,27,51,.07); }
.btn.is-disabled { opacity: .55; cursor: not-allowed; }

/* The tray is fixed to the bottom on every page now, so give it room and stack
   it above the product page's buy bar instead of on top of it. --compare-h is
   the tray's measured height, set by app.js (0 when the tray is closed). */
body.has-compare-bar { padding-bottom: var(--compare-h, 0px); }
@media (max-width: 940px) {
  .buybar { bottom: var(--compare-h, 0px); }
  body:has(.buybar).has-compare-bar { padding-bottom: calc(76px + var(--compare-h, 0px)); }
}

/* A short message where alert() used to stop everything ("de tray is vol"). */
.tv-toast {
  position: fixed; left: 50%; bottom: calc(var(--compare-h, 0px) + 18px); z-index: 60;
  transform: translate(-50%, 10px); opacity: 0; pointer-events: none;
  max-width: min(92vw, 460px); margin: 0; padding: 11px 18px; border-radius: var(--r-btn);
  background: var(--ink); color: #fff; font-weight: 600; font-size: .9rem; text-align: center;
  box-shadow: 0 14px 30px -14px rgba(11,27,51,.7);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tv-toast.is-on { opacity: 1; transform: translate(-50%, 0); }

/* ---------- comparison window ----------
   "Vergelijk" used to take you off the catalogue: you compared, went back, and
   landed at the top of the list again. The tray now opens the same comparison over
   the page instead, so your place in the list survives it. It is a real <dialog>,
   so Escape, the backdrop and the focus trap come from the browser. Without
   JavaScript the button stays what it is in the markup: a link to /vergelijk. */
.cmp-dialog {
  width: min(1120px, 96vw); max-height: 92vh; padding: 0; border: 0;
  border-radius: 16px; background: var(--tint); color: var(--ink-2);
  box-shadow: 0 30px 80px -20px rgba(11,27,51,.55);
  overflow: hidden; display: flex; flex-direction: column;
}
.cmp-dialog::backdrop { background: rgba(11,27,51,.55); }
.cmp-dialog-head {
  display: flex; align-items: center; gap: var(--sp-3); flex: none;
  padding: 14px 18px; background: var(--white); border-bottom: 1px solid var(--line-soft);
}
.cmp-dialog-head h2 { margin: 0; font-size: 1.15rem; }
.cmp-dialog-close {
  margin-left: auto; width: 38px; height: 38px; flex: none; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r-btn); background: var(--white);
  color: var(--muted); font-size: 1.05rem; line-height: 1;
}
.cmp-dialog-close:hover, .cmp-dialog-close:focus-visible { color: var(--ink); border-color: var(--muted); }
.cmp-dialog-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 16px 18px 20px; }
.cmp-dialog-body .cmp-cols { top: 0; }
.cmp-dialog-loading { padding: 30px 0; text-align: center; color: var(--muted); }
@media (max-width: 700px) {
  .cmp-dialog { width: 100vw; max-width: 100vw; max-height: 100vh; height: 100vh; border-radius: 0; }
}

/* ---------- image viewer ----------
   A photo used to open in a new tab, dropping the reader on a bare image file.
   The overlay is created by app.js on first use; without JS the links are still
   plain links to the file. */
/* `hidden` has to beat the `display` these rules set. The browser's own
   `[hidden] { display: none }` loses to any author rule of equal specificity, so
   without this the overlay would open once and never shut. */
.viewer[hidden], .viewer [hidden] { display: none !important; }
.viewer {
  position: fixed; inset: 0; z-index: 200; display: grid;
  grid-template-columns: auto 1fr auto; align-items: center; gap: var(--sp-2);
  padding: clamp(12px, 4vw, 40px);
  background: rgba(11, 27, 51, .82);
  backdrop-filter: blur(2px);
}
.viewer-figure {
  margin: 0; display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  min-width: 0; max-height: 100%;
}
.viewer-figure img {
  max-width: 100%; max-height: calc(100vh - 150px); width: auto; height: auto;
  object-fit: contain; background: #fff; border-radius: var(--r); box-shadow: var(--sh-2);
}
.viewer-figure figcaption {
  color: #fff; font-size: .9rem; text-align: center; max-width: 60ch;
  text-wrap: balance;
}
.viewer-figure .muted { color: rgba(255,255,255,.7); }
.viewer button {
  border: 0; background: rgba(255,255,255,.14); color: #fff; cursor: pointer;
  border-radius: 50%; width: 46px; height: 46px; font-size: 1.6rem; line-height: 1;
  display: grid; place-items: center;
  transition: background var(--dur) var(--ease);
}
.viewer button:hover, .viewer button:focus-visible { background: rgba(255,255,255,.28); }
.viewer button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.viewer-close { position: absolute; top: clamp(12px, 4vw, 40px); right: clamp(12px, 4vw, 40px); }
/* The page behind must not scroll under the overlay. */
body.viewer-open { overflow: hidden; }
@media (max-width: 640px) {
  .viewer { grid-template-columns: 1fr; grid-template-rows: 1fr auto; }
  .viewer-nav { position: absolute; bottom: clamp(12px, 4vw, 40px); }
  .viewer-prev { left: clamp(12px, 4vw, 40px); }
  .viewer-next { right: clamp(12px, 4vw, 40px); }
}
/* Say the photo can be enlarged, rather than leaving it to be discovered. */
.media-main { cursor: zoom-in; display: block; }
.media-gallery a { cursor: zoom-in; display: block; }

/* ---------- footer ---------- */
.site-footer { background: var(--white); border-top: 1px solid var(--line-soft); margin-top: var(--sp-7); padding: var(--sp-6) 0 var(--sp-5); }
.footer-grid { display: grid; grid-template-columns: 1.5fr repeat(3, 1fr); gap: var(--sp-5); }
@media (max-width: 860px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .footer-grid { grid-template-columns: 1fr; } }
.foot-brand { font-family: var(--font-display); font-size: 1.06rem; font-weight: 700; color: var(--ink); margin-bottom: .35em; }
.foot-h { font-family: var(--font-display); font-size: .76rem; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); margin-bottom: .7em; }
.foot-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 7px; }
.foot-list a { color: var(--ink-2); text-decoration: none; font-size: .92rem; }
.foot-list a:hover { color: var(--blue); text-decoration: underline; }
.disclosure { margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--line-soft); }
.disclosure p { max-width: 92ch; }

@media print {
  .site-header, .site-footer, .compare-bar, .buybar, .filters, .aside, .btn,
  .viewer { display: none !important; }
  body { background: #fff; color: #000; }
}

/* ---------- consent banner ----------
   Only ever rendered when the page carries a measurement ID, i.e. in production.
   A corner card rather than a blocking modal: the reader can ignore it and read
   on, because nothing is loaded or stored until they choose. */
.consent {
  position: fixed; z-index: 60; right: var(--sp-4); bottom: var(--sp-4);
  width: min(400px, calc(100vw - 2 * var(--sp-4)));
  background: var(--white); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: 0 18px 44px -20px rgba(11,27,51,.5); padding: var(--sp-4);
  font-size: .9rem; line-height: 1.5;
}
.consent h2 {
  font-size: 1rem; margin: 0 0 6px; color: var(--ink);
}
.consent p { margin: 0 0 var(--sp-3); }
.consent-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.consent-actions .btn { flex: 1 1 auto; }
@media (max-width: 560px) {
  .consent { right: var(--sp-2); left: var(--sp-2); bottom: var(--sp-2); width: auto; }
}
