/* ==========================================================================
   The "Zeg" transition curtain
   --------------------------------------------------------------------------
   Two things matter here:

   1. On the INCOMING page the curtain has to be covering at first paint,
      before any JavaScript runs. That is what the html.nsn-transition-in
      rules below do — the class is set by a tiny blocking script in <head>,
      so there is never a frame of unmasked content.

   2. The curtain must be completely inert when idle: hidden from the
      accessibility tree, non-interactive, and not painting.
   ========================================================================== */

.nsn-loader {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: grid;
	place-items: center;
	pointer-events: none;
	visibility: hidden;
}

.nsn-loader__panel {
	position: absolute;
	inset: 0;
	background: var(--nsn-ink);
	/* Parked below the fold; GSAP drives it from here. */
	transform: translate3d(0, 101%, 0);
	will-change: transform;
}

.nsn-loader__brand {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	/*
	 * Enough clearance that the rule never touches the 'g' descender. The
	 * supplied artwork has no rule, so it stays clearly secondary to the
	 * letterform rather than reading as part of it.
	 */
	gap: clamp(1.6rem, 3.4vw, 3rem);
	opacity: 0;
	will-change: opacity;
}

/*
 * Live MG12 text, not the supplied PNG — the per-letter stagger needs real
 * glyphs. Tracking is set to reproduce the supplied "Zeg" artwork: its ink
 * box is 1.673:1, and MG12 at this tracking measures 1.673:1.
 */
.nsn-loader__word {
	display: flex;
	font-size: clamp(3.5rem, 13vw, 11rem);
	line-height: 0.9;
	letter-spacing: -0.008em;
	color: var(--nsn-bone);
	/* Letters slide up from behind this edge. */
	overflow: hidden;
	padding-bottom: 0.08em;
}

.nsn-loader__letter {
	display: block;
	transform: translate3d(0, 110%, 0);
	will-change: transform;
}

.nsn-loader__rule {
	display: block;
	width: clamp(120px, 22vw, 280px);
	height: 1px;
	background: var(--nsn-bone-28);
	transform: scaleX(0);
	transform-origin: left center;
	will-change: transform;
}

/* --------------------------------------------------------------------------
   Active states
   -------------------------------------------------------------------------- */
.nsn-loader.is-active,
html.nsn-transition-in .nsn-loader,
html.nsn-transition-out .nsn-loader {
	visibility: visible;
}

html.nsn-transition-in,
html.nsn-transition-out {
	overflow: hidden;
}

/*
 * Pre-JS state for the incoming page: fully covered, wordmark already read.
 * work.js/transition.js takes over from exactly this state.
 */
html.nsn-transition-in .nsn-loader__panel {
	transform: translate3d(0, 0, 0);
}

html.nsn-transition-in .nsn-loader__brand {
	opacity: 1;
}

html.nsn-transition-in .nsn-loader__letter {
	transform: translate3d(0, 0, 0);
}

html.nsn-transition-in .nsn-loader__rule {
	transform: scaleX(1);
}

/* --------------------------------------------------------------------------
   Reduced motion: the curtain is decoration, so it simply never appears.
   Navigation still works, it is just immediate.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	.nsn-loader {
		display: none;
	}

	html.nsn-transition-in,
	html.nsn-transition-out {
		overflow: visible;
	}
}

/* ==========================================================================
   The loading screen
   --------------------------------------------------------------------------
   A different thing from the curtain above, and deliberately so. The curtain
   is a move between two pages; this is the wait before the first one. It is
   shown only on an arrival with nothing in front of it - a typed URL, a
   refresh, a link from elsewhere - because a click-through has already spent
   the download watching the curtain and does not need to be told to wait
   twice. inc/assets.php decides which of those happened, before first paint.

   Nothing here decides when the page is ready. The screen is armed by a class
   on <html> and leaves on nsn:revealed, which transition.js fires once the
   document and the page's primary image are both in - so it lasts exactly as
   long as the real wait, and not one frame longer.
   ========================================================================== */

.nsn-loading {
	position: fixed;
	inset: 0;
	/* Above the curtain, which is 200: on a direct load both are up, and this
	   is the one that should be read. */
	z-index: 300;
	display: grid;
	place-items: center;
	background: #fff;

	/*
	 * Black on white, for the wordmark and for the status line alike. The
	 * coral this screen used to carry is the Work index's, and it stays there:
	 * on the loading screen there is nothing for an accent to distinguish.
	 */
	color: var(--nsn-ink);

	/*
	 * Idle is the resting state, so a browser that never runs the head script
	 * cannot be left with the page covered.
	 */
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	transition: opacity 0.64s cubic-bezier(0.165, 0.84, 0.44, 1), visibility 0s linear 0.64s;
}

html.nsn-is-loading .nsn-loading {
	opacity: 1;
	visibility: visible;
	/* Nothing underneath can be clicked while this is up. */
	pointer-events: auto;
	transition: none;
}

/* Gone, and no longer animating anything behind the page. */
html.nsn-is-loaded .nsn-loading {
	display: none;
}

html.nsn-is-loading {
	overflow: hidden;
}

/*
 * One cover at a time.
 *
 * On a direct arrival the loading screen is what covers the page, so the
 * curtain must not also be standing behind it. It was, and the loading
 * screen's fade uncovered it: measured 22% of the way through that fade, the
 * curtain's bone wordmark and the loading screen's own were both on screen,
 * overlapping, with everything greyed by translucent white over near-black
 * ink. That is the doubled logo, and this rule is the whole of the fix.
 *
 * Hidden rather than torn out. transition.js goes on running its timeline
 * exactly as before and goes on firing nsn:revealed on the frame it always
 * did - which is the event the loading screen is waiting for. Only the paint
 * goes. A click-through never gets nsn-arrival, so the curtain there is
 * untouched: it still plays out, covers the navigation and lifts on the other
 * side, which is the one job it is for.
 *
 * The :not() is what keeps this to the arrival. nsn-arrival is sticky - it has
 * to be, or the curtain surfaces halfway through the fade - so on a page that
 * was loaded directly it is still set later, when the visitor clicks a link
 * and the curtain's outgoing half should play. Measured: without the :not()
 * that half ran invisibly, and the navigation lost its cover. nsn-transition-out
 * is set by transition.js the moment a departure begins, so excluding it hands
 * the curtain back for exactly that, and nothing else.
 *
 * Specificity is deliberate. html.nsn-transition-in .nsn-loader above also
 * counts (0,2,1), so this only wins by coming later in the sheet - it must
 * stay below it. Against html.nsn-transition-out .nsn-loader the :not() adds
 * its argument's weight, making this (0,3,1) - but it cannot match then
 * anyway, which is the point.
 */
html.nsn-arrival:not(.nsn-transition-out) .nsn-loader {
	visibility: hidden;
}

/*
 * One cell, two layers.
 *
 * The status line and the wordmark are never on screen together - phase 1 is
 * the line, phases 2 to 4 are the word - so they are stacked in the same grid
 * cell rather than set one above the other. That is what puts each of them on
 * the exact centre of the screen during its own phase, instead of the two
 * sharing a column whose centre belongs to neither of them.
 *
 * Reduced motion is the exception, and gets the column back below: there are
 * no phases there, both are shown at once, and they must not overlap.
 */
.nsn-loading__inner {
	display: grid;
	place-items: center;
	padding: var(--nsn-gutter);
}

.nsn-loading__word,
.nsn-loading__status {
	grid-area: 1 / 1;
}

/* --------------------------------------------------------------------------
   The wordmark
   -------------------------------------------------------------------------- */

/*
 * The wordmark inherits the overlay's black: on white that is the same
 * black-on-light reading the brand mark has everywhere else, and it is the
 * curtain's bone-on-ink inverted rather than a third treatment invented for
 * this screen.
 */
.nsn-loading__word {
	display: flex;
	margin: 0;
	font-size: clamp(3.25rem, 12vw, 9rem);
	line-height: 1;
	letter-spacing: -0.008em;
}

/* --------------------------------------------------------------------------
   The sequence

   The screen is not one loop any more, it is four phases in order, and the
   head script in inc/assets.php is what walks them:

     1  loading    the status line counts its dots; the wordmark is not there
     2  logo-in    the status line goes, the three characters arrive
     3  hold       the whole word stands still, about half a second
     4  logo-out   the three characters leave the way the brief describes
     5  revealed   and only then does the overlay fade and the page appear

   Phase 1 lasts as long as the page takes, so nothing here is a delay waiting
   to be discovered: it ends when the real arrival is announced, and never
   before the dots have had one full pass to be read. Phases 2 to 4 are fixed,
   because they are the animation itself.

   The characters are parked off-slot to begin with and each phase is a
   one-shot that fills forwards, so between phases they simply stay where the
   last one left them. That is what the half-second hold is made of - no
   keyframe, just the gap between one animation ending and the next starting.
   -------------------------------------------------------------------------- */

/*
 * Phase 1 shows the status line and nothing else. Hidden rather than merely
 * parked: the characters are already off-slot and clipped, but the brief asks
 * for no overlap at all, and two guarantees are cheaper than one.
 */
.nsn-loading__word {
	visibility: hidden;
}

html.nsn-load-logo .nsn-loading__word {
	visibility: visible;
}

/* And from phase 2 the status line is gone, in the same frame. */
html.nsn-load-logo .nsn-loading__status {
	visibility: hidden;
}

.nsn-loading__slot {
	display: block;
	overflow: hidden;
	padding-block: 0.08em 0.24em;
}

.nsn-loading__glyph {
	display: block;
	will-change: transform;
	animation-duration: 0.58s;
	animation-fill-mode: both;
}

/*
 * Down, up, and across - the same three moves, and the same two curves, that
 * the loop used. The arrival is the site's own easeOutQuart, so the word
 * settles the way everything else on the site settles; the departure is its
 * mirror, accelerating away.
 *
 * The three are offset by 0.16s rather than moving together, so they read as
 * three characters rather than one block sliding about. That stagger is why
 * the entry takes 0.9s rather than 0.58s: the g starts last and finishes last,
 * and the hold only begins once it has.
 */
.nsn-loading__glyph--z {
	transform: translate3d(0, -170%, 0);
}

.nsn-loading__glyph--e {
	transform: translate3d(0, 170%, 0);
	animation-delay: 0.16s;
}

/*
 * The g travels across rather than up or down, and it is the one character
 * that turns round: it comes in from the right, settles, and goes back out
 * the way it came. Both ends are therefore the same side, which is why the two
 * are named separately rather than one distance being negated - the return is
 * the point, not an oversight.
 */
.nsn-loading__glyph--g {
	--nsn-loading-g-enter: 220%;
	--nsn-loading-g-exit: 220%;
	transform: translate3d(var(--nsn-loading-g-enter), 0, 0);
	animation-delay: 0.32s;
}

/* Phase 2. */
html.nsn-load-logo .nsn-loading__glyph {
	animation-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}

html.nsn-load-logo .nsn-loading__glyph--z {
	animation-name: nsn-loading-z-in;
}

html.nsn-load-logo .nsn-loading__glyph--e {
	animation-name: nsn-loading-e-in;
}

html.nsn-load-logo .nsn-loading__glyph--g {
	animation-name: nsn-loading-g-in;
}

/*
 * Phase 4. Both classes are on the element by now, so these must come after
 * the phase 2 rules to win - equal specificity, later wins. Naming a different
 * animation is what restarts it, and each of these opens on translate3d(0,0,0),
 * which is exactly where the character is standing after the hold. There is
 * nothing to jump from.
 */
html.nsn-load-out .nsn-loading__glyph {
	animation-duration: 0.55s;
	animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);
}

html.nsn-load-out .nsn-loading__glyph--z {
	animation-name: nsn-loading-z-out;
}

html.nsn-load-out .nsn-loading__glyph--e {
	animation-name: nsn-loading-e-out;
}

html.nsn-load-out .nsn-loading__glyph--g {
	animation-name: nsn-loading-g-out;
}

@keyframes nsn-loading-z-in {
	from { transform: translate3d(0, -170%, 0); }
	to   { transform: translate3d(0, 0, 0); }
}

@keyframes nsn-loading-z-out {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(0, 170%, 0); }
}

@keyframes nsn-loading-e-in {
	from { transform: translate3d(0, 170%, 0); }
	to   { transform: translate3d(0, 0, 0); }
}

@keyframes nsn-loading-e-out {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(0, -170%, 0); }
}

@keyframes nsn-loading-g-in {
	from { transform: translate3d(var(--nsn-loading-g-enter), 0, 0); }
	to   { transform: translate3d(0, 0, 0); }
}

@keyframes nsn-loading-g-out {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(var(--nsn-loading-g-exit), 0, 0); }
}

/* --------------------------------------------------------------------------
   The line underneath
   -------------------------------------------------------------------------- */

/*
 * Dead centre, and nothing nudging it off.
 *
 * This carried a translateX of half the reserved dot slot, meant to put the
 * word rather than the whole line on the centre line. The sign was wrong: it
 * moved the line further from the centre instead of towards it, leaving the
 * box 11.1px left of centre at 1440 and about 10px at phone widths. Measured
 * both ways, and taken out rather than reversed - the line to centre is
 * "Loading...", dots included, which is what the box already is.
 */
.nsn-loading__status {
	margin: 0;
	font-size: var(--nsn-step-sm);
	letter-spacing: 0.14em;
}

/*
 * As wide as three dots whether one is showing or all of them are. The dots
 * count up in place and the word never moves.
 */
.nsn-loading__dots {
	display: inline-block;
	width: 1.5em;
	text-align: left;
}

.nsn-loading__dots i {
	font-style: normal;
}

/* The first dot is the resting state: "Loading." is there from the start. */
.nsn-loading__dots i:nth-child(2) {
	animation: nsn-loading-dot-2 0.9s steps(1, end) infinite;
}

.nsn-loading__dots i:nth-child(3) {
	animation: nsn-loading-dot-3 0.9s steps(1, end) infinite;
}

/*
 * Stepped, not eased: a dot is either typed or it is not. Three phases of
 * 300ms give "Loading." then "Loading.." then "Loading..." and back.
 */
@keyframes nsn-loading-dot-2 {
	0%,
	33.32% {
		opacity: 0;
	}

	33.33%,
	100% {
		opacity: 1;
	}
}

@keyframes nsn-loading-dot-3 {
	0%,
	66.65% {
		opacity: 0;
	}

	66.66%,
	100% {
		opacity: 1;
	}
}

/* --------------------------------------------------------------------------
   Reduced motion: the loading state stays, the movement goes.

   The screen still appears and still says what it is doing - that is
   information, not decoration, and it is the one thing here a visitor
   actually needs. What goes is the travel and the counting: the word stands
   still and reads "Loading...", complete, from the first frame.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	/*
	 * No phases either. The head script does not walk them under reduced
	 * motion, so nsn-load-logo never arrives to make the wordmark visible -
	 * it is shown here instead, standing complete beside the status line from
	 * the first frame. One state, no sequence, nothing moving.
	 */
	.nsn-loading__word {
		visibility: visible;
	}

	/* Both are shown at once here, so they need the column back. */
	.nsn-loading__inner {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: clamp(1.4rem, 3.2vw, 2.6rem);
	}

	.nsn-loading__glyph {
		animation: none;
		transform: none;
	}

	/*
	 * Matched selector for selector against the two rules above, not the
	 * shorter `.nsn-loading__dots i`. A media query adds no specificity, so
	 * the shorter form loses to :nth-child() and the dots go on counting -
	 * measured, before this was written out in full.
	 */
	.nsn-loading__dots i:nth-child(2),
	.nsn-loading__dots i:nth-child(3) {
		animation: none;
		opacity: 1;
	}

	.nsn-loading {
		transition-duration: 0.01ms;
		transition-delay: 0s;
	}
}
