/* One regular left-to-right wave starts on page load. The accelerating
   wave follows every 30 seconds, five times. Sound adds an independent
   continuous rotation to the outer tiles while it is playing. */
@media (min-width: 75rem) and (hover: hover) and (pointer: fine) {
	.launcher .box {
		--tile-x: 0s;
		--tile-y: 0s;
		--micro-delay: 0s;
		--flash-color: var(--palette-2);
		--sound-color: #0a0a0a;
		--sound-text-color: #ffffff;
		--glow-color: #ffff00;
		--sound-duration: 18s;
	}

	/* Small deterministic offsets keep tiles in the same row or column
	   from starting on the exact same frame without obscuring the wave. */
	.launcher .box:nth-child(4n + 1) { --micro-delay: 0.025s; }
	.launcher .box:nth-child(4n + 2) { --micro-delay: 0.05s; }
	.launcher .box:nth-child(4n + 3) { --micro-delay: 0.075s; }

	/* Five distinct but all very-dark, near-black tones instead of the
	   white pass before -- the on-phase text color below switches to
	   white to stay readable against these. Glow keeps its own
	   separate yellow/yellow-green cycle (--glow-color below), still
	   guaranteed different from whichever of these a tile gets. */
	.launcher .box:nth-child(5n + 1) { --sound-color: #0a0a0a; }
	.launcher .box:nth-child(5n + 2) { --sound-color: #0d0d10; }
	.launcher .box:nth-child(5n + 3) { --sound-color: #100d0a; }
	.launcher .box:nth-child(5n + 4) { --sound-color: #0a0d0a; }
	.launcher .box:nth-child(5n) { --sound-color: #0d0a0d; }

	/* Bright yellow-to-yellow-green range, 5 shades. */
	.launcher .box:nth-child(5n + 1) { --glow-color: #ffff00; }
	.launcher .box:nth-child(5n + 2) { --glow-color: #f2ff00; }
	.launcher .box:nth-child(5n + 3) { --glow-color: #e6ff00; }
	.launcher .box:nth-child(5n + 4) { --glow-color: #d9ff00; }
	.launcher .box:nth-child(5n) { --glow-color: #ccff00; }

	.box:is(.social, .phanpy, .abs, .config) { --flash-color: var(--palette-1); }
	.box:is(.code, .mealie, .calendar, .plaza, .sound, .data) { --flash-color: var(--palette-2); }
	.box:is(.lldap, .intra, .trek) { --flash-color: var(--palette-3); }
	.box.chat { --flash-color: var(--palette-4); }
	.box.hdm { --flash-color: var(--palette-5); }
	.box.guide { --flash-color: var(--palette-5); }

	/* X uses 0.125-second column steps; Y uses 0.5-second row steps. */
	.box.code { --tile-x: 0.125s; --tile-y: 1s; }
	.box.phanpy { --tile-x: 0.25s; --tile-y: 1s; }
	.box.abs { --tile-x: 0.25s; --tile-y: 1.5s; }
	.box.mealie { --tile-x: 0.375s; --tile-y: 1.5s; }
	.box.data { --tile-x: 0.5s; --tile-y: 0s; }
	.box.lldap { --tile-x: 0.5s; --tile-y: 1s; }
	.box.trek { --tile-x: 0.5s; --tile-y: 1.5s; }
	.box.chat { --tile-x: 0.625s; --tile-y: 1.5s; }
	.box.sound { --tile-x: 0.75s; --tile-y: 0s; }
	.box.social { --tile-x: 0.75s; --tile-y: 1s; }
	.box.guide { --tile-x: 0.875s; --tile-y: 1s; }
	.box.calendar { --tile-x: 0.875s; --tile-y: 1.5s; }
	.box.intra { --tile-x: 1s; --tile-y: 1s; }
	.box.config { --tile-x: 1s; --tile-y: 1.5s; }
	.box.hdm { --tile-x: 1.125s; --tile-y: 1s; }
	.box.plaza { --tile-x: 1.125s; --tile-y: 1.5s; }

	.launcher .frame > .box:not(.hero) .tile-spin {
		animation:
			desktop-turn-y 3.333s cubic-bezier(0.16, 1, 0.3, 1) 1 forwards,
			desktop-color-pulse 0.75s ease-in-out 1,
			desktop-twenty-turns 30s linear 5 forwards,
			desktop-peak-color 30s linear 5;
		animation-delay:
			calc(0.5s + var(--tile-x) + var(--micro-delay)),
			calc(0.625s + var(--tile-x) + var(--micro-delay)),
			calc(30s + var(--tile-x) + var(--micro-delay)),
			calc(30s + var(--tile-x) + var(--micro-delay));
	}

	.frame.sound-active > .box:not(.hero, .photo) {
		will-change: transform;
		animation:
			sound-intro 15s cubic-bezier(0.65, 0, 0.35, 1) 1 forwards,
			sound-spin var(--sound-duration) linear infinite;
		animation-delay:
			calc(var(--tile-x) + var(--micro-delay)),
			calc(15s + var(--tile-x) + var(--micro-delay));
	}

	/* Sound mode replaces the scheduled inner animations. Same duration
	   and delay as sound-spin above (not its own offset) -- that's what
	   keeps this in lockstep with sound-spin's 90-degree holds, so
	   color is only ever on while a tile is actually turning. */
	.frame.sound-active > .box:not(.hero, .photo) .tile-spin {
		animation: sound-color-wave var(--sound-duration) linear infinite;
		animation-delay: calc(15s + var(--tile-x) + var(--micro-delay));
	}

	/* These two tiles are coloured at rest in the regular layout. During
	   sound mode they join the neutral tiles between colour waves. */
	.frame.sound-active > :is(.guide, .sound) .tile-spin {
		--tile-bg: #b8b8b8;
		background-color: var(--tile-bg);
	}

	/* Photos rest at their natural crop. Sound mode starts one long,
	   continuous push-in and releases it more quickly when switched off. */
	.launcher .photo img {
		--photo-scale: 10;
		--photo-duration: 135s;
		--photo-origin: 50% 50%;
		transform-origin: var(--photo-origin);
		transition: transform 4s cubic-bezier(0.16, 1, 0.3, 1);
	}

	.launcher .photo-1 img { --photo-origin: 48% 52%; }
	.launcher .photo-2 img { --photo-scale: 8; --photo-duration: 160s; --photo-origin: 65% 40%; }
	.launcher .photo-3 img { --photo-scale: 12; --photo-duration: 180s; --photo-origin: 35% 60%; }
	.launcher .photo-4 img { --photo-scale: 9; --photo-duration: 125s; --photo-origin: 58% 48%; }

	.launcher .frame.sound-active > .photo img {
		transform: scale(var(--photo-scale));
		transition-duration: var(--photo-duration);
		transition-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
		will-change: transform;
	}

	.frame.sound-active > .box:nth-child(4n + 1) { --sound-duration: 16.5s; }
	.frame.sound-active > .box:nth-child(4n + 2) { --sound-duration: 19.5s; }
	.frame.sound-active > .box:nth-child(4n + 3) { --sound-duration: 21s; }
	.frame.sound-active > .box:nth-child(5n) { --sound-duration: 17.25s; }
}

/* The iMac layout has different coordinates. */
@media (min-width: 140rem) and (hover: hover) and (pointer: fine) {
	.box.hdm { --tile-x: 0.125s; --tile-y: 0.5s; }
	.box.config { --tile-x: 0.25s; --tile-y: 0.5s; }
	.box.plaza { --tile-x: 0.25s; --tile-y: 1.5s; }
	.box.social { --tile-x: 0.375s; --tile-y: 0.5s; }
	.box.guide { --tile-x: 0.375s; --tile-y: 2s; }
	.box.phanpy { --tile-x: 0.5s; --tile-y: 2s; }
	.box.data { --tile-x: 0.625s; --tile-y: 0.5s; }
	.box.sound { --tile-x: 0.625s; --tile-y: 3s; }
	.box.lldap { --tile-x: 1.125s; --tile-y: 3s; }
	.box.calendar { --tile-x: 1.25s; --tile-y: 0.5s; }
	.box.code { --tile-x: 1.25s; --tile-y: 1s; }
	.box.intra { --tile-x: 1.25s; --tile-y: 3s; }
	.box.mealie { --tile-x: 1.375s; --tile-y: 0.5s; }
	.box.chat { --tile-x: 1.5s; --tile-y: 1s; }
	.box.abs { --tile-x: 1.5s; --tile-y: 1.5s; }
	.box.trek { --tile-x: 1.625s; --tile-y: 1s; }

	.box.filler-8 { --tile-x: 0s; --tile-y: 0.5s; }
	.box.filler-2 { --tile-x: 0.125s; --tile-y: 1s; }
	.box.filler-6 { --tile-x: 0.25s; --tile-y: 2s; }
	.box.filler-7 { --tile-x: 0.375s; --tile-y: 2.5s; }
	.box.filler-11 { --tile-x: 1.5s; --tile-y: 3s; }
	.box.filler-3 { --tile-x: 1.625s; --tile-y: 0.5s; }
	.box.filler-4 { --tile-x: 1.75s; --tile-y: 0.5s; }
	.box.filler-5 { --tile-x: 1.75s; --tile-y: 1s; }
	.box.filler-9 { --tile-x: 1.875s; --tile-y: 0.5s; }
	.imac-filler { --flash-color: var(--palette-3); }
}

@keyframes desktop-turn-y {
	0% { transform: translateZ(0) rotateY(0deg); }
	100% { transform: translateZ(0) rotateY(360deg); }
}

@keyframes desktop-color-pulse {
	0%, 100% { background-color: var(--tile-bg); }
	35%, 65% { background-color: var(--flash-color); }
}

@keyframes desktop-twenty-turns {
	0% {
		transform: translateZ(0) rotateY(0deg);
		animation-timing-function: ease-in;
	}
	14% {
		transform: translateZ(0) rotateY(1800deg);
		animation-timing-function: linear;
	}
	26% {
		transform: translateZ(0) rotateY(5400deg);
		animation-timing-function: ease-out;
	}
	40%, 100% { transform: translateZ(0) rotateY(7200deg); }
}

@keyframes desktop-peak-color {
	0%, 12%, 28%, 100% { background-color: var(--tile-bg); }
	14%, 26% { background-color: var(--flash-color); }
}

/* Rises from true flat rest (0turn, the tile's actual idle pose
   before sound has ever been switched on) up to the 90-degree hold
   position and then just stays there -- hands off directly into
   sound-spin below, which starts its loop already sitting at that
   same 0.25turn angle. */
@keyframes sound-intro {
	0% { transform: perspective(40rem) rotateY(0turn); }
	35% { transform: perspective(40rem) rotateY(0.25turn); }
	100% { transform: perspective(40rem) rotateY(0.25turn); }
}

/* Every hold sits at a 90-degree-equivalent angle (0.25turn, then
   1.25turn -- rotateY is periodic mod 1turn so both read as the same
   sideways pose), and the loop itself starts and ends on a hold
   (0% and 100% are both 0.25turn-equivalent) so color and position
   stay in lockstep with sound-color-wave below: whenever color is
   off, rotation is exactly at rest on one of these two stops, never
   mid-turn. Between holds it spins at an uneven pace -- a quick burst
   then a slower stretch -- to the next stop. */
@keyframes sound-spin {
	0% { transform: perspective(40rem) rotateY(0.25turn); }
	22% { transform: perspective(40rem) rotateY(0.25turn); }
	30% { transform: perspective(40rem) rotateY(0.6turn); }
	48% { transform: perspective(40rem) rotateY(1.25turn); }
	70% { transform: perspective(40rem) rotateY(1.25turn); }
	78% { transform: perspective(40rem) rotateY(1.65turn); }
	100% { transform: perspective(40rem) rotateY(2.25turn); }
}

/* Rest points (0%, 22%, 48%, 70%, 100%) land exactly on sound-spin's
   90-degree holds above -- color is only ever on while the tile is
   actually mid-turn, off precisely when it's sitting still. */
@keyframes sound-color-wave {
	0%, 22%, 48%, 70%, 100% {
		background-color: transparent;
		box-shadow: none;
		color: inherit;
	}
	30%, 40%, 80%, 92% {
		background-color: var(--sound-color);
		box-shadow:
			0 0 0.08rem 0 var(--glow-color),
			0 0 0.16rem 0.01rem var(--glow-color);
		color: var(--sound-text-color);
	}
}

@media (prefers-color-scheme: dark) {
	.frame.sound-active > :is(.guide, .sound) .tile-spin {
		--tile-bg: #3a3a3a;
	}

	/* Dark mode: a dark-gray-to-black range (light mode gets a single
	   standard white instead, see below) -- text stays white for
	   contrast against these. */
	.launcher .box:nth-child(5n + 1) { --sound-color: #1a1a1a; }
	.launcher .box:nth-child(5n + 2) { --sound-color: #141414; }
	.launcher .box:nth-child(5n + 3) { --sound-color: #0d0d0d; }
	.launcher .box:nth-child(5n + 4) { --sound-color: #070707; }
	.launcher .box:nth-child(5n) { --sound-color: #000000; }
	.launcher .box:nth-child(5n + 1),
	.launcher .box:nth-child(5n + 2),
	.launcher .box:nth-child(5n + 3),
	.launcher .box:nth-child(5n + 4),
	.launcher .box:nth-child(5n) {
		--sound-text-color: #ffffff;
	}
}

/* Light mode: cold pale-yellow variation instead of dark mode's
   dark-gray-to-black range above, and skip the glow entirely. */
@media (prefers-color-scheme: light) {
	/* Cold (slightly green-leaning, not orange-warm) pale yellow
	   range, 5 shades. */
	.launcher .box:nth-child(5n + 1) { --sound-color: #f7f7a8; }
	.launcher .box:nth-child(5n + 2) { --sound-color: #f0f090; }
	.launcher .box:nth-child(5n + 3) { --sound-color: #eaea80; }
	.launcher .box:nth-child(5n + 4) { --sound-color: #e3e370; }
	.launcher .box:nth-child(5n) { --sound-color: #dcdc60; }

	.launcher .box:nth-child(5n + 1),
	.launcher .box:nth-child(5n + 2),
	.launcher .box:nth-child(5n + 3),
	.launcher .box:nth-child(5n + 4),
	.launcher .box:nth-child(5n) {
		--sound-text-color: #04210f;
		/* No glow in light mode -- transparent makes the box-shadow's
		   color stops invisible without needing to restructure the
		   shadow itself away from var(--glow-color). */
		--glow-color: transparent;
	}
}

@media (prefers-reduced-motion: reduce) {
	.launcher .frame > .box .tile-spin,
	.frame.sound-active > .box {
		animation: none;
		box-shadow: var(--tile-shadow);
	}

	.launcher .frame.sound-active > .photo img {
		transform: scale(1);
		transition: none;
	}
}
