/* Varve design system — flattened drop-in stylesheet.
   Generated from styles.css + tokens/*.css. Upload as one file and link it
   FIRST in <head>, before your own site CSS:
     <link rel="stylesheet" href="/css/varve.css">
   Everything else in your site can then reference the custom properties
   (var(--red-600), var(--font-mono), var(--space-5), …). */

@import url("https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,300..800;1,300..800&family=JetBrains+Mono:ital,wght@0,300..700;1,300..700&family=Silkscreen:wght@400;700&display=swap");

/* ===== tokens/fonts.css ===== */
/* Varve webfonts.
   NOTE: no font binaries were supplied with the source material. These are
   Google Fonts substitutions chosen to match the moodboard's technical-plate
   grotesque + data mono + bitmap display voices. Replace with licensed files
   when available; keep the token names. */

:root {
  --font-sans: "Archivo", "Helvetica Neue", Helvetica, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --font-pixel: "Silkscreen", "Archivo", monospace;
  --font-body: var(--font-sans);
  --font-display: var(--font-sans);
  --font-label: var(--font-mono);
  --font-mark: var(--font-pixel);
}

/* ===== tokens/colors.css ===== */
/* Varve colour. Monochrome substrate + one brand red + a narrow "signal" set
   lifted from the glitch/bitmap art. Signals are for data, states and art
   surfaces only — never for large fields of UI chrome. */
:root {
  /* Ink — warm-neutral greys running to near-black */
  --ink-900: #0a0a0a;
  --ink-800: #141414;
  --ink-700: #1f1f1f;
  --ink-600: #333333;
  --ink-500: #565656;
  --ink-400: #7a7a7a;
  --ink-300: #a3a3a3;
  --ink-200: #c9c7c2;
  --ink-100: #e4e2dc;

  /* Paper — warm off-whites */
  --paper-100: #f2f0ea;
  --paper-050: #f8f7f3;
  --paper-000: #fbfaf7;
  --white: #ffffff;

  /* Brand red — sampled from the Varve mark (rgb 168 32 32) */
  --red-700: #7a1717;
  --red-600: #a82020;
  --red-500: #c43535;
  --red-300: #e08a8a;
  --red-100: #f4dede;

  /* Signal — from the dither/datamosh plates. Sparingly, at small sizes. */
  --signal-acid: #c8e600;
  --signal-cyan: #14c8f0;
  --signal-magenta: #f5137f;
  --signal-blue: #2b2be0;

  /* Semantic — text */
  --text-primary: var(--ink-900);
  --text-secondary: var(--ink-600);
  /* ink-500, niet ink-400: dit token draagt de 10–11px mono labels en
     metadata, en ink-400 haalt op papier maar 4.0:1 — onder de AA-grens.
     ink-400 blijft wel in gebruik als lichtgrijs OP inkt (zie --ink-400
     directe consumers), waar het 4.6:1 haalt. */
  --text-muted: var(--ink-500);
  --text-inverse: var(--paper-000);
  --text-accent: var(--red-600);
  --text-link: var(--red-600);
  --text-link-hover: var(--red-700);

  /* Semantic — surface */
  --surface-page: var(--paper-050);
  --surface-card: var(--white);
  --surface-sunken: var(--paper-100);
  --surface-inverse: var(--ink-900);
  --surface-accent: var(--red-600);
  --surface-hover: var(--paper-100);
  --surface-active: var(--ink-100);

  /* Semantic — line */
  --border-hairline: var(--ink-200);
  --border-strong: var(--ink-900);
  --border-accent: var(--red-600);
  --border-inverse: var(--ink-600);

  /* Semantic — state */
  --state-focus: var(--red-600);
  --state-danger: var(--red-600);
  --state-ok: #1d7a3f;
  --state-warn: #a86a00;
  --state-disabled-fg: var(--ink-300);
  --state-disabled-bg: var(--paper-100);
}

[data-theme="dark"] {
  --text-primary: var(--paper-000);
  --text-secondary: var(--ink-300);
  /* ink-500, niet ink-400: dit token draagt de 10–11px mono labels en
     metadata, en ink-400 haalt op papier maar 4.0:1 — onder de AA-grens.
     ink-400 blijft wel in gebruik als lichtgrijs OP inkt (zie --ink-400
     directe consumers), waar het 4.6:1 haalt. */
  --text-muted: var(--ink-500);
  --text-inverse: var(--ink-900);
  --surface-page: var(--ink-900);
  --surface-card: var(--ink-800);
  --surface-sunken: #050505;
  --surface-inverse: var(--paper-000);
  --surface-hover: var(--ink-700);
  --surface-active: var(--ink-600);
  --border-hairline: var(--ink-600);
  --border-strong: var(--ink-100);
  --border-inverse: var(--ink-200);
  --state-disabled-fg: var(--ink-500);
  --state-disabled-bg: var(--ink-700);
}

/* ===== tokens/typography.css ===== */
/* Varve type. Small, tight, technical. Labels are mono and uppercase-tracked;
   display is grotesque set tight; the pixel face is reserved for the mark and
   for single-word interjections. */
:root {
  --text-2xs: 10px;
  --text-xs: 11px;
  --text-sm: 13px;
  --text-base: 15px;
  --text-md: 17px;
  --text-lg: 21px;
  --text-xl: 28px;
  --text-2xl: 38px;
  --text-3xl: 54px;
  --text-4xl: 76px;
  --text-5xl: 108px;

  --leading-flush: 0.92;
  --leading-tight: 1.05;
  --leading-snug: 1.25;
  --leading-body: 1.5;
  --leading-loose: 1.7;

  --tracking-tight: -0.02em;
  --tracking-flat: 0;
  --tracking-label: 0.08em;
  --tracking-wide: 0.16em;

  --weight-light: 300;
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  /* Composed roles */
  --type-display: var(--weight-semibold) var(--text-4xl) / var(--leading-flush) var(--font-display);
  --type-title: var(--weight-semibold) var(--text-2xl) / var(--leading-tight) var(--font-display);
  --type-heading: var(--weight-semibold) var(--text-lg) / var(--leading-snug) var(--font-display);
  --type-body: var(--weight-regular) var(--text-base) / var(--leading-body) var(--font-body);
  --type-body-sm: var(--weight-regular) var(--text-sm) / var(--leading-body) var(--font-body);
  --type-label: var(--weight-medium) var(--text-xs) / var(--leading-snug) var(--font-mono);
  --type-data: var(--weight-regular) var(--text-sm) / var(--leading-snug) var(--font-mono);
  --type-mark: var(--weight-regular) var(--text-md) / var(--leading-tight) var(--font-pixel);
}

/* ===== tokens/spacing.css ===== */
/* Varve space. 4px base; the 8px grid unit is the visible module — rules,
   gutters and card padding all land on it, like the specimen plates. */
:root {
  --grid-unit: 8px;
  --space-0: 0;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;
  --space-10: 128px;

  --gutter: var(--space-5);
  --pad-card: var(--space-5);
  --pad-card-tight: var(--space-4);
  --pad-control-x: var(--space-4);
  --pad-control-y: var(--space-2);

  --control-h-sm: 26px;
  --control-h-md: 34px;
  --control-h-lg: 44px;

  --measure: 66ch;
  --measure-narrow: 48ch;
  --page-max: 1280px;
  --rail-w: 248px;
}

/* ===== tokens/borders.css ===== */
/* Varve line & depth. Corners are square by default — the pixel grid does not
   round. Depth is a hard offset block, never a soft blur; the one soft shadow
   is reserved for lifted overlays. */
:root {
  --radius-0: 0px;
  --radius-sm: 2px;
  --radius-md: 4px;
  --radius-pill: 999px;
  --radius-default: var(--radius-0);

  --stroke-hair: 1px;
  --stroke-mid: 2px;
  --stroke-heavy: 3px;

  --border-hair: var(--stroke-hair) solid var(--border-hairline);
  --border-rule: var(--stroke-hair) solid var(--border-strong);
  --border-block: var(--stroke-mid) solid var(--border-strong);

  --shadow-none: none;
  --shadow-hard: 4px 4px 0 var(--ink-900);
  --shadow-hard-sm: 2px 2px 0 var(--ink-900);
  --shadow-hard-accent: 4px 4px 0 var(--red-600);
  --shadow-overlay: 0 18px 48px rgba(10, 10, 10, 0.22);

  --focus-ring: 0 0 0 2px var(--surface-card), 0 0 0 4px var(--state-focus);

  /* Surface textures lifted from the source art */
  /* Image-only values, so they are valid in background-image as well as in the
     background shorthand. Pair each with its companion --*-size token:
       background-image: var(--grid-overlay); background-size: var(--grid-overlay-size); */
  --dither-50: repeating-conic-gradient(var(--ink-900) 0% 25%, transparent 0% 50%);
  --dither-50-size: 2px 2px;
  --scanline: repeating-linear-gradient(to bottom, rgba(10, 10, 10, 0.06) 0 1px, transparent 1px 3px); /* @kind other */
  --grid-overlay: linear-gradient(to right, var(--border-hairline) 1px, transparent 1px),
    linear-gradient(to bottom, var(--border-hairline) 1px, transparent 1px);
  --grid-overlay-size: var(--grid-unit) var(--grid-unit);
}

/* ===== tokens/motion.css ===== */
/* Varve motion. Short, mechanical, no bounce. Things cut or step; they do not
   spring. The one expressive move is a 1-frame offset "snap" on press. */
:root {
  --dur-instant: 60ms; /* @kind other */
  --dur-fast: 110ms; /* @kind other */
  --dur-base: 180ms; /* @kind other */
  --dur-slow: 320ms; /* @kind other */
  --dur-scan: 900ms; /* @kind other */

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* @kind other */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* @kind other */
  --ease-step: steps(4, end); /* @kind other */
  --ease-linear: linear; /* @kind other */

  --transition-control: color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-instant: 0ms; /* @kind other */
    --dur-fast: 0ms; /* @kind other */
    --dur-base: 0ms; /* @kind other */
    --dur-slow: 0ms; /* @kind other */
  }
}

/* ===== tokens/base.css ===== */
/* Minimal element defaults so a bare page already reads as Varve. */
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--surface-page);
  color: var(--text-primary);
  font: var(--type-body);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}
h1, h2, h3, h4 { margin: 0; font-family: var(--font-display); letter-spacing: var(--tracking-tight); }
h1 { font: var(--type-display); letter-spacing: var(--tracking-tight); }
h2 { font: var(--type-title); letter-spacing: var(--tracking-tight); }
h3 { font: var(--type-heading); }
p { margin: 0 0 var(--space-4); max-width: var(--measure); }
a { color: var(--text-link); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
a:hover { color: var(--text-link-hover); text-decoration-thickness: 2px; }
code, kbd, samp { font-family: var(--font-mono); font-size: 0.92em; }
hr { border: 0; border-top: var(--border-hair); margin: var(--space-6) 0; }
::selection { background: var(--red-600); color: var(--paper-000); }
:focus-visible { outline: none; box-shadow: var(--focus-ring); }


/* ---------- mozaïek ------------------------------------------------------- */
/* Een band waarin niet elke tegel even groot is: staand beeld krijgt een hogere
   tegel dan liggend, en een uitgelichte tegel wordt twee kolommen breed. Begon
   bij de Instagram-band — daar staat 9:16 naast 16:9 naast vierkant — maar het
   is een opmaakpatroon van de hub, niet van die band, dus staat het hier en
   niet in ig/feed.css.

   De rijhoogte is één eenheid: een zesde van een gewone liggende tegel. Alles
   telt daarin op, dus de tegels blijven op hetzelfde raster staan.

       liggend     6 eenheden   beeld 16:9
       vierkant    9            beeld 1:1
       staand     12            beeld 3:4
       groot       twee kolommen breed; bij staand de volle 9:16-hoogte

   Waarom zesden en niet halven, wat het eerst was: vulStaart() hieronder rekt de
   onderste tegel van een kolom op om de trap onderaan weg te werken, en met
   halven was de kleinste stap die hij kon zetten meteen +50% hoogte. Een
   16:9-screenshot in de Projecten-band kwam zo op 0.99 breed-op-hoog uit — het
   beeld staat op cover, dus daar was de helft van weg. In zesden is de kleinste
   stap +17% en blijft het onderwerp staan.

   grid-auto-flow:dense laat een korte tegel terugvallen in een gat dat een hoge
   buurman openliet. De rafel die onderaan overblijft wordt door vulStaart() in
   ig/feed.js rechtgetrokken — dezelfde regel die de vensterlaag bij een gewone
   band toepast, alleen naar beneden in plaats van opzij.

   Een element kan zijn eigen container niet bevragen, dus staat container-type
   op de laag erboven: de sectie bij een hub-band, de feed-wortel bij Instagram. */
.v-band--mozaiek,
.ig-feed--mozaiek { container-type: inline-size; }

.v-mozaiek {
  --v-kolommen: 3;
  --v-kol: calc((100cqw - (var(--v-kolommen) - 1) * var(--space-4)) / var(--v-kolommen));
  /* De tekst onder het beeld: meta-rij, titel en beschrijving.

     Dit getal moet kloppen met wat de tekst wérkelijk hoog is, want het raster
     rekent zijn rijhoogte eruit en het beeld krijgt wat er overblijft. Stond er
     6,6rem terwijl een beschrijving van drie regels 144px hoog werd, dan ging
     dat verschil regelrecht van de foto af: gemeten 172px beeld op een kolom van
     393 breed, oftewel verhouding 2,3 waar 16:9 bedoeld was. De tegel werd tekst
     met een reepje beeld erboven.

     Daarom twee dingen tegelijk: de beschrijving wordt op twee regels afgekapt
     (hieronder), en dit getal is de hoogte die daarbij hoort. Nu klopt de
     reservering en landt het beeld precies op zijn bedoelde verhouding. */
  --v-lijf: 7.8rem;
  /* De kier tussen de rijen telt mee in de hoogte van een tegel: wie zes rijen
     beslaat krijgt er vijf kieren bij. Met een eenheid van een zesde loopt dat
     op tot ruim honderd pixels, en dan is een vierkante hoes ineens staand
     (gemeten: 0.76 in plaats van 1.0). Vandaar dat de vijf kieren van een
     liggende tegel vooraf van de eenheid af gaan; bij de hogere tegels blijft
     er een halve tot een hele kier speling over, en dat zie je niet. */
  --v-eenheid: calc((var(--v-kol) * 9 / 16 + var(--v-lijf) - 5 * var(--space-4)) / 6);
  grid-auto-rows: var(--v-eenheid);
  grid-auto-flow: row dense;
}
/* Het kolomaantal staat op drie plekken: hier en in de twee breekpunten die
   grid-template-columns omzetten. Lopen die uit elkaar, dan rekent het mozaïek
   met een andere kolombreedte dan er staat en wordt een staande tegel op een
   telefoon ineens liggend. In de vensterlaag telt --os-kol, en die weegt
   zwaarder dan de breekpunten. */
@media (max-width: 900px) { .v-mozaiek { --v-kolommen: 2; } }
@media (max-width: 560px) { .v-mozaiek { --v-kolommen: 1; } }
body.os-bureau .v-mozaiek { --v-kolommen: var(--os-kol, 3); }

/* Het beeld neemt wat de tegel overhoudt, niet de tekst. Grid en geen flex: de
   vensterlaag zet `body.os-labels .v-tile .v-body { flex: 1 1 auto }` en dat
   weegt zwaarder dan wat we hier op de tekst kunnen zetten — beeld en tekst
   deelden de ruimte dan alsnog, en een hoge tegel kreeg een grijze plaat onder
   het beeld. In een grid pakt de eerste rij de ruimte en is de tweede zo hoog
   als zijn inhoud. De absoluut geplaatste Beeld-knop van Studio telt niet mee. */
/* De tegelsoorten staan met naam in de selector, en niet als `> *`. Dat is geen
   netheid maar noodzaak: `.v-tile { display: flex }` staat in de pagina zelf en
   `.ig-tegel` in ig/feed.css, allebei ná dit bestand geladen. Eén klasse tegen
   één klasse wint dan de laatste, en de tegel bleef flex — met een beeld dat tot
   niets inzakte en de tekst bovenin. Twee klassen halen dat binnen. */
.v-mozaiek > .v-tile,
.v-mozaiek > .ig-tegel { grid-row: span 6; display: grid; grid-template-rows: minmax(0, 1fr) auto; }
/* Een leeg vak. Geen rand, geen vulling, niets om aan te wijzen — het is er
   alleen om het raster níét te laten sluiten, zodat je op een paar plekken door
   de band heen het doek ziet bewegen. De vormklassen hieronder gelden ook voor
   hem: die staan op .is-vierkant en niet op .v-tile. */
.v-mozaiek > .v-gat { grid-row: span 6; pointer-events: none; }

/* De naam van de band staat in het raster, in de eerste kolom. Laag — drie
   eenheden — zodat er naast en onder hem gewoon tegels staan en de categorie zich
   aandient in plaats van zich af te scheiden. */
.v-mozaiek > .v-band-title {
  grid-column: 1;
  grid-row: span 3;
  display: flex;
  align-items: flex-end;
  margin: 0;
}

.v-mozaiek > .is-vierkant { grid-row: span 9; }
.v-mozaiek > .is-staand   { grid-row: span 12; }
/* Ook hier drie klassen en niet twee: `.ig-feed--band .ig-beeld` zet 16:9 en
   staat in ig/feed.css, dat ná dit bestand komt. Met gelijke specificiteit hield
   elke Instagram-tegel zijn liggende bak, hoe staand het beeld ook was. */
.v-mozaiek > .v-tile > .v-shot,
.v-mozaiek > .ig-tegel > .ig-beeld { aspect-ratio: auto; min-height: 0; }

/* Het matglas onder de tegel komt uit varve-os.css (body.os-labels): de kaart is
   doorzichtig, de beeldstrook houdt zijn eigen ondergrond en het tekstvlak is een
   waas. Wat daar doorheen schemert is het doek op de achtergrond, niet de foto —
   het beeld blijft in zijn eigen vak staan, want dat vak is op zijn verhouding
   gekozen en eroverheen groeien betekent eraf snijden. */

/* Uitgelicht. Bij liggend en vierkant is dat twee kolommen breed; bij staand
   heeft breder geen zin — twee kolommen op ware 9:16 wordt een torenhoge tegel
   — dus daar betekent groot: de volle hoogte, onafgesneden. */
.v-mozaiek > .is-groot               { grid-column: span 2; }
.v-mozaiek > .is-groot.is-liggend    { grid-row: span 9; }
/* 15 en niet 12: twee kolommen breed vraagt ook twee kolommen hoogte, anders
   wordt een vierkante hoes een liggende strook en snijdt hij een kwart van zijn
   eigen beeld weg. Gemeten op de SoundCloud-band: 12 gaf 823x614 (1.34), 15 geeft
   823x798 (1.03). */
.v-mozaiek > .is-groot.is-vierkant   { grid-row: span 15; }
.v-mozaiek > .is-groot.is-staand     { grid-column: auto; grid-row: span 15; }

/* De beschrijving op twee regels. Niet om de tekst te knijpen maar om de tegel
   te laten kloppen: zolang de ene kaart één regel draagt en de andere drie, is
   er geen maat te reserveren die voor allebei klopt, en betaalt het beeld het
   verschil. Twee regels is genoeg om te zeggen waar iets over gaat; de rest
   staat achter de tegel. */
.v-mozaiek .v-desc,
.v-mozaiek .ig-tekst {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* In een staande tegel past staand beeld gewoon, dus daar hoeft de pillarbox van
   .v-shot--reel niet: die zette het beeld op ware hoogte met zwart ernaast. */
.v-mozaiek .v-shot--reel img {
  inset: 0;
  left: auto;
  width: 100%;
  height: 100%;
  transform: none;
  object-fit: cover;
}

/* Smal scherm: twee kolommen voor één tegel is dan de halve band of meer. Zakt
   een uitgelichte tegel terug naar één kolom, dan moet ook zijn extra hoogte mee
   terug — anders houdt hij de hoogte van een dubbelbrede tegel op enkele
   breedte en staat een liggende post ineens rechtop. */
@container (max-width: 34rem) {
  .v-mozaiek > .is-groot             { grid-column: auto; }
  .v-mozaiek > .is-groot.is-liggend  { grid-row: span 6; }
  .v-mozaiek > .is-groot.is-vierkant { grid-row: span 9; }
}
