/* ==========================================================================
   Work Project detail page
   --------------------------------------------------------------------------
   Native document scrolling throughout - unlike the listing, this page is not
   locked to the viewport. Scrolling collapses the information panel and leaves
   a fixed rail carrying the close button, its progress ring, the vertical
   title and the INFO control.
   ========================================================================== */

.nsn-detail {
	position: relative;
	/* Clears the fixed rail on the left at every desktop width. */
	--nsn-rail-w: clamp(54px, 5vw, 76px);
}

/* --------------------------------------------------------------------------
   Fixed rail
   -------------------------------------------------------------------------- */
/*
 * The compact rail: a full-height column below the header holding three things
 * - close at the top, the vertical title in the middle, INFO at the bottom.
 *
 * The balance is done with three equal rows rather than with gaps or absolute
 * offsets. The title sits in the middle row and is centred within it, so it
 * stays centred at any viewport height and can never drift into the two
 * controls, however long the project name is.
 */
.nsn-rail {
	position: fixed;
	left: 0;

	/*
	 * From the very top of the viewport, not from below the header, so the
	 * column of ink is unbroken. The header still sits over it; the padding
	 * below keeps the controls clear of it rather than a gap in the ground.
	 */
	top: 0;
	bottom: 0;
	z-index: 50;

	/* Compact: the old width left a wide empty band either side of the title. */
	width: clamp(92px, 8vw, 132px);

	display: grid;
	grid-template-rows: 1fr auto 1fr;
	justify-items: center;
	align-items: center;
	padding-top: calc(var(--nsn-header-h) + var(--nsn-space-2));
	padding-bottom: var(--nsn-space-3);
	padding-inline: 0;

	/*
	 * Solid ink, not a gradient. A faded band the width of the rail reads as a
	 * smear over the photograph however it is tuned, because the falloff has
	 * to finish inside the rail; a clean edge reads as what it is - a column
	 * of the site's own ground laid over the image.
	 */
	background: var(--nsn-ink);

	pointer-events: none;
	transition: opacity 0.45s var(--nsn-ease), visibility 0.45s;
}

/* Top and bottom rows: the controls hug their own end of the rail. */
.nsn-rail__close {
	align-self: start;
}

.nsn-rail__info {
	align-self: end;
}

.nsn-rail > * {
	position: relative;
	pointer-events: auto;
}

/*
 * The rail's own three parts sit above the panel whenever the panel is not
 * open - which includes every frame of the collapse.
 *
 * The panel is painted above the rail (see below), and during the collapse it
 * ends up covering exactly the rail's column before it disappears. Without
 * this, the vertical title and the two controls would fade back in underneath
 * that last strip of ink and the rail would look empty until the very last
 * frame. While the panel is open these parts are held at opacity 0 by the
 * timeline, so they are never raised over it.
 */
.nsn-detail:not(.is-panel-open) .nsn-rail > * {
	z-index: 70;
}

/*
 * Near the foot of the page only the vertical title steps aside.
 *
 * The close button and INFO stay: they have to work from every scroll
 * position, so hiding the whole rail down here - which is what this used to do
 * - took away the two controls exactly where a reader is most likely to want
 * them. The collision it was avoiding is solved properly below, by reserving
 * the rail's column in the sections that run full width.
 */
/*
 * Near the foot of the page the vertical title steps aside, so it cannot run
 * into the sections that reserve the rail's column.
 */
.nsn-rail.is-away .nsn-rail__vtitle {
	opacity: 0;
	visibility: hidden;
}

/* --- close button + progress ring --- */
.nsn-rail__close {
	position: relative;
	flex: none;
	display: grid;
	place-items: center;
	width: clamp(44px, 3.6vw, 52px);
	height: clamp(44px, 3.6vw, 52px);
	border-radius: 50%;
	color: var(--nsn-bone);
}

.nsn-rail__ring {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	/* Start the stroke at 12 o'clock and run clockwise. */
	transform: rotate(-90deg);
	overflow: visible;
}

.nsn-rail__ring circle {
	fill: none;
	stroke-width: 1.5;
	/* Legible on a light photograph as well as on ink. */
	vector-effect: non-scaling-stroke;
}

.nsn-rail__ring-track {
	stroke: var(--nsn-bone-28);
}

.nsn-rail__ring-value {
	stroke: var(--nsn-bone);
	stroke-linecap: round;
	/* Set from work-detail.js once the circumference is measured. */
	stroke-dasharray: 1;
	stroke-dashoffset: 1;
}

/* The cross itself, drawn rather than typed so it stays optically centred. */
.nsn-rail__x {
	position: relative;
	width: 40%;
	height: 40%;
}

.nsn-rail__x::before,
.nsn-rail__x::after {
	content: '';
	position: absolute;
	top: 50%;
	left: 0;
	width: 100%;
	height: 1px;
	background: currentColor;
}

.nsn-rail__x::before {
	transform: rotate(45deg);
}

.nsn-rail__x::after {
	transform: rotate(-45deg);
}

.nsn-rail__close:hover,
.nsn-rail__close:focus-visible {
	color: var(--nsn-bone);
}

.nsn-rail__close:hover .nsn-rail__ring-track {
	stroke: var(--nsn-bone-46);
}

/* --- vertical title --- */
/*
 * The vertical title. It is the middle row's only occupant and is centred in
 * it, so it does not need - and must not have - a flex growth that would let a
 * long name push into the controls. max-height keeps a very long title inside
 * its own row; the ellipsis is the honest end for one that still will not fit.
 */
.nsn-rail__vtitle {
	margin: 0;
	writing-mode: vertical-rl;
	text-orientation: mixed;

	/* Noticeably larger than the old step-xs, per the reference. */
	font-size: clamp(0.95rem, 1.05vw, 1.3rem);
	letter-spacing: 0.24em;
	text-transform: uppercase;
	color: var(--nsn-bone-72);

	max-height: min(58svh, 40rem);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* --- INFO --- */
.nsn-rail__info {
	flex: none;
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.55rem 0.5rem;
	font-size: var(--nsn-step-xs);
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--nsn-bone-72);
	writing-mode: vertical-rl;
	transition: color var(--nsn-dur-fast) var(--nsn-ease);

	/* Part of the compact state, so it is present from the start. */
	min-height: 44px;
	min-width: 44px;
	justify-content: center;
}

.nsn-rail__info:hover,
.nsn-rail__info:focus-visible {
	color: var(--nsn-bone);
}

/*
 * The rail is not hidden by a class any more.
 *
 * Switching its visibility off the moment the panel opened is what made the
 * two read as a hard swap - the rail vanished and a separate panel arrived
 * over the top of it. Its three parts are now moved and faded by the same
 * timeline that brings the panel in, so one component appears to change shape.
 * Only the pointer is taken away here, so nothing behind the panel is
 * clickable while it is open.
 */
.nsn-detail.is-panel-open .nsn-rail > * {
	pointer-events: none;
}

/*
 * Out of the way entirely once the reader reaches the closing sections. They
 * have their own full-width layout and the rail has nothing left to control
 * there, so leaving it would be an empty black column over the footer.
 */
.nsn-detail.is-lower .nsn-rail,
.nsn-detail.is-lower .nsn-detail__panel {
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
/*
 * One layer, not two columns. The image fills the section and the panel is an
 * overlay on top of it, so opening or closing the panel cannot resize the
 * image - which is what the old 42/58 grid did every time.
 */
.nsn-detail__hero {
	position: relative;
	/*
	 * A fixed band rather than a viewport height: the image is a masthead the
	 * reader scrolls past, not a screen they have to get through. 800px on
	 * desktop, scaled down where that would swallow a small screen.
	 */
	height: calc(800px + var(--nsn-header-h));
	overflow: hidden;
}

@media (max-width: 1024px) {
	.nsn-detail__hero {
		height: calc(min(62svh, 560px) + var(--nsn-header-h));
	}
}

@media (max-width: 700px) {
	.nsn-detail__hero {
		height: calc(min(54svh, 420px) + var(--nsn-header-h));
	}
}

/*
 * The information panel: off-canvas to the left until it is asked for, then
 * slid in over the image as one surface.
 */
/*
 * Fixed, not absolute.
 *
 * Absolute meant the panel belonged to the hero: pressing INFO from halfway
 * down the page opened it back up at the top of the document, out of sight,
 * which read as the page jumping to the top. Attached to the viewport it opens
 * where the reader already is, whatever the scroll position - and no ancestor
 * carries a transform or filter, so nothing traps it back into the hero.
 */
.nsn-detail__panel {
	position: fixed;

	/* Same reason as the rail: one unbroken column from the viewport top. */
	top: 0;
	left: 0;
	bottom: 0;

	/*
	 * Above the rail (50), and not only while the class is on.
	 *
	 * This is the fix for the reported clipping. The rail is an opaque ink
	 * column 115px wide at z-index 50; the panel used to be 40, so the rail
	 * painted its own ground over the panel's leftmost 115px. Both being the
	 * same ink the background looked continuous and only the writing gave it
	 * away - the first letters of every line sat underneath the rail, which is
	 * how PROJECT 09 came to read as OJECT 09. Nothing was clipped by overflow
	 * and nothing was mispositioned; the panel was simply in the wrong layer,
	 * and pointer-events: none on the rail hid that from hit-testing, which is
	 * why it measured as fine.
	 *
	 * It has to be unconditional rather than scoped to .is-panel-open: the
	 * class comes off at the first frame of the collapse, so a scoped rule put
	 * the writing back under the rail for the whole closing animation. Closed,
	 * the panel is clipped away and invisible, so the layer costs nothing.
	 */
	z-index: 60;

	width: clamp(320px, 30vw, 560px);
	max-width: 100%;

	/* One padding, so the close control and every line below it share a left
	   edge. Stretch rather than centre: the column sets its own rhythm. */
	display: flex;
	align-items: stretch;
	padding: calc(var(--nsn-header-h) + clamp(0.9rem, 2vh, 1.6rem))
		clamp(1.4rem, 2.4vw, 2.6rem) clamp(1.4rem, 3.2vh, 2.4rem);

	background: var(--nsn-ink);
	overflow-y: auto;
	overscroll-behavior: contain;

	/*
	 * Closed, and revealed by clipping rather than by sliding.
	 *
	 * The panel used to arrive with transform: translateX(-100%), which meant
	 * that for the first half of the opening it - and everything written in it
	 * - sat outside the left edge of the screen. Its text faded up while it was
	 * still out there, so the first letters of every line were off-screen until
	 * the travel finished. Clipping keeps the box exactly where it belongs at
	 * x: 0 and simply uncovers it from the left, so no character is ever
	 * outside the viewport and the panel is never translated past it.
	 */
	clip-path: inset(0 100% 0 0);
	visibility: hidden;
	will-change: clip-path;
}

/*
 * Open. No clip at rest, so the panel's own shadow is not cut off by the
 * reveal; the animation puts an inset on and takes it off again.
 */
.nsn-detail.is-panel-open .nsn-detail__panel {
	clip-path: none;
	visibility: visible;
}

/*
 * A column: the close control at the top, the writing under it, and the
 * metadata pushed to the foot by the auto margin. If the writing runs long the
 * auto margin collapses and the panel scrolls instead of clipping.
 */
.nsn-detail__panel-inner {
	display: flex;
	flex-direction: column;
	width: 100%;
	max-width: none;
}

.nsn-detail__panel .nsn-detail__eyebrow {
	margin-top: clamp(1.6rem, 4vh, 3rem);
}

.nsn-detail__panel .nsn-detail__meta {
	margin-top: auto;
	padding-top: clamp(1.4rem, 3vh, 2.2rem);
}

.nsn-detail__eyebrow {
	font-size: var(--nsn-step-xs);
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--nsn-bone-46);
	margin-bottom: var(--nsn-space-2);
}

.nsn-detail__title {
	font-size: clamp(2rem, 3.4vw, 3.25rem);
	line-height: 1.02;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	color: var(--nsn-bone);
}

.nsn-detail__subtitle {
	margin-top: 0.4rem;
	font-size: var(--nsn-step-lg);
	line-height: 1.15;
	letter-spacing: -0.01em;
	color: var(--nsn-bone-72);
}

.nsn-detail__body {
	margin-top: var(--nsn-space-3);
	font-size: var(--nsn-step-sm);
	color: var(--nsn-bone-72);
}

.nsn-detail__meta {
	margin-top: var(--nsn-space-4);
	padding-top: var(--nsn-space-2);
	border-top: 1px solid var(--nsn-rule);
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--nsn-space-2) var(--nsn-space-3);
}

.nsn-detail__meta-row dt {
	font-size: 0.625rem;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--nsn-bone-46);
}

.nsn-detail__meta-row dd {
	margin: 0.3rem 0 0;
	font-size: var(--nsn-step-sm);
	color: var(--nsn-bone);
}

/* --- feature image --- */
/* The base layer: the whole width, starting directly below the header. */
/*
 * From the very top of the page, not from below the header.
 *
 * The header draws its own gradient and sits over whatever is beneath it, so
 * the image runs up behind it the way the rest of the site's imagery does.
 * Starting it at the header's height left a band of flat ink across the top
 * of the masthead. The hero's height already counts that band, so the image
 * simply grows into it and nothing below moves.
 */
.nsn-detail__feature {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: 0;
	margin: 0;
	overflow: hidden;
	background-color: var(--nsn-ink);
}

.nsn-detail__feature-img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center 42%;
}

/* --------------------------------------------------------------------------
   Gallery
   --------------------------------------------------------------------------
   Twelve columns with a deliberate rhythm. Every <img> keeps its intrinsic
   width/height, so the box is reserved before the bytes land and the grid
   never shifts.
   -------------------------------------------------------------------------- */
.nsn-detail__gallery {
	padding: var(--nsn-space-5) var(--nsn-gutter);
	padding-left: calc(var(--nsn-gutter) + var(--nsn-rail-w) + var(--nsn-space-2));
	max-width: var(--nsn-maxw);
	margin: 0 auto;
}

/*
 * Two states, one markup.
 *
 * Base: an ordinary responsive grid. This is what a no-JS visitor sees, and
 * what paints in the frame before masonry.js runs - it is a real layout, not a
 * placeholder, so there is nothing to hide and no flash to cover.
 *
 * .is-masonry: the script has measured and taken over. Tiles become absolutely
 * positioned and the container gets an explicit height, so the two systems
 * never both try to lay out at once.
 */
.nsn-grid {
	--nsn-grid-gutter: clamp(12px, 1.6vw, 28px);

	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
	gap: var(--nsn-grid-gutter);
	list-style: none;
	margin: 0;
	padding: 0;
}

.nsn-grid.is-masonry {
	display: block;
	position: relative;
	grid-template-columns: none;
	gap: 0;
}

.nsn-grid__item {
	margin: 0;
}

/*
 * Pre-JS the tile holds its own shape from its intrinsic ratio, so the base
 * grid reserves the right box too. Under .is-masonry the script owns width and
 * height outright and the ratio is no longer consulted.
 */
.nsn-grid__item {
	aspect-ratio: var(--nsn-tile-ratio, 1.6);
}

.nsn-grid.is-masonry .nsn-grid__item {
	aspect-ratio: auto;
	top: 0;
	left: 0;
	will-change: transform;
}

/* The reveal fades these in; without JS they must never stay invisible. */
html.nsn-js .nsn-grid__item {
	opacity: 0;
}

html:not(.nsn-js) .nsn-grid__item,
html.nsn-reduced-motion .nsn-grid__item {
	opacity: 1;
}

.nsn-grid__figure {
	margin: 0;
	overflow: hidden;
	height: 100%;
}

.nsn-grid__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.nsn-grid__caption {
	margin-top: 0.6rem;
	font-size: var(--nsn-step-xs);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--nsn-bone-46);
}

/* --------------------------------------------------------------------------
   Contact / next project
   -------------------------------------------------------------------------- */
.nsn-pair {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	border-top: 1px solid var(--nsn-rule);
}

.nsn-pair__panel {
	position: relative;
	display: flex;
	align-items: flex-end;
	min-height: clamp(220px, 34svh, 420px);
	padding: var(--nsn-space-4) var(--nsn-space-3);
	overflow: hidden;
	isolation: isolate;
}

.nsn-pair__panel + .nsn-pair__panel {
	border-left: 1px solid var(--nsn-rule);
}

.nsn-pair__media {
	position: absolute;
	inset: 0;
	z-index: -1;
	overflow: hidden;
	background-color: var(--nsn-ink);
}

.nsn-pair__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Dimmed hard at rest; the hover brings it up. */
	opacity: 0.3;
	filter: brightness(0.5) saturate(0.8);
	will-change: transform, opacity;
}

.nsn-pair__content {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
}

.nsn-pair__eyebrow {
	font-size: var(--nsn-step-xs);
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--nsn-bone-46);
}

.nsn-pair__title {
	font-size: clamp(1.5rem, 2.6vw, 2.5rem);
	line-height: 1.04;
	letter-spacing: -0.028em;
	text-transform: uppercase;
	color: var(--nsn-bone);
}

.nsn-pair__sub {
	font-size: var(--nsn-step-sm);
	color: var(--nsn-bone-46);
}

.nsn-pair__arrow {
	margin-top: 0.6rem;
	font-size: var(--nsn-step-lg);
	color: var(--nsn-bone-72);
	transition: transform var(--nsn-dur) var(--nsn-ease);
}

.nsn-pair__panel:hover .nsn-pair__arrow,
.nsn-pair__panel:focus-visible .nsn-pair__arrow {
	transform: translateX(0.4rem);
}

/* Keyboard focus must read as strongly as hover. */
.nsn-pair__panel:focus-visible {
	outline: 2px solid var(--nsn-bone);
	outline-offset: -6px;
}

/* --------------------------------------------------------------------------
   Reduced motion
   --------------------------------------------------------------------------
   The panel never collapses, so all content stays on screen and the vertical
   title and INFO control are simply not needed. The progress ring keeps
   working - it is information, not decoration.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	/*
	 * The rotated title is decoration and a rotated line of text is hard work
	 * to read, so it still goes. INFO stays: it is the only way back to the
	 * project's description once the reader has scrolled past it, and hiding
	 * it here is what used to strand them - the panel would be sitting a whole
	 * document above the viewport with no control to bring it into view.
	 */
	.nsn-rail__vtitle {
		display: none;
	}

	.nsn-detail__panel {
		transition: none;
	}

	.nsn-pair__arrow {
		transition-duration: 0.01ms;
	}
}

/* ==========================================================================
   The floating information panel

   One element, two positions. Docked it is a block in the hero; floating it
   is attached to the viewport, which is the only way INFO can be useful to a
   reader who is already a screen or two down the page.

   It sits below the rail so the INFO and close controls stay clickable, and
   above the gallery and the footer so it is never read through them.
   ========================================================================== */

/*
 * There is no longer a second, floating variant of the panel: the panel is an
 * overlay in every state, so one set of rules describes it and the two can no
 * longer disagree. What the floating variant contributed - a hard edge against
 * the image and its own scrolling - belongs to the panel itself now.
 *
 * The hard edge is the hairline, and only the hairline. The floating variant
 * also carried a drop shadow - 24px to the right, blurred over 60 - which lay
 * as a wide band of black across the left of the photograph and read as a
 * second, softer sidebar beside the real one. A panel that spans the full
 * height of the page and meets the image on a straight vertical line has
 * nothing to cast a shadow onto: the line itself is the edge.
 */
.nsn-detail__panel {
	border-right: 1px solid var(--nsn-bone-14);
}

.nsn-detail__panel-inner:focus {
	outline: none;
}

.nsn-detail__panel-inner:focus-visible {
	outline: 2px solid var(--nsn-bone);
	outline-offset: -4px;
}

/* --------------------------------------------------------------------------
   The panel's own close control. It is inside the panel, and the panel only
   exists on screen when it is open, so it is simply always present.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Page entrance. The panel and feature image start invisible only when the
   curtain is up to hide them; without JS they are simply visible.
   -------------------------------------------------------------------------- */
html.nsn-js .nsn-detail__panel-inner,
html.nsn-js .nsn-detail__feature {
	opacity: 0;
}

html:not(.nsn-js) .nsn-detail__panel-inner,
html:not(.nsn-js) .nsn-detail__feature,
html.nsn-reduced-motion .nsn-detail__panel-inner,
html.nsn-reduced-motion .nsn-detail__feature,
.nsn-detail.is-entered .nsn-detail__panel-inner,
.nsn-detail.is-entered .nsn-detail__feature {
	opacity: 1;
}

/* --------------------------------------------------------------------------
   No JavaScript: the panel is open, because there is nothing to open it with.
   -------------------------------------------------------------------------- */

/*
 * The panel is closed by a clip and a visibility, and both are lifted by
 * .is-panel-open - a class only work-detail.js ever sets. With no script it is
 * never set, so the category, the title, the subtitle, the description and the
 * metadata all stayed clipped away and the page offered a photograph, a gallery
 * and not one word about the project.
 *
 * Absolute rather than the fixed it takes when the script is running. Fixed
 * pins it to the viewport for the length of the document, and with no way to
 * dismiss it, it would stand over the gallery the whole way down. Anchored to
 * the hero instead it reads exactly as the opened panel does - the same box, in
 * the same place, at every width - and then scrolls away with the masthead it
 * belongs to. Nothing is added to the markup, so a reader with JavaScript sees
 * no change at all.
 */
html:not(.nsn-js) .nsn-detail__panel {
	position: absolute;
	clip-path: none;
	visibility: visible;
	will-change: auto;
}

/*
 * One control opens the panel and the other closes it. Neither can do anything
 * without a script, so neither is offered. The rail's close is left alone: it
 * is a real link back to the Work index and it works.
 */
html:not(.nsn-js) .nsn-rail__info {
	display: none;
}

/*
 * The rail's rotated title stands in for the project name while the panel is
 * closed, and the script fades it out whenever the panel opens. Here the panel
 * is always open, so the title would simply be printed twice.
 */
html:not(.nsn-js) .nsn-rail__vtitle {
	visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
	/*
	 * The floating panel stays floating.
	 *
	 * This used to force it back to static, which meant a reader with reduced
	 * motion who pressed INFO from the foot of the page got the panel opened
	 * a full document above them - the very problem the floating panel exists
	 * to solve. Reduced motion should remove the animation, not the position:
	 * it still opens in the viewport, just without sliding in.
	 */
	.nsn-detail__panel {
		transition: none;
	}

	.nsn-detail__panel-inner,
	.nsn-detail__feature {
		opacity: 1 !important;
	}
}


/* ==========================================================================
   Room for the rail

   The rail is fixed to the left edge for the whole page, so anything that runs
   the full width would pass underneath it. These two do - the contact / next
   pair and the site footer - so on desktop they keep the rail's column clear
   and nothing ever overlaps. Below the rail's breakpoint there is no rail, so
   there is nothing to reserve.
   ========================================================================== */
@media (min-width: 901px) {
	/*
	 * The rail's width is declared on .nsn-detail, and the footer is not
	 * inside it - so referring to the variable there resolved to nothing, the
	 * calc() became invalid, and the whole declaration was dropped. The footer
	 * kept none of the space and the INFO button sat on top of the Zeg mark.
	 * Declaring it on the page itself puts it in scope for both.
	 */
	body.single-nsn_work {
		--nsn-rail-w: clamp(54px, 5vw, 76px);
	}

	/*
	 * Nothing to reserve any more. The rail is hidden from the moment these
	 * sections come into view, so holding its column open here only leaves an
	 * empty band down their left edge - which is the thing the reservation was
	 * meant to prevent. They run their own full width instead.
	 */
	.single-nsn_work .nsn-pair,
	.single-nsn_work .nsn-footer__inner {
		padding-left: var(--nsn-gutter);
	}
}

/* ==========================================================================
   The close control on the project page

   Scoped to .nsn-detail, and that matters: .nsn-rail__close and
   .nsn-rail__x above are shared markup. This stylesheet is loaded on the
   Journal article and the Contact page as well, and both draw their own close
   button from those same two rules - the Journal one at 52px, which is locked.
   Changing them in place would have resized three controls to change one, so
   the project page's figures are stated here instead and the shared rules are
   left exactly as they are.
   ========================================================================== */

.nsn-detail .nsn-rail__close {
	position: relative;
	flex: none;
	display: grid;
	place-items: center;
	width: clamp(35px, 3vw, 40px);
	height: clamp(35px, 3vw, 40px);
	border-radius: 50%;
	color: var(--nsn-bone);
}

.nsn-detail .nsn-rail__x {
	position: relative;
	width: 28%;
	height: 28%;
}

/* --------------------------------------------------------------------------
   The cross turns

   A whole turn, on the cross alone: the circle it sits in, the ring around it
   and the word above it are all left where they are - only the glyph moves,
   and it ends where it started, so there is no state to get stuck in. The
   two strokes are drawn by the pseudo-elements' own rotations, and rotating
   their parent carries both without disturbing either.

   On the way back out the turn simply runs the other way: it is a transition,
   not a one-shot, so a pointer that leaves mid-turn unwinds from wherever the
   cross had got to rather than jumping.
   -------------------------------------------------------------------------- */
.nsn-detail .nsn-rail__x {
	transition: transform 0.6s var(--nsn-ease);
}

@media (hover: hover) {
	.nsn-detail .nsn-rail__close:hover .nsn-rail__x {
		transform: rotate(360deg);
	}
}

/* Focus is not a pointer state, so it is not inside that query. */
.nsn-detail .nsn-rail__close:focus-visible .nsn-rail__x {
	transform: rotate(360deg);
}

/* --------------------------------------------------------------------------
   The word beside it

   To the right of the button, on its centre line, and absolutely placed so it
   is outside the flow entirely - the rail's rows do not know it is there and
   nothing moves when it appears, at any width.

   It runs inward over the image rather than outward off the screen, so unlike
   the Journal's control it has room for one line at a readable size.

   Revealed above the narrow breakpoint only. Below it the rail is a
   horizontal strip at the foot of the screen, and there is no hover there to
   show the word with; hidden and out of flow is how it stays, so that control
   is exactly what it was.
   -------------------------------------------------------------------------- */
.nsn-detail .nsn-rail__label {
	position: absolute;
	top: 50%;
	left: 100%;
	margin-left: 10px;
	transform: translateY(-50%);

	/*
	 * Beside the button, on one line. There is room here that the Journal's
	 * control does not have: the rail is at the edge of the page and the
	 * word runs inward over the image, so nothing is up against the screen.
	 */
	white-space: nowrap;
	font-size: clamp(0.5rem, 0.47rem + 0.08vw, 0.5625rem);
	line-height: 1.25;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--nsn-bone-72);

	opacity: 0;
	pointer-events: none;

	/* One transition, both ways: no steps, nothing repeating. */
	transition: opacity 0.22s var(--nsn-ease);
}

/* Focus is not a pointer state, so it is not inside the hover query below. */
@media (min-width: 901px) {
	.nsn-detail .nsn-rail__close:focus-visible .nsn-rail__label {
		opacity: 1;
	}
}

@media (min-width: 901px) and (hover: hover) {
	.nsn-detail .nsn-rail__close:hover .nsn-rail__label {
		opacity: 1;
	}
}

/*
 * Reduced motion: the cross does not turn at all, and the word arrives
 * without fading. Neither piece of information is taken away - only the
 * movement is.
 */
@media (prefers-reduced-motion: reduce) {
	.nsn-detail .nsn-rail__x {
		transition: none;
	}

	.nsn-detail .nsn-rail__close:hover .nsn-rail__x,
	.nsn-detail .nsn-rail__close:focus-visible .nsn-rail__x {
		transform: none;
	}

	.nsn-detail .nsn-rail__label {
		transition: none;
	}
}

/* --------------------------------------------------------------------------
   One close control, in both states

   The panel used to carry a close of its own, and the rail's faded away as it
   arrived. There is one button now, so it has to be reachable whether the
   panel is open or not - which means standing above it. The panel is fixed at
   z-index 60 and opaque; the rail is 50, so without this the single control
   would be painted over the moment the panel opened.

   The rail passes pointer events only to its children, so the button keeps
   its own hit area over the panel and the panel keeps the rest.
   -------------------------------------------------------------------------- */
/*
 * The rail rises over the panel, and drops its own ground while it is there.
 *
 * z-index on the button alone does nothing: the rail is a fixed element with
 * a z-index of its own, so it is a stacking context and its children are
 * stacked inside it - 80 on the button still sits under a panel at 60,
 * because the rail as a whole is at 50. The column has to be the thing that
 * rises.
 *
 * Which it can only do without its background. That ink is what the panel's
 * own layer comment warns about: an opaque column painting over the panel's
 * first 115px and taking the first letters of every line with it. Open, the
 * panel is the ground and the rail has no need of one - the title and INFO
 * are faded out by then, so the only thing left to paint is the button.
 */
.nsn-detail.is-panel-open .nsn-rail {
	z-index: 70;
	background: none;
}

/*
 * And it stays usable. Everything else in the rail keeps the pointer block
 * above, so nothing behind the panel can be clicked - but this is the page's
 * only close now, and it has to work in both states.
 */
.nsn-detail.is-panel-open .nsn-rail__close {
	pointer-events: auto;
}

/* --------------------------------------------------------------------------
   The wordmark in the sidebar

   The header renders the wordmark on every page but this one - see header.php
   - and here the same component is rendered at the head of the panel instead.
   One wordmark in the document and one link home, not two with one switched
   off; and because it is a child of the sidebar, the panel's own clip reveals
   it and the panel's own state hides it. Nothing new animates it and nothing
   new decides when it shows.
   -------------------------------------------------------------------------- */
/*
 * The panel starts at the top of the page now rather than below the header:
 * the wordmark is the first thing in it, and it stands where the header's copy
 * used to, on the panel's own left edge instead of the header's gutter.
 */
body.single-nsn_work .nsn-detail__panel {
	padding-top: clamp(1.4rem, 3.4vh, 2.6rem);
}

/*
 * And the writing keeps clear of the close control, which floats over the
 * panel from the rail rather than sitting in this column. The room it needs is
 * reserved under the wordmark - measured from the button itself, so the two
 * cannot drift apart - and that is what puts the reading order of the
 * reference on the page: wordmark, control, then the project.
 */
.nsn-detail__panel .nsn-logo {
	display: block;
	margin-bottom: calc(clamp(35px, 3vw, 40px) + 2.25rem);
}

/* --------------------------------------------------------------------------
   The header over the project image

   The site header carries its own wash of ink - a gradient from 0.94 at the
   top to nothing at the bottom - so it stays legible over whatever it happens
   to be crossing. On a project page that wash reads as a dark band ruled
   across the top of the feature image, which now runs the full height of the
   masthead behind it.

   Taken off here and nowhere else. The rule is scoped to the project page's
   own body class rather than to .nsn-header, because this stylesheet is
   loaded on the Journal article and the Contact page too, and the header is
   the global one everywhere else. Nothing about it changes but the ground it
   does or does not paint: same height, same position, same type, same colour.
   -------------------------------------------------------------------------- */
body.single-nsn_work .nsn-header {
	background: none;
}

