@layer components {
	/*
	 * Contact.
	 *
	 * The page itself is a sentence, an address and four accounts — not enough
	 * to fill a frame, and inventing copy to pad it out would be worse than
	 * leaving it bare. So the foot of the screen carries the rest of the site,
	 * each route holding a photograph from the section it leads to, and the
	 * frame behind everything answers to whichever one is being pointed at.
	 *
	 * That is also what makes it legible without a box drawn round the words:
	 * the address sits in the upper band where the scrim is heaviest, and the
	 * routes carry their own plates.
	 */
	.hero.has-contact {
		display: grid;
		height: 100svh;
		overflow: hidden;
	}

	.hero.has-contact .contact-frames,
	.hero.has-contact .hero-scrim {
		grid-area: 1 / 1;
	}

	/* ------------------------------------------------------------ the frame */

	.contact-frames {
		position: relative;
		overflow: hidden;
	}

	.contact-frame {
		position: absolute;
		inset: 0;
		opacity: 0;
		scale: 1.06;
		transition:
			opacity 900ms var(--ease-out),
			scale 2400ms var(--ease-out);
	}

	.contact-frame.is-current {
		opacity: 1;
		scale: 1;
	}

	.contact-frame img {
		width: 100%;
		height: 100%;
		object-fit: cover;
		display: block;
	}

	/*
	 * Heavier at the top, where the address is, and heavier again at the foot
	 * behind the routes. The band between is left clearer, so the photograph
	 * is still allowed to be a photograph.
	 */
	.hero.has-contact .hero-scrim {
		background:
			linear-gradient(to bottom, hsl(var(--over-scrim) / 0.82) 0%, hsl(var(--over-scrim) / 0.25) 52%),
			linear-gradient(to top, hsl(var(--over-scrim) / 0.8) 0%, hsl(var(--over-scrim) / 0.1) 40%),
			linear-gradient(to right, hsl(var(--over-scrim) / 0.45) 0%, hsl(var(--over-scrim) / 0) 62%);
	}

	/*
	 * A phone crops the frame to its middle, which on this photograph is the
	 * moon — and puts the signature straight over it. The top of the wash is
	 * carried further down to give the header something to sit on.
	 */
	@media (max-width: 60rem) {
		.hero.has-contact .hero-scrim {
			background:
				linear-gradient(to bottom, hsl(var(--over-scrim) / 0.88) 0%, hsl(var(--over-scrim) / 0.4) 34%),
				linear-gradient(to top, hsl(var(--over-scrim) / 0.85) 0%, hsl(var(--over-scrim) / 0.2) 46%),
				linear-gradient(hsl(var(--over-scrim) / 0.2), hsl(var(--over-scrim) / 0.2));
		}
	}

	/*
	 * Grain, over the photograph and under the words.
	 *
	 * A heavy scrim across a dark sky bands badly on an 8-bit display; noise
	 * breaks the bands up. It also gives the frame a single surface, so the
	 * five photographs that can appear here read as one page rather than five.
	 */
	.hero.has-contact::after {
		content: "";
		position: absolute;
		inset: 0;
		z-index: 1;
		pointer-events: none;
		opacity: 0.5;
		mix-blend-mode: overlay;
		background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
	}

	/* ----------------------------------------------------------- the layout */

	.contact {
		grid-area: 1 / 1;
		z-index: 2;
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto;
		grid-template-rows: 1fr auto;
		grid-template-areas:
			"say  elsewhere"
			"routes routes";
		align-items: center;
		column-gap: var(--space-2xl);
		min-height: 0;
		padding: calc(var(--header-h) + var(--space-m)) var(--gutter) var(--space-l);
		color: var(--over-ink);
	}

	/* ---------------------------------------------------------- the address */

	/*
	 * The address is the headline.
	 *
	 * A contact page exists to hand over one string, so it is set as the
	 * largest thing on the page and the sentence about being available is set
	 * as the caption it actually is. Every earlier version had that the other
	 * way round — a display-sized claim with the address in small print under
	 * it — which is a poster, not a contact page.
	 *
	 * It is set in a monospace, which is the face an address wants: the
	 * characters stand in a rank the way the string itself does, and it reads
	 * as something to copy rather than something to say. JetBrains Mono is
	 * variable from 300 to 700, so the wave of weight on approach survives the
	 * change of face.
	 */
	.contact-say {
		grid-area: say;
		align-self: center;
		display: grid;
		gap: clamp(var(--space-xs), 2vh, var(--space-m));
		min-width: 0;
	}

	/* Who, and where — one line, with a rule holding the two apart. */
	.contact-intro {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--space-2xs) var(--space-s);
	}

	.contact-intro .contact-where::before {
		content: "";
		display: inline-block;
		width: clamp(1.25rem, 3vw, 2.5rem);
		height: 1px;
		margin-right: 0.9em;
		vertical-align: middle;
		background: color-mix(in oklab, var(--over-ink) 32%, transparent);
	}

	.contact-caption {
		margin: 0;
		max-width: 46ch;
		font-size: clamp(0.95rem, min(1.35vw, 2.1vh), 1.2rem);
		line-height: 1.45;
		color: color-mix(in oklab, var(--over-ink) 74%, transparent);
		text-wrap: pretty;
	}

	.contact-address {
		margin: 0;
		font-family: var(--font-mono);
		font-size: clamp(1.6rem, min(7vw, 11vh), 6.25rem);
		font-weight: 300;
		line-height: 1.12;
		/*
		 * A monospace sets its own rhythm — the letters already stand in a
		 * rank — so the tracking Inter needed pulling in would close the
		 * characters onto each other here.
		 */
		letter-spacing: -0.03em;
	}

	.contact-mail {
		display: inline-block;
		max-width: 100%;
		color: var(--over-ink);
		text-decoration: none;
		cursor: pointer;
	}

	/*
	 * The mask hides the letters while they are still a line below, but it must
	 * not crop the ones that have arrived — plain overflow:hidden cut the tail
	 * off every j and the @. `overflow: clip` with a margin lets descenders
	 * paint past the edge while the incoming letters, a full line down, stay
	 * hidden.
	 */
	.contact-mail-text {
		display: block;
		overflow: clip;
		overflow-clip-margin: 0.32em;
	}

	/*
	 * One span per character, each carrying its index. The reveal rises out of
	 * a clipped line and the hover sends a wave of weight left to right —
	 * neither is possible with the string set as a single run of text.
	 */
	/* Local part and domain: the break goes between them or nowhere. */
	.kin-part {
		display: inline-block;
		white-space: nowrap;
	}

	.kin {
		display: inline-block;
		transition:
			font-weight 420ms var(--ease-out),
			translate 380ms var(--ease-out),
			opacity 380ms var(--ease-out);
		transition-delay: calc(var(--i) * 14ms);
	}

	.has-js .contact-address .kin {
		translate: 0 105%;
		opacity: 0;
		transition-delay: calc(220ms + var(--i) * 26ms);
	}

	.has-js .is-ready .contact-address .kin {
		translate: 0 0;
		opacity: 1;
	}

	.contact-mail:hover .kin,
	.contact-mail:focus-visible .kin {
		font-weight: 600;
	}

	/* Held down, the whole address presses in like the button it is. */
	.contact-mail:active {
		scale: 0.995;
	}

	.contact-hint {
		display: flex;
		align-items: center;
		gap: 0.6em;
		margin: 0;
		font-size: var(--step--2);
		letter-spacing: 0.2em;
		text-transform: uppercase;
		color: color-mix(in oklab, var(--over-ink) 48%, transparent);
		transition: color var(--dur) var(--ease-out);
	}

	.contact-hint::before {
		content: "";
		flex: none;
		width: clamp(1.5rem, 4vw, 3rem);
		height: 1px;
		background: currentcolor;
	}

	/* It says press, so it is a button and it is big enough to press. */
	.contact-hint-button {
		padding: 0.5em 0;
		font: inherit;
		letter-spacing: inherit;
		text-transform: inherit;
		color: inherit;
		transition: color var(--dur-fast) var(--ease-out);
	}

	.contact-hint-button:hover,
	.contact-hint-button:focus-visible {
		color: var(--over-ink);
	}

	.contact-hint.is-done {
		color: var(--over-ink);
	}

	.contact-where {
		margin: 0;
		font-size: var(--step--2);
		letter-spacing: 0.22em;
		text-transform: uppercase;
		color: color-mix(in oklab, var(--over-ink) 58%, transparent);
	}

	.contact-elsewhere {
		grid-area: elsewhere;
		align-self: center;
		display: grid;
		gap: var(--space-xs);
		justify-items: end;
		text-align: right;
	}

	.contact-socials {
		display: grid;
		gap: clamp(var(--space-2xs), 1.4vh, var(--space-s));
		margin: 0;
		padding: 0;
		list-style: none;
		justify-items: end;
	}

	.has-js .contact-elsewhere > *,
	.has-js .contact-socials li {
		opacity: 0;
		translate: 0.75rem 0;
		transition:
			opacity 800ms var(--ease-out),
			translate 800ms var(--ease-out);
		transition-delay: calc(340ms + var(--i, 0) * 80ms);
	}

	.has-js .is-ready .contact-elsewhere > *,
	.has-js .is-ready .contact-socials li {
		opacity: 1;
		translate: 0 0;
	}

	/*
	 * Named, not drawn — on a contact page a bare circle is not an answer —
	 * and set as words rather than pills, so they read as a line beside the
	 * location instead of four more buttons under the one that matters. As
	 * pills the fourth wrapped on its own and sat there like a mistake.
	 */
	.contact-socials a {
		position: relative;
		font-size: var(--step--1);
		letter-spacing: 0.02em;
		color: color-mix(in oklab, var(--over-ink) 70%, transparent);
		text-decoration: none;
		transition: color var(--dur-fast) var(--ease-out);
	}

	.contact-socials a::after {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		bottom: -0.25em;
		height: 1px;
		background: currentcolor;
		transform: scaleX(0);
		transform-origin: right;
		transition: transform var(--dur) var(--ease-out);
	}

	.contact-socials a:hover,
	.contact-socials a:focus-visible {
		color: var(--over-ink);
	}

	.contact-socials a:hover::after,
	.contact-socials a:focus-visible::after {
		transform: scaleX(1);
	}

	/* ----------------------------------------------------------- the routes */

	.routes {
		grid-area: routes;
		display: grid;
		grid-template-columns: repeat(4, minmax(0, 1fr));
		gap: clamp(var(--space-2xs), 1vw, var(--space-s));
		align-self: end;
	}

	.route {
		position: relative;
		display: grid;
		align-content: end;
		gap: 0.15rem;
		min-height: clamp(7rem, 20vh, 11rem);
		padding: var(--space-s);
		border-radius: var(--radius-lg);
		overflow: hidden;
		text-decoration: none;
		color: var(--over-ink);
		border: 1px solid color-mix(in oklab, var(--over-ink) 14%, transparent);
		transition:
			border-color var(--dur) var(--ease-out),
			translate var(--dur) var(--ease-out);
	}

	.has-js .routes .route {
		opacity: 0;
		translate: 0 1.5rem;
		transition:
			opacity 900ms var(--ease-out) var(--route-delay, 0ms),
			translate 900ms var(--ease-out) var(--route-delay, 0ms),
			border-color var(--dur) var(--ease-out);
	}

	.has-js .is-ready .routes .route {
		opacity: 1;
		translate: 0 0;
	}

	.route:hover,
	.route:focus-visible {
		border-color: color-mix(in oklab, var(--over-ink) 55%, transparent);
		translate: 0 -0.25rem;
	}

	/* The picture is the plate: dimmed, so the words on it stay the words. */
	.route-shot {
		position: absolute;
		inset: 0;
		z-index: -1;
	}

	.route-shot::after {
		content: "";
		position: absolute;
		inset: 0;
		background: linear-gradient(to top, hsl(var(--over-scrim) / 0.9) 15%, hsl(var(--over-scrim) / 0.5));
		transition: background var(--dur) var(--ease-out);
	}

	.route:hover .route-shot::after {
		background: linear-gradient(to top, hsl(var(--over-scrim) / 0.82) 10%, hsl(var(--over-scrim) / 0.3));
	}

	.route-shot img {
		width: 100%;
		height: 100%;
		object-fit: cover;
		display: block;
		transition: scale var(--dur-slow) var(--ease-out);
	}

	.route:hover .route-shot img {
		scale: 1.06;
	}

	.route-body {
		display: grid;
		gap: 0.1em;
		min-width: 0;
	}

	.route-label {
		font-size: var(--step--1);
		line-height: 1.15;
	}

	.route-note {
		font-size: var(--step--2);
		color: color-mix(in oklab, var(--over-ink) 66%, transparent);
	}

	.route-arrow {
		position: absolute;
		top: var(--space-s);
		right: var(--space-s);
		font-size: 0.9rem;
		opacity: 0;
		translate: -0.4rem 0;
		transition:
			opacity var(--dur) var(--ease-out),
			translate var(--dur) var(--ease-out);
	}

	.route:hover .route-arrow,
	.route:focus-visible .route-arrow {
		opacity: 0.8;
		translate: 0 0;
	}

	/* ------------------------------------------------------------ the phone */

	/*
	 * Stacked, and the routes pair up rather than shrinking to four slivers.
	 * The address keeps the top of the screen, where the scrim is heaviest.
	 */
	@media (max-width: 60rem) {
		.contact {
			padding-top: calc(var(--header-h) + var(--space-s));
			padding-bottom: var(--space-m);
			gap: var(--space-m);
		}

		/*
		 * Three clusters with a hole in the middle was the trouble: everything
		 * crammed into the top third, a void, then the accounts, then the
		 * routes. The address and the accounts are one group now, centred in
		 * what the routes leave, and the routes hold the foot.
		 */
		/*
		 * Three bands, evenly spread: the address at the top, the mark in the
		 * middle, the routes at the foot.
		 *
		 * Giving the address the free space and letting it centre itself only
		 * moved the hole under it — the space has to be shared between the
		 * bands, not handed to one of them. Spread this way there is no gap
		 * larger than the others anywhere on the screen.
		 */
		.contact {
			display: flex;
			flex-direction: column;
			justify-content: space-between;
			gap: clamp(var(--space-s), 3vh, var(--space-l));
		}

		.contact-say {
			min-height: 0;
		}

		/*
		 * The lines of the address block were 12px apart while 73px sat idle
		 * under them. Because the bands are spread with space-between, air
		 * given here is taken straight out of that gap — the block breathes
		 * and the hole closes by the same amount.
		 */
		.contact-say {
			justify-items: center;
			text-align: center;
			gap: clamp(var(--space-s), 2.6vh, var(--space-l));
		}

		.contact-intro {
			justify-content: center;
			gap: var(--space-2xs) var(--space-s);
		}

		/*
		 * No column to stand in on a phone, so the accounts lie back down —
		 * and the label above them goes. One short word centred over a
		 * full-width row is a line that is almost entirely empty, and these
		 * four name themselves better than any heading names them.
		 */
		.contact-elsewhere {
			justify-items: center;
			text-align: center;
		}

		.contact-elsewhere .cell-kicker {
			display: none;
		}

		/*
		 * X takes the space above the row.
		 *
		 * Its name is a single letter, which is why it kept ending up as a
		 * ragged tail on the end of three long words — and why it works set
		 * large on its own: the mark and the name are the same character. It
		 * fills the gap the centred address leaves under itself, and the three
		 * that read as words stay a row of words.
		 */
		.contact-socials {
			display: grid;
			grid-template-columns: repeat(3, auto);
			justify-content: center;
			align-items: baseline;
			gap: clamp(var(--space-2xs), 2vh, var(--space-m)) clamp(0.5rem, 2.6vw, 1.1rem);
		}

		.contact-socials li {
			grid-row: 2;
		}

		.contact-socials li[data-social="twitter"] {
			grid-row: 1;
			grid-column: 1 / -1;
			justify-self: center;
		}

		.contact-socials a {
			font-size: var(--step--2);
			letter-spacing: 0.1em;
			text-transform: uppercase;
		}

		.contact-socials li[data-social="twitter"] a {
			display: block;
			font-size: clamp(3.5rem, min(23vw, 13vh), 7.5rem);
			line-height: 0.85;
			letter-spacing: -0.02em;
			text-transform: none;
			color: color-mix(in oklab, var(--over-ink) 88%, transparent);
		}

		/* The sweep under a word is wrong under a mark this size. */
		.contact-socials li[data-social="twitter"] a::after {
			display: none;
		}

		.contact-socials li[data-social="twitter"] a:hover,
		.contact-socials li[data-social="twitter"] a:focus-visible {
			color: var(--over-ink);
			scale: 1.06;
		}

		.contact-socials li[data-social="twitter"] a {
			transition:
				color var(--dur-fast) var(--ease-out),
				scale var(--dur) var(--ease-out);
			transform-origin: center;
		}

		/*
		 * The address breaks at the @ rather than shrinking to fit a phone: two
		 * big lines read better than one small one, and it is the page.
		 */
		.contact-address {
			font-size: clamp(2.1rem, min(11vw, 6.5vh), 3.4rem);
			letter-spacing: -0.035em;
			line-height: 1.08;
		}

		.contact-hint {
			justify-content: center;
		}

		.contact-hint::before {
			width: 1.5rem;
		}


		.routes {
			grid-template-columns: repeat(2, minmax(0, 1fr));
		}

		.route {
			min-height: clamp(5.5rem, 15vh, 8rem);
			padding: var(--space-xs);
		}

		.route-arrow {
			display: none;
		}
	}

	/* Shorter phones: the routes give up their notes before the page overflows. */
	@media (max-width: 60rem) and (max-height: 46rem) {
		.contact {
			padding-top: calc(var(--header-h) + var(--space-2xs));
			gap: var(--space-s);
		}

		.contact-say {
			gap: clamp(var(--space-2xs), 1.6vh, var(--space-s));
		}

		.contact-address {
			font-size: clamp(1.35rem, 7vw, 2rem);
		}

		.contact-caption,
		.route-note {
			display: none;
		}

		.route {
			min-height: 4.5rem;
		}
	}

	@media (max-width: 60rem) and (max-height: 38rem) {
		.contact-address {
			font-size: clamp(1.15rem, 6vw, 1.6rem);
		}

		.contact-hint {
			display: none;
		}

		.contact-socials a {
			font-size: 0.66rem;
			letter-spacing: 0.05em;
		}

		.contact-socials li[data-social="twitter"] a {
			font-size: clamp(2.5rem, min(18vw, 11vh), 4rem);
		}

		.route {
			min-height: 3.75rem;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.contact-frame,
		.route,
		.route-shot img,
		.kin {
			transition-duration: 1ms;
		}
	}
}
