/* District Supply, self-hosted. Both cuts are single weight — no wght or wdth
   axis — so hierarchy here comes from size, colour and tracking, never weight.
   font-synthesis is off so nothing fakes a bold and smears the face. */
@font-face {
  font-family: "District Supply";
  src: url("../fonts/DistrictSupply-Weathered.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
/* the clean cut, declared and ready — swap the name in --sans to use it */
@font-face {
  font-family: "District Supply Clean";
  src: url("../fonts/DistrictSupply-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

/* ==========================================================================
   KhLabs — Ink & Machine
   White paper, one ink field. Nothing is filled with colour — accent is a
   contour: three concentric rings, green then yellow then red, drawn outward
   from the edge. The seam between the halves carries down as the centre line
   of the work grid.
   ========================================================================== */

:root {
  --paper:      #FFFFFF;
  --paper-lift: #F4F4F2;   /* tile backs, behind loading media */
  --ink:        #1C1B18;   /* dense, faintly warm — black ink, not UI grey */
  --ash:        #5C5C56;

  /* the signature contour — these only ever outline, never fill */
  --green:  #278E43;
  --yellow: #FEBD11;
  --red:    #ED2024;

  --ring-lg: 0 0 0 4px var(--green), 0 0 0 8px var(--yellow), 0 0 0 12px var(--red);
  --ring-sm: 0 0 0 2px var(--green), 0 0 0 4px var(--yellow), 0 0 0  6px var(--red);
  --ring-0:  0 0 0 0   var(--green), 0 0 0 0   var(--yellow), 0 0 0  0   var(--red);

  --underline: linear-gradient(90deg, var(--green) 0 33.33%,
                               var(--yellow) 33.33% 66.66%, var(--red) 66.66%);

  --rule:       color-mix(in srgb, var(--ink) 14%, transparent);
  --rule-inv:   color-mix(in srgb, var(--paper) 22%, transparent);

  --nav-h:      54px;
  --peek:       46px;      /* how much of the work grid shows under the hero */
  --pad:        clamp(18px, 3.4vw, 48px);

  --sans: "District Supply Clean", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --ease: cubic-bezier(.22, .61, .36, 1);
}

/* --------------------------------------------------------------- reset --- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  font-synthesis: none;
  font-size: 15px;
  line-height: 1.58;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, svg, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
h1, h2, h3, p, figure, blockquote, ul { margin: 0; padding: 0; }
ul { list-style: none; }

/* green is the innermost ring, and reads on both the paper and the ink half */
:focus-visible { outline: 3px solid var(--green); outline-offset: 2px; }

/* the rings arrive in order: green, then yellow past it, then red past that */
@keyframes ring-lg {
  0%   { box-shadow: var(--ring-0); }
  38%  { box-shadow: 0 0 0 4px var(--green), 0 0 0 4px var(--yellow), 0 0 0 4px var(--red); }
  70%  { box-shadow: 0 0 0 4px var(--green), 0 0 0 8px var(--yellow), 0 0 0 8px var(--red); }
  100% { box-shadow: var(--ring-lg); }
}
@keyframes ring-sm {
  0%   { box-shadow: var(--ring-0); }
  38%  { box-shadow: 0 0 0 2px var(--green), 0 0 0 2px var(--yellow), 0 0 0 2px var(--red); }
  70%  { box-shadow: 0 0 0 2px var(--green), 0 0 0 4px var(--yellow), 0 0 0 4px var(--red); }
  100% { box-shadow: var(--ring-sm); }
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--paper); padding: 10px 16px;
}
.skip:focus { left: 8px; top: 8px; }

/* ------------------------------------------------------------ typography - */
.u-label {                    /* metadata: set by width, not by tracked caps */
  font-size: 12px;
  letter-spacing: .045em;
  color: var(--ash);
}
.u-prose p { max-width: 62ch; margin-bottom: 1.1em; }
.u-prose p:last-child { margin-bottom: 0; }
/* a quieter aside under the main copy — set apart by a rule, not a box */
.u-prose .note {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
  font-size: 13px;
  line-height: 1.6;
  color: var(--ash);
  max-width: 66ch;
}
.u-prose a {
  color: var(--ink);
  padding-bottom: 2px;
  background: var(--underline) no-repeat 0 100% / 100% 2px;
}

/* ------------------------------------------------------------------ nav --- */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--nav-h);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0 var(--pad);
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid var(--rule);
}
/* On the landing page the nav is withheld until the work appears. */
.nav[data-reveal="scroll"] {
  transform: translateY(-100%);
  opacity: 0;
  transition: transform .34s var(--ease), opacity .2s linear;
  pointer-events: none;
}
.nav[data-reveal="scroll"].is-on { transform: none; opacity: 1; pointer-events: auto; }

.nav__set { display: flex; gap: 22px; align-items: center; }
.nav__link {
  font-size: 14px;
  letter-spacing: .012em;
  padding: 4px 0;
  border-bottom: 1px solid transparent;
  transition: border-color .18s linear, color .18s linear;
}
.nav__link:hover { border-bottom-color: var(--ink); }
.nav__link[aria-current="page"] {
  border-bottom-color: transparent;
  background: var(--underline) no-repeat 0 100% / 100% 2px;
}

.nav__mark img { height: 17px; width: auto; }

/* language, top right of the bar. The chosen one wears a filled pill — two
   shades of grey next to each other did not read as a state at a glance. */
.lang {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 2px;
  font-size: 12.5px;
  letter-spacing: .06em;
}
.lang__opt {
  line-height: 1;
  border-radius: 999px;
  padding: 9px 10px 6px;      /* lopsided on purpose: see the tag note */
  color: var(--ash);
  transition: color .18s linear, background-color .18s linear;
}
.lang__opt:hover { color: var(--ink); }
.lang__opt[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* ----------------------------------------------------------------- hero --- */
.hero {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--rule);
}
/* Beide Hälften im selben Format wie die Projektkacheln weiter unten: 16:9,
   oben an der Seite verankert statt über die ganze Bildschirmhöhe. Dadurch
   steht das Raster schon beim Laden im Bild, statt erst nach einem Scroll. */
.hero__paper, .hero__ink {
  aspect-ratio: 16 / 9;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(24px, 4vw, 56px);
  min-width: 0;
  min-height: 0;     /* sonst drückt der Inhalt das Verhältnis auseinander */
  overflow: hidden;
}
.hero__paper {
  align-items: center;
  text-align: center;
  border-right: 1px solid var(--rule);   /* the seam */
}
.hero__ink {
  background: var(--ink);
  align-items: flex-start;
  padding-inline: clamp(26px, 4.2vw, 64px);
  /* enger als die Papierhälfte: dort steht nur das Zeichen und darf atmen,
     hier muss ein Zitat von rund 300 Zeichen in die feste Höhe passen */
  padding-block: clamp(20px, 2.6vw, 38px);
}

/* --- the mark ------------------------------------------------------------- */
.mark { width: min(78%, 500px); height: auto; }
.mark .glyph { fill: var(--ink); }
.mark .plate-c, .mark .plate-m { opacity: 0; }

/* Flicker states — swapped in briefly by JS, like a press hiccup.
   Add your own as .mark.v-yourname and list it in LOGO.variants (main.js). */
.mark.v-outline .glyph { fill: none; stroke: var(--ink); stroke-width: 9; }
.mark.v-offset .plate-c { opacity: 1; fill: var(--green); mix-blend-mode: multiply; }
.mark.v-offset .plate-m { opacity: 1; fill: var(--red);   mix-blend-mode: multiply; }
.mark.v-offset .glyph   { opacity: .82; }
.mark.v-ghost  .glyph   { opacity: .12; }

.tagline {
  margin-top: clamp(24px, 3.2vw, 38px);
  font-size: 17px;
  letter-spacing: .05em;
  color: var(--ash);
}

/* --- testimonials, as a deck ----------------------------------------------
   Every card sits in the same grid cell, so the deck is as tall as the longest
   quote. Depth is one custom property; JS only ever changes --p.
   ------------------------------------------------------------------------ */
.quotes { width: 100%; max-width: 33rem; }

.deck {
  display: grid;
  padding-right: 36px;       /* room for the two cards showing alongside */
  touch-action: pan-y;
  cursor: grab;
}
.deck.is-dragging { cursor: grabbing; }

/* The deck steps sideways: each card behind sits a little to the right and a
   little smaller, so its right edge shows past the one in front. main.js sets
   transform and opacity directly — driving them from a custom property leaves
   Chrome rendering each card one step behind its actual depth. */
.card {
  grid-area: 1 / 1;
  background: var(--paper);
  color: var(--ink);
  border-radius: 16px;
  padding: clamp(26px, 2.7vw, 36px);
  transform-origin: 50% 50%;
  transition: transform .5s var(--ease), opacity .35s linear;
  will-change: transform;
}
.card:not([data-front]) { pointer-events: none; }

/* without JS the cards would all pile into the one grid cell */
html:not(.js) .card:not(:first-child) { display: none; }

/* the outgoing card is thrown off, then re-joins the back of the deck */
.card.is-out { transition: transform .42s cubic-bezier(.4, 0, .2, 1), opacity .3s linear; }
.card.is-still { transition: none; }

/* Die Zitate stehen in einer 16:9-Hälfte, also in fester Höhe. Die Größe ist
   darauf abgestimmt: ein Zitat von rund 300 Zeichen muss hineinpassen, ohne
   dass der Rest abgeschnitten wird. */
.card__text {
  font-size: clamp(14px, 1.15vw, 16.5px);
  line-height: 1.5;
  letter-spacing: -.003em;
  text-wrap: pretty;
}
.card__by {
  margin-top: clamp(12px, 1.3vw, 16px);
  font-size: 12.5px;
  line-height: 1.4;
}
.card__by span { display: block; color: var(--ash); }

.quotes__foot { margin-top: 12px; }
.quotes__dots { display: flex; gap: 7px; }
.dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--rule-inv);
  transition: background-color .2s linear, transform .2s var(--ease);
}
.dot[aria-selected="true"] { background: var(--paper); transform: scale(1.28); }

/* ----------------------------------------------------------------- work --- */
.work { display: grid; grid-template-columns: 1fr 1fr; }

/* Die Navigation liegt fest über der Seite. Auf den Unterseiten schafft die
   .page-Hülle den nötigen Abstand; auf der Startseite beginnt das Raster
   direkt unter <main>, und ohne das hier verschwände seine erste Reihe
   darunter. Die versteckte Überschrift davor zählt nicht — sie ist aus dem
   Fluss genommen, weshalb :first-child hier nicht greift. */
main > .work { margin-top: var(--nav-h); }
.page .work, .page + .work { margin-top: 0; }
/* 16:9 and edge to edge, after bureauborsche.com: the previews butt against
   each other with no rule between them, so the grid reads as one surface
   rather than a table of framed pictures. */
.tile {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--paper-lift);
}
.tile:nth-child(2n) { border-right: 0; }
.tile__media { width: 100%; height: 100%; object-fit: cover; transition: transform .55s var(--ease); }
.tile:hover .tile__media,
.tile:focus-visible .tile__media { transform: scale(1.025); }

/* ------------------------------------------------------- cycling tile ---
   Several views of one project, fading through each other in place. They are
   stacked rather than swapped so nothing reflows between frames, and the
   crossfade is long enough to read as one image changing rather than as a
   slideshow clicking over. */
.frames { position: absolute; inset: 0; background: #0B0B0A; }

/* Stacked, not faded. Every frame stays fully opaque and the current one is
   simply lifted above the others, so there is never a moment with no picture
   on the tile — which is what the white flash was: a frame whose pixels the
   browser had dropped while it was invisible, showing the pale tile back
   underneath until it had decoded them again.
   Keeping them all painted also keeps them all decoded. */
.frames .frame {
  position: absolute;
  inset: 0;
  z-index: 0;
  transition: none;
}
.frames .frame.is-on { z-index: 1; }

/* and a dark back behind them, so even the first paint has nothing pale to
   show through */
.tile--frames { background: #0B0B0A; }

/* No zoom under the pointer either: the tile is already moving, and a second
   motion on top of it only muddies what you are looking at. */
.tile--frames:hover .tile__media,
.tile--frames:focus-visible .tile__media { transform: none; }

/* ------------------------------------------------- Stimmen als Kachel ---
   Dieselbe Fläche wie ein Projekt, nur ohne Bild: dunkel, mit dem Zitat
   darin. Dadurch bleibt das Raster ein Raster — die Stimmen sind ein Feld
   darin, kein eigener Abschnitt davor. */
.tile--quotes {
  background: var(--ink);
  color: var(--paper);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;      /* die schmalere Karte mittig im Feld */
  padding: clamp(14px, 1.4vw, 18px);
  cursor: default;
}
/* Schmaler als die Kachel, dafür hoch: eine breite, flache Karte zwingt das
   Zitat in sehr lange Zeilen, die sich schlecht lesen. Bei dieser Breite
   bricht der Text früher um, die Karte füllt die Höhe, und das Ganze wirkt
   wie ein Zettel im Feld statt wie ein Banner darüber. */
.tile--quotes .quotes { max-width: 27rem; }
.tile--quotes .deck { align-content: stretch; }
.tile--quotes .card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
/* Im Karussell deutet der Rand rechts die nächste Karte an. In der Kachel
   gibt es nur zwei Stimmen und keinen Überblendrand — dort liest sich der
   Streifen als Fehler statt als Hinweis. */
.tile--quotes .deck { padding-right: 0; }

/* 16:9 als Mindestmaß, nicht als Fessel.
   Ein Projektbild verträgt jedes Format — es wird beschnitten. Ein Zitat von
   300 Zeichen nicht: in einer schmalen Spalte braucht es mehr Höhe, sonst
   wird der Schluss abgeschnitten. Die Kachel ist deshalb mindestens so hoch
   wie ein Projekt daneben, damit die Reihe bündig steht, und wächst darüber
   hinaus, wenn der Text es verlangt. */
.tile--quotes {
  aspect-ratio: auto;
  min-height: calc((100vw / 2) * 9 / 16);
  overflow: visible;
}
@media (max-width: 640px) {
  .tile--quotes { min-height: calc(100vw * 9 / 16); }
}

.tile__tags { position: absolute; top: 14px; left: 14px; z-index: 2; display: flex; gap: 6px; }

/* Tags sit on artwork we do not control, so each carries its own contrast.
   District Supply has an asymmetric ascent/descent (13/7 against a 9.46 cap at
   12.5px), which leaves text riding 3px high in a pill. The vertical padding is
   deliberately lopsided to sit the cap band on the pill's centre line. */
.tag {
  border-radius: 999px;
  text-align: center;
  padding: 8px 13px 3px;      /* +3 top / -3 bottom: see note above */
  font-size: 12.5px;
  line-height: 1.25;
  letter-spacing: .012em;
  white-space: nowrap;
  transition: background-color .18s linear, color .18s linear,
              border-color .18s linear, box-shadow .2s linear;
}
.tag--client { background: var(--paper); color: var(--ink);   border: 1px solid var(--ink);
               box-shadow: var(--ring-0); }
.tag--kind   { background: var(--ink);   color: var(--paper); border: 1px solid var(--paper); }
/* the hairline steps aside the moment the contour arrives — matching the tag's
   own fill rather than going transparent, so no gap opens under the ring */
.tile:hover .tag--client,
.tile:focus-visible .tag--client {
  border-color: var(--paper);
  animation: ring-sm .42s var(--ease) forwards;
}

/* ------------------------------------------------------- contact button --- */
.fab {
  position: fixed;
  right: clamp(24px, 2.4vw, 34px);    /* clearance for the 12px contour */
  bottom: clamp(24px, 2.4vw, 34px);
  z-index: 120;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 15.5px 22px 10.5px;   /* lopsided on purpose: see the tag note */
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font-size: 15px;
  letter-spacing: .012em;
  box-shadow: var(--ring-lg);
  transition: transform .2s var(--ease);
}
/* with JS the contour draws itself on once the hero has settled */
.js .fab { box-shadow: var(--ring-0); animation: ring-lg .62s var(--ease) 1.2s forwards; }
.fab:hover { transform: translateY(-2px); }
.fab:active { transform: translateY(0); }

/* --------------------------------------------------------- contact sheet -- */
.sheet {
  width: min(520px, calc(100vw - 32px));
  max-height: min(88dvh, 780px);
  padding: 0;
  border: 0;
  border-radius: 20px;
  background: var(--paper);
  color: var(--ink);
  overflow: auto;
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .4);
}
.sheet::backdrop { background: rgba(18, 18, 16, .55); backdrop-filter: blur(3px); }
.sheet__inner { padding: clamp(24px, 3.4vw, 36px); }
.sheet__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.sheet__title {
  font-size: clamp(22px, 2.4vw, 28px);
  letter-spacing: -.014em;
}
.sheet__lede { margin-top: 8px; color: var(--ash); max-width: 42ch; }
.sheet__x {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  display: grid; place-items: center;
  color: var(--ash);
  transition: background-color .18s linear, border-color .18s linear, color .18s linear;
}
/* The cross is drawn, not typed. District Supply sets × high in its line box,
   so centring the box left the glyph visibly above the middle of the circle —
   and a pixel nudge would only have been right at one font size. */
.sheet__x svg {
  width: 13px; height: 13px;
  stroke: currentColor; stroke-width: 1.5; stroke-linecap: round;
  fill: none;
}
.sheet__x:hover { background: var(--paper-lift); border-color: var(--ink); color: var(--ink); }

/* step 1: pick the kind of enquiry */
.sheet__back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 10px;
  font-size: 12.5px;
  letter-spacing: .03em;
  color: var(--ash);
  transition: color .18s linear;
}
.sheet__back:hover { color: var(--ink); }

.picker { margin-top: 26px; display: grid; gap: 12px; }
.pick {
  display: grid;
  gap: 4px;
  text-align: left;
  padding: 18px 20px 20px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  box-shadow: var(--ring-0);
  transition: border-color .18s linear, box-shadow .2s linear;
}
/* A card carrying only a title has to centre that one line, and District
   Supply's asymmetric metrics leave it ~4px high. The card with a description
   balances as a block instead, so it keeps the even padding. */
.pick:not(:has(.pick__d)) { padding-top: 22px; padding-bottom: 16px; }
.pick:hover { border-color: var(--ink); }
.pick:focus-visible { outline: none; border-color: var(--ink); animation: ring-sm .42s var(--ease) forwards; }
.pick__t { font-size: 17px; letter-spacing: -.01em; }
.pick__d { font-size: 13.5px; line-height: 1.5; color: var(--ash); }

.form { margin-top: 26px; display: grid; gap: 15px; }
.field { display: grid; gap: 6px; }
.field > span {
  font-size: 12.5px;
  letter-spacing: .03em;
  color: var(--ash);
}
.field input, .field select, .field textarea {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--paper);
  font-size: 15px;
  box-shadow: var(--ring-0);
  transition: border-color .16s linear, box-shadow .2s linear;
}
.field textarea { min-height: 118px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: 0;
  border-color: var(--ink);
  animation: ring-sm .42s var(--ease) forwards;
}
.field--pair { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }

.form__send {
  margin-top: 4px;
  padding: 14px 22px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 15px;
  letter-spacing: .012em;
  transition: background-color .18s linear;
}
.form__send { box-shadow: var(--ring-0); transition: box-shadow .2s linear; }
.form__send:hover { animation: ring-sm .42s var(--ease) forwards; }
.form__note { font-size: 12.5px; color: var(--ash); }
.form__note[data-state="error"] { color: var(--red); }
.form__note[data-state="ok"]    { color: var(--green); }

/* ------------------------------------------------------- the lead form ----
   The project enquiry is a qualification with a booking at the end, not a
   contact form. Three panes: the easy question first, the personal details
   last. Only one pane is in the DOM flow at a time, so the sheet keeps its
   own height and nothing jumps. */

.lead { margin-top: 20px; }
.lead__progress {
  margin-top: 10px;
  font-size: 12px;
  letter-spacing: .045em;
  color: var(--ash);
}

/* the honeypot. Off-screen rather than display:none — see the markup note */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* a pane. The fieldset itself stays a plain block: a grid or flex fieldset
   puts the legend in the wrong place in Safari. The grid lives one level in. */
.pane { border: 0; margin: 0; padding: 0; min-width: 0; }
.pane__legend { display: block; width: 100%; padding: 0; }
.pane__h {
  font-size: 17px;
  letter-spacing: -.01em;
  line-height: 1.3;
}
.pane__h:focus { outline: none; }
.pane__h:focus-visible { outline: 3px solid var(--green); outline-offset: 4px; }
.pane__body { display: grid; gap: 15px; margin-top: 16px; }

/* pane 1 — cards, not a dropdown: one click, no keyboard */
.choices { display: grid; gap: 10px; }
.choice {
  display: block;
  padding: 15px 18px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  font-size: 16px;
  letter-spacing: -.005em;
  cursor: pointer;
  box-shadow: var(--ring-0);
  transition: border-color .18s linear, background-color .18s linear, box-shadow .2s linear;
}
.choice input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.choice:hover { border-color: var(--ink); }
.choice:has(:checked) { border-color: var(--ink); background: var(--paper-lift); }
.choice:has(:focus-visible) { border-color: var(--ink); animation: ring-sm .42s var(--ease) forwards; }

/* a labelled group of pills — budget, start */
.group { border: 0; margin: 0; padding: 0; min-width: 0; }
.group > legend {
  padding: 0;
  font-size: 12.5px;
  letter-spacing: .03em;
  color: var(--ash);
}
.group__hint { margin-top: 2px; font-size: 12.5px; color: var(--ash); }
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 9px; }
.chip {
  padding: 9px 15px 8px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: 14px;
  cursor: pointer;
  box-shadow: var(--ring-0);
  transition: border-color .18s linear, background-color .18s linear, color .18s linear, box-shadow .2s linear;
}
.chip input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.chip:hover { border-color: var(--ink); }
.chip:has(:checked) { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.chip:has(:focus-visible) { border-color: var(--ink); animation: ring-sm .42s var(--ease) forwards; }

/* optional is a state of the field, not a separate line of copy */
.field > span em {
  font-style: normal;
  color: color-mix(in srgb, var(--ash) 62%, transparent);
}
.field textarea[rows] { min-height: 0; }

/* errors say what to do, and only appear once the field has been left */
.field__err {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--red);
}
.field__count { font-size: 12px; color: var(--ash); justify-self: end; }
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--red); }

.consent {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 10px;
  align-items: start;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ash);
  cursor: pointer;
}
.consent input {
  width: 17px; height: 17px;
  margin: 2px 0 0;
  accent-color: var(--ink);
  cursor: pointer;
}
.consent a {
  color: var(--ink);
  padding-bottom: 2px;
  background: var(--underline) no-repeat 0 100% / 100% 2px;
}

.cf:not([hidden]) { min-height: 65px; }

.form__send[disabled] { opacity: .55; cursor: default; }
.form__send[disabled]:hover { animation: none; box-shadow: var(--ring-0); }

/* --- the calendar, once the lead is already saved -------------------------- */
.cal {
  margin-top: 22px;
  min-height: 300px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  overflow: hidden;
  background: var(--paper-lift);
}
.cal iframe { display: block; width: 100%; border: 0; }
.cal__note {
  padding: 26px 20px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ash);
  text-align: center;
}
.cal__note a {
  color: var(--ink);
  padding-bottom: 2px;
  background: var(--underline) no-repeat 0 100% / 100% 2px;
}
.lead__skip {
  margin-top: 16px;
  font-size: 13px;
  letter-spacing: .02em;
  color: var(--ash);
  transition: color .18s linear;
}
.lead__skip:hover { color: var(--ink); }

/* --------------------------------------------------------------- pages --- */
.page {
  padding: calc(var(--nav-h) + clamp(56px, 9vw, 128px)) var(--pad) clamp(64px, 10vw, 140px);
  max-width: 1180px;
}
.page__title {
  font-size: clamp(38px, 6.6vw, 82px);
  line-height: 1.02;
  letter-spacing: -.018em;
  max-width: 16ch;
  text-wrap: balance;
}
.page__lede {
  margin-top: clamp(22px, 3vw, 34px);
  font-size: clamp(17px, 1.7vw, 21px);
  line-height: 1.5;
  max-width: 52ch;
  text-wrap: pretty;          /* keeps a short lede off a one-word last line */
  color: color-mix(in srgb, var(--ink) 76%, var(--paper));
}
.page--legal { max-width: 720px; }
/* the headings are the same weight as the text, so the text steps back a tone */
.page--legal .u-prose p { color: color-mix(in srgb, var(--ink) 78%, var(--paper)); }
.page--legal h2 {
  font-size: 17px;
  letter-spacing: .008em;
  margin: 44px 0 12px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}
.page--legal h2:first-of-type { margin-top: 48px; }

.slab {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  gap: clamp(16px, 4vw, 56px);
  padding: clamp(32px, 4.4vw, 54px) 0;
  border-top: 1px solid var(--rule);
}
.slab:first-of-type { margin-top: clamp(48px, 7vw, 90px); }

.services { display: grid; gap: 26px; }

/* Numbered markers, because this genuinely is a sequence — first you sign up,
   then you introduce someone, then you get paid. */
.steps {
  counter-reset: step;
  display: grid;
  gap: 24px;
  list-style: none;
}
.step {
  display: grid;
  grid-template-columns: 1.6rem minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
.step::before {
  counter-increment: step;
  content: counter(step);
  font-size: 12.5px;
  letter-spacing: .045em;
  color: var(--ash);
  padding-top: .45em;
}
.step h3 {
  font-size: clamp(17px, 1.7vw, 20px);
  letter-spacing: -.012em;
}
.step p {
  margin-top: 5px;
  max-width: 54ch;
  color: color-mix(in srgb, var(--ink) 74%, var(--paper));
}
.service h3 {
  font-size: clamp(20px, 2.2vw, 27px);
  letter-spacing: -.012em;
}
.service p { margin-top: 7px; max-width: 54ch; color: color-mix(in srgb, var(--ink) 74%, var(--paper)); }

/* --------------------------------------------------------------- footer --- */
.footer {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px 30px;
  min-height: 44px;
  /* the same padding the floating card uses for its offsets, so that when it
     comes to rest here it lands exactly where it was hovering */
  padding: 12px var(--pad) var(--pad);
  border-top: 1px solid var(--rule);
  font-size: 12.5px;
  letter-spacing: .02em;
  color: var(--ash);
}
/* stacked and starting at the top edge, so the legal links read as a short
   column rather than a line trailing along the bottom */
.footer__legal { display: grid; }
.footer nav { display: flex; flex-direction: column; gap: 5px; }

/* A notch paler than the links above it: this is a statement of fact, not
   somewhere to click, and it should not compete with them for attention. */
.footer__copy {
  margin-top: 10px;
  color: color-mix(in srgb, var(--ash) 58%, var(--paper));
}
.footer a { transition: color .18s linear; }
.footer nav a:hover { color: var(--ink); }

/* ------------------------------------------------------------- vcard ---
   Who to talk to, and how. It floats bottom right while there is still page
   to read, then comes to rest in the footer — the same idea as the contact
   button it replaces, but saying who is on the other end.

   The landing is seamless rather than animated: the floating offsets and the
   footer's padding are both var(--pad), so the card occupies the same spot
   in either state and switching between them moves nothing. */

/* The card stays pinned to the bottom whatever the links do: its resting
   place has to keep matching where it floats, or the landing gains a jump. */
.vcard-slot { margin-left: auto; align-self: flex-end; }

.vcard {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  width: min(272px, calc(100vw - var(--pad) * 2));
  padding: 10px;
  border-radius: 14px;
  /* so the colour seam is cut by the card's own corners instead of running
     straight past them */
  overflow: hidden;
  background: var(--ink);
  color: var(--paper);
  /* Anchored at the corner it sits in, so growing on hover pushes it up and
     to the left rather than out past the edge of the window. */
  transform-origin: 100% 100%;
  transition: transform .2s var(--ease), box-shadow .2s var(--ease);
  /* The card is dark and so are the hero and the work grid it floats over,
     where a shadow alone leaves it dissolving into the background. The inset
     hairline draws the edge on those; the shadows do the work on paper. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--paper) 15%, transparent),
              0 1px 2px rgba(0,0,0,.10),
              0 12px 32px -8px rgba(0,0,0,.30);
}

/* the signature contour, as a seam along the top rather than a ring — a
   12px halo around something this size would shout */
.vcard::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: var(--underline);
  z-index: 1;
}

/* A touch bigger under the pointer — enough to say "this is a thing you can
   use", not enough to shove the page around. Focus-within counts too, so
   reaching it by keyboard lifts it the same way. Pointer devices only: on a
   touchscreen :hover sticks after a tap and the card would stay enlarged. */
@media (hover: hover) {
  .vcard:hover,
  .vcard:focus-within {
    transform: scale(1.035);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--paper) 22%, transparent),
                0 2px 4px rgba(0,0,0,.12),
                0 18px 40px -10px rgba(0,0,0,.38);
  }
}

@media (prefers-reduced-motion: reduce) {
  .vcard { transition: none; }
  .vcard:hover, .vcard:focus-within { transform: none; }
}

/* A square, kept square by aspect-ratio rather than by whatever height the
   text column happens to have — a portrait stretched to fill a taller box
   crops to a letterbox of a face. It is a tile in its own right, with its own
   corners, so the leftover millimetres beside it read as spacing rather than
   as the picture having come up short. */
.vcard__face {
  width: 84px;
  aspect-ratio: 1;
  height: auto;
  object-fit: cover;
  object-position: 50% 18%;   /* the face sits high in the frame */
  border-radius: 9px;
  background: var(--paper-lift);
}

.vcard__body { padding: 0 2px 0 11px; min-width: 0; }

.vcard__label {
  font-size: 9.5px;
  line-height: 1.25;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 52%, transparent);
}
.vcard__name {
  font-size: 14.5px;
  line-height: 1.3;
  letter-spacing: -.005em;
  color: var(--paper);
}

.vcard__lines { margin-top: 7px; display: grid; gap: 0; }
.vcard__line {
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: .004em;
  color: color-mix(in srgb, var(--paper) 78%, transparent);
  transition: color .16s linear;
  overflow-wrap: anywhere;
}
.vcard__line:hover { color: var(--paper); }

.vcard__cta {
  margin-top: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 7px 14px 5px;        /* lopsided like the fab: see the tag note */
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font-size: 12.5px;
  letter-spacing: .012em;
  transition: box-shadow .2s var(--ease), transform .2s var(--ease);
}
.vcard__cta:hover { transform: translateY(-1px); box-shadow: var(--ring-sm); }
.vcard__cta:active { transform: translateY(0); }

/* Floating, until the footer catches up with it. Only with scripting, and
   only where there is room: on a phone the card stays in the footer and the
   small round button keeps doing the floating. */
@media (min-width: 900px) {
  .js .vcard-slot { min-height: var(--vcard-h, 0px); }
  .js .vcard--float {
    position: fixed;
    right: var(--pad);
    bottom: var(--pad);
    z-index: 120;
  }
  .js .fab { display: none; }
}

/* On a phone the button floats and the card sits in the footer, so while the
   footer is on screen the two would overlap. */
.is-docked .fab { opacity: 0; pointer-events: none; }
.fab { transition: transform .2s var(--ease), opacity .2s linear; }

/* ----------------------------------------------------------- responsive --- */
@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; min-height: 0; }
  .hero__paper {
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    min-height: calc(58dvh - var(--peek));
    padding-block: clamp(52px, 11vw, 88px);
  }
  .hero__ink { min-height: 46dvh; padding-block: clamp(40px, 8vw, 64px); }
  .mark { width: min(70%, 330px); }
  .quotes { max-width: none; }
  .slab { grid-template-columns: 1fr; gap: 14px; }
}

@media (max-width: 640px) {
  .work { grid-template-columns: 1fr; }
  .tile { aspect-ratio: 16 / 9; }
  /* Ausnahme, die nach .tile stehen muss, um zu greifen: die Stimmenkachel
     hält 16:9 nur als Mindestmaß — Text lässt sich nicht beschneiden wie ein
     Bild. */
  .tile--quotes { aspect-ratio: auto; min-height: calc(100vw * 9 / 16); }
  .nav { grid-template-columns: auto 1fr auto; gap: 12px; }
  .nav__set { gap: 15px; }
  .nav__mark { justify-self: center; }
  .lang { gap: 5px; font-size: 12.5px; }
  /* The card lives in the footer here and the round button hides behind it,
     so the old clearance for that button is just a gap now. */
  .footer { padding-right: var(--pad); }
  .vcard-slot { width: 100%; }
  .field--pair { grid-template-columns: 1fr; }
  .chips { gap: 7px; }
  .chip { padding: 9px 13px 8px; font-size: 13.5px; }
  .cal { min-height: 340px; }
  .fab { padding: 12px 18px; }
  /* the sheet becomes a bottom sheet on a phone */
  .sheet {
    width: 100vw;
    max-width: 100vw;
    margin: auto 0 0;
    max-height: 92dvh;
    border-radius: 20px 20px 0 0;
  }
}

/* ------------------------------------------------------- reduced motion --- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* The way out, said plainly rather than tucked under the field in grey. This
   is the sentence that rescues somebody whose week is full, and a hint they
   skim past does not rescue anybody. The rule on the left carries the brand's
   three colours, which is what makes it read as something to notice rather
   than another line of small print. */
.field__callout {
  position: relative;
  margin-top: 8px;
  padding: 10px 0 10px 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ash);
}
/* --underline runs left to right, so a vertical border cut from it shows only
   the first third — green. This one runs top to bottom instead. */
.field__callout::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: linear-gradient(180deg, var(--green) 0 33.33%,
              var(--yellow) 33.33% 66.66%, var(--red) 66.66%);
}
