/* ==========================================================================
   HEATSINK, the game's own stylesheet
   ==========================================================================

   Loaded after css/style.css and only on this game's subsite (see
   scs_game_asset() and inc/game-site.php). It carries the two things the shared
   sheet cannot: this game's typeface and palette, and the four blocks the
   studio site has no equivalent of.

   The rules it inherits from are good; almost nothing here is a reset. What it
   does change, it changes for a reason written next to it.

   Brand law, from the game's own design system, and the reason for most of the
   decisions below:

     Heat is the brand.       The page is correct when heat is legible, felt and
                              consequential.
     Six colours, no seventh.  ice is the player, violet the enemy, amber mid
                              heat, crit death, paper text, void ground.
     Violet is never text and never a call to action. Crit is never a call to
                              action either. Ice is the only one that asks to be
                              pressed.
     Instrument panel, not arcade cabinet. No scanlines, no glitch type, no
                              synthwave. Thin bright strokes on near black.
     Glow, never shadow.       No box-shadow with a black offset anywhere.
     Nothing appears instantly, nothing moves linearly, nothing takes longer
                              than 420ms.

   Sections:
     1  Tokens: palette, type, the heat ramp
     2  Corrections to the shared sheet
     3  The backdrop
     4  Hero
     5  The gauge
     6  Weapons
     7  The cycle
     8  Story
     9  Spec
    10  The signup
    11  Reduced motion
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens

   Scoped to the body class rather than :root so that nothing here can leak
   into the studio site if the two ever share a document, and so the whole
   palette is one selector to read.
   -------------------------------------------------------------------------- */
.game-heatsink {
	/* The six, and only the six. Measured on the void: paper 15.9:1,
	   ice 13.9:1, amber 11.1:1, crit 5.5:1, violet 4.9:1. Violet clears AA at
	   large sizes and is still never used for text, because in this game it
	   means enemy and a word that means enemy is a word that is lying. */
	--ice: #5BE9FF;
	--amber: #FFB13C;
	--crit: #FF2E4C;
	--violet: #8A5CFF;

	/* The studio sheet sets --dim at 42% of paper, which is 3.41:1 on the void
	   and therefore below AA for the sizes it is used at. The game's own design
	   system fixes secondary text at 58%, which measures 5.63:1, and says not to
	   lower it. Raised here rather than in the shared sheet because the studio
	   site is not this file's to change. */
	--dim: rgb(220 230 240 / 58%);

	/* Where the gauge is now. --heat is 0 to 100 and --hot is the ramp read at
	   that point, both written by site.js on the one element that owns them.
	   Everything that can be hot reads these, so the section moves as one
	   instrument: a panel where three of five parts agree reads as broken. */
	--heat: 0;
	--hot: var(--ice);

	/* Chakra Petch, 400 and 700, and nothing else: not for a heading, not for a
	   numeral, not for a micro-label. The two faces are preloaded from game.php
	   and the @font-face rules sit with the rest in css/style.css. */
	--display: 'Chakra Petch', system-ui, -apple-system, 'Segoe UI', sans-serif;
	--body: 'Chakra Petch', system-ui, -apple-system, 'Segoe UI', sans-serif;
	--mono: 'Chakra Petch', ui-monospace, 'Cascadia Mono', Consolas, monospace;

	font-weight: 400;

	/* Every temperature, percentage, multiplier and score on the page, in one
	   treatment. A figure that shuffles its digits sideways as it ticks is the
	   fastest way for an instrument to look unfinished. */
	font-variant-numeric: tabular-nums lining-nums;
}

/* --------------------------------------------------------------------------
   2. Corrections to the shared sheet

   Three, and each of them is the shared rule being right for vermilion and
   wrong for ice.
   -------------------------------------------------------------------------- */

/* White on #5BE9FF is 1.9:1 and unreadable. The accent is a light colour here,
   so a filled button takes the ground as its ink instead: void on ice is
   13.9:1. */
.game-heatsink .btn-primary { color: var(--void); }
.game-heatsink .btn-primary:hover { --btn-bg: color-mix(in oklab, var(--accent) 82%, white); }

/* Archivo carries 800 with a tight track. Chakra Petch is a squarer, wider
   face whose bold is 700, and negative tracking on it closes the counters up
   until the caps run together. */
.game-heatsink h1,
.game-heatsink h2,
.game-heatsink h3,
.game-heatsink h4 { font-weight: 700; letter-spacing: 0; }

/* The micro-label track. In the shared sheet the label face is a monospace,
   which spaces itself; this one does not. */
.game-heatsink .eyebrow,
.game-heatsink .pill,
.game-heatsink .btn { letter-spacing: .16em; }

/* --------------------------------------------------------------------------
   3. The backdrop

   The studio's backdrop is a flight of stairs cut from the studio's mark, and
   the flight is the studio's, not this game's. Both of its layers are replaced
   here rather than recoloured.

   What this page stands on is the object it is named after. The viewport is one
   sink and it is always the same way up, whatever the scroll position: the base
   is at the bottom of the screen where the heat is, the fins run up out of it,
   and the air at the top is cold. That is the only reading of a page-wide
   background this game can have that is not decoration.

     ::before   the thermal field. Crit at the base, amber sitting just off it,
                ice high and to one side, and grain over the three of them.
     ::after    the fin stack. Seventy-nine fins across a 1680px tile, which is
                wider than a laptop viewport, so the repeat is never on screen.
                No two are alike: the gap before each is 6 to 78px, the width is
                a hairline or a 2px fin or a 3px rail, the weight is one of
                five, and each carries one of five vertical profiles. Some run
                the full height, some die a third of the way down, one profile
                is broken into segments by the haze. Evenly pitched identical
                lines are half a grid, which is the thing this whole backdrop
                exists to not be.

                It is built the other way up from everything else here: the tile
                is a mask, so it holds no colour at all, only which fins exist
                and how far down each survives. The colour under it is two
                gradients, the fins' own steady ice and a shimmer crest that
                rises through them. Regenerate the tile with .tools/fins.py.

   --page-heat is the gauge's own reading, quantised and published on the root
   by site.js, and the base of the sink is lit by it: hold the trigger and the
   room warms with the instrument. It is the reason the backdrop is this game's
   and not a texture that happens to be blue. Emphasis only. The arc, the
   numeral and the multiplier already carry the reading, the ceiling here is
   eight points of alpha, and with no script at all the fallback is 0 and the
   page is simply cold.

   Nothing in either layer clears 20%, and only at full heat. The hero glow and
   the gauge are the bright objects on this page; a background that competes
   with them is a background somebody turns off later. Six colours, no seventh:
   every value below is ice, amber, crit or paper.
   -------------------------------------------------------------------------- */
.game-heatsink::before {
	background:
		var(--grain) 0 0 / 180px 180px,
		radial-gradient(130% 46% at 50% 102%,
			rgb(255 46 76 / calc(12% + var(--page-heat, 0) * 8%)), transparent 74%),
		radial-gradient(80% 34% at 28% 100%,
			rgb(255 177 60 / calc(9% + var(--page-heat, 0) * 5%)), transparent 74%),
		radial-gradient(72% 46% at 90% -8%, rgb(91 233 255 / 8%), transparent 72%);
}

/* content is restored because the shared sheet takes it away from every game
   subsite that has not asked for a second layer (.game-site::after). */
.game-heatsink::after {
	content: '';
	/* Taller than the viewport at both ends, so the drift below has somewhere to
	   travel without dragging an edge of the layer into view. */
	inset: -8vh 0;
	mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='1680' height='100' viewBox='0 0 1680 100' preserveAspectRatio='none'><defs><linearGradient id='a' x2='0' y2='1'><stop offset='0' stop-opacity='1'/><stop offset='0.55' stop-opacity='0.55'/><stop offset='1' stop-opacity='0'/></linearGradient><linearGradient id='b' x2='0' y2='1'><stop offset='0' stop-opacity='1'/><stop offset='0.35' stop-opacity='0.3'/><stop offset='0.7' stop-opacity='0'/></linearGradient><linearGradient id='c' x2='0' y2='1'><stop offset='0' stop-opacity='0.5'/><stop offset='0.28' stop-opacity='1'/><stop offset='0.62' stop-opacity='0.35'/><stop offset='0.9' stop-opacity='0'/></linearGradient><linearGradient id='d' x2='0' y2='1'><stop offset='0' stop-opacity='1'/><stop offset='0.12' stop-opacity='1'/><stop offset='0.2' stop-opacity='0'/><stop offset='0.3' stop-opacity='0.8'/><stop offset='0.52' stop-opacity='0.8'/><stop offset='0.6' stop-opacity='0'/><stop offset='0.68' stop-opacity='0.45'/><stop offset='0.88' stop-opacity='0'/></linearGradient><linearGradient id='e' x2='0' y2='1'><stop offset='0' stop-opacity='0.85'/><stop offset='0.8' stop-opacity='0.7'/><stop offset='1' stop-opacity='0'/></linearGradient></defs><path fill='url(%23c)' fill-opacity='0.9' d='M20 0h2v100h-2zM269 0h2v100h-2zM708 0h2v100h-2zM1046 0h3v100h-3z'/><path fill='url(%23c)' fill-opacity='0.58' d='M37 0h1v62h-1zM978 0h1v100h-1z'/><path fill='url(%23a)' fill-opacity='0.58' d='M46 0h1v100h-1zM201 0h1v100h-1zM1091 0h1v80h-1zM1663 0h1v100h-1z'/><path fill='url(%23a)' fill-opacity='0.44' d='M66 0h1v58h-1zM194 0h1v75h-1zM234 0h1v100h-1zM345 0h1v100h-1zM668 0h1v100h-1zM936 0h1v100h-1zM1128 0h1v100h-1zM1370 0h1v100h-1zM1618 0h1v84h-1z'/><path fill='url(%23b)' fill-opacity='0.58' d='M82 0h1v100h-1zM224 0h1v100h-1zM603 0h1v79h-1zM881 0h1v56h-1z'/><path fill='url(%23b)' fill-opacity='0.3' d='M101 0h1v100h-1zM124 0h1v100h-1zM1151 0h1v100h-1zM1390 0h1v100h-1z'/><path fill='url(%23e)' fill-opacity='0.3' d='M133 0h1v100h-1zM772 0h1v68h-1zM1599 0h1v100h-1z'/><path fill='url(%23e)' fill-opacity='0.44' d='M148 0h1v100h-1zM336 0h1v100h-1zM1001 0h1v100h-1zM1121 0h1v100h-1zM1168 0h1v100h-1z'/><path fill='url(%23d)' fill-opacity='0.3' d='M164 0h1v63h-1zM1354 0h1v100h-1zM1454 0h1v81h-1z'/><path fill='url(%23a)' fill-opacity='0.16' d='M185 0h1v80h-1zM315 0h1v100h-1zM812 0h1v100h-1zM1519 0h1v53h-1z'/><path fill='url(%23c)' fill-opacity='0.3' d='M250 0h1v100h-1zM566 0h1v100h-1zM749 0h1v69h-1zM1639 0h1v100h-1z'/><path fill='url(%23e)' fill-opacity='0.58' d='M286 0h1v100h-1zM582 0h1v100h-1zM917 0h1v100h-1zM1269 0h1v100h-1zM1286 0h1v100h-1z'/><path fill='url(%23a)' fill-opacity='0.9' d='M295 0h2v100h-2zM363 0h2v61h-2zM1438 0h2v100h-2zM1499 0h3v100h-3zM1562 0h2v86h-2z'/><path fill='url(%23b)' fill-opacity='0.44' d='M378 0h1v65h-1zM1476 0h1v59h-1z'/><path fill='url(%23d)' fill-opacity='0.44' d='M402 0h1v53h-1zM943 0h1v100h-1z'/><path fill='url(%23e)' fill-opacity='0.16' d='M477 0h1v100h-1zM499 0h1v100h-1zM962 0h1v100h-1zM1302 0h1v100h-1z'/><path fill='url(%23d)' fill-opacity='0.58' d='M522 0h1v100h-1zM1420 0h1v100h-1z'/><path fill='url(%23e)' fill-opacity='0.9' d='M543 0h2v100h-2z'/><path fill='url(%23a)' fill-opacity='0.3' d='M692 0h1v100h-1zM726 0h1v100h-1zM896 0h1v74h-1zM1099 0h1v100h-1zM1538 0h1v100h-1z'/><path fill='url(%23c)' fill-opacity='0.44' d='M789 0h1v100h-1zM1023 0h1v100h-1zM1206 0h1v100h-1zM1397 0h1v100h-1zM1584 0h1v100h-1z'/><path fill='url(%23b)' fill-opacity='0.16' d='M1070 0h1v100h-1z'/><path fill='url(%23c)' fill-opacity='0.16' d='M1188 0h1v100h-1z'/></svg>");
	mask-size: 1680px 100%;
	/* The light in the fins. Underneath, their own: ice, cooling on the way down.
	   Over it the shimmer, one soft crest in a band 180vh tall, so a crest
	   crosses the stack once a cycle instead of a pulse arriving on a beat.
	   Mask alpha and background alpha multiply, which is what sets the range:
	   the brightest rail lands near 9%, a typical hairline near 4%, the faintest
	   near 1.5%, and a crest roughly doubles whichever fin it is crossing. */
	background:
		linear-gradient(180deg,
			transparent 0 16%,
			rgb(91 233 255 / 3.5%) 27%,
			rgb(214 244 255 / 8%) 33%,
			rgb(91 233 255 / 3.5%) 39%,
			transparent 52%) 0 0 / 100% 180vh,
		linear-gradient(180deg, rgb(91 233 255 / 12%) 0%, rgb(91 233 255 / 5.5%) 100%);
}

/* Heat rises, so the crest does too. It moves the background and not the layer:
   a transform would carry the mask along with it and the fins would slide
   sideways in the dark instead of lighting up where they stand. Exactly one
   band of travel, so the loop has no seam in it. */
@keyframes hs-fin-shimmer {
	from { background-position: 0 0, 0 0; }
	to   { background-position: 0 -180vh, 0 0; }
}

/* The parallax. 8vh of travel across a page that is many screens long, which is
   depth rather than an effect, and on a stack of fins it is also invisible:
   every line in the mask is a straight vertical, and a straight vertical slid
   vertically lands back on itself. The only fins that move at all are the ones
   that die partway down, and those fade out rather than end, so nothing in the
   layer reads as having moved.

   What the eye can read on a wall of verticals is the pitch between them
   opening, so the layer widens by a tenth across the same scroll. That is the
   stack coming toward you: the same depth cue, and the right one for an object
   you are falling into. A fin near the centre still barely stirs and one at the
   edge travels about a finger, which keeps the gradient of motion that makes it
   depth instead of a pan. Widen, never narrow: the layer is inset to the
   viewport, and any scale below 1 would drag its own edges into view. */
@keyframes hs-fin-drift {
	from { transform: translate3d(0, 4vh, 0) scale(1); }
	to   { transform: translate3d(0, -4vh, 0) scale(1.1); }
}

/* The drift runs off the scroll position, so it costs nothing while nothing is
   scrolling, and where the browser cannot drive an animation off scroll the
   @supports leaves a still backdrop, which is what this was before. Off under
   prefers-reduced-motion: parallax is the one thing on this site that can make
   somebody feel ill. */
@supports (animation-timeline: scroll()) {
	@media (prefers-reduced-motion: no-preference) {
		.game-heatsink::after {
			animation: hs-fin-drift linear both;
			animation-timeline: scroll(root block);
		}
	}
}

/* The shimmer runs off a clock, which means a full-viewport layer repainting
   for as long as the page is open. That is a desktop-only price: this game
   ships on phones, the phone has better uses for the frame, and a stack this
   faint loses very little by holding still. The drift is repeated in here
   rather than left to the block above because the two share one property, and a
   second `animation` replaces the list instead of extending it. */
@media (prefers-reduced-motion: no-preference) and (min-width: 900px) {
	.game-heatsink::after { animation: hs-fin-shimmer 15s linear infinite; }

	@supports (animation-timeline: scroll()) {
		.game-heatsink::after {
			animation: hs-fin-shimmer 15s linear infinite,
			           hs-fin-drift linear both;
			animation-timeline: auto, scroll(root block);
		}
	}
}

/* --------------------------------------------------------------------------
   4. Hero

   Copy left, a real capture right, and above the fold on a 360px phone:
   wordmark, headline, one sub-line, one button. Everything else earns its place
   by being scrolled to.
   -------------------------------------------------------------------------- */
.hs-hero {
	position: relative;
	padding-block: clamp(4.5rem, 12vw, 8rem) clamp(2.5rem, 5vw, 4rem);
	overflow: hidden;
}

/* Two glows, ice over crit, sitting where the device sits. Not a shadow and
   not a border: it is the light the instrument throws, which is the whole
   material language of the game.

   The box is taller than the hero and .hs-hero clips its overflow, so the top
   of it is a straight cut, and that cut lands at the top of the hero, which is
   the bottom of the sticky header. Both radials were still carrying around 7%
   of alpha where they met it, so the page opened on a hard horizontal line
   ruled under the nav. The mask fades the layer up out of the dark before the
   cut can reach it: 17% is where the clip sits, being the 20 of this box's 120
   that lives above the hero, and full strength arrives at 33%, just above the
   ice centre so the bright half of the layer is untouched. The crit centre
   moves down for the same reason: at 12% its own peak sat in the cut. */
.hs-hero-glow {
	position: absolute;
	inset: -20% -10% auto;
	height: 120%;
	pointer-events: none;
	background:
		radial-gradient(46% 46% at 72% 34%, rgb(91 233 255 / 16%), transparent 70%),
		radial-gradient(52% 40% at 26% 24%, rgb(255 46 76 / 9%), transparent 72%);
	mask-image: linear-gradient(180deg, transparent 0 17%, #000 33%);
}

.hs-hero-inner {
	position: relative;
	z-index: 1;
	display: grid;
	gap: clamp(2.5rem, 6vw, 4rem);
	align-items: center;
}

.hs-headline { display: grid; gap: clamp(1rem, 2.5vw, 1.6rem); margin: 0 0 1rem; }

/* On phones the complete lockup is a centred hero mark, not a small label
   attached to the left edge. Desktop returns it to the title screen's
   height-based sizing so it keeps its weight beside the device. */
.hs-wordmark {
	width: min(82vw, 20rem);
	height: auto;
	max-width: 100%;
	object-fit: contain;
	object-position: center;
	justify-self: center;
}
.hs-wordmark--text {
	font: 700 clamp(3rem, 9vw, 5rem)/1 var(--display);
	letter-spacing: .04em;
	color: var(--ice);
}

.hs-headline-line { font-size: var(--t-hero); line-height: 1.02; }

.hs-sub {
	margin: 0 0 1.1rem;
	max-width: 22ch;
	font: 700 clamp(1.15rem, .95rem + 1.1vw, 1.9rem)/1.25 var(--display);
	color: var(--ice);
}
.hs-lede {
	margin: 0 0 1.8rem;
	max-width: 46ch;
	font-size: var(--t-lead);
	color: var(--muted);
	text-wrap: pretty;
}
.hs-support {
	margin: 1.4rem 0 0;
	max-width: 44ch;
	font-size: .95rem;
	color: var(--dim);
}

/* The device. A picture of a phone, so it takes a phone's corner radius: the
   10px cap in the design system governs the site's own chrome, and a handset
   drawn with a 10px corner does not read as a handset. */
.hs-device { margin: 0; display: grid; gap: .9rem; justify-items: center; }
.hs-phone {
	position: relative;
	width: min(100%, 19rem);
	padding: 8px;
	border: 1px solid rgb(91 233 255 / 34%);
	border-radius: 34px;
	background: var(--surface-2);
	/* Glow, not shadow: a coloured spread in the accent, no black offset. */
	filter: drop-shadow(0 0 34px rgb(91 233 255 / 18%));
}
.hs-phone img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 9 / 16;
	object-fit: cover;
	border-radius: 27px;
}
.hs-device-cap {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: .2rem .8rem;
	font: 400 .78rem/1.4 var(--mono);
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--dim);
}
.hs-device-cap b { color: var(--crit); font-weight: 700; }

.hs-meta { margin-top: clamp(2.5rem, 5vw, 3.5rem); }

@media (min-width: 900px) {
	.hs-hero-inner { grid-template-columns: 1.15fr .85fr; }
	.hs-wordmark {
		width: auto;
		height: clamp(5rem, 13vw, 9rem);
		object-position: left center;
		justify-self: start;
	}
	.hs-device { justify-items: end; }
	.hs-phone { width: min(100%, 20.5rem); }
}

/* --------------------------------------------------------------------------
   5. The gauge

   The one thing this page does that a store listing cannot. Everything in it
   reads --heat and --hot off the section, which site.js writes once per frame,
   so the arc, the numerals, the rules and the glow are one instrument rather
   than several that agree most of the time.
   -------------------------------------------------------------------------- */
.hs-gauge {
	position: relative;
	overflow: hidden;
	border-block: 1px solid color-mix(in oklab, var(--hot) 48%, var(--line));
	/* The section itself takes the temperature. calc on --heat rather than a
	   class per band, so it is continuous: no steps, no bands. */
	background:
		radial-gradient(82% 105% at 82% 48%,
			color-mix(in oklab, var(--hot) calc(var(--heat) * 0.28%), transparent),
			transparent 70%),
		radial-gradient(62% 90% at 0 50%,
			color-mix(in oklab, var(--hot) calc(var(--heat) * 0.10%), transparent),
			transparent 76%),
		var(--surface);
	transition: background .3s var(--ease), border-color .12s linear;
}
.hs-gauge::after {
	content: '';
	position: absolute;
	z-index: 0;
	inset: 0;
	pointer-events: none;
	background:
		radial-gradient(115% 125% at 72% 48%,
			color-mix(in oklab, var(--crit) 58%, transparent), transparent 78%),
		color-mix(in oklab, var(--crit) 12%, transparent);
	opacity: 0;
}
.hs-gauge-inner {
	position: relative;
	z-index: 1;
	display: grid;
	gap: clamp(2.5rem, 6vw, 4.5rem);
	align-items: center;
}
.hs-gauge .sec-title span {
	color: var(--hot);
	transition: color .12s linear, text-shadow .12s linear;
}
@media (min-width: 900px) {
	.hs-gauge-inner { grid-template-columns: 1fr minmax(20rem, 26rem); }
}

.hs-numbers {
	margin: clamp(1.8rem, 4vw, 2.6rem) 0 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
	gap: 1px;
	background: var(--line);
	border: 1px solid var(--line);
}
.hs-numbers > div { background: var(--void); padding: .9rem 1.1rem; }
.hs-numbers dt {
	font: 400 .78rem/1.4 var(--mono);
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--dim);
}
.hs-numbers dd { margin: .25rem 0 0; font-weight: 700; color: var(--text); }

.hs-gauge-machine {
	position: relative;
	display: grid;
	justify-items: center;
	gap: 1.2rem;
	isolation: isolate;
}

.hs-readouts {
	display: flex;
	gap: clamp(1.5rem, 5vw, 3rem);
	margin: 0;
	text-align: center;
}
.hs-readouts p { margin: 0; display: grid; gap: .3rem; }
.hs-readouts span {
	font: 400 .78rem/1.4 var(--mono);
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--dim);
}
.hs-readouts b {
	font: 700 clamp(1.8rem, 1.3rem + 2vw, 2.8rem)/1 var(--display);
	color: var(--hot);
	transition: color .12s linear;
}

.hs-arc { width: min(100%, 19rem); height: auto; display: block; }
.hs-arc path, .hs-arc polygon { fill: none; stroke-linecap: round; }

.hs-arc-track { stroke: var(--line-strong); stroke-width: 12; }

/* pathLength is 100 on the arc, so the dash array is the percentage itself and
   nothing has to know the real length of a 270 degree sweep. */
.hs-arc-fill {
	stroke: var(--hot);
	stroke-width: 12;
	/* The gap is longer than the path so the pattern cannot repeat and leave a
	   second cap sitting at the far end of the arc. */
	stroke-dasharray: var(--heat) 999;
	/* A zero-length dash with a round cap is still a dot. --heat is 0 to 100, so
	   this reaches full opacity within the first percent and hides the dot at
	   rest without a class or a second state to keep in step. */
	opacity: min(1, var(--heat));
	filter: drop-shadow(0 0 8px color-mix(in oklab, var(--hot) 55%, transparent));
}

/* The redline mark: a tick across the arc at the fraction game.php names, laid
   out in sections.php so the number and the mark cannot drift apart. Dim until
   the gauge reaches it, then lit. */
.hs-arc-redline {
	stroke: var(--crit);
	stroke-width: 4;
	opacity: .4;
	transition: opacity .2s var(--ease);
}
.hs-gauge.is-redline .hs-arc-redline { opacity: 1; }

.hs-arc-core {
	stroke: var(--hot);
	stroke-width: 4;
	filter: drop-shadow(0 0 8px color-mix(in oklab, var(--hot) 55%, transparent));
	transition: stroke .12s linear;
}
.hs-arc-core-body {
	fill: var(--void) !important;
}
.hs-arc-core-fill { fill: var(--amber) !important; stroke: none; }

/* The death burst is clipped by the gauge section, not by the machine. Long
   streaks can cross the instrument without ever creating page overflow. */
.hs-death-fx {
	position: absolute;
	inset: 0;
	z-index: 4;
	pointer-events: none;
	overflow: visible;
}
.hs-death-particle {
	position: absolute;
	left: var(--x);
	top: var(--y);
	display: block;
	width: var(--length);
	height: 2px;
	border-radius: 999px;
	transform-origin: left center;
	opacity: 0;
	will-change: transform, opacity;
	animation: hs-death-streak var(--life) cubic-bezier(.12, .68, .18, 1) var(--delay) both;
}
.hs-death-particle--critical {
	background: var(--crit);
	box-shadow: 0 0 5px var(--crit), 0 0 11px color-mix(in oklab, var(--crit) 58%, transparent);
}
.hs-death-particle--amber {
	background: var(--amber);
	box-shadow: 0 0 4px var(--amber), 0 0 9px color-mix(in oklab, var(--amber) 50%, transparent);
}
@keyframes hs-death-streak {
	0% {
		opacity: 0;
		transform: translate(-50%, -50%) rotate(var(--angle)) scaleX(.2);
	}
	8% { opacity: 1; }
	58% { opacity: .95; }
	100% {
		opacity: 0;
		transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) rotate(var(--angle)) scaleX(.38);
	}
}

.hs-hold {
	display: grid;
	place-content: center;
	place-items: center;
	min-width: min(100%, 16rem);
	min-height: 3.4rem;
	padding: .85rem 1.6rem;
	border: 1px solid color-mix(in oklab, var(--hot) 55%, transparent);
	background: color-mix(in oklab, var(--hot) 8%, transparent);
	color: var(--text);
	font: 700 .84rem/1 var(--display);
	letter-spacing: .16em;
	text-transform: uppercase;
	cursor: pointer;
	/* The one control on the page a visitor is meant to hold down. Stopping the
	   browser turning a long press into a text selection or a context menu is
	   what makes holding feel like holding rather than like a bug. */
	touch-action: none;
	user-select: none;
	-webkit-user-select: none;
	-webkit-touch-callout: none;
	transition: border-color .2s var(--ease), background .2s var(--ease);
}
.hs-hold[aria-pressed='true'] { background: color-mix(in oklab, var(--hot) 18%, transparent); }

.hs-burnout {
	margin: 0;
	min-height: 1.4rem;
	font: 700 .9rem/1.4 var(--display);
	letter-spacing: .22em;
	text-transform: uppercase;
	color: var(--crit);
	opacity: 0;
	transition: opacity .12s var(--ease);
}
.hs-gauge.is-burnout .hs-burnout { opacity: 1; }
.hs-gauge.is-burnout .hs-arc-fill { stroke: var(--crit); }
.hs-gauge.is-burnout .hs-arc-core {
	opacity: 0;
	transform: scale(.82);
	transform-origin: 160px 159px;
}

/* Past the danger line the instrument picks up the pulse, at the frequency the
   game uses. Colour is never the only channel: the numeral, the label and the
   arc length all say the same thing, so this is emphasis, never information. */
@keyframes hs-pulse {
	from { opacity: 1; }
	to   { opacity: .72; }
}
@keyframes hs-danger-strobe {
	from { opacity: .12; }
	to   { opacity: .58; }
}
@keyframes hs-max-flash {
	0%   { z-index: 2; opacity: .12; }
	8%   { z-index: 2; opacity: .86; }
	18%  { z-index: 2; opacity: .2; }
	29%  { z-index: 2; opacity: .76; }
	42%  { z-index: 2; opacity: .18; }
	55%  { z-index: 0; opacity: .66; }
	70%  { z-index: 0; opacity: .24; }
	100% { z-index: 0; opacity: .38; }
}
.hs-gauge.is-danger .hs-arc-fill {
	animation: hs-pulse 385ms steps(2, jump-none) infinite alternate;
}
.hs-gauge.is-danger:not(.is-burnout)::after {
	animation: hs-danger-strobe 190ms steps(2, jump-none) infinite alternate;
}
.hs-gauge.is-danger .sec-title span {
	text-shadow: 0 0 18px color-mix(in oklab, var(--hot) 48%, transparent);
}
.hs-gauge.is-burnout .sec-title span { color: var(--crit); }
.hs-gauge.is-burnout::after {
	z-index: 0;
	background: var(--crit);
	animation: hs-max-flash 720ms steps(1, end) both;
}

/* --------------------------------------------------------------------------
   6. Weapons

   Six cards, each keeping its own colour from its .tres and never recoloured.
   On a phone it is a snap rail with the next card's edge showing, because a
   carousel nobody can see the edge of is a section nobody scrolls.
   -------------------------------------------------------------------------- */
.hs-weapons {
	list-style: none;
	margin: 0;
	padding: 0 0 1rem;
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: min(78%, 17rem);
	gap: clamp(.8rem, 2vw, 1.2rem);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	overscroll-behavior-x: contain;
	scrollbar-width: thin;
}
.hs-weapon {
	position: relative;
	scroll-snap-align: start;
	display: grid;
	/* code, name, tagline, curve, heat line. The tagline row is the one that
	   flexes, so the six curves sit on one line across the row however long the
	   copy above them runs. Six curves that do not line up are six charts; six
	   that do are one comparison. */
	grid-template-rows: auto auto 1fr auto auto;
	gap: .55rem;
	padding: clamp(1.1rem, 2.5vw, 1.5rem);
	background: rgb(11 13 20 / 70%);
	/* The notch and the line that draws it, both from the shared sheet, in this
	   card's own colour rather than the site's. See "The notched corner" in
	   css/style.css for why the diagonal has to be painted back. */
	--notch-line: color-mix(in oklab, var(--wpn) 20%, transparent);
	border: 1px solid var(--notch-line);
	clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, 0 100%);
	transition: --notch-line .3s var(--ease);
}
.hs-weapon::before {
	content: '';
	position: absolute;
	inset: -1px;
	pointer-events: none;
	background: linear-gradient(to bottom right,
			transparent 0 calc(50% - 1px), var(--notch-line) calc(50% - 1px) 100%)
		100% 100% / var(--cut) var(--cut) no-repeat;
}
.hs-weapon:hover { --notch-line: color-mix(in oklab, var(--wpn) 55%, transparent); }

.hs-weapon-code {
	margin: 0;
	font: 400 .76rem/1 var(--mono);
	letter-spacing: .2em;
	color: var(--dim);
}
.hs-weapon-name { font-size: 1.25rem; color: var(--wpn); }
.hs-weapon-tag { margin: 0; color: var(--text); font-size: .95rem; }
.hs-weapon-heat { margin: 0; color: var(--muted); font-size: .85rem; }

/* Heat on Y, time on X. Six of these side by side are the argument that the
   weapons are six different games, and they cost a line of path data each. */
.hs-curve { width: 100%; height: auto; margin: .4rem 0; }
.hs-curve-axis { fill: none; stroke: var(--line-strong); stroke-width: 1; }
.hs-curve-line {
	fill: none;
	stroke: var(--wpn);
	stroke-width: 2.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	filter: drop-shadow(0 0 4px color-mix(in oklab, var(--wpn) 45%, transparent));
}

@media (min-width: 900px) {
	/* Enough room for all six at once, so the comparison happens in one look
	   instead of in six swipes. */
	.hs-weapons {
		grid-auto-flow: row;
		grid-template-columns: repeat(3, 1fr);
		grid-auto-columns: auto;
		overflow: visible;
	}
}

/* The capture strip is six phones, so it is drawn as six phones. The shared
   gallery gives the grid and the lightbox; this gives it the frame, and matches
   the hero's so the two read as the same object photographed six times. */
.game-heatsink .gallery--portrait .shot-btn {
	border-radius: 18px;
	border-color: rgb(91 233 255 / 22%);
	background: var(--surface-2);
	padding: 4px;
	transition: border-color .3s var(--ease), filter .3s var(--ease);
}
.game-heatsink .gallery--portrait .shot-btn img { border-radius: 14px; }
.game-heatsink .gallery--portrait .shot-btn:hover {
	border-color: rgb(91 233 255 / 60%);
	filter: drop-shadow(0 0 18px rgb(91 233 255 / 22%));
}

/* --------------------------------------------------------------------------
   7. The cycle

   The readings are the section. One treatment, one column, so 2140 to 41 reads
   as a single falling series at a glance.
   -------------------------------------------------------------------------- */
.hs-sectors { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.hs-sector {
	display: grid;
	grid-template-columns: 2.6rem 1fr;
	gap: .3rem 1.2rem;
	align-items: baseline;
	padding: clamp(1.1rem, 2.5vw, 1.6rem) 0;
	border-bottom: 1px solid var(--line);
}
.hs-sector-n {
	margin: 0;
	font: 400 .82rem/1.4 var(--mono);
	letter-spacing: .12em;
	color: var(--dim);
}
.hs-sector-body { min-width: 0; }
.hs-sector-name { font-size: var(--t-h3); }
.hs-sector-line { margin: .4rem 0 0; color: var(--muted); max-width: 60ch; }

/* --temp is the heat ramp read at this sector's own reading, set per row in
   sections.php. 2140 K prints at the crit end and 41 K at the ice end, so the
   column does not only fall, it cools while it falls. */
.hs-sector-reading {
	grid-column: 2;
	margin: .7rem 0 0;
	/* A grid, not a flex row: the numerals right-align against a fixed unit, so
	   2140 and 41 share an edge and the six of them read as one falling column
	   rather than as six figures that happen to be near each other. */
	display: grid;
	grid-template-columns: 1fr auto;
	align-items: baseline;
	gap: .3rem;
	color: var(--temp, var(--amber));
}
.hs-sector-reading b { font: 700 clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem)/1 var(--display); text-align: right; }
.hs-sector-reading span { font: 400 .8rem/1 var(--mono); letter-spacing: .1em; color: var(--dim); }
/* No offset and the row's own colour: a glow, not a shadow. */
.hs-sector-reading b { text-shadow: 0 0 22px color-mix(in oklab, var(--temp, var(--amber)) 45%, transparent); }

/* Redacted, not blurred. A blur reads as a loading state and invites a
   screenshot with the filter removed; a rule through the name is a decision
   somebody made, which is the impression that is actually true here. */
.hs-sector.is-redacted .hs-sector-name { color: var(--dim); }
.hs-sector.is-redacted s { text-decoration-color: var(--crit); text-decoration-thickness: 2px; }
.hs-sector.is-redacted .hs-sector-line {
	font: 400 .8rem/1.4 var(--mono);
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--dim);
}
/* The series keeps falling whether or not the name is printed, so the reading
   on a redacted row is the same size and the same colour as every other. */

.hs-sectors-caption {
	margin: clamp(1.5rem, 3vw, 2.2rem) 0 0;
	max-width: 52ch;
	color: var(--dim);
	font-size: .92rem;
}

@media (min-width: 700px) {
	.hs-sector {
		grid-template-columns: 3rem 1fr 8rem;
		align-items: center;
	}
	.hs-sector-reading { grid-column: 3; margin: 0; }
}

/* --------------------------------------------------------------------------
   8. Story

   One column, quiet, nothing beside it competing for the eye. The close is the
   last thing on the section on purpose.
   -------------------------------------------------------------------------- */
.hs-story {
	padding-block: var(--sec-y);
	background:
		radial-gradient(70% 50% at 50% 0%, rgb(91 233 255 / 7%), transparent 70%),
		var(--void);
	border-block: 1px solid var(--line);
	text-align: center;
}
.hs-story-inner { max-width: 44rem; }
.hs-story .eyebrow { justify-content: center; }

.hs-quote {
	margin: clamp(1.8rem, 4vw, 2.6rem) 0 0;
	padding: clamp(1.4rem, 3vw, 2rem);
	border-left: 2px solid var(--ice);
	background: rgb(11 13 20 / 70%);
	text-align: left;
}
.hs-quote p {
	margin: 0;
	font: 400 clamp(1.05rem, .95rem + .6vw, 1.35rem)/1.6 var(--display);
	color: var(--text);
}
.hs-story-body {
	margin: clamp(1.6rem, 3vw, 2.2rem) auto 0;
	max-width: 52ch;
	color: var(--muted);
}
.hs-story-close {
	margin: clamp(1.8rem, 4vw, 2.6rem) 0 0;
	font: 700 clamp(1.4rem, 1.1rem + 1.6vw, 2.2rem)/1.2 var(--display);
	color: var(--crit);
}

/* --------------------------------------------------------------------------
   9. Spec
   -------------------------------------------------------------------------- */
.hs-spec {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	/* Wide enough that three columns never fit. There are eight items, and eight
	   in threes leaves a hole in the last row of a grid whose gaps are drawn as
	   rules, which reads as a missing item rather than as a spare cell. */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
	gap: 1px;
	background: var(--line);
	border: 1px solid var(--line);
}
.hs-spec li {
	background: var(--void);
	padding: clamp(1.2rem, 2.5vw, 1.6rem);
	display: grid;
	align-content: start;
	gap: .4rem;
}
.hs-spec h3 {
	font-size: 1rem;
	color: var(--text);
	display: flex;
	gap: .6rem;
	align-items: baseline;
}
/* A tick drawn as a rule rather than as a glyph: no emoji anywhere on this
   site, in the interface or in the copy. */
.hs-spec h3::before {
	content: '';
	flex: none;
	width: .9rem;
	height: 2px;
	background: var(--ice);
	transform: translateY(-.25rem);
}
.hs-spec p { margin: 0; color: var(--muted); font-size: .92rem; }

/* --------------------------------------------------------------------------
   10. The signup

   The shared module (inc/modules/notify-form.php) drawn the way the mock had
   it: the wordmark centred and sitting into the eyebrow under it, a rule above
   the whole block, and the field the only thing on the page besides the gauge
   that asks to be touched. Nothing here is new markup, only this game's
   treatment of the studio's.
   -------------------------------------------------------------------------- */
.game-heatsink #notify { border-top: 1px solid var(--line); }

.game-heatsink .notify { max-width: 46rem; }

/* Closer to the line under it than the shared sheet puts it. This file carries
   its own air at the top and almost none at the foot, where the wordmark's own
   TRANSMISSION PENDING sits: pull it much further and the heading lands on
   that line, leave the shared 1.4rem and there is a hole under the mark. */
.game-heatsink .notify-logo { width: min(300px, 74vw); margin-bottom: .5rem; }

.game-heatsink .notify-form { max-width: 40rem; margin-inline: auto; }

/* Ice is the only colour on this site that asks to be pressed, and the field
   is the one place a visitor is asked to press something. Glow, never shadow:
   the ring is light coming off the instrument, not a drop shadow under it. */
.game-heatsink .notify-row input:focus {
	border-color: var(--ice);
	box-shadow: 0 0 0 1px color-mix(in oklab, var(--ice) 45%, transparent),
	            0 0 14px color-mix(in oklab, var(--ice) 22%, transparent);
}

/* Confirmed reads as ice rather than the shared sheet's green: six colours, no
   seventh, and this page does not get to invent one for a status line. */
.game-heatsink .notify-status.is-ok { color: var(--ice); }
.game-heatsink .notify-status.is-bad { color: var(--crit); }

/* --------------------------------------------------------------------------
   11. Reduced motion

   A hard contract inherited from the game: every animation collapses, and the
   gauge keeps working, because it is state rather than decoration. Nothing here
   removes information, which is the line the setting must never cross.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	.hs-gauge.is-danger .hs-arc-fill { animation: none; }
	.hs-gauge.is-danger:not(.is-burnout)::after { animation: none; opacity: .2; }
	.hs-gauge.is-burnout::after { z-index: 0; animation: none; opacity: .24; }
	.hs-death-fx { display: none; }
	.hs-gauge.is-burnout .hs-arc-core { opacity: 1; transform: none; }
	.hs-weapons { scroll-snap-type: none; }
	.hs-weapon:hover { transform: none; }
}
