Rolemain
KeyboardLightbox: triggers are buttons (Enter/Space opens); while open the page behind is inert so focus stays in the dialog — the close button takes it on open and the trigger takes it back on close; Escape (native cancel), close button, and backdrop all dismiss
WhenAny case study route (/project/[slug]) and its full-screen media viewing behaviour.
Not whenHome, Variant, or System — those never render project content or the Lightbox.
One static page per work YAML entrygetStaticPaths() generates a route for every file in src/data/work
The return is a trail in the flow, and the page carries no other chromeThe sticky toolbar it replaces was the only piece of chrome on the site: full-bleed, opaque, carrying a hairline nothing else earns, and pinned even though the nav cluster beside it scrolls away. Its IntersectionObserver project name existed only because a bar that height had to justify itself. The trail is a line of type on the byline's column, sitting on the cluster's centre line across the page from it, and it scrolls with everything else
A rule closes every group, and there are fourPageFlow's rule, applied here: after the narrative, after impact-and-quote, after Credits, and after the next project — which is also what holds the footer off the reading. The page used to draw one after every section, seven in all, which makes all the boundaries equal and then none of them means anything. The narrative sections are separated from each other by --space-sections and their labels alone
Impact and the quote are one group; the narrative sections precede themThe numbers and what was said about them are one subject, so they sit in a .flow at --space-blocks with no rule between. An earlier attempt moved impact into the head, echoing home's evidence-before-claim order; that is wrong on a case study, where the numbers are what the quote speaks to rather than what the headline argues from
The page ends on one SummaryCard, not the prior-work rowHome's grid is the head of a page that has not said anything yet, where five parallel facts are the argument. At the end of a case study the argument is made, and a row of equal chips asks the reader to start over. The card is the next entry in the prior-work order, wrapping — the same order the home row states, so "next" means the same thing in both places
The credit is team[0], and the role beside his name is that engagement's, not his current oneThe work collection carries Alan as the first team member with the role he held for that project — "Lead Product Designer" on GSK, "UX/UI Designer & Front-end Developer" on Google. It is per-project and it changes between them, so neither the byline's standing title nor a new field is the answer: the fact was already recorded, in the one place that knows it. role_summary is the paragraph under it
The credit names the role and not the employer, unlike every other member of the teamThe organisations row two steps below already states it, and captions it as exactly the thing the comma-clause would say — "Interbrand · My employer". Setting it beside his name as well is the same fact twice in one section. It also does not fit: "UX/UI Designer & Front-end Developer, Cheerful Twentyfirst" runs 585px against the 504px the body measure leaves beside a large mark, so on Google and WSJ the line wrapped to a second line for no reason a reader could use. Inside TeamPanel the pair stays, because there is no organisations row under a list entry to carry it
Sections and people are both labelled in the label column, at .t-body-monoThe section label used to be .t-body-lg-mono — a 24px mono label against 16px serif prose, so it out-measured the thing it annotates, where home pairs a 16px mono label with 24px serif prose. The quote attribution briefly moved out of the column entirely, because a long role clipped there; it moved back once Identity started wrapping, which is what the clipping was actually about. One column, one type role, every label on the page
Lightbox opens only from elements with data-lightbox-triggerSet by Media.astro when its lightbox prop is true (the default for surface/fill variants)
Lightbox closes via Escape, backdrop click, or the close buttonThree redundant dismissal paths so no user gets stuck in the overlay
Lightbox is a modal <dialog>, opened with showModal()The platform supplies the modal contract: role and aria-modal, top-layer paint above all page chrome, an inert page behind it so focus is contained, focus to the close button on open, and back to the trigger on close
Lightbox videos autoplay muted and loop only when the source Media used playback="loop"Matches the inline playback behaviour so the lightbox doesn't surprise the viewer with sound or a frozen frame