/* Nobedan — components.
 *
 * The named pieces of the spec-sheet grammar: display moments, numeric
 * matter, the price block, full-bleed figures, the colophon. Everything here
 * assumes the page grid and rule system in layout.css.
 */

/* ── Display moments ──────────────────────────────────────────────────────
 * Grand does the drama by size and by the weight axis. 1980 Black Wide is
 * rationed to about three moments per artifact: the title, the price, one
 * reveal. Never a text face, never two moments side by side.
 */

.display {
	font-family: var(--font-grand);
	font-size: var(--text-display);
	font-weight: var(--weight-light);
	line-height: var(--leading-display);
	letter-spacing: var(--track-display);
	max-width: none;
	text-wrap: balance;
}

.display--thin {
	font-weight: var(--weight-thin);
}

.display--regular {
	font-weight: var(--weight-regular);
}

.display--medium {
	font-weight: var(--weight-medium);
}

.display--bold {
	font-weight: var(--weight-bold);
}

.moment {
	font-family: var(--font-1980);
	font-weight: 800;
	font-size: var(--text-mega);
	line-height: var(--leading-mega);
	letter-spacing: var(--track-mega);
	max-width: none;
	/* 1980 is wide: a long word at a small viewport has nowhere to go, so
	 * moments are short by construction. Belt and braces. */
	overflow-wrap: break-word;
	hyphens: none;
}

.moment--caps {
	text-transform: uppercase;
}

.moment--accent {
	color: var(--accent);
}

/* ── The hero register — the one sanctioned exception ─────────────────────
 * Outside the design elements (masthead, colophon, the rationed 1980
 * moments) the document has three sizes: caption, body, heading. `.hero` is
 * the single treatment allowed to leave them, and it does so by the same
 * 1.5 ratio — one step above heading, 2.25x body. It is copy, not a
 * heading: the question a section opens with, the sentence a section rests
 * on, the number on a card. One per section at most; if a page wants two
 * heroes in a row, one of them is a lede.
 *
 * Sizes between body and hero are the failure this replaces: a paragraph
 * "a bit bigger and a bit lighter" than body reads as a mistake, not as
 * emphasis.
 */
.hero {
	font-size: var(--text-hero);
	font-weight: var(--weight-light);
	line-height: var(--leading-head);
	letter-spacing: var(--track-head);
	max-width: var(--measure);
	text-wrap: balance;
}

/* A lede is heading size set as copy — Light, so it never competes with a
 * heading of the same size. It is on the scale, not beside it. */
.lede {
	font-size: var(--text-heading);
	font-weight: var(--weight-light);
	line-height: var(--leading-tight);
	letter-spacing: var(--track-head);
	max-width: var(--measure);
}

/* The footnote-admission voice: caption size, muted, never boxed. */
.note {
	font-size: var(--text-caption);
	line-height: var(--leading-tight);
	color: var(--ink-muted);
	max-width: var(--measure);
}

.eyebrow {
	font-size: var(--text-caption);
	font-weight: var(--weight-bold);
	letter-spacing: var(--track-label);
	text-transform: uppercase;
	color: var(--ink-muted);
}

/* ── Numbers ──────────────────────────────────────────────────────────────
 * Grand's figures are uniform width in every weight on hand (0.600em; Bold
 * 0.5996em), so columns of digits align natively — verified with fontTools,
 * 2026-08-24. Punctuation is *not* uniform (the comma is 0.144em in Regular
 * and 0.233em in Bold), so a numeric column must hold one weight throughout
 * and align right. Both rules are baked in here.
 */

.num {
	font-variant-numeric: tabular-nums lining-nums;
	text-align: right;
	white-space: nowrap;
}

/* A ledger is the rule laws applied to a table. Every rule rides above the
 * line it introduces — the column heads included, which is why their rule is
 * on top of them and there is none beneath: an underlined header row is an
 * underline, and the language has none. Cells are adjacent, so a rule
 * crosses the whole table without a break. Rank is width, ink is ink.
 */
.ledger {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--text-body);
	line-height: var(--leading-tight);
	font-weight: var(--weight-regular);
}

.ledger th,
.ledger td {
	text-align: left;
	vertical-align: baseline;
	padding-block: var(--space-xs) var(--space-s);
	border-top: var(--rule-hairline-width) solid var(--rule-color);
}

.ledger td + td,
.ledger th + th,
.ledger th + td,
.ledger td + th {
	padding-left: var(--space-m);
}

.ledger thead th {
	border-top: var(--rule-medium-width) solid var(--rule-color);
	border-bottom: none;
	padding-block: var(--space-2xs);
	font-size: var(--text-caption);
	font-weight: var(--weight-bold);
	letter-spacing: var(--track-label);
	text-transform: uppercase;
	color: var(--ink-muted);
}

.ledger .num,
.ledger th.num {
	text-align: right;
	font-variant-numeric: tabular-nums lining-nums;
	white-space: nowrap;
}

.ledger tbody th {
	font-weight: var(--weight-medium);
}

.ledger tfoot td,
.ledger tfoot th {
	border-top: var(--rule-medium-width) solid var(--rule-color);
	font-weight: var(--weight-medium);
}

/* ── The nested ledger ────────────────────────────────────────────────────
 * Hierarchy inside a table is indentation, and the rule follows the indent:
 * it starts at the horizontal position of the line it introduces and runs to
 * the end of the row. As the indent deepens the rule gets thinner, so depth
 * is legible from the rules alone.
 *
 *   ────────────────────────────────────────────────  depth 0, medium
 *   Pricing   Month 1                        $30,000
 *             ──────────────────────────────────────  depth 1, hairline
 *             Month 2                        $30,000
 *             ──────────────────────────────────────
 *             Month 3   Week 1               $10,000
 *                       ──────────────────────────── depth 2, fine
 *                       Week 2               $10,000
 *             ──────────────────────────────────────  total, back to medium
 *             Total                          $90,000
 *
 * Markup: one `tr` per line, `class="depth-N"`, with the cells before the
 * indent left empty so the columns stay locked. The total row takes
 * `.ledger-total` on top of its depth, which restores the heavier weight —
 * a total closes a block, so it is a step up, not a step down.
 */

.ledger--nested tbody tr > *,
.ledger--nested tfoot tr > * {
	border-top: var(--rule-hairline-width) solid var(--rule-color);
}

/* Depth 0 opens the table (or a new top-level block): full width, medium. */
.ledger--nested tr.depth-0 > * {
	border-top-width: var(--rule-medium-width);
}

/* A nested ledger's first body row opens the block, whether or not the table
 * bothers with depth classes — so a flat table gets the grammar for free. */
.ledger--nested tbody tr:first-child > * {
	border-top-width: var(--rule-medium-width);
}

/* Unless a header row already drew that opening rule, in which case the first
 * body row does not draw a second one under it. */
.ledger--nested thead + tbody tr:first-child > * {
	border-top: none;
}

/* Depth 1 starts one column in. */
.ledger--nested tr.depth-1 > *:nth-child(1) {
	border-top: none;
}

/* Depth 2 starts two columns in, at the finest weight. */
.ledger--nested tr.depth-2 > *:nth-child(-n + 2) {
	border-top: none;
}

.ledger--nested tr.depth-2 > *:nth-child(n + 3) {
	border-top-width: var(--rule-fine-width);
}

/* A total closes a block, so it is a step back up in weight. Its indent is
 * whatever depth class it also carries; the depth rules above already clear
 * the leading cells, and they outrank this one. */
.ledger--nested tr.ledger-total > *,
.ledger--nested tfoot tr > * {
	border-top-width: var(--rule-medium-width);
}

/* One weight per numeric column, the total included: Grand's figures are
 * uniform width in every weight but its `$` and comma are not, so a Medium
 * total steps out of the column it totals. The rule above it and its own
 * label are what mark it. */
.ledger--nested tfoot .num {
	font-weight: var(--weight-regular);
}

/* Wide numeric matter on a narrow screen scrolls in its own box rather than
 * shoving the page sideways. */
.scroll-x {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

/* A squeezed ledger is worse than a scrolled one: below this width the cells
 * wrap to three lines each and the spec-sheet register collapses. Hold the
 * floor and let the box scroll. */
.scroll-x > .ledger {
	min-width: 30rem;
}

/* Prose directly under a table — the terms under a fee schedule. The last
 * row's own padding is measured against the row above it, not against a
 * paragraph, so the stack's default gap reads as a fourth table row. This
 * is the gap that says the table has ended. */
.ledger + p,
.row-body > .ledger + p,
.row-body > .scroll-x + p {
	margin-top: var(--space-m);
}

/* ── The price, printed ───────────────────────────────────────────────── */

.price {
	display: grid;
	gap: var(--space-s);
}

.price-figure {
	font-size: var(--text-display);
	font-weight: var(--weight-thin);
	line-height: 0.85;
	letter-spacing: var(--track-display);
	font-variant-numeric: tabular-nums lining-nums;
}

.price-figure .amount {
	display: block;
}

/* The unit sits on its own line: a display figure and a body-size phrase
 * cannot share a line at 0.85 leading without tearing a hole in the block. */
.price-figure .unit {
	display: block;
	margin-top: var(--space-2xs);
	font-size: var(--text-heading);
	font-weight: var(--weight-regular);
	line-height: var(--leading-tight);
	letter-spacing: var(--track-head);
}

.serial {
	font-size: var(--text-caption);
	font-weight: var(--weight-bold);
	letter-spacing: var(--track-label);
	text-transform: uppercase;
	font-variant-numeric: tabular-nums lining-nums;
}

/* ── Figures ──────────────────────────────────────────────────────────────
 * Every full-bleed photograph shows work being done; photos are
 * interruptions between ruled sections, never decoration inside one.
 */

figure {
	margin: 0;
}

figure.bleed img,
figure.bleed .figure-ground {
	width: 100%;
	display: block;
}

/* Stand-in for an image while the photo pull is outstanding: an ink field
 * at a fixed ratio, so layout can be judged before the photographs land. */
.figure-ground {
	aspect-ratio: 16 / 9;
	background: var(--ink);
}

figcaption {
	max-width: calc(var(--sheet-max) + 2 * var(--page-margin));
	margin-inline: auto;
	padding: var(--space-xs) var(--page-margin) var(--space-m);
	font-size: var(--text-caption);
	font-weight: var(--weight-bold);
	letter-spacing: var(--track-label);
	text-transform: uppercase;
	color: var(--ink-muted);
	line-height: var(--leading-tight);
}

figcaption .caption-text {
	display: block;
	font-weight: var(--weight-regular);
	letter-spacing: var(--track-body);
	text-transform: none;
	font-size: var(--text-caption);
	color: var(--ink-muted);
	margin-top: var(--space-3xs);
	max-width: var(--measure);
}

/* ── Cards — the graphic register ─────────────────────────────────────────
 * A card is a graphic, not three more paragraphs: a numeral, one word
 * naming it, and a short note read after the number rather than instead of
 * it. Two consequences, and they are the whole component.
 *
 * It carries NO rule of its own. A rule per column would be a rule stopping
 * at a column boundary, which law three forbids; and the row the cards sit
 * in has already drawn the rule that introduces the whole set, so a rule per
 * card would stack a rule on that one. The gutter and the numerals separate
 * them — that is what a graphic block does.
 *
 * Its text sets in the graphic register (tokens.css): the numeral above the
 * hero, the supporting line a half step below body. Both live here and
 * nowhere else on the page.
 */

.cards {
	display: grid;
	/* No rules between the cards, so the stacked rendition needs real air
	 * where a rule used to do the separating. */
	gap: var(--space-xl) var(--gutter);
	max-width: none;
}

.cards > .card {
	max-width: none;
	margin-top: 0;
	/* The graphic register is inherited by the card's paragraph; the
	 * numeral and the lede set their own sizes below. */
	font-size: var(--text-graphic);
	line-height: var(--leading-body);
}

.cards li + li {
	margin-top: 0;
}

@media (min-width: 48em) {
	.cards {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		row-gap: var(--space-l);
	}
}

/* The number is the card. Bigger than the hero and simpler — no commas
 * where an abbreviation will do ("8M", not "8,000,000") — and clamped so
 * three of them across a wide row never collide. */
.card-number {
	font-size: var(--text-graphic-figure);
	font-weight: var(--weight-light);
	line-height: var(--leading-display);
	letter-spacing: var(--track-display);
	font-variant-numeric: tabular-nums lining-nums;
	max-width: none;
}

/* A suffix inside a number is not a second number: it drops a full step of
 * the ladder so the figure keeps the size to itself. */
.card-ratio {
	font-size: calc(var(--text-graphic-figure) / 1.5);
	letter-spacing: var(--track-head);
	white-space: nowrap;
}

/* One word, under the number, naming what the number counts. That is the
 * label register's job description — so the lede takes it, and no new size
 * is spent on a single word. */
.card-lede {
	margin-top: var(--space-2xs);
	font-size: var(--text-caption);
	font-weight: var(--weight-bold);
	letter-spacing: var(--track-label);
	line-height: var(--leading-tight);
	text-transform: uppercase;
	max-width: none;
}

.card > p + p {
	margin-top: var(--space-xs);
}

.card > .card-lede + p {
	margin-top: var(--space-s);
}

/* The register, available by name to any other graphic block that earns it.
 * If it is holding a sentence someone is meant to read as prose, it is the
 * wrong class. */
.graphic {
	font-size: var(--text-graphic);
	line-height: var(--leading-body);
}

/* ── Exhibits ─────────────────────────────────────────────────────────────
 * A figure inside the content column rather than across the page: a
 * screenshot, a receipt, a photograph shown at reading scale. Three widths,
 * because a figure that is always full width stops being a choice. The
 * caption is caption size and sits under the frame; nothing is boxed.
 */

.exhibit {
	margin-block: var(--space-l);
	max-width: 100%;
}

.exhibit > .figure-ground,
.exhibit > img {
	width: 100%;
}

.exhibit figcaption {
	max-width: var(--measure);
	margin-inline: 0;
	padding: var(--space-xs) 0 0;
}

.exhibit--hero > .figure-ground {
	aspect-ratio: 3 / 2;
}

.exhibit--photo {
	max-width: 30rem;
}

.exhibit--photo > .figure-ground {
	aspect-ratio: 4 / 3;
}

.exhibit--small {
	max-width: 20rem;
}

.exhibit--small > .figure-ground {
	aspect-ratio: 4 / 3;
}

/* ── Colophon ─────────────────────────────────────────────────────────────
 * The heavy rule is once per artifact, and the masthead has it: it is the
 * top edge of the sheet. The colophon closes the document rather than
 * opening it, and a second 5px rule at the foot made the page look hemmed
 * rather than finished. It takes the section weight — the same rule every
 * numbered section opens on, which is what the colophon is.
 */

.colophon {
	border-top: var(--rule-medium-width) solid var(--rule-color-strong);
	padding-block: var(--space-m) var(--space-2xl);
	font-size: var(--text-caption);
	color: var(--ink-muted);
	display: grid;
	gap: var(--space-m);
}

.colophon p {
	max-width: var(--measure);
}

.colophon .changelog {
	display: grid;
	grid-template-columns: max-content minmax(0, 1fr);
	column-gap: var(--space-m);
	row-gap: var(--space-3xs);
	max-width: var(--measure);
}

.colophon .changelog dt {
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.colophon .changelog dd {
	margin: 0;
}

@media (min-width: 48em) {
	.colophon {
		grid-template-columns: var(--label-col) minmax(0, 1fr);
		column-gap: var(--gutter);
	}
}

/* ── Colophon, in the accent block ────────────────────────────────────────
 * PREPARED, NOT ENABLED. One hue per artifact: today it is the Forge title
 * block under the masthead. If the header gives the colour up, this is
 * where it goes — the document closing on the same field it opened on.
 *
 * Add `colophon--accent` to the existing <footer class="colophon">; no other
 * markup changes. It must be a direct child of `.page` for the bleed to
 * reach the window edge, which it already is.
 *
 * There is no rule above it, deliberately: a rule immediately followed by a
 * colour block draws a line the block was already drawing. The edge of the
 * ground IS the rule here.
 */

.colophon--accent {
	grid-column: full;
	border-top: none;
	/* Full-bleed ground, content still on the sheet measure: the padding
	 * absorbs whatever is left over once the sheet has taken its width, so
	 * no inner wrapper is needed. */
	padding-inline: max(
		var(--page-margin),
		calc((100% - var(--sheet-max)) / 2)
	);
	padding-block: var(--space-2xl);
	background: var(--accent-ground);
	color: var(--ink-base);
	--ink-muted: color-mix(in oklab, var(--ink-base) 74%, var(--accent));
	--ink-faint: color-mix(in oklab, var(--ink-base) 54%, var(--accent));
	--rule-color: var(--ink-base);
	--rule-color-quiet: var(--ink-base);
	--rule-color-strong: var(--ink-base);
}

/* ── A rule never stacks on a rule ────────────────────────────────────────
 * A `.row` draws the rule that introduces everything inside it. When the
 * first thing in the row would draw its own rule as well, the reader gets
 * two rules a few pixels apart and neither of them means anything — the
 * section rule stops reading as the opening of a section and starts reading
 * as a border. So the first item hangs off the section's rule: it drops its
 * own, and the row's rule takes the heavier of the two weights, so nothing
 * loses rank in the trade.
 *
 * A `.row--flush` draws no rule at all, so there is nothing to hang off and
 * the item keeps its own — which is what the `:not()` on every selector
 * below is for.
 *
 * These selectors are deliberately general: the law is about the junction of
 * a row and a ruled thing, not about any one page's table.
 */

.row:not(.row--flush):has(> .row-body > .ledger:first-child),
.row:not(.row--flush):has(> .row-body > .scroll-x:first-child > .ledger),
.row:not(.row--flush):has(> .row-body > .spec-list--ruled:first-child) {
	border-top-width: var(--rule-medium-width);
}

/* A table with column heads: the head row draws the rule that would stack. */
.row:not(.row--flush) > .row-body > .ledger:first-child > thead > tr:first-child > *,
.row:not(.row--flush) > .row-body > .scroll-x:first-child > .ledger > thead > tr:first-child > *,
/* A table without them: its first body row does. */
.row:not(.row--flush) > .row-body > .ledger:first-child:not(:has(thead)) > tbody > tr:first-child > *,
.row:not(.row--flush) > .row-body > .scroll-x:first-child > .ledger:not(:has(thead)) > tbody > tr:first-child > *,
/* And in the stacked phone rendition the rule is on the row, not the cells. */
.row:not(.row--flush) > .row-body > .ledger:first-child > tbody > tr:first-child,
.row:not(.row--flush) > .row-body > .scroll-x:first-child > .ledger > tbody > tr:first-child,
/* A ruled label/value list opens on its first pair. */
.row:not(.row--flush) > .row-body > .spec-list--ruled:first-child > dt:first-of-type,
.row:not(.row--flush) > .row-body > .spec-list--ruled:first-child > dd:first-of-type {
	border-top: none;
}

/* A bare `hr` at the top of a row is the row's own rule, drawn twice and a
 * few pixels lower. It is not a thin rule — it is not a rule at all. */
.row:not(.row--flush) > .row-body > hr:first-child {
	display: none;
}

/* The same junction one level down: an `hr` standing immediately above
 * something that draws its own opening rule. The `hr` is the one that keeps
 * the rule, because it is the one with the spacing law on its side — four
 * parts of air above, one below — and it takes the heavier of the two
 * weights. What follows drops its own. */

hr:has(+ .ledger),
hr:has(+ .scroll-x > .ledger) {
	border-top-width: var(--rule-medium-width);
}

hr + .ledger > thead > tr:first-child > *,
hr + .scroll-x > .ledger > thead > tr:first-child > *,
hr + .ledger:not(:has(thead)) > tbody > tr:first-child > *,
hr + .scroll-x > .ledger:not(:has(thead)) > tbody > tr:first-child > *,
hr + .ledger > tbody > tr:first-child,
hr + .scroll-x > .ledger > tbody > tr:first-child,
hr + .spec-list--ruled > dt:first-of-type,
hr + .spec-list--ruled > dd:first-of-type {
	border-top: none;
}

/* ── Utilities ────────────────────────────────────────────────────────── */

.stack > * + * {
	margin-top: var(--space-s);
}

.stack-l > * + * {
	margin-top: var(--space-l);
}

.measure {
	max-width: var(--measure);
}

.full-measure {
	max-width: none;
}

.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}
