/* GENERATED by tools/build.py (house style 2026-11-11) from css/fonts.css, css/tokens.css, css/base.css, css/components.css.
   Edit those files, then run python3 tools/build.py. */

/* ===== css/fonts.css ===== */
/* =============================================================================
   Geexer house style: the font
   Archivo variable (weight 100 to 900, width 62 to 125 percent), self-hosted as one latin WOFF2,
   SIL Open Font License 1.1 (fonts/README.md). The path is relative to this stylesheet: keep
   css/ and fonts/ side by side, or change the url() when you copy the files into a project.
   Over http(s) this is all a page needs. A page opened straight from disk (file://) cannot load a
   font file in Chrome (CORS); fonts/archivo-embedded.css carries the same face as a data URI for
   that case (the showcase uses it).
   ============================================================================= */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("../fonts/archivo-normal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Local stand-in during the swap period, metrics nudged toward Archivo to limit layout shift. */
@font-face {
  font-family: "Archivo Fallback";
  src: local("Arial"), local("Helvetica"), local("Liberation Sans");
  size-adjust: 103%;
  ascent-override: 90%;
  descent-override: 24%;
  line-gap-override: 0%;
}

/* ===== css/tokens.css ===== */
/* =============================================================================
   Geexer house style: tokens
   The one place where the house style's values live. Every other file of the kit (base.css,
   components.css, the showcase) takes its colours, sizes and timings from here, and so must every
   project that builds on it. No hex value outside this file, except inside the SVG data URIs of
   the lattice tiles below and a client's own logo ground (HOUSE-STYLE.md, Colour).

   Source: geexer.nl (geexer-website03, site-src/styles/01-tokens.css), asset version 26,
   2026-11-10. The site names its colours in Dutch (--zwart, --wit, --blauw, --geel, --grijs,
   --grijs-licht, --fact-bg, --rood); the kit uses the English names below, with the same values.
   The mapping table is in HOUSE-STYLE.md, Tokens.

   Worlds. A world is a set of semantic tokens (--fg, --bg-theme, --line, --accent, ...). The dark
   world is the default on :root; [data-theme="light"] and [data-theme="blue"] redefine it. The
   attribute works on <html> (the page's world) and on any element inside it (a band, the footer,
   a panel), because custom properties inherit: a black footer on a white page is simply
   <footer data-theme="dark">. Components read only the semantic tokens, so they need no rules of
   their own per world. Nothing follows prefers-color-scheme: the world is chosen per page.
   ============================================================================= */
:root {
  /* ---- Primitives: four colours, two greys for small text, navy, red for errors only ---- */
  --black: #000000;
  --white: #FFFFFF;
  --blue: #1E90FF;          /* Geexer blue, fixed (Michael, 2026-09-19) */
  --yellow: #FFD600;        /* the primary button, the status pulse and a warning in data, on black only */
  --slate: #8A93A6;         /* muted small text on black (6.8:1) */
  --slate-dark: #5B6472;    /* muted small text on white (6.0:1) */
  --navy: #0D1526;          /* inner face of the hexagon fact tiles, bot chat bubbles, empty logo tiles */
  --red: #FF5C5C;           /* outage, invalid input and a critical value, on black (6.9:1) */
  --red-dark: #C62828;      /* the same on white (5.6:1) */

  /* ---- The dark world (default): white on black ---- */
  --fg: var(--white);
  --fg-rgb: 255, 255, 255;
  --fg-muted: var(--slate);
  --line: rgba(255, 255, 255, .15);          /* 1px rules between rows */
  --bg-theme: var(--black);
  --bg-rgb: 0, 0, 0;
  --accent: var(--blue);                     /* graphic accent: bullets, underlines, focus, wipes */
  --accent-ink: var(--blue);                 /* accent as small text (allowed on black only) */
  --focus: var(--blue);
  --red-text: var(--red);
  --link-hover: var(--blue);                 /* running-text link colour on hover */
  --link-hover-line: 1px;
  --btn-primary-bg: var(--yellow);           /* yellow only on black: the light and blue worlds */
  --btn-primary-fg: var(--black);            /* turn the primary button into the dark one */
  --btn-primary-wipe: var(--white);
  --btn-primary-wipe-fg: var(--black);
  --pulse-display: block;                    /* the yellow status pulse: black worlds only */
  --status-ok: var(--blue);                  /* status colours: the dot and a meter's fill when all is well */
  --status-warning: var(--yellow);           /* a value that gets tight: yellow on black only */
  --status-critical: var(--red);             /* a limit reached or nearly reached */
  --marquee-bg: var(--blue);
  --marquee-fg: var(--black);
  --logo-filter: none;
  --fact-face: var(--navy);
  --fact-edge: rgba(255, 255, 255, .22);
  --fact-ink: var(--white);
  --fact-label: var(--slate);
  --fact-hover-face: var(--blue);
  --fact-hover-ink: var(--black);
  --rl-alpha: .55;                           /* pool peak opacity */
  --comb-world: var(--comb-blue);            /* the lattice of the world floor */
  --theme-color: #000000;                    /* for <meta name="theme-color"> (copy it by hand) */

  /* ---- Layout ---- */
  --gutter: clamp(16px, 4vw, 64px);          /* the one side margin; no max-width container */
  --section-y: clamp(3.5rem, 7vw, 6rem);     /* above and below a section: 56px phone, 96px from 1372px */
  --head-gap: clamp(1.5rem, 3.4vw, 3rem);    /* between a section title and what follows */
  --col-gap: clamp(2rem, 6vw, 8rem);         /* the one column gap: every two-column block is two equal halves */
  --header-h: 4.85rem;                       /* 1.05rem padding around a 44px row */
  --measure-lead: 46ch;
  --measure-body: 62ch;
  --measure-legal: 74ch;

  /* ---- Type scale (one ladder; phones get their own below) ---- */
  --fs-h1: clamp(2rem, min(6vw, 11vh), 6.5rem);       /* hero headline, up to four lines */
  --fs-h1-long: clamp(2rem, min(5.2vw, 9vh), 5.5rem); /* hero headline of five or six lines */
  --fs-h2: calc(var(--fs-h1-long) * .8);              /* section titles */
  --fs-h3: clamp(1.375rem, 2.75vw, 2.5rem);           /* display titles inside a section */
  --fs-sub: clamp(1.25rem, 1.8vw, 1.6rem);            /* subtitle: sentence case, 800, 118 percent */
  --fs-lead: clamp(1.125rem, .5rem + 1.25vw, 1.75rem);
  --fs-body: 1.0625rem;                               /* 17px */
  --fs-figure: clamp(1.5rem, 2.4vw, 2.25rem);         /* prices, uptime, fact values */
  --fs-fact: .9375rem;                                /* 15px: text that carries a fact or a price */
  --fs-ui: .8125rem;                                  /* 13px: interface text only */

  /* ---- Type roles: one family in both roles (Archivo variable) ---- */
  --font-body: "Archivo", "Archivo Fallback", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: var(--font-body);
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;   /* code and raw data only */

  /* ---- Shape ---- */
  --skew: -12deg;                                                   /* the slash: the only lean */
  --hex: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);  /* flat-top hexagon */
  --slash-shape: polygon(22% 0, 100% 0, 78% 100%, 0 100%);          /* the logo's parallelogram */
  --crop-shape: polygon(14.2% 0, 100% 0, 85.8% 100%, 0 100%);       /* 12 degree crop on a 3:2 box */
  --crop-left: polygon(14.2% 0, 100% 0, 100% 100%, 0 100%);         /* a photo of a person: lean on the left only */

  /* ---- Motion ---- */
  --ease-out: cubic-bezier(.16, 1, .3, 1);     /* arrivals */
  --ease-slash: cubic-bezier(.7, 0, .2, 1);    /* wipes along the slash */
  --ease-io: cubic-bezier(.65, 0, .35, 1);     /* small loops (the scroll dash) */

  /* ---- Layers ---- */
  --z-world: 0;
  --z-content: 1;
  --z-chat: 50;
  --z-menu: 60;
  --z-header: 70;
  --z-preloader: 100;
  --z-skip: 300;

  /* ---- Fixed chat launcher (only when a page has one) ---- */
  --launcher-size: 58px;
  --launcher-inset: clamp(16px, 4vw, 40px);
  --launcher-room: 0px;   /* rows that end in a value keep this strip free; geexer.nl sets it under 1280px */

  /* ---- The lattice: flat-top hexagons, R = 70 (140px wide), tile 210 x 121 ---- */
  --comb-black: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='210' height='121' viewBox='0 0 210 121'%3E%3Cpath d='M140 0L175 60.5L140 121M70 0L35 60.5L70 121M70 0H140M70 121H140M175 60.5H245M-35 60.5H35' fill='none' stroke='%23000' stroke-opacity='.22' stroke-width='4' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E");
  --comb-white: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='210' height='121' viewBox='0 0 210 121'%3E%3Cpath d='M140 0L175 60.5L140 121M70 0L35 60.5L70 121M70 0H140M70 121H140M175 60.5H245M-35 60.5H35' fill='none' stroke='%23fff' stroke-opacity='.07' stroke-width='2' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E");
  --comb-blue: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='210' height='121' viewBox='0 0 210 121'%3E%3Cpath d='M140 0L175 60.5L140 121M70 0L35 60.5L70 121M70 0H140M70 121H140M175 60.5H245M-35 60.5H35' fill='none' stroke='%231E90FF' stroke-opacity='.55' stroke-width='3' stroke-linecap='square' stroke-linejoin='miter'/%3E%3C/svg%3E");
  --comb-size: 210px 121px;
}

/* Phones (under 640px): the headline drops to 100 percent width (base.css) and is sized on the
   column: the longest line of the geexer.nl home headline, "IT die gewoon werkt.", measures
   12.44em at that width, so 1/12.6 of the column keeps a 17-character line on one line at every
   phone width. Section titles stay a step under it, display titles at 18px. */
@media (max-width: 639.98px) {
  :root {
    --header-h: 4.55rem;
    --fs-h1: min(calc((100vw - 2 * var(--gutter)) / 12.6), 2rem);
    --fs-h1-long: var(--fs-h1);
    --fs-h2: min(6.15vw, 1.5rem);
    --fs-h3: 1.125rem;
    --col-gap: 1rem;
  }
}

/* ---- Page-level browser surfaces: text selection and the scrollbar follow the PAGE's world (the
   attribute on <html>), not a band's. Yellow on the black page (Michael, 2026-11-10: "text markeren
   op de homepage kleur moet de gele accentkleur worden, evenals de scroll balk"), Geexer blue on
   white pages, their black footer included. These tokens are set on <html> only, so a band with
   its own data-theme never changes them. ---- */
:root,
html[data-theme="dark"] {
  --selection-bg: var(--yellow);
  --selection-fg: var(--black);
  --scrollbar: var(--yellow);
}
html[data-theme="light"] {
  --selection-bg: var(--blue);
  --selection-fg: var(--black);
  --scrollbar: var(--blue);
}
html[data-theme="blue"] {          /* unused world: black, like every other accent on blue */
  --selection-bg: var(--black);
  --selection-fg: var(--blue);
  --scrollbar: var(--black);
}

/* ---- The light world: black on white (every page of geexer.nl except the home) ---- */
[data-theme="light"] {
  --fg: var(--black);
  --fg-rgb: 0, 0, 0;
  --fg-muted: var(--slate-dark);
  --line: rgba(0, 0, 0, .15);
  --bg-theme: var(--white);
  --bg-rgb: 255, 255, 255;
  --accent: var(--blue);
  --accent-ink: var(--black);                /* blue on white is 3.2:1: never small text */
  --focus: var(--blue);
  --red-text: var(--red-dark);
  --link-hover: var(--black);                /* the text stays black, the underline turns blue */
  --link-hover-line: 2px;
  --btn-primary-bg: var(--black);
  --btn-primary-fg: var(--white);
  --btn-primary-wipe: var(--blue);
  --btn-primary-wipe-fg: var(--black);
  --pulse-display: none;
  --status-ok: var(--blue);
  --status-warning: var(--black);            /* yellow on white is 1.4:1: the foreground, the word says it */
  --status-critical: var(--red-dark);
  --marquee-bg: var(--blue);
  --marquee-fg: var(--black);
  --logo-filter: none;
  --fact-face: var(--white);
  --fact-edge: rgba(0, 0, 0, .3);
  --fact-ink: var(--black);
  --fact-label: var(--slate-dark);
  --fact-hover-face: var(--blue);
  --fact-hover-ink: var(--black);
  --rl-alpha: .62;
  --comb-world: var(--comb-black);
  --theme-color: #FFFFFF;
}

/* ---- The dark world again, for a black element inside a light or blue page (the footer, the
   menu, a closing band): the same values as :root ---- */
[data-theme="dark"] {
  --fg: var(--white);
  --fg-rgb: 255, 255, 255;
  --fg-muted: var(--slate);
  --line: rgba(255, 255, 255, .15);
  --bg-theme: var(--black);
  --bg-rgb: 0, 0, 0;
  --accent: var(--blue);
  --accent-ink: var(--blue);
  --focus: var(--blue);
  --red-text: var(--red);
  --link-hover: var(--blue);
  --link-hover-line: 1px;
  --btn-primary-bg: var(--yellow);
  --btn-primary-fg: var(--black);
  --btn-primary-wipe: var(--white);
  --btn-primary-wipe-fg: var(--black);
  --pulse-display: block;
  --status-ok: var(--blue);
  --status-warning: var(--yellow);
  --status-critical: var(--red);
  --marquee-bg: var(--blue);
  --marquee-fg: var(--black);
  --logo-filter: none;
  --fact-face: var(--navy);
  --fact-edge: rgba(255, 255, 255, .22);
  --fact-ink: var(--white);
  --fact-label: var(--slate);
  --fact-hover-face: var(--blue);
  --fact-hover-ink: var(--black);
  --rl-alpha: .55;
  --comb-world: var(--comb-blue);
  --theme-color: #000000;
}

/* ---- The blue world: black on Geexer blue. Defined, not yet used by any shipped page (it was
   meant for a werkwijze page of geexer.nl). Known gap: its muted text (black at .62) is 3.7:1,
   under the 4.5:1 for small text; settle that with Michael before the first page uses it. ---- */
[data-theme="blue"] {
  --fg: var(--black);
  --fg-rgb: 0, 0, 0;
  --fg-muted: rgba(0, 0, 0, .62);
  --line: rgba(0, 0, 0, .2);
  --bg-theme: var(--blue);
  --bg-rgb: 30, 144, 255;
  --accent: var(--black);                    /* on blue every blue accent turns black */
  --accent-ink: var(--black);
  --focus: var(--black);
  --red-text: var(--black);                  /* errors on blue: black at weight 800 (components.css) */
  --link-hover: var(--black);
  --link-hover-line: 2px;
  --btn-primary-bg: var(--black);
  --btn-primary-fg: var(--white);
  --btn-primary-wipe: var(--blue);
  --btn-primary-wipe-fg: var(--black);
  --pulse-display: none;
  --status-ok: var(--black);
  --status-warning: var(--black);            /* on blue every signal is black: the word carries it */
  --status-critical: var(--black);
  --marquee-bg: var(--black);
  --marquee-fg: var(--blue);
  --logo-filter: brightness(0);              /* the blue wordmark turns black on blue */
  --fact-face: var(--navy);
  --fact-edge: rgba(0, 0, 0, .22);
  --fact-ink: var(--white);
  --fact-label: var(--slate);
  --fact-hover-face: var(--black);
  --fact-hover-ink: var(--white);
  --rl-alpha: .68;
  --comb-world: var(--comb-black);
  --theme-color: #1E90FF;
}

/* ===== css/base.css ===== */
/* =============================================================================
   Geexer house style: base
   Reset, page, browser surfaces (selection, scrollbar, focus), the type system and a few
   utilities. Needs tokens.css (and fonts.css for the face). Source: geexer.nl,
   site-src/styles/03-base.css, 04-type.css and 07-skip-link.css, asset version 26.
   ============================================================================= */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  background: var(--bg-theme);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
  scrollbar-color: var(--scrollbar) var(--bg-theme);
  scrollbar-width: thin;
  overflow-x: hidden;              /* fallback (Safari < 16): never a horizontal scrollbar */
  overflow-x: clip;
}
body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg-theme);
  color: var(--fg);
  font-family: var(--font-body);
  font-weight: 400;
  font-stretch: 100%;
  font-size: var(--fs-body);
  line-height: 1.5;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
body.nav-open { overflow: hidden; }

/* An element with its own world paints it (the footer, a band, a panel). The header and the
   menu paint themselves (components.css). Zero specificity, so any component may override it. */
:where(body [data-theme]:not(#site-header)) { background-color: var(--bg-theme); color: var(--fg); }

::selection { background: var(--selection-bg); color: var(--selection-fg); }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-theme); }
::-webkit-scrollbar-thumb { background: var(--scrollbar); }

h1, h2, h3, h4, p, ul, ol, dl, dd, figure, address, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, canvas { display: block; }
img { max-width: 100%; height: auto; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
button { cursor: pointer; }
address, em, i, cite { font-style: normal; }   /* there is no italic face: never synthesise one */
[hidden] { display: none; }
template { display: none; }

:focus { outline: none; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
#main:focus { outline: none; }

main { display: block; position: relative; }
/* Anchors and keyboard focus never land under the fixed header. :where() keeps it at zero
   specificity so component margins win. */
section[id], [id].anchor { scroll-margin-top: calc(var(--header-h) + 1rem); }
:where(a[href], button, input, select, textarea, summary, [tabindex]) {
  scroll-margin-top: calc(var(--header-h) + 1rem);
  scroll-margin-bottom: 1rem;
}
/* No browser tap square: every control has its own pressed, hover and focus look. */
:where(a, button, [role="radio"], summary, label) { -webkit-tap-highlight-color: transparent; }

p, li, dd { color: rgba(var(--fg-rgb), .86); }
h1, h2, h3, h4, strong { color: var(--fg); }
p, li, dt, dd { text-wrap: pretty; }   /* no one-word last lines */

/* -----------------------------------------------------------------------------
   Type. The display voice is the only place capitals come from: write headlines in sentence
   case in the HTML, the class sets them in capitals.
   ----------------------------------------------------------------------------- */
.display,
.hero-title,
.section-title,
.title,
.display-xl,
.error-code {
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: 125%;
  text-transform: uppercase;
  line-height: .86;
  letter-spacing: -.02em;
  font-kerning: normal;
  overflow-wrap: normal;     /* a display word is never broken mid-word */
}
.hero-title { font-size: var(--fs-h1); color: var(--fg); }
.hero-title:has(.line:nth-child(5)) { font-size: var(--fs-h1-long); }   /* five or six lines */
.hero-title--xl { font-size: clamp(2.4rem, min(11vw, 22vh), 15rem); }  /* at most two lines of 11 characters */
.section-title { font-size: var(--fs-h2); margin: 0 0 var(--head-gap); color: var(--fg); }
.title { font-size: var(--fs-h3); color: var(--fg); }
.display-xl { font-size: clamp(2rem, 9.5vw, 12rem); }
@media (max-width: 639.98px) {
  .hero-title { font-stretch: 100%; }   /* phones: normal width keeps the authored lines */
}

/* Subtitle: sentence case, the heavy voice at 118 percent (table, tier, contact, legal headings) */
.subtitle {
  font-weight: 800;
  font-stretch: 118%;
  font-size: var(--fs-sub);
  line-height: 1.1;
  letter-spacing: -.01em;
  color: var(--fg);
}
/* Label: interface text only (navigation, pills, footer, switches): 13px, never uppercase */
.small-ui {
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-ui);
  line-height: 1.4;
  letter-spacing: 0;
}
/* Caption: small text that carries a fact or a price: never under 15px */
.caption {
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-fact);
  line-height: 1.4;
  letter-spacing: 0;
}
/* Figure: prices, uptime, fact values; tabular. .cur sets the euro sign smaller. */
.figure {
  display: inline-flex;
  align-items: baseline;
  gap: .35em;
  font-weight: 800;
  font-stretch: 118%;
  font-size: var(--fs-figure);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -.01em;
  white-space: nowrap;
  color: var(--fg);
}
.figure .cur { font-size: .6em; font-weight: 700; }
.figure .per { font-weight: 600; font-stretch: 112%; font-size: var(--fs-fact); letter-spacing: 0; color: var(--fg-muted); }

.lead {
  max-width: var(--measure-lead);
  font-size: var(--fs-lead);
  line-height: 1.4;
  text-wrap: pretty;
  color: rgba(var(--fg-rgb), .86);
}
.copy { max-width: var(--measure-body); }
.copy p + p, .prose p + p { margin-top: 1em; }
.muted { color: var(--fg-muted); }
.tabular { font-variant-numeric: tabular-nums; }
code, kbd, samp, pre { font-family: var(--font-mono); font-size: .92em; }

/* Line masks: a headline is split into .line > .line-inner so each line can rise from a mask
   (components.css, Motion). Lines of at most 17 characters, spaces included. */
.line {
  display: block;
  clip-path: inset(-.22em 0 0 0);   /* headroom for accented capitals (the É of ÉÉN) */
  padding-bottom: .06em;
  margin-bottom: -.06em;
}
.line-inner { display: block; }
/* A line with an accented capital under another line: the accent reaches .89em over the
   baseline and the line box is .86em, so it touches the line above. Mark such a line
   .line--accent (geexer.nl's build.py does it automatically). */
.line--accent:not(:first-child) { margin-top: .1em; }

/* -----------------------------------------------------------------------------
   Skip link, visually hidden text, spacing utilities
   ----------------------------------------------------------------------------- */
.skip-link {
  position: fixed;
  top: 1rem;
  left: 1rem;
  z-index: var(--z-skip);
  padding: .9rem 1.3rem;
  background: var(--blue);
  color: var(--black);
  font-weight: 700;
  font-stretch: 112%;
  font-size: var(--fs-fact);
  text-decoration: none;
}
.skip-link:not(:focus-visible),
.visually-hidden {
  position: absolute !important;
  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;
}
/* For the one-off gaps where two components meet. A Content-Security-Policy without
   'unsafe-inline' allows no style attributes; these replace them. */
.mt-0 { margin-top: 0 !important; }
.mt-1-5 { margin-top: 1.5rem !important; }
.mt-2 { margin-top: 2rem !important; }
.mt-3 { margin-top: 3rem !important; }
.mt-4 { margin-top: 4rem !important; }
.mt-section { margin-top: clamp(3rem, 6vw, 5rem) !important; }

/* ===== css/components.css ===== */
/* =============================================================================
   Geexer house style: components
   Every component of geexer.nl that another project can reuse, ported from the site's
   site-src/styles/ (asset version 26) with the site's class names, so markup and rules can be
   moved between the two. Three differences from the site, all on purpose:
   - colours come from the semantic world tokens (tokens.css) instead of per-world override
     rules, so a component also works inside a band that sets its own data-theme;
   - the hero's world sits inside the hero (.hero > .world) instead of at the top of <main>;
   - the status pulse runs twice, not six times (Michael, 2026-11-11, WCAG 2.2.2); geexer.nl
     itself keeps six.
   Plus what Geexer tools need and the site does not have: the meters and status words at the end
   of section 14 (Michael, 2026-11-11; HOUSE-STYLE.md, section 12).
   Needs tokens.css and base.css. Behaviour (header state, menu, reveals, marquee, tier switch)
   is js/geexer.js; without JavaScript everything here is visible and usable.
   Sections: 1 buttons and links  2 header  3 menu  4 sections and layout  5 hero and world
   6 pools  7 marquee  8 ruled lists  9 prices  10 facts  11 FAQ  12 clients and photo
   13 forms  14 status and meters  15 legal prose  16 closing band  17 footer  18 steps and 404
   19 motion  20 reduced motion, print, forced colours
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1. Buttons: a block leaning 12 degrees behind an upright label. No icons, no arrows.
   Write the label in a <span>: <a class="btn btn-primary" href="..."><span>Stel je vraag</span></a>
   ----------------------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  isolation: isolate;
  margin: 0;
  padding: 1.05rem 2rem;
  border: 0;
  background: none;
  color: var(--black);
  font-family: var(--font-body);
  font-weight: 700;
  font-stretch: 112%;
  font-size: var(--fs-fact);
  line-height: 1;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.btn span { position: relative; z-index: 2; transition: color .3s var(--ease-out); }
.btn::before,
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  transform: skewX(var(--skew));
  transform-origin: left center;
  transition: transform .45s var(--ease-slash), background-color .3s, border-color .3s;
}
.btn::after { z-index: 1; transform: skewX(var(--skew)) scaleX(0); }
/* The wipe answers a hovering pointer and keyboard focus. Hover sits behind (hover: hover): a
   tap on a touch screen would leave it stuck. */
.btn:focus-visible::before { transform: skewX(var(--skew)) scale(1.06, 1.1); }
.btn:focus-visible::after { transform: skewX(var(--skew)) scale(1.06, 1.1) scaleX(1); }
@media (hover: hover) {
  .btn:hover::before { transform: skewX(var(--skew)) scale(1.06, 1.1); }
  .btn:hover::after { transform: skewX(var(--skew)) scale(1.06, 1.1) scaleX(1); }
}
.btn:active::before,
.btn:active::after { transition-duration: .12s; }
.btn[disabled],
.btn[aria-disabled="true"] { cursor: wait; opacity: .7; }

/* Primary: the one action that matters (on geexer.nl: "Stel je vraag", to the contact form).
   Yellow with a white wipe in a dark world; in the light and blue worlds the tokens turn it into
   the dark button (black block, blue wipe). */
.btn-primary { color: var(--btn-primary-fg); }
.btn-primary::before { background: var(--btn-primary-bg); }
.btn-primary::after { background: var(--btn-primary-wipe); }
.btn-primary:focus-visible span { color: var(--btn-primary-wipe-fg); }
@media (hover: hover) { .btn-primary:hover span { color: var(--btn-primary-wipe-fg); } }

/* Ghost: the second route. A 1px outline in the foreground; the wipe fills with it. */
.btn-ghost { color: var(--fg); }
.btn-ghost::before { background: transparent; border: 1px solid rgba(var(--fg-rgb), .8); }
.btn-ghost::after { background: var(--fg); }
.btn-ghost:focus-visible span { color: var(--bg-theme); }
@media (hover: hover) { .btn-ghost:hover span { color: var(--bg-theme); } }

/* Dark: black block, white label, blue wipe with a black label */
.btn-dark { color: var(--white); }
.btn-dark::before { background: var(--black); }
.btn-dark::after { background: var(--blue); }
.btn-dark:focus-visible span { color: var(--black); }
@media (hover: hover) { .btn-dark:hover span { color: var(--black); } }

.btn-small { padding: .72rem 1.25rem; font-size: var(--fs-ui); }
.btn-block { width: 100%; }
@media (max-width: 639.98px) { .btn-small { min-height: 44px; } }

/* Text link: 600, a 1px underline in the foreground that grows to 3px in the accent */
.link {
  position: relative;
  display: inline-block;
  padding-bottom: .3em;
  color: var(--fg);
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-fact);
  line-height: 1.2;
  text-decoration: none;
}
.link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transition: height .25s var(--ease-out), background-color .25s;
}
.link:hover::after,
.link:focus-visible::after { height: 3px; background: var(--accent); }

/* Links in running text. Never on a .link or a button inside such a block (two lines). On white
   the text stays black and the underline turns blue (blue text on white is 3.2:1). */
:is(.prose, .copy, .legal, .faq-a, .lead) a:not(.link, .btn) {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  transition: color .25s, text-decoration-color .25s;
}
:is(.prose, .copy, .legal, .faq-a, .lead) a:not(.link, .btn):hover {
  color: var(--link-hover);
  text-decoration-color: var(--accent);
  text-decoration-thickness: var(--link-hover-line);
}

/* A button with its second route beside it (a ghost button or a text link) */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
.actions:has(> .link) { column-gap: 2rem; }
.actions > .link::before { content: ""; position: absolute; inset: -.75rem -.5rem; }   /* the link gets the button's tap height */
@media (max-width: 639.98px) {
  .actions { width: 100%; }
  .actions .btn { flex: 1 1 100%; }   /* phones: every button fills its own row */
}

/* -----------------------------------------------------------------------------
   2. Header: fixed and transparent over the first screen; after 80px of scroll (js: .is-scrolled)
   a frosted bar in the colour of the world under it. The script also gives the header the
   data-theme of the band under its bottom edge, so it reads white over a black footer on a white
   page. From 1024px the links stand centred on the window; under 1024px the burger opens the menu.
   ----------------------------------------------------------------------------- */
#site-header {
  --bar-line: rgba(var(--fg-rgb), .1);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.05rem var(--gutter);
  color: var(--fg);
}
#site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: rgba(var(--bg-rgb), 0);
  box-shadow: inset 0 -1px 0 transparent;
  -webkit-backdrop-filter: none;   /* none at rest: even blur(0) makes a composited layer */
  backdrop-filter: none;
  transition: background-color .4s, box-shadow .4s, backdrop-filter .4s, -webkit-backdrop-filter .4s;
}
#site-header.is-scrolled::before {
  background: rgba(var(--bg-rgb), .72);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  box-shadow: inset 0 -1px 0 var(--bar-line);
}
#site-header.is-scrolled[data-theme="dark"]::before { background: rgba(0, 0, 0, .85); }   /* over a black band on a white page */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  #site-header.is-scrolled::before { background: rgba(var(--bg-rgb), .94); }
}
@media (prefers-reduced-transparency: reduce) {
  #site-header::before,
  #site-header.is-scrolled::before { -webkit-backdrop-filter: none; backdrop-filter: none; }
  #site-header.is-scrolled::before { background: rgba(var(--bg-rgb), .94); }
}
html:not(.js) #site-header { position: absolute; }   /* no script, no bar: let it scroll away */

.logo { display: block; flex: none; }
.logo img { height: 22px; width: auto; filter: var(--logo-filter); transition: filter .3s; }

.main-nav { display: flex; gap: clamp(1.2rem, 2.2vw, 2.4rem); }
.main-nav a {
  position: relative;
  padding: .3rem 0;
  white-space: nowrap;
  color: rgba(var(--fg-rgb), .85);
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-ui);
  line-height: 1.4;
  text-decoration: none;
  transition: color .35s;
}
.main-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: skewX(var(--skew)) scaleX(0);
  transform-origin: left center;
  transition: transform .35s var(--ease-slash);
}
.main-nav a:hover,
.main-nav a:focus-visible,
.main-nav a[aria-current="page"] { color: var(--fg); }
.main-nav a:hover::after,
.main-nav a:focus-visible::after,
.main-nav a[aria-current="page"]::after { transform: skewX(var(--skew)) scaleX(1); }

.header-right { display: flex; align-items: center; gap: 1rem; flex: none; }
.nav-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  margin-right: -10px;
  padding: 0;
  border: 0;
  background: none;
}
.nav-toggle span {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--fg);
  transform-origin: center;
  transition: transform .4s var(--ease-out), opacity .25s, background-color .3s;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
html:not(.js) .nav-toggle { display: none; }
.nav-nojs { display: inline-block; padding: .3rem 0; color: var(--fg); font-weight: 600; font-stretch: 112%; font-size: var(--fs-ui); }

/* While the menu is open only the logo, the primary button and the close cross stay */
#site-header .main-nav,
#site-header .status-pill { transition: opacity .3s var(--ease-out), visibility 0s; }
body.nav-open #site-header .main-nav,
body.nav-open #site-header .status-pill { opacity: 0; visibility: hidden; transition: opacity .2s linear, visibility 0s linear .2s; }

@media (max-width: 79.99em) {
  #site-header .status-pill { display: none; }   /* the header's pill needs 1280px (in em: follows a larger browser font) */
}
@media (max-width: 1023.98px) {
  .main-nav { display: none; }
}
@media (min-width: 1024px) {
  #site-header {
    display: grid;
    grid-template-columns: minmax(max-content, 1fr) auto minmax(max-content, 1fr);
    min-height: var(--header-h);
  }
  #site-header .logo { justify-self: start; }
  #site-header .main-nav { justify-self: center; }
  #site-header .header-right { justify-self: end; }
  #site-header .nav-toggle,
  #site-header .nav-nojs { display: none; }
}
@media (max-width: 639.98px) {
  #site-header { padding: .9rem var(--gutter); }
  .header-right { gap: .6rem; }
}
@media (max-width: 419.98px) {
  #site-header { gap: .75rem; }
  .header-right { gap: .5rem; }
  .logo img { height: 18px; }
  #site-header .btn-small { padding: .6rem .8rem; }
}
@media (max-width: 359.98px) {
  .logo img { height: 16px; }
  #site-header .btn-small { padding: .6rem .65rem; }
}

/* -----------------------------------------------------------------------------
   3. Menu overlay: black on every page, full screen, phone and tablet only (under 1024px). It
   opens as a band leaning 12 degrees that grows from the centre line (.6s) and closes in .35s;
   the links rise from their masks 70ms apart, the aside fades up last. js: .is-open on the
   overlay, body.nav-open, aria-expanded on the burger, Escape and a focus trap.
   ----------------------------------------------------------------------------- */
#menu-overlay {
  --wipe-off: 10.63vh;    /* tan(12deg) x 50vh: half the run of the slanted edge */
  --wipe-in: .6s;
  --wipe-out: .35s;
  --index-w: 2rem;
  --shift: .42em;
  position: fixed;
  inset: 0;
  z-index: var(--z-menu);
  background: var(--black);
  color: var(--white);
  visibility: hidden;
  pointer-events: none;
  clip-path: polygon(calc(50% + var(--wipe-off)) 0, calc(50% + var(--wipe-off)) 0, calc(50% - var(--wipe-off)) 100%, calc(50% - var(--wipe-off)) 100%);
  transition: clip-path var(--wipe-out) var(--ease-slash), visibility 0s linear var(--wipe-out);
}
#menu-overlay.is-open {
  visibility: visible;
  pointer-events: auto;
  clip-path: polygon(0 0, calc(100% + 2 * var(--wipe-off)) 0, 100% 100%, calc(0% - 2 * var(--wipe-off)) 100%);
  transition: clip-path var(--wipe-in) var(--ease-slash), visibility 0s;
}
.menu-inner {
  position: relative;
  z-index: 1;
  height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: 2.5rem;
  padding: calc(var(--header-h) + 1.5rem) var(--gutter) var(--gutter);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.menu-links { display: grid; gap: .35em; }
.menu-links a {
  position: relative;
  display: grid;
  grid-template-columns: var(--index-w) minmax(0, 1fr);
  align-items: center;
  min-height: 3rem;          /* a 48px touch target per link */
  color: var(--white);
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: 125%;
  text-transform: uppercase;
  line-height: .86;
  letter-spacing: -.02em;
  font-size: clamp(1.6rem, 8.4vw, 5rem);
  white-space: nowrap;
  text-decoration: none;
  opacity: 0;
  transition: opacity .15s linear;
}
#menu-overlay.is-open .menu-links a { opacity: 1; transition: color .35s var(--ease-out), opacity .35s var(--ease-out); }
.menu-links a[aria-current="page"] { color: var(--blue); }
.menu-index {
  font-weight: 700;
  font-stretch: 112%;
  font-size: var(--fs-ui);
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: tabular-nums;
  color: var(--slate);
}
.menu-links li:nth-child(1) > a { --i: 0; }
.menu-links li:nth-child(2) > a { --i: 1; }
.menu-links li:nth-child(3) > a { --i: 2; }
.menu-links li:nth-child(4) > a { --i: 3; }
.menu-links li:nth-child(5) > a { --i: 4; }
.menu-links li:nth-child(6) > a { --i: 5; }
.menu-links li:nth-child(7) > a { --i: 6; }
.menu-links li:nth-child(8) > a { --i: 7; }
.menu-links .line { transition: transform .4s var(--ease-slash); }
.menu-links .line-inner { transform: translateY(110%) skewY(4deg); transition: transform 0s linear .2s; }
#menu-overlay.is-open .menu-links .line-inner { transform: none; transition: transform .85s var(--ease-out) calc(.25s + var(--i, 0) * .07s); }
/* the brand slash before the hovered or focused label */
.menu-links a::before {
  content: "";
  position: absolute;
  left: var(--index-w);
  top: calc(50% - .35em);
  width: .3em;
  height: .7em;
  background: var(--blue);
  opacity: 0;
  transform: translateX(-.45em) skewX(var(--skew));
  transition: transform .4s var(--ease-slash), opacity .25s var(--ease-out);
}
.menu-links a:focus-visible { color: var(--blue); outline: none; }
.menu-links a:focus-visible .line { transform: translateX(var(--shift)); }
.menu-links a:focus-visible::before { opacity: 1; transform: translateX(0) skewX(var(--skew)); }
@media (hover: hover) and (pointer: fine) {
  .menu-links a:hover { color: var(--blue); }
  .menu-links a:hover .line { transform: translateX(var(--shift)); }
  .menu-links a:hover::before { opacity: 1; transform: translateX(0) skewX(var(--skew)); }
  #menu-overlay.is-open .menu-links:has(a:hover) a:not(:hover):not(:focus-visible) { opacity: .35; }
}
.menu-aside {
  display: grid;
  gap: .85rem;
  justify-items: start;
  padding-top: 2rem;
  border-top: 1px solid rgba(255, 255, 255, .15);
  color: rgba(255, 255, 255, .8);
  font-size: 1rem;
  line-height: 1.5;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity .15s linear, transform 0s linear .15s;
}
#menu-overlay.is-open .menu-aside { opacity: 1; transform: none; transition: opacity .5s var(--ease-out) .55s, transform .7s var(--ease-out) .55s; }
.menu-aside p { color: rgba(255, 255, 255, .8); }
.menu-aside .menu-claim { font-family: var(--font-display); font-weight: 900; font-stretch: 125%; text-transform: uppercase; line-height: .86; color: var(--blue); font-size: 1.4rem; margin-bottom: .5rem; }
.menu-aside > a { color: var(--white); font-weight: 700; font-size: 1.25rem; text-decoration: none; transition: color .25s; }
.menu-aside > a:hover,
.menu-aside > a:focus-visible { color: var(--blue); }
/* the ghost index: the current or hovered item's number, outlined, bottom right (js writes data-index) */
.menu-ghost {
  position: absolute;
  right: var(--gutter);
  bottom: calc(var(--gutter) - .06em);
  z-index: 0;
  pointer-events: none;
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: 125%;
  font-size: clamp(6rem, 24vw, 14rem);
  line-height: .8;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, .6);
  opacity: 0;
  transition: opacity .15s linear;
}
.menu-ghost::before { content: attr(data-index); display: block; }
#menu-overlay.is-open .menu-ghost { opacity: 1; transition: opacity .6s var(--ease-out) .5s; }
@media (max-width: 699.98px) { .menu-ghost { display: none; } }
/* phones: the links at thumb height, the aside above them */
@media (max-width: 639.98px) {
  .menu-inner { display: flex; flex-direction: column; gap: 1.5rem; padding-top: calc(var(--header-h) + 1rem); }
  .menu-links { gap: .15em; }
  .menu-aside { order: -1; margin-top: auto; gap: .5rem; padding: 0 0 1.25rem; border-top: 0; border-bottom: 1px solid rgba(255, 255, 255, .15); }
  .menu-aside .menu-claim { font-size: 1.125rem; margin-bottom: .25rem; }
  .menu-aside > a { font-size: 1.0625rem; }
}
/* the menu is phone and tablet only: from 1024px the header shows every link */
@media (min-width: 1024px) { #menu-overlay { display: none; } }

/* -----------------------------------------------------------------------------
   4. Sections and layout. Full bleed: one gutter, no max-width container, measures in ch.
   ----------------------------------------------------------------------------- */
.section { position: relative; padding: var(--section-y) var(--gutter); }
.section--tight { padding-top: calc(var(--section-y) * .6); padding-bottom: calc(var(--section-y) * .6); }
.section--flush-top { padding-top: 0; }
section, .section, .legal { overflow-x: clip; }
.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  margin-bottom: var(--head-gap);
}
.section-head .section-title { margin-bottom: 0; }
.section-head .lead { margin-top: 1rem; }
/* From 1024px a title shares its row with the lead that follows it: two equal halves, the lead
   on the shared second-column line, bottom-aligned with the title. */
@media (min-width: 1024px) {
  .section:has(> .section-head + .lead) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--col-gap);
    align-content: start;
  }
  .section:has(> .section-head + .lead) > * { grid-column: 1 / -1; }
  .section:has(> .section-head + .lead) > .section-head { grid-column: 1; margin-bottom: 0; }
  .section:has(> .section-head + .lead) > .section-head + .lead { grid-column: 2; align-self: end; margin: 0; }
}
@media (max-width: 899.98px) {
  .section-head { flex-direction: column; align-items: flex-start; gap: 1rem; }
}
/* What follows a head and its lead starts a head gap lower (zero specificity: a component's own
   margin and the .mt-* utilities win). On geexer.nl each following block sets this itself. */
:where(.section > .section-head + .lead) + * { margin-top: var(--head-gap); }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(2.5rem, 5vw, 4.5rem) var(--col-gap); }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(2rem, 4vw, 3.5rem) clamp(1.5rem, 4vw, 4rem); }
.split { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 2rem var(--col-gap); align-items: start; }
@media (min-width: 1280px) { .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
@media (max-width: 1023.98px) { .split { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 899.98px) { .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); } }
.stack > * + * { margin-top: 1rem; }
.stack-lg > * + * { margin-top: 2rem; }

/* Slash bullets: the brand mark as a list marker */
.points { display: grid; gap: .45rem; max-width: 48ch; color: rgba(var(--fg-rgb), .78); font-size: var(--fs-fact); }
.points li { position: relative; padding-left: 1.4em; }
.points li::before {
  content: "";
  position: absolute;
  left: .05em;
  top: .5em;
  width: .5em;
  height: .48em;
  background: var(--accent);
  transform: skewX(var(--skew));
}
/* The call-to-action row that ends a section, on a 2px rule */
.cta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem 3rem;
  margin-top: clamp(2rem, 4vw, 3.5rem);
  padding-top: 2rem;
  border-top: 2px solid var(--fg);
}
.cta-row p { max-width: var(--measure-lead); font-size: 1.125rem; }
.cta-row--start { justify-content: flex-start; }
@media (min-width: 900px) {
  .cta-row--start { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--col-gap); }
}
@media (max-width: 639.98px) { .cta-row > .btn { flex: 1 1 100%; } }
/* a ruled list in front of a cta-row ends without its own last line: the row's rule closes it */
:is(.extras, .service-list, ol.service-index):has(+ .cta-row) > :last-child { border-bottom: 0; }

/* -----------------------------------------------------------------------------
   5. Hero and the world. The hero is the first screen: bottom-aligned, the headline in the sky,
   the copy on the floor. Its backdrop, .world, is the first child of the hero: a CSS lattice
   floor in perspective (lit blue on black), an optional poster of the 3D honeycomb, and a fade
   into the page colour at the bottom. .hero--compact is the short page head without a world
   (legal pages, tools).
   ----------------------------------------------------------------------------- */
.hero {
  position: relative;
  z-index: var(--z-content);
  isolation: isolate;
  -webkit-user-select: none;
  user-select: none;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: calc(var(--header-h) + 3rem) var(--gutter) clamp(2.5rem, 7vh, 5rem);
  overflow: clip;
}
.hero a, .hero button { -webkit-user-select: auto; user-select: auto; }
.hero--compact { min-height: 0; padding-top: calc(var(--header-h) + clamp(3rem, 9vh, 6rem)); padding-bottom: clamp(2rem, 5vh, 4rem); }
.hero-inner { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); justify-items: start; gap: clamp(1rem, 2.8vh, 2.2rem); max-width: 100%; }
.hero-sub { max-width: var(--measure-lead); font-size: clamp(1.0625rem, 1.3vw, 1.25rem); color: rgba(var(--fg-rgb), .86); }
.hero-actions { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
.hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem 2rem;
  margin-top: clamp(.2rem, 1.5vh, 1rem);
  color: var(--fg);
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-fact);
  line-height: 1.4;
}
.hero-meta li { position: relative; display: inline-flex; align-items: center; gap: .6em; padding-left: 1.1em; color: var(--fg); white-space: nowrap; }
.hero-meta li::before { content: ""; position: absolute; left: 0; top: .46em; width: .5em; height: .48em; background: var(--accent); transform: skewX(var(--skew)); }
.hero-meta li.has-pill { padding-left: 0; }
.hero-meta li.has-pill::before { display: none; }
@media (min-width: 640px) { .hero-meta { font-size: 1rem; } }
@media (max-width: 639.98px) {
  .hero { padding-top: calc(var(--header-h) + 2.2rem); }
  .hero-actions { width: 100%; }
  .hero-actions .btn { flex: 1 1 auto; }
  .hero-meta { gap: .5rem 1.2rem; }
  .hero-meta li { white-space: normal; }
}

.world {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background: var(--bg-theme);
}
/* the floor: the lattice tile laid down in perspective from a horizon at 44 percent */
.world::after {
  content: "";
  position: absolute;
  left: -40%;
  right: -40%;
  top: 44%;
  height: 130%;
  background-image: var(--comb-world);
  background-size: var(--comb-size);
  background-position: 50% 0;
  transform-origin: 50% 0;
  transform: perspective(70vh) rotateX(62deg);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 16%, black 72%, transparent 100%), radial-gradient(ellipse 60% 100% at 50% 30%, black 45%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(to bottom, transparent 0%, black 16%, black 72%, transparent 100%), radial-gradient(ellipse 60% 100% at 50% 30%, black 45%, transparent 100%);
  mask-composite: intersect;
}
/* a lit horizon in the dark world only */
.world--lit::after {
  background-image: var(--comb-world), radial-gradient(ellipse 55% 60% at 50% 0%, rgba(30, 144, 255, .38), rgba(30, 144, 255, .08) 45%, transparent 70%);
  background-size: var(--comb-size), 100% 100%;
}
.world-poster { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; object-position: 50% 40%; }
/* The landscape posters are a 9:4 frame plus a quarter of its height of floor below (2304 x 1280):
   a box of 125 percent, top-aligned, lets the 9:4 frame fill the hero and hangs the extra floor
   below it, clipped by .world. */
@media (orientation: landscape) { .world-poster { bottom: auto; height: 125%; object-position: 50% 0; } }
.world::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 16%;
  z-index: 3;
  background: linear-gradient(to bottom, rgba(var(--bg-rgb), 0), rgba(var(--bg-rgb), 1));
}

/* -----------------------------------------------------------------------------
   6. Pools: a soft radial vignette in the page colour behind text that sits over the world or a
   lattice. Never a scrim, a panel or a rectangle.
   ----------------------------------------------------------------------------- */
.pool, .hero-inner { position: relative; isolation: isolate; }
.pool::before,
.hero-inner::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -45% -40%;
  background-color: var(--bg-theme);
  opacity: var(--rl-alpha);
  pointer-events: none;
  -webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, black 0%, rgba(0, 0, 0, .7) 35%, rgba(0, 0, 0, .2) 70%, transparent 100%);
  mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, black 0%, rgba(0, 0, 0, .7) 35%, rgba(0, 0, 0, .2) 70%, transparent 100%);
}
.hero-inner::before { inset: 38% 20% -42% -32%; opacity: calc(var(--rl-alpha) * .9); }
.hero--compact .hero-inner::before { inset: -30% 10% -40% -32%; }
.pool--tall::before { inset: -52% -36%; }
.pool--soft::before { opacity: calc(var(--rl-alpha) * .6); }
@media (max-width: 899.98px) { .hero-inner::before { inset: 14% -40% -40%; } }

/* -----------------------------------------------------------------------------
   7. Marquee: a blue band at skewY(-3deg) of display words with slash separators. It drifts at
   40px a second while on screen (js), and stands still under reduced motion or without script.
   Put the word set in the track twice: the loop wraps at one set's width.
   ----------------------------------------------------------------------------- */
.marquee {
  position: relative;
  z-index: 2;
  margin: calc(2.7vw + .75rem) 0;   /* the skew lifts one end by tan(3deg) x half the width */
  padding: clamp(.35rem, .5vw, .45rem) 0;
  background: var(--marquee-bg);
  color: var(--marquee-fg);
  overflow: hidden;
  transform: skewY(-3deg);
  transform-origin: center;
}
.marquee-track { display: flex; width: max-content; white-space: nowrap; will-change: transform; }
.marquee-set {
  display: flex;
  align-items: center;
  gap: 1.2em;
  padding-right: 1.2em;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 125%;
  text-transform: uppercase;
  line-height: .86;
  letter-spacing: -.02em;
  font-size: clamp(1.5rem, 3vw, 2.6rem);
  color: inherit;
  opacity: .85;
}
.marquee-set span { display: block; }
.slash { flex: none; display: inline-block; width: .58em; height: .6em; background: currentColor; clip-path: var(--slash-shape); }

/* -----------------------------------------------------------------------------
   8. Ruled lists, not boxes. Rows separated by 1px lines in the foreground at 15 percent.
   - .service-list > .service-row: number, display title, description, a value line
   - ol.service-index: a numbered list of names in the title voice
   - .service-grid: two columns of short teasers with a subtitle each
   ----------------------------------------------------------------------------- */
.service-list { border-top: 1px solid var(--line); }
.service-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; padding: clamp(1.25rem, 2vw, 2rem) 0; border-bottom: 1px solid var(--line); }
@media (min-width: 1024px) { .service-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.5rem var(--col-gap); } }
.service-row > :first-child { display: grid; align-content: start; gap: .75rem; }
.service-body { display: grid; gap: 1rem; align-content: start; justify-items: start; }
.service-row .service-index,
.service-index-num {
  display: inline-flex;
  align-items: baseline;
  font-weight: 900;
  font-stretch: 125%;
  font-size: 1.25rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--fg-muted);
}
.service-row .service-index::after,
.service-index-num::after { content: ""; display: inline-block; width: .5em; height: .55em; margin-left: .5em; background: var(--accent); transform: skewX(var(--skew)); }
.service-title { font-family: var(--font-display); font-weight: 900; font-stretch: 125%; text-transform: uppercase; line-height: .86; letter-spacing: -.02em; font-size: var(--fs-h3); color: var(--fg); }
.service-desc { max-width: 52ch; font-size: clamp(1.0625rem, 1.15vw, 1.1875rem); color: rgba(var(--fg-rgb), .86); }
.service-price { font-weight: 700; font-stretch: 112%; font-size: var(--fs-lead); line-height: 1.25; letter-spacing: -.005em; color: var(--fg); }

ol.service-index { border-top: 1px solid var(--line); }
ol.service-index > li { border-bottom: 1px solid var(--line); }
.service-index-item { display: grid; grid-template-columns: 4.25rem minmax(0, 1fr); align-items: baseline; align-content: center; padding: clamp(1rem, 1.7vw, 1.6rem) 0; color: var(--fg); }
.service-index-num { grid-column: 1; }
.service-index-name { grid-column: 2; font-family: var(--font-display); font-weight: 900; font-stretch: 125%; text-transform: uppercase; line-height: .86; letter-spacing: -.02em; font-size: var(--fs-h3); color: var(--fg); }
@media (max-width: 639.98px) {
  .service-index-item { grid-template-columns: 3rem minmax(0, 1fr); min-height: 48px; padding: .8rem 0; }
  .service-index-num { font-size: var(--fs-fact); }
}

.service-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--col-gap); border-top: 1px solid var(--line); }
.service-grid > * { display: grid; gap: .7rem; align-content: start; justify-items: start; padding: clamp(1.6rem, 3vw, 2.5rem) 0; border-bottom: 1px solid var(--line); }
.service-grid h3 { font-weight: 800; font-stretch: 118%; font-size: var(--fs-sub); line-height: 1.1; letter-spacing: -.01em; }
.service-grid p { max-width: 48ch; }
@media (max-width: 899.98px) { .service-grid { grid-template-columns: minmax(0, 1fr); } }

/* -----------------------------------------------------------------------------
   9. Prices: the rate block between two 2px rules, the tier switch, price tables with tabular
   figures and a small euro sign, the extras list. Rows are plain (no link, no hover).
   ----------------------------------------------------------------------------- */
.rate { display: flex; flex-wrap: wrap; align-items: baseline; gap: 1rem 3rem; padding: clamp(1.5rem, 3vw, 2.5rem) 0; border-top: 2px solid var(--fg); border-bottom: 2px solid var(--fg); }
.rate-amount { display: inline-flex; align-items: baseline; gap: .3em; font-weight: 800; font-stretch: 118%; font-size: clamp(3rem, 7vw, 7rem); line-height: 1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--fg); }
.rate-cur { font-size: .45em; font-weight: 700; }
.rate-unit { font-size: max(var(--fs-fact), .26em); font-weight: 600; font-stretch: 112%; letter-spacing: 0; color: var(--fg-muted); }
.rate p { max-width: var(--measure-lead); flex: 1 1 24rem; }
@media (min-width: 900px) { .rate { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--col-gap); } }

/* The tier switch: leaning outline buttons, the chosen one filled (role="radiogroup") */
.tier-switch-wrap { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.5rem; }
.tier-switch-wrap .label { font-weight: 600; font-stretch: 112%; color: var(--fg); }
.tier-switch { display: inline-flex; gap: .6rem; }
.tier-switch button {
  position: relative;
  isolation: isolate;
  padding: .72rem 1.4rem;
  border: 0;
  background: none;
  color: var(--fg);
  font-weight: 700;
  font-stretch: 112%;
  font-size: var(--fs-fact);
  line-height: 1;
  transition: color .3s;
}
.tier-switch button::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border: 1px solid rgba(var(--fg-rgb), .35);
  transform: skewX(var(--skew));
  transition: background-color .3s, border-color .3s;
}
@media (hover: hover) { .tier-switch button:hover::before { border-color: var(--fg); } }
.tier-switch button[aria-checked="true"] { color: var(--bg-theme); }
.tier-switch button[aria-checked="true"]::before { background: var(--fg); border-color: var(--fg); }
html:not(.js) .tier-switch-wrap { display: none; }

.price-tables { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3rem var(--col-gap); }
@media (max-width: 899.98px) { .price-tables { grid-template-columns: minmax(0, 1fr); } }
.price-heading { padding-bottom: .7rem; margin-bottom: .2rem; border-bottom: 2px solid var(--fg); font-weight: 800; font-stretch: 118%; font-size: var(--fs-sub); line-height: 1.1; letter-spacing: -.01em; color: var(--fg); }
.price-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1.5rem; padding: 1.2rem 0; padding-right: var(--launcher-room); border-bottom: 1px solid var(--fg); color: var(--fg); }
.price-name { font-weight: 500; font-stretch: 104%; font-size: clamp(1.125rem, 1.5vw, 1.375rem); }
.price-amount { display: inline-flex; align-items: baseline; gap: .35em; font-weight: 800; font-stretch: 118%; font-size: var(--fs-figure); font-variant-numeric: tabular-nums; line-height: 1; letter-spacing: -.01em; white-space: nowrap; color: var(--fg); }
.price-cur { font-size: .6em; font-weight: 700; }
.price-per { font-weight: 600; font-stretch: 112%; font-size: var(--fs-fact); letter-spacing: 0; color: var(--fg-muted); }
.price-amount > :empty { display: none; }
.price-amount .is-request { font-size: max(.5em, var(--fs-fact)); font-weight: 600; font-stretch: 112%; letter-spacing: 0; }
.plan { padding-bottom: 1.2rem; }
.plan-features { display: grid; gap: .45rem; padding: 1rem 0 0; color: rgba(var(--fg-rgb), .78); font-size: var(--fs-fact); }
.plan-features li { position: relative; padding-left: 1.4em; }
.plan-features li::before { content: ""; position: absolute; left: .05em; top: .5em; width: .5em; height: .48em; background: var(--accent); transform: skewX(var(--skew)); }
.plan-note { margin-top: 1rem; color: var(--fg-muted); font-size: var(--fs-fact); }
.extras { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--col-gap); }
.extras > div { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; padding: 1rem 0; padding-right: var(--launcher-room); border-bottom: 1px solid var(--fg); }
.extras dt { font-weight: 500; font-stretch: 104%; color: var(--fg); }
.extras dd { margin: 0; font-weight: 800; font-stretch: 118%; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--fg); letter-spacing: -.01em; }
@media (min-width: 640px) { .extras dd { text-align: right; } }
@media (max-width: 899.98px) { .extras { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 639.98px) {
  .price-row { flex-direction: column; align-items: flex-start; gap: .35rem; padding-right: 0; }
  .extras > div { flex-direction: column; align-items: flex-start; gap: .15rem; padding: .85rem 0; }
  .extras dd { white-space: normal; }
}

/* -----------------------------------------------------------------------------
   10. Facts: hexagon tiles nested like a comb. Measured or decided numbers only, written as they
   are: never counted up.
   ----------------------------------------------------------------------------- */
.facts {
  --cell: clamp(150px, 20vw, 260px);
  --cell-h: calc(var(--cell) * .8660254);
  --gx: 5px;
  --gy: 5px;
  display: flex;
  align-items: flex-start;
}
.fact {
  position: relative;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: var(--cell);
  height: var(--cell-h);
  padding: 0 calc(var(--cell) * .14);
  text-align: center;
  color: var(--fact-ink);
  background: var(--fact-edge);
  clip-path: var(--hex);
  transition: color .35s;
}
.fact::before { content: ""; position: absolute; inset: 1.5px; z-index: 0; background: var(--fact-face); clip-path: var(--hex); transition: background-color .35s var(--ease-out); }
.fact > * { position: relative; z-index: 1; }
.fact + .fact { margin-left: calc(var(--cell) * -.25 + var(--gx)); }
.fact:nth-child(even) { margin-top: calc(var(--cell-h) * .5 + var(--gy)); }
.fact-value { display: block; font-weight: 800; font-stretch: 118%; font-size: clamp(1.9rem, 3vw, 2.8rem); font-variant-numeric: tabular-nums; line-height: 1; letter-spacing: -.02em; white-space: nowrap; color: inherit; }
.fact-label { display: block; margin-top: .5rem; font-weight: 600; font-stretch: 100%; font-size: var(--fs-fact); line-height: 1.4; color: var(--fact-label); transition: color .35s; }
@media (hover: hover) {
  .fact:hover { color: var(--fact-hover-ink); }
  .fact:hover::before { background: var(--fact-hover-face); }
  .fact:hover .fact-label { color: var(--fact-hover-ink); }
}
@media (max-width: 899.98px) {
  .facts { flex-wrap: wrap; max-width: calc(var(--cell) * 1.75 + var(--gx)); }
  .fact:nth-child(3) { margin-left: 0; margin-top: calc(var(--cell-h) * -.5 + var(--gy)); }
  .fact:nth-child(4) { margin-top: var(--gy); }
}

/* -----------------------------------------------------------------------------
   11. FAQ: native <details>; a 3px accent bar slides down beside an open item, the plus turns
   into a cross. Works without JavaScript.
   ----------------------------------------------------------------------------- */
.faq-list { max-width: 62rem; border-top: 1px solid var(--line); }
.faq-item { position: relative; border-bottom: 1px solid var(--line); }
.faq-item::before {
  content: "";
  position: absolute;
  left: calc(-1 * clamp(8px, 1.6vw, 22px));
  top: 1.3rem;
  bottom: 1.3rem;
  width: 3px;
  background: var(--accent);
  transform: skewX(var(--skew)) scaleY(0);
  transform-origin: top center;
  transition: transform .45s var(--ease-slash);
}
.faq-item[open]::before { transform: skewX(var(--skew)) scaleY(1); }
.faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.4rem 0;
  list-style: none;
  cursor: pointer;
  color: var(--fg);
  font-weight: 500;
  font-stretch: 104%;
  font-size: clamp(1.0625rem, 1.4vw, 1.25rem);
  line-height: 1.35;
  transition: color .35s;
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q::marker { content: ""; }
.faq-q:hover { color: var(--link-hover); }
.faq-q:hover .faq-icon { color: var(--accent); }
.faq-q:focus-visible { outline-offset: 6px; }
.faq-icon { position: relative; flex: none; width: 14px; height: 14px; transition: transform .4s var(--ease-out); }
.faq-icon::before,
.faq-icon::after { content: ""; position: absolute; left: 0; top: 6px; width: 14px; height: 2px; background: currentColor; }
.faq-icon::after { transform: rotate(90deg); }
.faq-item[open] .faq-icon { transform: rotate(45deg); color: var(--accent); }
.faq-a { padding-bottom: 1.4rem; max-width: 60ch; }
.faq-a p { color: rgba(var(--fg-rgb), .78); }
.faq-a p + p { margin-top: .8em; }

/* -----------------------------------------------------------------------------
   12. Client tiles and the person photo. A logo on its own ground inside the 12 degree crop.
   Set the ground per client with a class (.client--name { --card-bg: #... }), the one colour a
   page may add. A client without a logo, or a logo that fails to load (js adds .is-fallback to a
   [data-fallback] wrapper), gets the text tile: the name in the display voice.
   ----------------------------------------------------------------------------- */
.clients { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(1.5rem, 3vw, 3rem) var(--col-gap); }
.clients--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 899.98px) { .clients { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 639.98px) { .clients, .clients--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 1rem; } }
.client { display: block; text-decoration: none; color: inherit; }
.client-logo {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  aspect-ratio: 3 / 2;
  padding: 14%;
  background: rgba(var(--fg-rgb), .24);   /* the 1px edge along the crop */
  clip-path: var(--crop-shape);
  overflow: hidden;
}
.client-logo::before { content: ""; position: absolute; inset: 1px; z-index: -1; background: var(--card-bg, var(--navy)); clip-path: var(--crop-shape); }
.client-logo img { width: 100%; height: 100%; max-width: none; max-height: none; object-fit: contain; transition: transform .6s var(--ease-out); }
.client:hover .client-logo img,
.client:focus-visible .client-logo img { transform: scale(1.05); }
.client-logo:is(.client-logo--text, .is-fallback) { --card-bg: var(--bg-theme); container-type: inline-size; background: rgba(var(--fg-rgb), .3); }
.client-wordmark,
.client-logo.is-fallback::after {
  display: block;
  max-width: 100%;
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: 125%;
  font-size: clamp(1rem, 13cqi, 2.75rem);
  line-height: .9;
  letter-spacing: -.01em;
  text-transform: uppercase;
  text-align: center;
  text-wrap: balance;
  overflow-wrap: anywhere;
  hyphens: auto;
  color: var(--fg);
}
.client-logo.is-fallback > img { display: none; }
.client-logo.is-fallback::after { content: attr(data-fallback); content: attr(data-fallback) / ""; font-size: clamp(.625rem, 11cqi, 2.5rem); }
.client .caption { display: grid; grid-template-columns: minmax(0, 1fr); gap: .15rem; padding: 1rem .5rem 0 0; }
.client .caption strong { font-size: 1rem; font-weight: 700; font-stretch: 112%; color: var(--fg); overflow-wrap: break-word; }
.client .caption span { font-weight: 500; font-stretch: 100%; color: var(--fg-muted); overflow-wrap: break-word; hyphens: auto; }

/* A photo of a person: square, the lean on the left edge only so the person stays whole */
.person-photo { max-width: 560px; }
.person-photo img { width: 100%; aspect-ratio: 1; object-fit: cover; object-position: 50% 30%; clip-path: var(--crop-left); }

/* -----------------------------------------------------------------------------
   13. Forms: no boxes. A field is a 1px line at 50 percent (3:1 on both worlds), the label
   floats up and turns to the accent, a 2px accent line draws in on focus. Give every input
   placeholder=" " so :placeholder-shown can tell empty from filled.
   ----------------------------------------------------------------------------- */
.field { position: relative; margin-bottom: 1.6rem; }
.field input,
.field textarea,
.field select {
  display: block;
  width: 100%;
  margin: 0;
  padding: 1.1rem 0 .6rem;
  border: 0;
  border-bottom: 1px solid rgba(var(--fg-rgb), .5);
  border-radius: 0;
  background: transparent;
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.4;
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}
.field textarea { min-height: 7.5rem; resize: vertical; }
.field label {
  position: absolute;
  left: 0;
  top: 1.1rem;
  color: var(--fg-muted);
  font-size: var(--fs-body);
  line-height: 1.4;
  pointer-events: none;
  transform-origin: left top;
  transition: transform .3s var(--ease-out), color .3s;
}
.field input:focus ~ label,
.field input:not(:placeholder-shown) ~ label,
.field textarea:focus ~ label,
.field textarea:not(:placeholder-shown) ~ label,
.field select:focus ~ label,
.field select:valid ~ label,
.field.is-filled label { transform: translateY(-1.1rem) scale(.8); color: var(--accent); }
.field::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .45s var(--ease-slash);
}
.field:focus-within::after { transform: scaleX(1); }
.field-textarea::after { bottom: 6px; }
.field input:user-invalid,
.field textarea:user-invalid,
.field select:user-invalid,
.field.is-invalid :is(input, textarea, select) { border-bottom-color: var(--red-text); }
.field select { cursor: pointer; padding-right: 2rem; }
.field select option { background: var(--bg-theme); color: var(--fg); }
.field-select::before {
  content: "";
  position: absolute;
  right: 2px;
  top: 1.35rem;
  width: 9px;
  height: 9px;
  border-right: 1.5px solid var(--fg-muted);
  border-bottom: 1.5px solid var(--fg-muted);
  transform: rotate(45deg);
  pointer-events: none;
  transition: border-color .3s;
}
.field-select:focus-within::before { border-color: var(--accent); }
.field :is(input, textarea, select):-webkit-autofill {
  -webkit-box-shadow: 0 0 0 1000px var(--bg-theme) inset;
  -webkit-text-fill-color: var(--fg);
  caret-color: var(--fg);
  transition: background-color 9999s ease-in-out 0s;
}
.field-error { margin-top: .4rem; color: var(--red-text); font-weight: 600; font-stretch: 112%; font-size: var(--fs-fact); }
/* A field people retype again and again (a search, a filter) carries a "Clear" action at its end:
   one click empties it and puts the cursor back. It shows only while the field has text. It is a
   text button, not a cross: buttons carry no icons (rule 16). Login, password and one-shot form
   fields do not get one. Markup, the button after the label:
   <div class="field field-clearable"><input placeholder=" "><label>...</label>
   <button class="field-clear" type="button">Clear</button></div>. Behaviour: js/geexer.js, 9. */
.field-clearable input { padding-right: 5rem; }
.field-clear {
  position: absolute;
  right: 0;
  top: .5rem;
  z-index: 1;
  min-height: 44px;
  padding: 0 .1rem 0 .75rem;
  border: 0;
  background: none;
  color: var(--fg-muted);
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-ui);
  line-height: 1;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  cursor: pointer;
  transition: color .3s, text-decoration-color .3s;
}
.field-clear:focus-visible { color: var(--fg); text-decoration-color: currentColor; }
@media (hover: hover) { .field-clear:hover { color: var(--fg); text-decoration-color: currentColor; } }
.field-clearable input:placeholder-shown ~ .field-clear { display: none; }
.check { display: flex; gap: .8rem; align-items: flex-start; margin-bottom: 1.6rem; font-size: var(--fs-fact); color: rgba(var(--fg-rgb), .86); }
.check input { flex: none; width: 18px; height: 18px; margin: .15rem 0 0; accent-color: var(--blue); }
.hp { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }   /* honeypot field for bots */
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.5rem; margin-top: .4rem; }
@media (max-width: 639.98px) { .form-actions .btn { width: 100%; } }
.form-status { min-height: 1.4em; font-weight: 600; font-stretch: 112%; font-size: var(--fs-fact); line-height: 1.4; }
.form-status.is-ok { color: var(--accent-ink); }
.form-status.is-error { color: var(--red-text); }
[data-theme="blue"] .form-status.is-error { font-weight: 800; }
.form-status a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .22em; }
.form-note { margin-top: 1.4rem; font-weight: 500; font-stretch: 112%; font-size: var(--fs-fact); color: var(--fg-muted); }
.contact-mail { display: inline-block; max-width: 100%; font-weight: 800; font-stretch: 118%; font-size: clamp(1.75rem, 4vw, 4.25rem); white-space: nowrap; line-height: 1; letter-spacing: -.02em; color: var(--fg); text-decoration: none; transition: color .35s; }
.contact-mail:hover,
.contact-mail:focus-visible { color: var(--link-hover); }

/* -----------------------------------------------------------------------------
   14. Status: a 12px hexagon (grey pending, blue ok, red down) in a leaning outline pill. When a
   state arrives the dot pulses yellow twice (4.8s, under the 5 seconds of WCAG 2.2.2, so it needs
   no pause control) and stops: add .is-pulsing to the host. Yellow only on black, so the pulse
   shows in dark worlds only. Then the meters and status words of tools (see the end of 14).
   ----------------------------------------------------------------------------- */
.status-dot { position: relative; display: inline-block; width: 12px; height: 12px; flex: none; isolation: isolate; vertical-align: middle; }
.status-dot::before { content: ""; position: absolute; inset: 1px; background: var(--fg-muted); clip-path: var(--hex); transition: background-color .4s; }
.status-dot::after { content: ""; position: absolute; inset: -4px; z-index: -1; display: var(--pulse-display); background: var(--yellow); clip-path: var(--hex); opacity: 0; }
[data-state="ok"] .status-dot::before { background: var(--status-ok); }
[data-state="down"] .status-dot::before,
[data-state="danger"] .status-dot::before { background: var(--red); }
[data-state="ok"].is-pulsing .status-dot::after { animation: dot-pulse 2.4s var(--ease-out) 2; }
@keyframes dot-pulse {
  0%   { transform: scale(.55); opacity: .8; }
  70%  { transform: scale(1.35); opacity: 0; }
  100% { transform: scale(1.35); opacity: 0; }
}
.status-pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: .6em;
  padding: .55rem 1rem;
  color: var(--fg);
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-ui);
  line-height: 1.4;
  text-decoration: none;
  white-space: nowrap;
  isolation: isolate;
}
.status-pill::before { content: ""; position: absolute; inset: 0; z-index: -1; border: 1px solid rgba(var(--fg-rgb), .2); transform: skewX(var(--skew)); transition: border-color .3s, background-color .3s; }
.status-pill:hover::before,
.status-pill:focus-visible::before { border-color: rgba(var(--fg-rgb), .6); background: rgba(var(--fg-rgb), .04); }
.status-pill--reserve { min-width: 15.5rem; justify-content: center; }   /* reserves the longest state so nothing shifts */
.status-list { border-top: 1px solid var(--line); }
.status-item { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: .5rem 1.5rem; padding: 1rem 0; padding-right: var(--launcher-room); border-bottom: 1px solid var(--line); color: var(--fg); }
.status-item .name { font-weight: 600; font-stretch: 112%; color: var(--fg); }
.status-live { display: inline-flex; align-items: center; gap: .6em; color: var(--fg); white-space: nowrap; font-weight: 600; font-stretch: 112%; font-size: var(--fs-fact); }
[data-theme="dark"] [data-state="down"] .status-live,
:root:not([data-theme="light"], [data-theme="blue"]) [data-state="down"] .status-live { color: var(--red-text); }
:is([data-state="unknown"], [data-state="pending"], [data-state="maintenance"]) .status-live { color: var(--fg-muted); }
.status-uptime { display: inline-flex; align-items: baseline; justify-content: flex-end; gap: .5em; }
.status-uptime .figure { min-width: 7ch; justify-content: flex-end; }
.status-uptime .caption { color: var(--fg-muted); white-space: nowrap; }
@media (max-width: 639.98px) {
  .status-item { grid-template-columns: minmax(0, 1fr) auto; gap: .35rem .75rem; }
  .status-item .name { grid-column: 1; grid-row: 1; }
  .status-live { grid-column: 1; grid-row: 2; }
  .status-uptime { grid-column: 2; grid-row: 1 / 3; align-self: center; flex-direction: column; align-items: flex-start; gap: .15rem; }
  .status-uptime .figure { font-size: clamp(1.125rem, 5.6vw, 1.5rem); min-width: 0; }
}

/* Meters and status words (tools; first built for Claude usage, Michael 2026-11-11). A meter is
   a value against its cap: a 6px track (the foreground at 15 percent), a square fill in a status
   colour, and at most one tick, for the time passed in the period: 2px wide, leaning 12 degrees,
   with a cut-out halo in the page colour (a line, not a shadow). The fill width and the tick
   position come in as percentages through --value and --elapsed, set from JavaScript with
   style.setProperty (never a style attribute). The status word beside a meter says the state in
   words, so colour is never the only signal: a calm word is muted and bold, a warning
   --status-warning (yellow on black, the foreground elsewhere), a limit reached or nearly reached
   --status-critical.
     <div class="meter meter--warning" role="img" aria-label="72% gebruikt">
       <span class="meter-fill"></span><span class="meter-tick"></span></div>
     <span class="status-word status-word--warning">Wordt krap</span> */
.meter { --meter-h: 6px; --meter-tick: 20px; position: relative; height: var(--meter-h); background: rgba(var(--fg-rgb), .15); }
.meter--large { --meter-h: 8px; --meter-tick: 26px; }   /* the one meter in a data hero */
.meter-fill { position: absolute; inset: 0 auto 0 0; width: var(--value, 0%); min-width: var(--meter-h); max-width: 100%; background: var(--status-ok); }
.meter--warning .meter-fill { background: var(--status-warning); }
.meter--critical .meter-fill { background: var(--status-critical); }
.meter-tick {
  position: absolute;
  left: var(--elapsed, 0%);
  top: 50%;
  width: 2px;
  height: var(--meter-tick);
  margin: calc(var(--meter-tick) / -2) 0 0 -1px;
  background: var(--fg);
  box-shadow: 0 0 0 1.5px var(--bg-theme);
  transform: skewX(var(--skew));
}
.status-word { font-weight: 700; font-stretch: 112%; color: var(--fg-muted); }
.status-word--warning { color: var(--status-warning); }
.status-word--critical { color: var(--status-critical); }

/* -----------------------------------------------------------------------------
   15. Legal prose and the leaning notice box (on geexer.nl: the "Concept" notice)
   ----------------------------------------------------------------------------- */
.legal { position: relative; max-width: var(--measure-legal); padding: clamp(2rem, 5vw, 4rem) var(--gutter) var(--section-y); }
.legal .meta { color: var(--fg-muted); margin-bottom: 2rem; }
.legal h2 { font-weight: 800; font-stretch: 118%; font-size: var(--fs-sub); line-height: 1.15; letter-spacing: -.01em; margin: 2.8rem 0 .9rem; }
.legal h3 { font-weight: 700; font-stretch: 112%; font-size: 1.125rem; line-height: 1.25; margin: 1.8rem 0 .5rem; }
.legal p { margin: 0 0 1em; }
.legal ul, .legal ol { padding-left: 1.4rem; margin: 0 0 1em; }
.legal ul { list-style: square; }
.legal ol { list-style: decimal; }
.legal li { margin-bottom: .5rem; }
.legal ul > li::marker { color: var(--accent); }
.legal ol > li::marker { color: var(--fg); font-weight: 700; }   /* numbers are text: never blue on white */
.legal dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: .4rem 1.5rem; margin: 0 0 1.2em; }
.legal dt { font-weight: 700; color: var(--fg); }
.legal dd { margin: 0; }
.legal code { color: var(--fg); }
.box { position: relative; isolation: isolate; padding: 1rem 1.4rem; margin: 0 0 2rem; }
.box::before { content: ""; position: absolute; inset: 0; z-index: -1; border: 1px solid var(--accent); transform: skewX(var(--skew)); }
.box p { margin: 0; color: var(--fg); }

/* -----------------------------------------------------------------------------
   16. Closing band: the black band a page ends in, before the footer. One display sentence, the
   primary button (yellow, it is black here), a second route. Markup:
   <section class="closing" data-theme="dark"><h2 class="section-title closing-title">...</h2>...</section>
   ----------------------------------------------------------------------------- */
.closing { position: relative; display: grid; justify-items: start; gap: clamp(2rem, 4vw, 3.25rem); padding: var(--section-y) var(--gutter) clamp(1.5rem, 3vw, 3rem); background: var(--bg-theme); color: var(--fg); }
.closing .closing-title { margin: 0; }

/* -----------------------------------------------------------------------------
   17. Footer: always black (<footer class="footer" data-theme="dark">). The wordmark between the
   gutters over a faint white lattice, four columns of 13px links under accent headings, a thin
   bottom row with slash separators.
   ----------------------------------------------------------------------------- */
.footer { position: relative; z-index: 1; background: var(--black); color: var(--white); }
.footer-wordmark { position: relative; overflow: hidden; padding: clamp(2.5rem, 6vw, 5.5rem) 0 clamp(2rem, 3.5vw, 3.5rem); }
.footer-wordmark::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--comb-white);
  background-size: var(--comb-size);
  background-position: 50% 100%;
  -webkit-mask-image: radial-gradient(ellipse 70% 90% at 50% 70%, black 30%, transparent 100%);
  mask-image: radial-gradient(ellipse 70% 90% at 50% 70%, black 30%, transparent 100%);
}
.footer-wordmark img { position: relative; display: block; width: calc(100% - 2 * var(--gutter)); height: auto; margin: 0 auto; }
.footer-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2.5rem var(--col-gap); padding: clamp(2rem, 4vw, 4rem) var(--gutter) clamp(2.5rem, 5vw, 5rem); }
.footer-col { display: grid; gap: .55rem; align-content: start; justify-items: start; font-weight: 600; font-stretch: 112%; font-size: var(--fs-ui); line-height: 1.4; }
.footer-col h2 { color: var(--blue); font-size: 1em; font-weight: 600; margin-bottom: .4rem; }
.footer-col a { color: rgba(255, 255, 255, .7); text-decoration: none; transition: color .25s; }
.footer-col a:hover,
.footer-col a:focus-visible { color: var(--white); }
.footer-col p { color: rgba(255, 255, 255, .7); }
.footer-col .status-pill { max-width: 100%; white-space: normal; color: var(--white); }
.footer-col .status-pill::before { border-color: rgba(255, 255, 255, .25); }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: .5rem 2rem;
  padding: 1.4rem var(--gutter) calc(1.4rem + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(255, 255, 255, .1);
  color: var(--slate);
  font-weight: 600;
  font-stretch: 112%;
  font-size: var(--fs-ui);
  line-height: 1.4;
}
.footer-bottom p { color: var(--slate); display: flex; flex-wrap: wrap; gap: .3rem 0; }
.footer-bottom span + span::before,
.footer-bottom a + a::before {
  content: "";
  display: inline-block;
  width: .4em;
  height: .75em;
  margin: 0 .7em;
  vertical-align: -.05em;
  background: rgba(255, 255, 255, .35);
  transform: skewX(var(--skew));
}
.footer-bottom a { color: var(--slate); text-decoration: none; transition: color .25s; }
.footer-bottom a:hover,
.footer-bottom a:focus-visible { color: var(--white); }
@media (max-width: 1279.98px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 899.98px) {
  .footer-bottom p { flex-direction: column; gap: .15rem; }
  .footer-bottom span + span::before,
  .footer-bottom a + a::before { display: none; }
}
@media (max-width: 559.98px) {
  .footer-grid > .footer-col:nth-child(n+3) { grid-column: 1 / -1; }
  .footer-bottom { flex-direction: column; }
}
@media (max-width: 639.98px) {
  .footer-col > a:not(.status-pill),
  .footer-bottom a { padding-block: .55rem; }   /* 44px pitch for 13px links */
}

/* -----------------------------------------------------------------------------
   18. Steps (a numbered process) and the 404
   ----------------------------------------------------------------------------- */
.steps { display: grid; }
.step { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "num title" "num text"; column-gap: clamp(1.5rem, 4vw, 4rem); row-gap: 1.2rem; align-items: center; padding: clamp(2rem, 4vw, 3.5rem) 0; border-top: 2px solid var(--fg); }
.step:first-child { border-top: 0; padding-top: 0; }
.step-num, .step-title { font-family: var(--font-display); font-weight: 900; font-stretch: 125%; text-transform: uppercase; line-height: .86; letter-spacing: -.02em; }
.step-num { grid-area: num; font-size: clamp(5rem, 16vw, 16rem); line-height: .8; color: var(--fg); }
.step-title { grid-area: title; align-self: end; font-size: clamp(1.8rem, 4.6vw, 4.5rem); color: var(--fg); }
.step-text { grid-area: text; align-self: start; max-width: 40ch; font-size: clamp(1.0625rem, 1.3vw, 1.25rem); font-weight: 500; color: var(--fg); }
@media (max-width: 899.98px) {
  .step { grid-template-columns: minmax(0, 1fr); grid-template-areas: "num" "title" "text"; row-gap: .8rem; }
  .step-num { font-size: clamp(4.5rem, 22vw, 9rem); }
}
.error-code { font-size: clamp(6rem, 26vw, 26rem); line-height: .8; color: var(--accent); }

/* -----------------------------------------------------------------------------
   19. Motion. Only under JavaScript (html.js) and only without a motion preference; otherwise
   everything is simply visible. Each plays once: the page is still within seconds.
   - .hero-title lines rise from their masks .1s apart, the copy around it fades up, the hero's
     buttons only rise (readable in the first frame).
   - [data-reveal]: fades up when it enters the viewport (js adds .is-in);
     [data-reveal="lines"]: its .line-inner children rise from their masks;
     [data-reveal="wipe"]: a picture is uncovered by a clip-path leaning like the slash.
   - [data-reveal-delay="1|2|3"] staggers siblings by .1s.
   Fill mode backwards, never both: a filled transform keeps a stacking context after the end.
   ----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  html.js .hero-title .line-inner { animation: line-up 1.1s var(--ease-out) backwards; animation-delay: calc(.15s + var(--i, 0) * .1s); }
  .hero-title .line:nth-child(2) .line-inner { --i: 1; }
  .hero-title .line:nth-child(3) .line-inner { --i: 2; }
  .hero-title .line:nth-child(4) .line-inner { --i: 3; }
  .hero-title .line:nth-child(5) .line-inner { --i: 4; }
  .hero-title .line:nth-child(6) .line-inner { --i: 5; }
  html.js :is(.hero-sub, .hero-meta) { animation: fade-up .5s var(--ease-out) backwards; animation-delay: calc(.15s + var(--j, 0) * .05s); }
  html.js .hero-actions { animation: rise-up .5s var(--ease-out) backwards; animation-delay: .2s; }
  .hero-meta { --j: 2; }
  @keyframes line-up { from { transform: translateY(110%) skewY(6deg); } }
  @keyframes fade-up { from { opacity: 0; transform: translateY(16px); } }
  @keyframes rise-up { from { transform: translateY(16px); } }

  html.js [data-reveal] { opacity: 0; transform: translateY(28px); transition: opacity .9s var(--ease-out), transform .9s var(--ease-out); }
  html.js [data-reveal].is-in { opacity: 1; transform: none; }
  html.js [data-reveal="lines"] { opacity: 1; transform: none; transition: none; }
  html.js [data-reveal="lines"] .line-inner { transform: translateY(110%) skewY(6deg); transition: transform 1.1s var(--ease-out); }
  html.js [data-reveal="lines"] .line:nth-child(2) .line-inner { transition-delay: .1s; }
  html.js [data-reveal="lines"] .line:nth-child(3) .line-inner { transition-delay: .2s; }
  html.js [data-reveal="lines"] .line:nth-child(4) .line-inner { transition-delay: .3s; }
  html.js [data-reveal="lines"].is-in .line-inner { transform: none; }
  html.js [data-reveal="wipe"] {
    --wipe-s: 21%;
    opacity: 1;
    transform: none;
    clip-path: polygon(-10% -10%, -10% -10%, calc(-10% - var(--wipe-s)) 110%, calc(-10% - var(--wipe-s)) 110%);
    transition: clip-path .9s var(--ease-out);
  }
  html.js [data-reveal="wipe"].is-in { clip-path: polygon(-10% -10%, calc(110% + var(--wipe-s)) -10%, 110% 110%, -10% 110%); }
  html.js [data-reveal][data-reveal-delay="1"] { transition-delay: .1s; }
  html.js [data-reveal][data-reveal-delay="2"] { transition-delay: .2s; }
  html.js [data-reveal][data-reveal-delay="3"] { transition-delay: .3s; }
  html.js .footer-wordmark img { opacity: 0; transform: translateY(25%); transition: opacity 1.1s var(--ease-out), transform 1.1s var(--ease-out); }
  html.js .footer-wordmark.is-in img { opacity: 1; transform: none; }
}

/* -----------------------------------------------------------------------------
   20. Reduced motion, print and forced colours
   ----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
  .status-dot::after { display: none; }
  .marquee-track { transform: none !important; }
}
@media print {
  #site-header, #menu-overlay, .world, .hero-actions, .footer-wordmark, .skip-link, .marquee { display: none !important; }
  html, body { background: white !important; color: black !important; }
  * { color: black !important; }
  .hero { min-height: 0; padding-top: 2rem; }
  .footer, .closing, [data-theme] { background: white !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; }
  .meter, .meter * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .meter { background: none; outline: 1px solid black; }
  .meter-fill { background: black !important; }                         /* no yellow on paper */
  .meter--critical .meter-fill { background: var(--red-dark) !important; }
  .meter-tick { background: black; box-shadow: 0 0 0 1.5px white; }
}
/* Windows contrast mode drops backgrounds: the meter keeps an edge, its fill and its tick */
@media (forced-colors: active) {
  .meter { outline: 1px solid CanvasText; }
  .meter .meter-fill { forced-color-adjust: none; background: Highlight; }   /* beats the state colours; the word carries the state */
  .meter-tick { forced-color-adjust: none; background: CanvasText; box-shadow: 0 0 0 1.5px Canvas; }
}
