/* minimal atlas — layout and type roles. Specificity (0,1,0) throughout: a consumer
   overrides with any two-attribute selector, or by redefining the token on the
   element — no !important, no JS stripping. Values are the 1440 artwork values the
   components used to write inline; inline now keeps only per-instance state
   (gridArea, cursor, transition, state colour, ...style). */

/* column pair */
[data-col-pair]{display:grid;grid-template-columns:var(--col-index-width) var(--col-content-width);column-gap:var(--col-gap);row-gap:var(--media-gap)}
[data-subcol]{display:grid;grid-template-columns:var(--col-sub) minmax(0,1fr)}

/* core */
[data-header]{padding:var(--interval-half) var(--page-margin) 0;display:grid;grid-template-columns:1fr var(--nav-work-width) var(--nav-about-width);align-items:start;background:var(--surface-page)}
[data-header] > [data-brand]{font:var(--type-brand);white-space:nowrap}
[data-header] > [data-nav]{font:var(--type-nav)}
[data-canvas]{width:var(--page-width);margin:0 auto;background:var(--surface-page);padding-bottom:calc(var(--interval) * 2)}
[data-canvas] > [data-body]{padding:0 var(--page-margin)}
[data-section-heading]{display:block;font:var(--type-nav);text-align:right;padding-right:var(--page-margin)}
[data-section-heading="records"]{padding-right:0;position:sticky;top:0}
[data-section-heading="news"]{padding-right:0;margin-top:calc(var(--interval) * 2)}
[data-filter-link]{display:inline-flex;gap:var(--interval-line);align-items:baseline;font-family:var(--font-index);font-size:var(--size-nav-secondary);line-height:var(--leading-nav-secondary)}
[data-filter-link] > [data-arrow]{font-family:var(--font-content)}
/* Selection is weight; colour is hover and SYSTEM. Proto Red appears on a hovered link
   and on a SYSTEM term only — every other selected state (header page, WORK TYPE, NEWS
   filters) is Near Black Bold, unselected is Near Black Regular, and a page state has no
   hover. Components write no colour inline. */
[data-header] > a{color:var(--state-default);transition:var(--transition-state)}
[data-header] > [data-nav][aria-current="page"]{color:var(--state-default);font-weight:var(--weight-bold)}
[data-section-heading]{color:var(--state-default)}
[data-section-heading] > a{color:inherit}
[data-filter-link]{color:var(--state-default)}
[data-filter-link][data-state="inactive"]{color:var(--state-default)}
[data-filter-link][aria-current]{color:var(--state-default);font-weight:var(--weight-bold)}
[data-filter-link]:hover{color:var(--state-default)} /* a page state has no hover; the base a:hover would paint it */
[data-menu]{color:var(--state-default);transition:var(--transition-state)}
[data-menu="type"][aria-pressed="true"]{color:var(--state-default);font-weight:var(--weight-bold)} /* ALL needs no rule: "no filter" is never a selection */
[data-menu="keyword"]{color:var(--text-on-index)}
[data-menu="keyword"][aria-pressed="true"]{color:var(--state-selected)}
[data-menu]:not([aria-pressed="true"]):not([data-menu="system"]):hover,[data-menu="all"]:hover{color:var(--link-hover)}

/* WORK menu */
/* WORK menu (measured 2026-09-11): the width is a rule — the measured widest SYSTEM word
   (--menu-content) plus two insets, rounded up to a multiple of 5, so it holds still when the
   padding or the type changes. NOT max-content (it counts the scrollbar, so the open state grew
   wider than the closed one) and no min-width guard — --menu-content IS the no-wrap guard.
   scrollbar-gutter reserves the scrollbar in both states and the gutter is SUBTRACTED from the
   right inset, so left and right read the same gh. Height: the panel scrolls on a short viewport. */
nav[data-work-menu]{position:absolute;left:var(--header-nav-x);top:var(--menu-top,var(--interval));width:round(up,calc(var(--menu-content) + 2 * var(--menu-inset)),5px);max-width:calc(100vw - 2 * var(--page-margin));max-height:calc(100vh - var(--menu-top,var(--interval)) - var(--page-margin));overflow-y:auto;scrollbar-gutter:stable;padding:var(--menu-inset);padding-right:calc(var(--menu-inset) - var(--menu-gutter));background:var(--field-index);z-index:20}
/* menu type reads its token whole (TYPE 21/28, SYSTEM 18/28); the blank line above ALL is one TYPE line */
[data-menu]{display:block;font:var(--type-menu-type)}
[data-menu="all"]{margin-top:var(--leading-menu-type)}
/* one layer at a time (2026-09-21): the TYPE list folds while SYSTEM is expanded; the head row keeps ALL, or the pressed TYPE in its place. Routes are exclusive, so the head never shows two filters */
nav[data-work-menu]:has(> [data-menu="system"][aria-expanded="true"]) > [data-menu="type"]:not([aria-pressed="true"]),
nav[data-work-menu]:has(> [data-menu="system"][aria-expanded="true"]):has(> [data-menu="type"][aria-pressed="true"]) > [data-menu="all"]{display:none}
nav[data-work-menu]:has(> [data-menu="system"][aria-expanded="true"]) > [data-menu="all"]{margin-top:0}
/* an arrow points only at hidden content, at the end of its row: closed ↓ (list below), open ↑ (TYPE above) — one arrow in the panel at any time. The ↑ is the SYSTEM button's ::after one row up, so pressing it toggles SYSTEM, not ALL */
[data-menu="system"]{position:relative;display:flex;justify-content:space-between} /* 2026-09-21: was gap:var(--arrow-gap) */
[data-menu="system"] > [data-arrow]{font-family:var(--font-content);font-size:var(--size-title-sub);line-height:var(--leading-menu-type);font-weight:var(--weight-regular)} /* the arrow is a mark, not the state; weight stays on the word */
[data-menu="system"][aria-expanded="true"] > [data-arrow]{display:none}
[data-menu="system"][aria-expanded="true"]::after{content:"↑";position:absolute;right:0;bottom:100%;font-family:var(--font-content);font-size:var(--size-title-sub);line-height:var(--leading-menu-type);font-weight:var(--weight-regular)}
[data-menu="system"][aria-expanded="true"]{margin-bottom:var(--interval-line)} /* rule → first SYSTEM word: the open button owns the whole gap at the rule's own distance (lg both sides). 2026-09-21: was --leading-meta stacked on the list's --interval (≈78) */
/* the menu's keyword disclosure. Its own name: data-system-list is a record's SYSTEM value
   cell, and this collapse rule under that name hid every PROJECT's SYSTEM row — one name
   meaning two things, so the next rule written against it had the same chance of hitting the
   wrong element (scoping to nav[data-work-menu] would have worked and left that trap standing). */
[data-keyword-list]{margin-top:0} /* the gap above is the open button's (one owner) */
[data-keyword-list]:not([data-open]){height:0;overflow:hidden;visibility:hidden;margin:0} /* closed list stays in layout for the panel's width, out of tab order and the a11y tree */
[data-menu="keyword"]{font:var(--type-system);font-weight:var(--weight-medium)} /* reversed type reads one weight heavier (halation): a role set on --field-index drops one step so it reads equal to the same word in a record. --type-system itself stays 600 */

/* index */
[data-row]{min-height:var(--record-pitch)}
[data-row="project"]{min-height:var(--media-row)}
[data-row="project"] > [data-index-cell]{display:grid;grid-template-rows:auto 1fr}
[data-row="project"] h2{font:var(--type-title);max-width:var(--col-index-width)}
[data-row="news"] > [data-index-cell]{padding-top:var(--interval-line)}
[data-row="news"] [data-line]{font-family:var(--font-content);font-size:var(--size-meta);line-height:var(--leading-meta)}
[data-row="news"] [data-line="kind"]{font-family:var(--font-index)}
[data-row="news"] h3{font:var(--type-title)} /* same role as [data-row="project"] h2 */
[data-content]{padding-top:var(--open-copy)}
[data-content] > div > [data-framework-line]{margin-top:var(--interval-half)}
[data-list="home"]{margin-top:calc(var(--interval) * 2)}
[data-list="news"]{margin-top:var(--interval)}
[data-worklist]{display:grid;gap:calc(var(--row-pitch) - var(--media-row));margin-top:var(--interval-half)}
[data-worklist-head],[data-filter-row]{padding-top:var(--open-label)}
[data-label-cell]{padding-left:var(--col-sub)}
[data-top-row]{margin-top:var(--record-pitch)}
[data-earlier-row]{margin-top:var(--interval)}
[data-footer-row]{display:flex;align-items:flex-end;justify-content:space-between;margin-top:calc(var(--interval) * 2)}
[data-copyright]{font:var(--type-meta-value)}

/* media rows — column widths and slot heights are measured (MediaRow / rowGeometry.js),
   so only ground, ratio and the caption strip live here. min-height: 0 everywhere the
   row nests: the automatic minimum would let content push a slot past its geometry. */
[data-media-row],[data-media-row] > [data-line],[data-line] > [data-col],[data-slot]{min-height:0;min-width:0}
[data-slot]{position:relative;min-width:0;overflow:hidden}
[data-slot] > [data-empty-slot]{background:var(--surface-media)}
/* the plate spans index column and field; text rows keep the 410 / 920 lines */
[data-modules][data-plate]{grid-column:1 / -1}
/* a PDF slot (2026-09-11; the page owns its size 2026-09-12): a publication takes a row —
   width IS rank in this system — and what the slot provides is the MOUNT, not the object:
   black ground, gh of air, and the page standing on the bottom edge. It declares no height.
   The PAGE carries the size ([data-document-page] below), which is what lets a row of two
   slots be as tall as the taller volume while the shorter one keeps its own size with black
   above it — two volumes of one work as they stand on a shelf. No rule is needed for that
   equality: a grid row is already as tall as its tallest item.
   The page shows whole, never cropped: what a crop takes off a document is usually the
   argument. Distance to whatever sits before or after it is lg, not --media-gap (that gap
   belongs to pictures inside one row) and not g, which read as a change of section rather
   than the next row; the rule hangs on the slot, so both sides hold — inside the gapped
   plate the gap is subtracted so the sum still reads lg, and two adjacent slots collapse to
   one lg between them, which is the intended reading.
   Two publications may share a row (data-paired, the plate's call): the parts of one work
   stand side by side, each in half the field. Two is the ceiling — at a third the column is
   narrow enough that the mount ratio decides every page's size and the window never does,
   so a publication would stop being the same object on a row of its own. */
/* 2026-09-16e: the slot publishes --document-page-height (a fact, in points); the scale is this
   relation, computed here so it holds from first layout, whenever the stylesheet arrives */
[data-document-slot]{position:relative;min-width:0;width:100%;padding:var(--interval-half) 0;display:grid;place-items:end center;background:var(--near-black);margin-block:var(--interval-line);--document-scale:min(1,calc(var(--document-page-height,var(--document-reference)) / var(--document-reference)))}
/* the page's three terms, all relations. Width: a share of the mount (--document-mount),
   scaled by how large the real book is (--document-scale, derived above from the page height
   the slot publishes). Shape: the file's own (--document-page-ratio, width / height), with √2 held until
   it opens so the row does not reflow. Ceiling: the consumer's readable height less the two
   gh insets, so the mount stands in one view.
   Which term binds depends on the field, and that is the point — measured on the Handbook,
   field 1340, cap 965: alone, the ceiling binds and the surplus width becomes surround
   (PART II page 556 × 785 in an 835 slot, 29% black a side); paired at 665, the mount ratio
   binds (403 × 569 in a 619 slot, 20% a side, with PART I 290 × 383 in that same 619). */
[data-document-page]{width:calc(100% * var(--document-mount) * var(--document-scale,1));aspect-ratio:var(--document-page-ratio,calc(1 / var(--document-ratio)));max-height:calc(var(--document-cap,100vh) * var(--document-scale,1) - 2 * var(--interval-half));display:grid;place-items:center;min-height:0}
[data-modules] > [data-document-slot]{grid-column:1 / -1;margin-block:calc(var(--interval-line) - var(--media-gap))}
/* the pair needs no wrapper: the plate is four columns and every module spans all of them, so
   a paired slot is simply the one child that takes two. Column gap is --media-gap — the gap
   between pictures inside one row, which is what the pair is. */
[data-modules][data-plate] > *{grid-column:1 / -1}
[data-modules][data-plate] > [data-document-slot][data-paired]{grid-column:span 2}
/* 2026-09-16g: the page box is the containing block and owns the size ([data-document-page]
   above); the media — poster or canvas — fills it and contain keeps the ratio. Same device as
   [data-page] (0916c). Positional, not max-*: the canvas has intrinsic pixel dimensions and a
   percentage max-height against an aspect-ratio box is not enough to hold it. */
[data-document-page]{position:relative}
[data-document-page] > :where(canvas,img){position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:block}
/* 2026-09-16c: the same sentence for a preview page — inside an auto-tracked [data-slot] a
   percentage max-height on the poster computes to none (no definite height), so the bound has
   to be positional: the box is the containing block, the media fills it, contain keeps the ratio */
[data-page]{position:relative}
[data-page] > :where(canvas,img){position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
/* a picked page in a preview row reads as the same object as a slot: same Near Black ground,
   same gh inset, page whole. Without it the row shows the PAGE's own white where the ratio
   doesn't fill the cell, so one publication has two appearances across two pages. */
[data-slot]:has(> [data-page]){background:var(--near-black);padding:var(--interval-half) 0;display:grid;place-items:center}
/* a slot's caption is a solid strip on its own bottom edge — nothing moves, and
   without a pointer the gallery carries the captions instead */
[data-caption-strip]{position:absolute;left:0;right:0;bottom:0;padding:var(--interval-line);background:var(--near-black);color:var(--text-inverse);font:var(--type-caption);opacity:0;pointer-events:none;transition:opacity var(--duration-state) var(--easing-state)}
@media (hover:hover){[data-slot]:hover [data-caption-strip],[data-document-slot]:hover [data-caption-strip]{opacity:1}}

/* about */
[data-about]{padding:var(--open-rule) var(--page-margin) 0;display:grid;gap:var(--interval)}
[data-about] > section > [data-index-cell]{display:grid;align-self:stretch}
[data-about] > section > [data-index-cell][data-has-aside]{grid-template-rows:auto 1fr}
[data-accordion-toggle],[data-accordion-title]{font:var(--type-title)}
[data-accordion-toggle]{display:flex;gap:var(--interval-line)}
[data-prose]{display:grid;gap:var(--space-30);max-width:var(--col-content-width);font:var(--type-body)}
[data-prose][data-size="meta"]{font:var(--type-caption)}
[data-divider-index],[data-divider-platform]{font:var(--type-title-sub);margin-top:calc(var(--interval) * 2)}
[data-contact-wrap]{margin-top:var(--interval-half)}
[data-contact]{margin-top:calc(var(--interval) * 2)}
/* CV sits under BIOGRAPHY in the same index column and is the same layer of the page, so it
   reads the composed token those accordion titles read \u2014 not its own spelling of the same
   trio (which left the role with two spellings) and not Semibold, which in this system reads
   as a selected state CV never has (2026-09-12). */
[data-cv]{font:var(--type-title)}
[data-about] [data-framework-line]{margin-top:var(--interval-half)}
[data-about] [data-prose][data-follows]{margin-top:var(--interval-half)}
[data-about] [data-list="system-index"]{margin-top:var(--interval-half)}
[data-system-field="red"]{background:var(--field-system);padding:40px;margin:-40px}

/* framework line + system keyword */
[data-framework-line]{font:var(--type-title-sub);max-width:var(--col-content-width)}
[data-keyword] > button{display:block;cursor:pointer;font-family:var(--font-content);font-size:var(--size-body);line-height:var(--leading-body);font-weight:var(--weight-medium)} /* Medium: sub-heading class, 2026-09-10 */
[data-keyword][data-size="menu"] > button{font-size:var(--size-meta);line-height:var(--leading-meta)}
[data-keyword] > p{font-family:var(--font-content);font-size:var(--size-body);line-height:var(--leading-body);max-width:var(--col-content-width)}
[data-keyword][data-open]{margin-bottom:var(--interval-half)}

/* PROJECT page */
/* PROJECT opens on a rule, as ABOUT does: --open-rule from the nav line to the rule, one interval from the rule to the title */
/* the title is the first row's index cell — one cell, static, like every index cell in the
   system. The record's first pair stands beside it; the rest of the colophon spans both
   columns below. (2026-09-16: replaces the sticky title that travelled over the plate.) */
/* the first pair spans both tracks like every other colophon row; the title stands in the
   pair's own empty index cell (same grid area, h1 painted on top). One index column, owned by
   the col-pair grammar, as everywhere else on the site. (2026-09-16h) */
[data-colophon]{padding:var(--open-rule) var(--page-margin) 0;display:grid;grid-template-columns:var(--col-index-width) minmax(0,1fr)}
[data-colophon] > h1{grid-column:1;grid-row:1;align-self:start;margin-top:calc(var(--rule-weight) + var(--interval));position:relative;z-index:1}
[data-colophon] > h1 + [data-col-pair]{grid-column:1 / -1;grid-row:1}
[data-colophon] > :not(h1){grid-column:1 / -1}
[data-colophon] > [data-col-pair]:first-child,[data-colophon] > h1 + [data-col-pair]{border-top:var(--rule-weight) solid var(--text-primary);padding-top:var(--interval)}
[data-colophon] h1{font:var(--type-title)}
[data-colophon] > h1{color:var(--text-primary)}
/* the record is bracketed by two rules, gh inside each — the same setting as the colophon lists */
[data-colophon] > [data-col-pair][data-index]{margin-top:var(--interval);padding:var(--interval-half) 0;border-top:var(--rule-weight) solid var(--text-primary);border-bottom:var(--rule-weight) solid var(--text-primary)}
/* four columns, so "the whole field" and "half of it" are positions on one lattice: every
   module spans all four, a paired publication spans two. */
[data-modules]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--media-gap)}
[data-colophon] > [data-modules]{margin-top:var(--interval)}
[data-colophon] > [data-list]{margin-top:var(--interval)}
/* [data-related] never set display:grid inline — the columns declaration is kept as written, inert until a consumer adds display */
[data-related]{grid-template-columns:var(--meta-label-width) 1fr}
[data-related] > [data-label]{font:var(--type-meta-label)}
[data-related] a{font:var(--type-title-sub)}

/* records */
dl[data-record]{margin:0;display:grid;grid-template-columns:var(--meta-label-width) 1fr}
dl[data-record] > dt{font:var(--type-meta-label)}
dl[data-record] > dd{margin:0;font:var(--type-meta-value)}
dl[data-record] > dd[data-system-list]{font:var(--type-meta-label)}
dl[data-record] > dd[data-actors]{display:grid;gap:var(--interval-line)}
/* a key inside a value column: the ACTORS connectors and a CREDIT line's role prefix
   are one role, Medium — between the value (Regular) and the outer label (Semibold) */
dl[data-record] > dd [data-value-key]{font-weight:var(--weight-medium)}
[data-system]{display:block;cursor:pointer}
[data-meta] > [data-line]{font:var(--type-meta-value)}
[data-meta] > [data-line="type"]{font:var(--type-meta-index)}
[data-meta] > [data-line="system"]{font:var(--type-meta-label)}
/* hanging indent under the arrow (2026-09-10): every SYSTEM line is one run "↓ WORD"; the
   run "↓ " measures 0.875em of its own type, so a wrapped word starts under its first letter.
   One-line words are unchanged — padding and indent cancel. */
[data-meta] > [data-line="system"],dl[data-record] [data-system],[data-keyword] > button{padding-left:0.875em;text-indent:-0.875em}

/* media caption */
[data-caption]{width:var(--col-index-width);font:var(--type-caption);transition:opacity var(--duration-state) var(--easing-state)}
