/* =========================================================================
   EYN, the company above the three sites. DRAFT stylesheet.
   =========================================================================
   THE ONE IDEA
   ------------
   The sky's lattice IS the mark's lattice, at the same spacing.

   The mark is drawn on a 5x5 lattice inside a 90 unit box: its dot columns
   sit at 1.9, 23.4, 45.0, 66.5 and 88.0, so the pitch is 21.5 units and the
   middle node lands on the box centre. Two rules follow, and the whole page
   hangs off them:

     1. one grid cell EQUALS one step of the mark's own lattice, so the mark
        is exactly 90 / 21.5 = 4.186 cells wide. The sky is then the mark's
        own dots continuing outward, not a background it sits on top of.
     2. a sky dot must land exactly on the stage centre, where the mark's
        centre node is. A tile with a centred dot repeats its dots at
        origin + (n + 1/2) x cell, so the layer starts half a cell before
        centre: background-position: calc(50% - cell/2).

   That is why the cell here is coarser than eyn.art's: the grid logic is
   the same (one --grid-size knob, coarse regimes below lg that divide the
   viewport into whole cells), but the constant is chosen so that ONE cell
   is one logo step and the mark still reads at the size it deserves.

   Colour: the EYN Solutions palette, since this page is the parent of
   eyn-solutions.com. Hovering a division repaints the whole page in that
   division's own pair. eyn.art's orange belongs to eyn.art, and only
   arrives here while you are pointing at eyn.art.
   ========================================================================= */

/* Registered so they can be TRANSITIONED. A var() inside a gradient is not
   animatable on its own: the browser only interpolates a custom property it
   knows the type of, which is what @property declares. */
@property --brand-a {
	syntax: "<color>";
	inherits: true;
	initial-value: #4776e6;
}
@property --brand-b {
	syntax: "<color>";
	inherits: true;
	initial-value: #8e54e9;
}
/* A third stop, because one of the three logos is not a two-colour ramp: the
   studio's mark runs deep violet → purple → orange, with the purple at 35.6%.
   Its default is the colour a plain two-stop ramp already has at that
   position, so every pair that IS two colours renders exactly as before. */
@property --brand-m {
	syntax: "<color>";
	inherits: true;
	initial-value: #6069e7;
}

/* The signature dots. U+2059 (⁙) is in neither Quicksand nor Vidaloka, so a
   browser borrows the shape from a system font and the mark's own punctuation
   changes from machine to machine. This 496-byte face carries that single
   glyph, cut from Noto Serif Display (OFL, licence beside the file), and its
   unicode-range holds it to that one character wherever the family is listed.
   Same face the other two sites serve. */
@font-face {
	font-family: "EYN Dots";
	src: url("./fonts/eyn-dots.woff2") format("woff2");
	unicode-range: U+2059;
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}

:root {
	/* EYN Solutions dark tokens (scss/Themes/_dark-tokens.generated.scss) */
	--ink: #0f0f14;
	--star: #e4e4ea;
	--muted: #9696a4;
	/* --faint is 3.6:1 on the page background: fine for a hairline, not
	   enough for text. Anything with words in it uses --muted (6.6:1). */
	--faint: #6a6a78;

	/* The live pair. Everything coloured on this page reads these two. */
	--brand-a: #4776e6;
	--brand-b: #8e54e9;
	--brand-m: color-mix(in srgb, var(--brand-a) 64.4%, var(--brand-b));

	--frame: 4px;
	/* RULE 1. One cell is one step of the mark's lattice. The mark is
	   4.186 cells wide, so this constant is what sets its size on screen.
	   The second term is a ceiling, not a second opinion. Below the mark sit
	   five cells and then the third division, so the height it needs is
	   5*cell PLUS that block, which is a constant ~190px rather than a
	   share of the viewport. That is why the cap is (50vh - 190px)/5 and
	   not a plain vh multiple: a proportional cap is wrong at both ends.
	   On any normal desktop the vw term is the smaller of the two and
	   nothing changes; on a wide, short window this is what keeps the whole
	   constellation on screen. */
	--grid-size: min(
		calc(3.9vw + 2px),
		max(30px, calc((50vh - 190px) / 5))
	);
	/* The mark is sized so ITS dots land on the sky's. In the artwork's own
	   units the lattice runs 2.1 -> 97.9, so four cells span 95.8 of the
	   100-unit box and the box is 400/95.8 cells wide. The old export was
	   drawn in a 90-unit box with an 86-unit lattice (4.186), so this number
	   moves whenever the artwork is re-exported. */
	--mark-w: calc(var(--grid-size) * 4.17537);
	--dot: rgba(255, 255, 255, 0.16);

	--display: "EYN Dots", "Vidaloka", Georgia, Times, serif;
	--body: "EYN Dots", "Quicksand", "Helvetica Neue", Arial, sans-serif;

	/* Short: the pair is driven by the cursor while the divisions are on
	   stage, so this is smoothing between pointer samples, not a hover
	   fade. Anything longer reads as the colour lagging the hand. */
	transition:
		--brand-a 0.18s linear,
		--brand-m 0.18s linear,
		--brand-b 0.18s linear;
}
/* Set for the length of the journey back to the resting pair, when the
   divisions leave the stage. Slower and eased, because settling is not the
   same gesture as following a cursor. */
html.brand-resting {
	transition:
		--brand-a 0.7s ease,
		--brand-m 0.7s ease,
		--brand-b 0.7s ease;
}
/* Coarse regimes, same idea as eyn.art: below lg the cell divides the
   viewport into a whole number of cells rather than following a vw curve,
   so the lattice stays even and the mark keeps a sane share of the screen. */
@media (max-width: 991px) {
	:root {
		--grid-size: calc((100vw - 2 * var(--frame)) / 13);
	}
}
@media (max-width: 767px) {
	:root {
		--grid-size: calc((100vw - 2 * var(--frame)) / 10);
	}
}

/* Each division owns a pair. Hover or keyboard focus anywhere inside one
   and the page takes its colours: frame, mark, the light behind it. */
/* Each division is painted by the gradient inside its own logo, read off the
   icon in the SVG the branch shows. */
html:has(.branch--consulting:is(:hover, :focus-within)) {
	--brand-a: #4776e6;
	--brand-b: #8e54e9;
}
html:has(.branch--identity:is(:hover, :focus-within)) {
	--brand-a: #4816e9;
	--brand-m: #6707f8;
	--brand-b: #ff7c1f;
}
html:has(.branch--products:is(:hover, :focus-within)) {
	/* placeholder third pair, waiting for the products brand to exist */
	--brand-a: #4776e6;
	--brand-b: #22c1a4;
}

*,
*::before,
*::after {
	box-sizing: border-box;
}
html {
	-webkit-text-size-adjust: 100%;
}
body {
	margin: 0;
	background: var(--ink);
	color: var(--star);
	font-family: var(--body);
	font-size: 16px;
	line-height: 1.65;
}
h1,
h2,
h3 {
	font-family: var(--display);
	font-weight: 400;
	/* Vidaloka's own heading setting, lifted from the consulting site
	   (scss/Components/type.scss): tight tracking and a 1.1666 leading. */
	letter-spacing: -0.04em;
	line-height: 1.1666;
	margin: 0 0 0.5em;
	text-wrap: balance;
}
p {
	margin: 0 0 1em;
	text-wrap: pretty;
}
a {
	color: inherit;
}
ul,
dl,
dd {
	margin: 0;
	padding: 0;
}
li {
	list-style: none;
}
:focus-visible {
	outline: 2px solid var(--brand-b);
	outline-offset: 3px;
}
.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}
.skip {
	position: fixed;
	top: -100%;
	left: 16px;
	z-index: 60;
	padding: 10px 18px;
	background: var(--star);
	color: var(--ink);
	text-decoration: none;
}
.skip:focus {
	top: 16px;
}

.eyebrow,
.hint,
.facts dt {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--muted);
	margin: 0;
}
/* the division kinds speak louder and wider than the other labels */
.branch__kind {
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.5em;
	text-transform: uppercase;
	color: var(--muted);
	margin: 0;
}

/* A division wearing its own lockup instead of a set heading: the file is
   the site's real logo, so nothing here recolours it. Sized on the display
   line it replaces, and the alt carries the name for anyone not seeing it. */
.branch__logo {
	margin: 0 0 0.5em;
}
.branch__logo img {
	display: block;
	/* sized by HEIGHT, not width: the two lockups share a 96-unit design
	   height but not a width (430 vs 520), so equal widths rendered the
	   wider one smaller. Equal heights is what reads as equal. */
	height: 51px;
	width: auto;
	max-width: 100%;
}

/* ─── Links ──────────────────────────────────────────────────────────
   One behaviour for every link on the site, lifted from the consulting
   site's .btn-mail (scss/Components/buttons.scss). It is a SEQUENCE, and
   the delays are the whole point:

     hover   the gradient rule wipes away to the right (300ms), and only
             once it has gone does the arrow slide in behind it (300ms,
             delayed by 300ms)
     leave   the arrow drops immediately, the rule grows back after 300ms

   So the two never occupy the same space at the same time: the underline
   leaves, the arrow arrives, and on the way out they trade places again.
   Both wear the live pair, so links drift with the cursor field too. */
.btn-mail,
.branch__link,
.facts a,
.star__link {
	position: relative;
	display: inline-block;
	text-decoration: none;
	/* padding is the 24px target, the negative margin gives the layout
	   back; the rule below is positioned against the text, not the box */
	padding: 6px 0 7px;
	margin: -6px 0 -7px;
	border: 0;
}
.btn-mail::before,
.branch__link::before,
.facts a::before,
.star__link::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 5px;
	height: 1px;
	background-image: linear-gradient(
		to right,
		var(--brand-a) 0%,
		var(--brand-m) 35.6%,
		var(--brand-b) 100%
	);
	transform-origin: right center;
	transition: transform 300ms 300ms ease-in-out;
}
.btn-mail::after,
.branch__link::after,
.facts a::after,
.star__link::after {
	content: "\27F6";
	position: absolute;
	top: 50%;
	right: -2em;
	background-image: linear-gradient(
		160deg,
		var(--brand-a),
		var(--brand-m) 35.6%,
		var(--brand-b)
	);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
	opacity: 0;
	transform: translate(-50%, -50%);
	transition:
		opacity 300ms ease-in-out,
		transform 300ms ease-in-out;
}
.btn-mail:hover::before,
.btn-mail:focus-visible::before,
.branch__link:hover::before,
.branch__link:focus-visible::before,
.facts a:hover::before,
.facts a:focus-visible::before,
.star__link:hover::before,
.star__link:focus-visible::before {
	transform: scale(0, 1);
	transition-delay: 0ms;
}
.btn-mail:hover::after,
.btn-mail:focus-visible::after,
.branch__link:hover::after,
.branch__link:focus-visible::after,
.facts a:hover::after,
.facts a:focus-visible::after,
.star__link:hover::after,
.star__link:focus-visible::after {
	transform: translate(0, -50%);
	opacity: 1;
	transition-delay: 300ms, 300ms;
}
/* A sequence built out of delays has nothing to say when motion is off:
   the rule simply stays, and the arrow never comes. */
@media (prefers-reduced-motion: reduce) {
	.btn-mail::before,
	.branch__link::before,
	.facts a::before,
	.star__link::before,
	.btn-mail::after,
	.branch__link::after,
	.facts a::after,
	.star__link::after {
		transition: none;
	}
	.btn-mail:hover::before,
	.branch__link:hover::before,
	.facts a:hover::before,
	.star__link:hover::before {
		transform: none;
	}
	.btn-mail:hover::after,
	.branch__link:hover::after,
	.facts a:hover::after,
	.star__link:hover::after {
		opacity: 0;
	}
}

/* ─── Fixed layers: frame, sky, mark, stars, top bar ─────────────────── */
/* The frame holds the viewport, not the document, so it stays whole at
   every scroll position.

   It is a BACKGROUND with the middle cut out, not a border-image, and that
   is what lets it move: the other EYN sites animate this gradient by
   sliding its background-position across an oversized four stop ramp
   (bgGradientAnim, 6s linear, in scss/Components/animations.scss), and a
   border-image has no background position to slide. Same trick here, with
   the interior masked away so only the frame itself paints. The stops read
   the live pair, so it keeps drifting with the cursor at the same time. */
.frame {
	position: fixed;
	inset: 0;
	z-index: 50;
	pointer-events: none;
	border: var(--frame) solid transparent;
	background-image: linear-gradient(
		160deg,
		var(--brand-a),
		var(--brand-m) 8.9%,
		var(--brand-b) 25%,
		var(--brand-a) 50%,
		var(--brand-m) 58.9%,
		var(--brand-b) 75%
	);
	background-size: 300% 300%;
	background-position: top left;
	background-origin: border-box;
	animation: frame-drift 6s linear infinite;
	/* keep the border band, drop everything inside it */
	-webkit-mask:
		linear-gradient(#000 0 0) padding-box,
		linear-gradient(#000 0 0);
	-webkit-mask-composite: xor;
	mask:
		linear-gradient(#000 0 0) padding-box,
		linear-gradient(#000 0 0);
	mask-composite: exclude;
}
@keyframes frame-drift {
	0% {
		background-position: top left;
	}
	100% {
		background-position: bottom right;
	}
}
@media (prefers-reduced-motion: reduce) {
	.frame {
		animation: none;
	}
}

.sky {
	position: fixed;
	inset: 0;
	z-index: 0;
	pointer-events: none;
}
.sky__grid {
	position: absolute;
	inset: 0;
	background-image: radial-gradient(
		circle at center,
		var(--dot) 0 1.1px,
		transparent 1.7px
	);
	background-size: var(--grid-size) var(--grid-size);
	/* RULE 2: a dot exactly on the stage centre, which is where the mark's
	   own centre node sits.
	   Plain 50%, and the reason is worth keeping: a background percentage
	   does NOT resolve against the container, it resolves against
	   (container - tile). So 50% already places the tile origin at
	   (W - cell)/2, and a tile whose dot is centred then paints dots at
	   W/2 + n*cell: the centre is a dot. Subtracting half a cell "to centre
	   it" is the intuitive move and it is wrong, it lands every sky dot
	   exactly BETWEEN two of the mark's. */
	background-position: 50% 50%;
	/* the sky has depth: stars thin out toward the edges */
	-webkit-mask-image: radial-gradient(
		78% 68% at 50% 50%,
		#000 0%,
		rgba(0, 0, 0, 0.55) 55%,
		rgba(0, 0, 0, 0.12) 100%
	);
	mask-image: radial-gradient(
		78% 68% at 50% 50%,
		#000 0%,
		rgba(0, 0, 0, 0.55) 55%,
		rgba(0, 0, 0, 0.12) 100%
	);
}
/* The halo. CIRCLE with a length radius, not a percentage pair: percentage
   radii are read against the box's width and height separately, so on a
   wide viewport they draw an oval. Sized off the mark so it always sits in
   the same relationship to it, and breathing slowly, which is the only
   thing on the page that moves on its own. */
.sky__glow {
	position: absolute;
	inset: 0;
	/* Radii are multiples of the mark, so the halo keeps its relationship to
	   it at every size. 1.5 and 2.4 put the light back where the old
	   percentage version had it: those percentages were read against the
	   VIEWPORT, which made them both far wider than a mark-relative circle
	   of the same-looking number, and oval into the bargain. */
	background:
		radial-gradient(
			circle calc(var(--mark-w) * 1.5) at 50% 50%,
			color-mix(in srgb, var(--brand-a) 15%, transparent),
			transparent 72%
		),
		radial-gradient(
			circle calc(var(--mark-w) * 2.4) at 50% 50%,
			color-mix(in srgb, var(--brand-b) 12%, transparent),
			transparent 74%
		);
	animation: breathe 9s ease-in-out infinite;
}
/* Slow and wide enough to be felt. On a glow this soft a few percent of
   scale is invisible, so the breath carries most of its weight in opacity. */
@keyframes breathe {
	0%,
	100% {
		transform: scale(0.94);
		opacity: 0.68;
	}
	50% {
		transform: scale(1.12);
		opacity: 1;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sky__glow {
		animation: none;
	}
}

/* ─── Twinkles ───────────────────────────────────────────────────────── */
/* Lifted from eyn.art: a lit dot rather than a glowing blob. A 2px core the
   exact size of a printed dot, and a halo barely wider, graded through
   explicit alpha stops because a straight ramp to transparent reads as a
   flat disc with an edge. The script seats each one on a lattice point, so
   what lights up is always a star that was already there. */
.twinkle {
	--size: 8px;
	--peak: 0.8;
	--dur: 1.1s;
	--delay: 0s;
	position: fixed;
	z-index: 1;
	width: var(--size);
	height: var(--size);
	margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
	background:
		radial-gradient(
			closest-side,
			color-mix(in srgb, currentColor 55%, transparent) 0%,
			color-mix(in srgb, currentColor 22%, transparent) 32%,
			color-mix(in srgb, currentColor 6%, transparent) 60%,
			transparent 100%
		),
		radial-gradient(closest-side, currentColor 0%, currentColor 100%);
	background-repeat: no-repeat;
	background-position: center;
	background-size:
		100% 100%,
		2px 2px;
	opacity: 0;
	pointer-events: none;
	color: var(--star);
}
.twinkle--a {
	color: var(--brand-a);
}
.twinkle--b {
	color: var(--brand-b);
}
.twinkle--w {
	color: #fff;
}
.twinkle.is-lit {
	animation: twinkle var(--dur) cubic-bezier(0.2, 0.7, 0.3, 1) var(--delay)
		both;
}
@keyframes twinkle {
	0% {
		opacity: 0;
		transform: scale(0.55);
	}
	22% {
		opacity: var(--peak);
		transform: scale(1);
	}
	100% {
		opacity: 0;
		transform: scale(0.8);
	}
}
@media (prefers-reduced-motion: reduce) {
	.twinkle {
		display: none;
	}
}

.mark {
	position: fixed;
	top: 50%;
	left: 50%;
	z-index: 1;
	width: var(--mark-w);
	translate: -50% -50%;
	pointer-events: none;
}
.mark__svg {
	display: block;
	width: 100%;
	height: auto;
}
.mark__a {
	stop-color: var(--brand-a);
}
.mark__m {
	stop-color: var(--brand-m);
}
.mark__b {
	stop-color: var(--brand-b);
}
@media (max-width: 991px) {
	/* one column: the copy has to pass over the mark, so it becomes a
	   watermark rather than moving off its dots */
	.mark {
		opacity: 0.3;
	}
}

.topbar {
	position: fixed;
	top: 0;
	right: 0;
	z-index: 40;
	padding: calc(var(--grid-size) * 0.6) calc(var(--grid-size) * 0.9);
}
/* The bar floats over whatever is scrolling under it, and on a narrow screen
   that is the prose itself. A wash of the ground fades out before it reaches
   the lattice, so the address stays readable without a bar being drawn
   across the sky. */
.topbar::before {
	content: "";
	position: absolute;
	top: -1.5rem;
	right: -1.5rem;
	bottom: -2.5rem;
	left: -3.5rem;
	z-index: -1;
	pointer-events: none;
	background: radial-gradient(
		120% 100% at 100% 0%,
		var(--ink) 30%,
		color-mix(in srgb, var(--ink) 70%, transparent) 62%,
		transparent 100%
	);
}
/* The consulting site's mail link: hung icon, gradient rule under the
   address that wipes out from the right on hover. */
.btn-mail {
	margin-left: 1.75em;
	font-size: 14px;
	font-weight: 700;
	color: var(--star);
	white-space: nowrap;
}
/* The hung icon, same geometry the consulting site gives it: an icon font
   glyph rather than a box, so it is placed by line box and not by size. */
.btn-mail .ph-thin {
	position: absolute;
	top: 50%;
	left: -1.75em;
	margin-top: -0.5em;
	font-size: inherit;
	line-height: 1;
}

/* ─── Named stars ────────────────────────────────────────────────────── */
/* Fixed, like the sky they belong to: if they scrolled they would slide off
   the lattice and the claim that a named star IS one of the sky's dots
   would come apart in motion. Positions are in cells from the centre. */
.stars {
	position: fixed;
	inset: 0;
	z-index: 2;
	pointer-events: none;
	opacity: 0;
}
/* The li is a ZERO-SIZED point sitting exactly on its lattice dot. The star
   is drawn on that point and the label hangs off it, so no text metric can
   ever push the star off the grid: the dot is the grid element, the name is
   just attached to it. */
.stars li {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 0;
	height: 0;
	translate: calc(var(--c) * var(--grid-size))
		calc(var(--r) * var(--grid-size));
}
.stars li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0;
	width: 5px;
	height: 5px;
	margin: -2.5px 0 0 -2.5px;
	border-radius: 50%;
	background: var(--brand-a);
	box-shadow: 0 0 8px 1px color-mix(in srgb, var(--brand-a) 60%, transparent);
}
/* Name over link. The dot on the lattice stays the anchor; this block is
   centred on it and hangs to its right. */
.star {
	position: absolute;
	left: 16px;
	top: 0;
	transform: translateY(-50%);
	display: block;
	white-space: nowrap;
	pointer-events: auto;
}
.star__name {
	display: block;
	font-family: var(--display);
	font-size: clamp(14px, 1.05vw, 20px);
	line-height: 1.15;
	color: var(--star);
}
/* Two sites stacked under one label: each link hides a 24px target behind a
   negative margin, and hidden targets overlap, which drops the first link's
   rule onto the ascenders of the second. The lower one takes its room back. */
.facts dd br + a {
	margin-top: 7px;
}
.star__link {
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--muted);
}
/* the underline and the arrow come from the shared link treatment; the
   only thing a star link adds is that its name brightens on hover */
.star__link:hover,
.star__link:focus-visible {
	color: var(--star);
}

/* Narrow screens do not hide the stars any more, they relayout them: see
   the flow layout at the end of this file. */

/* ─── Chapters: flow mode (mobile, no JS, reduced motion) ────────────── */
main {
	position: relative;
	z-index: 3;
}
.chapter__pin {
	padding: calc(var(--grid-size) * 1.5);
}
.chapter--open .chapter__pin {
	min-height: 88svh;
	display: grid;
	align-content: end;
}
h1 {
	font-size: clamp(34px, 4.4vw, 76px);
	margin-bottom: 0.4em;
}
.lead {
	font-size: clamp(16px, 1.2vw, 20px);
	color: var(--muted);
	max-width: 32em;
}
/* The scroll indicator: fixed to the stage, bottom centre, all the way
   down. It belongs to the journey rather than to the first screen, so it
   never joins a chapter and never fades with one.

   It carries two faces in the same spot. Near the end of the last chapter
   the invitation to scroll has nothing left to offer, so it hands over to
   a thank you. Scrubbed by the chapter's own progress, like everything
   else on the page. */
.hint {
	position: fixed;
	/* seated on the content's own left edge: the chapters pad their pin by
	   a cell and a half, and the cue lines up with what it invites you to
	   scroll toward */
	left: calc(var(--grid-size) * 1.5);
	bottom: calc(var(--grid-size) * 0.8);
	z-index: 40;
	display: grid;
	margin: 0;
	pointer-events: none;
	--end: clamp(0, calc((var(--p-company, 0) - 0.72) / 0.2), 1);
}
.hint > span {
	grid-area: 1 / 1;
	display: flex;
	align-items: center;
	gap: 8px;
	white-space: nowrap;
}
.hint__scroll {
	text-align: left;
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	color: inherit;
	padding: 0;
	border: 0;
	background: none;
	opacity: calc(1 - var(--end));
	pointer-events: auto;
	cursor: pointer;
}

.hint__thanks {
	opacity: var(--end);
}
/* Flow mode has no chaptered journey and publishes every progress as 1,
   which would leave the thank you showing from the top of the page. */
html:not(.scrolly) .hint__scroll {
	opacity: 1;
}
html:not(.scrolly) .hint__thanks {
	opacity: 0;
}
.hint__icon {
	font-size: 15px;
	line-height: 1;
	color: var(--brand-a);
	animation: bob 1.9s ease-in-out infinite;
}
/* The heart takes the live pair, like the key words in the titles do. */
.hint__heart {
	font-size: 15px;
	line-height: 1;
	background-image: linear-gradient(
		120deg,
		var(--brand-a),
		var(--brand-m) 35.6%,
		var(--brand-b)
	);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
}
@keyframes bob {
	0%,
	100% {
		transform: translateY(-3px);
		opacity: 0.45;
	}
	50% {
		transform: translateY(3px);
		opacity: 1;
	}
}
.galaxy {
	/* never wider than the space left of the centred mark: 26em on roomy
	   screens, shrinking with the viewport so the copy and the logo never
	   meet (half the screen, minus half the mark, the pin's padding and a
	   cell of breathing room) */
	max-width: min(
		26em,
		calc(50vw - var(--mark-w) / 2 - var(--grid-size) * 2.5)
	);
}
.galaxy h2,
.company h2 {
	font-size: clamp(26px, 2.4vw, 42px);
}
.galaxy p {
	color: var(--muted);
}
.branch {
	max-width: 24em;
	margin-bottom: calc(var(--grid-size) * 0.8);
}
.branch h3 {
	font-size: clamp(20px, 1.8vw, 30px);
	margin: 0.75em 0;
}
.branch__lead {
	color: var(--muted);
	font-size: 18px;
	font-weight: 100;
}
.branch__link {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
}
/* Key words in the titles carry the live pair, so they drift with the
   cursor in the divisions chapter along with everything else. */
.grad,
.sep {
	background-image: linear-gradient(
		120deg,
		var(--brand-a),
		var(--brand-m) 35.6%,
		var(--brand-b)
	);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
}
.company {
	width: min(780px, 100%);
}
.facts {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
	gap: calc(var(--grid-size) * 0.5) calc(var(--grid-size) * 0.8);
	margin-top: calc(var(--grid-size) * 0.8);
	padding-top: calc(var(--grid-size) * 0.6);
	border-top: 1px solid rgba(244, 244, 246, 0.12);
}
.facts dd {
	font-size: 15px;
	margin-top: 4px;
}
/* The last links on the page were the only ones still wearing a browser
   underline in flat grey. Same gradient rule as the divisions and the
   stars, so every link on the site is underlined by the live pair, and
   the same padding trick keeps a 24px target without moving the layout. */
.facts a {
	font-size: inherit;
}
.colophon {
	margin-top: calc(var(--grid-size) * 0.8);
	font-size: 12px;
	color: var(--muted);
}

/* =========================================================================
   SCROLLY MODE
   =========================================================================
   Same engine as the eyn.art portfolio: every chapter is a tall track with
   a sticky pin, and the script writes --p (0 to 1) on it as you travel
   through. Nothing is pinned to the wheel and nothing is hijacked.

   Seam rule: a chapter's content must be invisible at --p 0 and at --p 1
   (the first chapter's start and the last one's end excepted), so the two
   pinned frames that slide over each other at a handover are identical and
   the cut disappears. The mark, the sky and the frame are FIXED layers, so
   they never take part in a handover at all.
   ========================================================================= */
html.scrolly .chapter {
	height: calc(var(--len, 2) * 100svh);
}
/* Every chapter after the first starts ONE VIEWPORT EARLY.

   Without this there is a dead screen between each pair: a pin stops being
   sticky when its box's bottom reaches the viewport bottom, which is a
   whole viewport before the next box's top arrives, so the reader scrolls
   a full screen with the outgoing chapter already faded out and the
   incoming one not yet begun. Pulling each chapter up by 100svh makes the
   next --p start exactly where the previous one reaches 1: the handover is
   immediate, and the page is three viewports shorter. */
html.scrolly .chapter + .chapter {
	margin-top: -100svh;
}
/* The chapters now overlap while one scrolls out over the other, and an
   invisible block still takes clicks. Only the chapter on stage is
   interactive. */
html.scrolly .chapter__pin {
	pointer-events: none;
}
html.scrolly[data-chapter="playground"] #playground .chapter__pin,
html.scrolly[data-chapter="galaxy"] #galaxy .chapter__pin,
html.scrolly[data-chapter="branches"] #branches .chapter__pin,
html.scrolly[data-chapter="company"] #company .chapter__pin {
	pointer-events: auto;
}
html.scrolly .chapter__pin {
	position: sticky;
	top: 0;
	height: 100svh;
	padding: calc(var(--grid-size) * 1.5);
	display: grid;
	align-content: center;
	overflow: clip;
}
/* Content rides its chapter's window: it fades in as the chapter arrives
   and out as it leaves, as one block. Plain opacity, nothing per word and
   nothing travelling across it.

   No translate: these blocks are placed in whole cells from the centre,
   and a slide would take them off the lattice for the length of the
   transition. They arrive where they belong and simply become visible. */
html.scrolly .act {
	--in: 1;
	--out: 0;
	opacity: calc(var(--in) * (1 - var(--out)));
	translate: none;
}

/* The opening screen holds its words, then drops them quickly. The ramp is
   narrow on purpose: the last unit leaves at --lead (0.9) OF the ramp, so
   the distance the reader actually scrolls through the vanish is
   0.9 x ramp x the chapter's span, and a wide ramp here made the title
   trail away over most of a screen. Starting later and ending sooner keeps
   the seam empty before the handover. */
html.scrolly .chapter--open .act {
	--out: clamp(0, calc((var(--p, 0) - 0.25) / 0.75), 1);
}
html.scrolly .chapter--galaxy .act,
html.scrolly .chapter--branches .act {
	--in: clamp(0, calc(var(--p, 0) / 0.2), 1);
	--out: clamp(0, calc((var(--p, 0) - 0.78) / 0.2), 1);
}
html.scrolly .chapter--company .act {
	--in: clamp(0, calc(var(--p, 0) / 0.25), 1);
	--out: 0;
}

/* Chapter 1: the words sit low so the mark owns the middle. */
html.scrolly .chapter--open .chapter__pin {
	align-content: end;
	min-height: 0;
}
/* Chapter 2: copy to one side, stars in the sky on the other. */
html.scrolly .chapter--galaxy .chapter__pin {
	justify-content: start;
}
/* Chapter 3: the constellation.
   Every distance here is a whole number of cells, measured from the logo's
   OUTER DOT COLUMN (2 cells from centre), not from its box edge, because
   the dots are what the grid is made of:

       logo dot (col 2) | 1 cell | line (col 3 to col 4) | 1 cell | content

   so the connector is exactly one cell long and runs between two real
   lattice dots, in the same colour a star wears. The content starts on
   column 5. Nothing here is a soft gradient or an approximate gap. */
html.scrolly .chapter--branches .chapter__pin {
	display: block;
}
/* Entrance is opacity only: a slide would carry the blocks, and their
   connectors, off the lattice for the length of the transition. */
html.scrolly .chapter--branches .act {
	translate: none;
}
html.scrolly .branch {
	position: absolute;
	margin: 0;
	/* Four cells is the right measure on a wide screen. The floor matters
	   near the 992px threshold, where four cells is only about 160px and
	   the paragraph would set at twenty characters a line. The block's
	   INNER edge is what the one-cell spec pins, so its width is free. */
	width: max(calc(var(--grid-size) * 4), 17em);
	max-width: none;
}
html.scrolly .branch--consulting {
	right: calc(50% + 5 * var(--grid-size));
	top: 50%;
	transform: translateY(-50%);
	text-align: right;
}
/* the block-level lockup follows the text to the right edge */
html.scrolly .branch--consulting .branch__logo img {
	margin-left: auto;
}
html.scrolly .branch--identity {
	left: calc(50% + 5 * var(--grid-size));
	top: 50%;
	transform: translateY(-50%);
}
html.scrolly .branch--products {
	left: 50%;
	top: calc(50% + 5 * var(--grid-size));
	transform: translateX(-50%);
	text-align: center;
	/* the bottom seat is the shallowest: wider lines keep the lead off the
	   scroll hint below it */
	width: max(calc(var(--grid-size) * 6), 26em);
}
/* The connectors. One cell long, one pixel thick, plain, in the same
   colour a star wears, running between two real lattice dots (column 3 to
   column 4, and the matching row below the mark).

   Each one GROWS out of the logo: the transform origin is the end nearest
   the mark, so the line reaches toward its division rather than fading in
   as a whole. It draws itself early in the chapter and retracts the same
   way on the way out. */
html.scrolly .wire {
	position: absolute;
	background: var(--brand-a);
	--draw: clamp(0, calc((var(--p-branches, 0) - 0.08) / 0.22), 1);
	--pull: clamp(0, calc((var(--p-branches, 0) - 0.78) / 0.18), 1);
	--len: calc(var(--draw) * (1 - var(--pull)));
}
html.scrolly .wire--consulting,
html.scrolly .wire--identity {
	top: 50%;
	width: var(--grid-size);
	height: 1px;
	margin-top: -0.5px;
}
/* left of the mark: the near end is the RIGHT one, so it grows leftward */
html.scrolly .wire--consulting {
	right: calc(50% + 3 * var(--grid-size));
	transform: scaleX(var(--len));
	transform-origin: right center;
}
html.scrolly .wire--identity {
	left: calc(50% + 3 * var(--grid-size));
	transform: scaleX(var(--len));
	transform-origin: left center;
}
/* below the mark: the near end is the TOP one */
html.scrolly .wire--products {
	left: 50%;
	top: calc(50% + 3 * var(--grid-size));
	width: 1px;
	margin-left: -0.5px;
	height: var(--grid-size);
	transform: scaleY(var(--len));
	transform-origin: center top;
}
html:not(.scrolly) .wire {
	display: none;
}

/* Chapter 4: the details take the sky as well. Each block is placed in
   CELLS from the centre, exactly like the named stars, so the company
   reads as one more constellation around the mark rather than a panel
   underneath it. Left-hand blocks are set right so they point inward. */
html.scrolly .company {
	position: absolute;
	inset: 0;
	width: auto;
	/* opacity only on the way in: these sit on the lattice, and a slide
	   would take the whole constellation off it while it arrives */
	translate: none;
}
html.scrolly .facts {
	position: absolute;
	inset: 0;
	display: block;
	margin: 0;
	padding: 0;
	border: 0;
}
html.scrolly .at {
	position: absolute;
	left: 50%;
	top: 50%;
	width: max-content;
	max-width: calc(var(--grid-size) * 4);
	margin: 0;
	translate: calc(var(--c) * var(--grid-size))
		calc(var(--r) * var(--grid-size));
}
html.scrolly .at--left {
	text-align: right;
	/* grown leftward from its dot, so the dot stays the anchor */
	translate: calc(var(--c) * var(--grid-size) - 100%)
		calc(var(--r) * var(--grid-size));
}
html.scrolly .company h2 {
	margin: 0;
}
html.scrolly .colophon {
	margin: 0;
}

/* The stars belong to chapter 2: they come up staggered as it opens and are
   gone before it hands over. --i is the star's index, so each one has its
   own place in the queue without a single extra rule. */
html.scrolly .stars {
	opacity: 1;
}
html.scrolly .stars li {
	--in: clamp(0, calc((var(--p-galaxy, 0) - (0.06 + var(--i) * 0.022)) / 0.14), 1);
	--out: clamp(0, calc((var(--p-galaxy, 0) - 0.8) / 0.18), 1);
	/* Opacity ONLY. A scale or a slide would move the star off its dot for
	   the length of the transition, and a star that drifts off the lattice
	   while arriving contradicts the one thing this page is claiming. They
	   light up where they already are. */
	opacity: calc(var(--in) * (1 - var(--out)));
}

/* In flow mode the stars stop being a sky device and become the list of
   projects they always were underneath. See the flow layout at the end. */

@media (prefers-reduced-motion: reduce) {
	.hint__icon {
		animation: none;
	}
	:root {
		transition: none;
	}
}


/* =========================================================================
   FLOW LAYOUT (narrow screens, no JS, reduced motion)
   =========================================================================
   No pins, no chapters passing a fixed mark: a plain document that happens
   to be printed on the same sky. The rules here are about what a single
   column needs, not about undoing the desktop.
   ========================================================================= */
@media (max-width: 991px) {
	/* The mark stays FIXED and centred even here, because that is the only
	   way its dots keep landing on the sky's dots. What changes is its
	   weight: it introduces the page at full strength and then recedes as
	   the copy starts scrolling over it (--depth is published by the
	   script, 0 at the top, 1 after one screen). */
	.mark {
		opacity: calc(0.9 - 0.72 * var(--depth, 0));
	}
	/* The scroll cue is a fixed overlay, and on one column it sits on top
	   of whatever is passing underneath. A phone does not need to be told
	   that a page scrolls. */
	.hint {
		display: none;
	}
	.topbar {
		padding: calc(var(--grid-size) * 0.5) calc(var(--grid-size) * 0.7);
	}
	.btn-mail {
		font-size: 13px;
	}
	/* Chapter rhythm: each section gets room of its own and a hairline
	   between, so the document reads as chapters rather than as one run. */
	.chapter__pin {
		padding: calc(var(--grid-size) * 1.6) calc(var(--grid-size) * 1);
	}
	.chapter + .chapter .chapter__pin {
		border-top: 1px solid rgba(244, 244, 246, 0.09);
	}
	/* The opening screen: the mark owns the middle, the words sit under it */
	.chapter--open .chapter__pin {
		min-height: 100svh;
		align-content: end;
		padding-bottom: calc(var(--grid-size) * 1.6);
	}
	.lead {
		font-size: 16px;
		line-height: 1.6;
	}
	.galaxy,
	.branch,
	.company {
		max-width: none;
	}
	.branch {
		margin-bottom: calc(var(--grid-size) * 1.2);
	}
	.branch:last-of-type {
		margin-bottom: 0;
	}
	.facts {
		grid-template-columns: 1fr 1fr;
		gap: calc(var(--grid-size) * 0.7);
	}

	/* The named stars become what they always were underneath: a list of
	   projects. They are inside the galaxy chapter in the markup precisely
	   so they can fall back to this instead of disappearing with the sky. */
	.stars {
		position: static;
		display: grid;
		grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
		gap: calc(var(--grid-size) * 0.6) calc(var(--grid-size) * 0.5);
		margin-top: calc(var(--grid-size) * 1.2);
		opacity: 1;
		pointer-events: auto;
	}
	.stars li {
		position: relative;
		width: auto;
		height: auto;
		top: auto;
		left: auto;
		translate: none;
		padding-left: 16px;
	}
	.stars li::before {
		top: 0.5em;
		left: 0;
		margin: 0;
	}
	.star {
		position: static;
		transform: none;
		white-space: normal;
	}
	.star__name {
		font-size: 17px;
	}
	.star__link {
		padding: 6px 0 7px;
		margin: -6px 0 -7px;
	}
}
@media (max-width: 520px) {
	.facts {
		grid-template-columns: 1fr;
	}
	.stars {
		grid-template-columns: 1fr;
	}
}
