/* raster.css — RASTER D for the browser. Draft (voorzet), 2026-09-15.
 *
 * The whole type system is the four declarations of spec/DESIGN-D.md:
 *
 *     font: 400 20px/24px "IBM Plex Mono"   →  advance 0.6em = 12px, line 1.2em = 24px
 *     cell 12 × 24 · macro 24 × 24 · subcell 12 × 12
 *
 * Everything else here is tokens and a handful of patterns. Two rules drive the shape:
 *
 *  - Polarity is ONE switch (DECISIONS B2). Every colour is `--rs-ink` or `--rs-paper`;
 *    fills, textures and box-drawing are *masks* over `--rs-ink`, never images with a
 *    colour baked in. `.rs-dark` swaps the two tokens and every pattern inverts exactly.
 *  - Graphics live on the macro square, not in the font (DECISIONS D2, E4). Plex Mono's own
 *    box glyphs are 1.3em tall and draw a 1.36px light rule; see web/spike/FINDINGS.md.
 *    `raster.js` repaints box, block and shade characters as masked 24×24 cells while the
 *    character itself stays in the DOM, so the text is still the canonical string (E2).
 */

/* Metrics overrides make the font's content area exactly the 24px line box: ascent 20px,
   descent 4px, no half-leading, baseline on a whole pixel. Every face on a line must agree,
   so the blocks fonts below carry the same metrics natively (tools/blocks_font.py). */
@font-face {
	font-family: "IBM Plex Mono";
	font-weight: 400;
	font-display: block; /* a fallback face silently breaks the 12px advance */
	ascent-override: 100%; descent-override: 20%; line-gap-override: 0%;
	src: url("fonts/plex-mono-Regular-latin1.woff2") format("woff2");
}
@font-face {
	font-family: "IBM Plex Mono";
	font-weight: 600;
	font-display: block;
	ascent-override: 100%; descent-override: 20%; line-gap-override: 0%;
	src: url("fonts/plex-mono-SemiBold-latin1.woff2") format("woff2");
}
/* Box, block and shade glyphs inside Plex Mono text come from a 1KB font whose outlines
   are whole pixels at 20px. Declared after the Plex faces: for U+2500–259F it wins.
   Descriptors must match the Plex faces exactly, or the browser treats them as a separate
   face instead of one composite (a 400–600 range silently dropped Plex to a fallback). */
@font-face {
	font-family: "IBM Plex Mono";
	font-weight: 400;
	font-display: block;
	unicode-range: U+2500-259F;
	src: url("fonts/raster-blocks-text.woff2") format("woff2");
}
@font-face {
	font-family: "IBM Plex Mono";
	font-weight: 600;
	font-display: block;
	unicode-range: U+2500-259F;
	src: url("fonts/raster-blocks-text.woff2") format("woff2");
}
/* Graphics tier: the same glyphs on the 24×24 macro square (DECISIONS D2). */
@font-face {
	font-family: "RASTER Blocks Macro";
	font-display: block;
	src: url("fonts/raster-blocks-macro.woff2") format("woff2");
}

:root {
	/* lattice */
	--rs-cw: 12px;       /* text cell width  = 0.6em */
	--rs-ch: 24px;       /* text cell height = 1.2em */
	--rs-macro: 24px;    /* graphics cell    = 2 × cw */
	--rs-sub: 12px;      /* subcell          = cw     */

	/* tone — canonical light (spec/DESIGN.md, emissive light) */
	--rs-ink: #1A1A17;
	--rs-paper: #F5F2E8;

	/* rule ladder (DECISIONS: 1 field · 2 group · 3 section · 6 masthead) */
	--rs-rule-field: 1px;
	--rs-rule-group: 2px;
	--rs-rule-section: 3px;
	--rs-rule-masthead: 6px;

	/* weights (DECISIONS C3) */
	--rs-regular: 400;
	--rs-bold: 600;
}

/* Polarity: the exact complement, one switch. Emissive dark from spec/DESIGN.md. */
.rs-dark {
	--rs-ink: #E6E2D6;
	--rs-paper: #1C1B18;
	background: var(--rs-paper);
	color: var(--rs-ink);
}

*, *::before, *::after { box-sizing: border-box; border-radius: 0; }

.rs {
	margin: 0;
	background: var(--rs-paper);
	color: var(--rs-ink);
	font: var(--rs-regular) 20px/var(--rs-ch) "IBM Plex Mono", monospace;
	font-variant-numeric: tabular-nums;
	font-kerning: none;
	-webkit-font-smoothing: antialiased;  /* macOS Chrome/Safari dilate glyph edges otherwise: */
	-moz-osx-font-smoothing: grayscale;   /* a grey 1px fringe on every rule. Same for Firefox. */
	font-variant-ligatures: none;
}
.rs b, .rs strong, .rs th { font-weight: var(--rs-bold); }

/* User-agent defaults are the first thing the lint catches (p margins are 1em = 20px, off
   the lattice). Zero them; vertical rhythm comes from whole-cell gaps only. */
.rs :is(p, h1, h2, h3, h4, ul, ol, dl, dd, figure, pre, blockquote, table) { margin: 0; padding: 0; font: inherit; }

/* A fluid page snaps its width down to whole cells, so its right edge is on the lattice too. */
.rs-page { inline-size: round(down, 100%, var(--rs-cw)); }
.rs-stack { display: flex; flex-direction: column; gap: var(--rs-ch); }

/* Blocks. Sizes are whole cells; rules are drawn *inside* the box (outline + inset
   offset) so they never push content off the lattice. */
.rs-block   { display: block; margin: 0; padding: 0; }
.rs-panel   { position: relative; padding: var(--rs-ch) var(--rs-cw); outline: var(--rs-rule-group) solid var(--rs-ink); outline-offset: calc(-1 * var(--rs-rule-group)); }
.rs-invert  { background: var(--rs-ink); color: var(--rs-paper); }
.rs-label   { font-weight: var(--rs-bold); text-transform: uppercase; }
.rs-rot     { display: inline-block; transform: rotate(-90deg); } /* labels only, whole block (D3) */

/* Graphics on the macro square, no JavaScript: plain text in the macro font. */
.rs .rs-macro { display: block; margin: 0; white-space: pre; font-family: "RASTER Blocks Macro", "IBM Plex Mono", monospace; }

/* Fallback kept for comparison (web/spike/boxdraw.html): raster.js wraps each character of .rs-graphic in
   <span class="rs-m" data-g="…">; the mask for data-g is injected by raster.js. */
.rs-graphic { display: block; margin: 0; white-space: pre; line-height: var(--rs-macro); font: inherit; }
.rs-m {
	display: inline-block;
	inline-size: var(--rs-macro);
	block-size: var(--rs-macro);
	vertical-align: top;
	overflow: hidden;
	color: transparent;            /* the glyph stays selectable and copyable */
	background: var(--rs-ink);
	mask-repeat: no-repeat;
	mask-size: var(--rs-macro) var(--rs-macro);
}
.rs-m[data-g=" "] { background: transparent; }
