
		*, *::before, *::after { box-sizing: border-box; }

		:root {
			--canvas-ratio: 0.5625;
			--item-height: 80px;
			--reel-width: 33.333%;
			--slots-font-family: Inter, system-ui, sans-serif;
			--slots-heading-font-family: Impact, Haettenschweiler, "Arial Black", system-ui, sans-serif;
			--slots-spin-button-font-family: var(--slots-heading-font-family);
			--slots-spin-button-font-size: 28px;
			--slots-retry-button-font-family: var(--slots-heading-font-family);
			--slots-retry-button-font-size: 28px;
			--slots-claim-button-font-family: var(--slots-heading-font-family);
			--slots-claim-button-font-size: 28px;
			--slots-win-title-font-family: var(--slots-heading-font-family);
			--slots-win-title-font-size: clamp(1.65rem, 5dvh, 3rem);
			--slots-win-text-font-family: var(--slots-font-family);
			--slots-win-text-font-size: clamp(0.9rem, 2.2dvh, 1.35rem);
				--slots-page-background-color: #09090b;
			--slots-body-background-image: none;
			--slots-body-background-image-desktop: none;
			--slots-machine-frame-image: none;
			--slots-spin-button-image: none;
			--slots-win-background-image: none;
			--slots-win-background-image-desktop: none;
			--slots-win-overlay-background-image: none;
			--slots-win-overlay-background-image-desktop: none;
			--slots-win-stage-background-image: var(--slots-win-background-image);
			--slots-win-stage-background-image-desktop: var(--slots-win-background-image-desktop);
			--slots-win-panel-image: none;
			--slots-logo-top: 5%;
			--slots-logo-width: 64%;
			--slots-logo-z-index: 6;
			--slots-logo-idle-name: none;
			--slots-logo-shadow-display: none;
			--slots-logo-shadow-color: rgba(16, 0, 40, 0.7);
			--slots-logo-backdrop-display: none;
			--slots-logo-backdrop-top: -7%;
			--slots-logo-backdrop-width: 70%;
			--slots-logo-card-display: none;
			--slots-logo-card-top: 0%;
			--slots-logo-card-width: 18%;
			--slots-logo-card-left: 20%;
			--slots-logo-card-right: 20%;
			--slots-logo-card-z-index: 5;
			--slots-logo-card-idle-name: none;
			--slots-logo-card-idle-right-name: none;
			--slots-machine-backdrop-display: none;
			--slots-machine-backdrop-top: 28%;
			--slots-machine-backdrop-width: 70%;
			--slots-machine-backdrop-z-index: 3;
			--slots-machine-backdrop-spin-name: none;
			--slots-machine-backdrop-spin-duration: 26s;
			--slots-machine-top: 28%;
			--slots-machine-width: 70%;
			--slots-machine-aspect-ratio: 1 / 1;
			--slots-machine-fill-background: none;
			--slots-machine-fill-top: 0%;
			--slots-machine-fill-left: 0%;
			--slots-machine-fill-width: 100%;
			--slots-machine-fill-height: 100%;
			--slots-machine-frame-z-index: 0;
			--slots-machine-foreground-display: none;
			--slots-machine-foreground-top: 0%;
			--slots-machine-foreground-left: 0%;
			--slots-machine-foreground-width: 100%;
			--slots-machine-foreground-height: 100%;
			--slots-machine-foreground-object-fit: contain;
			--slots-machine-foreground-z-index: 2;
			--slots-window-top: 0%;
			--slots-window-left: 0%;
			--slots-window-width: 100%;
			--slots-window-height: 100%;
			--slots-window-background: rgba(255, 255, 255, 0.08);
			--slots-window-mask-display: none;
			--slots-reel-background: none;
			--slots-reel-background-1: var(--slots-reel-background);
			--slots-reel-background-2: var(--slots-reel-background);
			--slots-reel-background-3: var(--slots-reel-background);
			--slots-reel-row-line-color: transparent;
			--slots-reel-row-line-thickness: 0px;
			--slots-reel-row-line-spin-blur: 0px;
			--slots-reel-col-line-color: transparent;
			--slots-reel-col-line-thickness: 0px;
			--slots-symbol-idle-fx-display: block;
			--slots-symbol-idle-tilt-name: staticSymbolTilt;
			--slots-symbol-lite-idle-name: none;
			--slots-reel-enter-name: none;
			--slots-symbol-size: 88%;
			--slots-video-symbol-size: 100%;
			--slots-win-line-top: 36%;
			--slots-win-line-left: 0%;
			--slots-win-line-width: 100%;
			--slots-win-line-height: 28%;
			--slots-button-top: 76%;
			--slots-button-width: 70%;
			--slots-button-aspect-ratio: 800 / 244;
			--slots-button-pulse-name: none;
			--slots-button-glow-display: block;
			--slots-button-glow-color: rgba(255, 222, 35, 0.5);
			--slots-button-glow-inset: -14% -6%;
			/* The outward halo rides on the button art itself (see .spin-btn img
			   below): a gaussian drop-shadow chain fades to zero in every
			   direction and follows rays and straight bars that the stretched
			   silhouette mask would cut hard. A theme mutes it with `none`. */
			--slots-button-halo-filter:
				drop-shadow(0 0 0.8dvh var(--slots-button-glow-color))
				drop-shadow(0 0 1.8dvh var(--slots-button-glow-color))
				drop-shadow(0 0 3.6dvh var(--slots-button-glow-color));
			/* The claim halo follows the spin halo's theme-tuned chain; a theme
			   can still override it separately. */
			--slots-win-claim-halo-filter: var(--slots-button-halo-filter);
			/* app.js points these at the button art, so both the halo behind a
			   CTA and the gleam sweeping across it take the plate's own
			   outline. The default masks everything out, so a theme whose
			   button art never resolves shows neither effect rather than a
			   bare rectangle. */
			--slots-button-silhouette-mask-image: linear-gradient(transparent, transparent);
			--slots-win-claim-silhouette-mask-image: linear-gradient(transparent, transparent);
			--slots-foreground-display: none;
			--slots-foreground-top: 65.68%;
			--slots-foreground-width: 86.48%;
			--slots-foreground-z-index: 3;
			--slots-bottom-display: none;
			--slots-bottom-top: auto;
			--slots-bottom-bottom: 0;
			--slots-bottom-width: 100%;
			--slots-bottom-z-index: 6;
			--slots-ember-display: none;
			--slots-ember-color: #ffcf6f;
			--slots-side-decor-display: none;
			--slots-side-decor-left-display: none;
			--slots-side-decor-top: 62%;
			--slots-side-decor-left-top: var(--slots-side-decor-top);
			--slots-side-decor-right-top: var(--slots-side-decor-top);
			--slots-side-decor-right: 0%;
			--slots-side-decor-left: 0%;
			--slots-side-decor-width: 28%;
			--slots-side-decor-z-index: 7;
			--slots-side-decor-left-scale: none;
			--slots-side-decor-scale: none;
			--slots-side-decor-float-name: sideDecorFloat;
			--slots-side-decor-left-float-delay: 0s;
			--slots-win-title-art-display: none;
			--slots-win-title-art-top: 8%;
			--slots-win-title-art-width: 84%;
			--slots-win-title-enter-name: winDrop;
			--slots-win-title-enter-duration: 500ms;
			--slots-win-title-idle-name: none;
			--slots-win-panel-top: 24%;
			--slots-win-panel-width: 74%;
			--slots-win-panel-aspect-ratio: 853 / 380;
			--slots-win-panel-z-index: 2;
			--slots-win-panel-inner-display: none;
			--slots-win-panel-inner-top: 12%;
			--slots-win-panel-inner-left: 10%;
			--slots-win-panel-inner-width: 80%;
			--slots-win-panel-inner-height: 68%;
			--slots-win-panel-inner-background: transparent;
			--slots-win-panel-inner-radius: 3%;
			--slots-win-hero-display: none;
			--slots-win-hero-top: 0.34%;
			--slots-win-hero-width: 100%;
			--slots-win-hero-z-index: 1;
			--slots-win-claim-top: 56%;
			--slots-win-claim-width: 55.6%;
			--slots-win-claim-aspect-ratio: 600 / 183;
			--slots-win-claim-z-index: 3;
			--slots-win-claim-pulse-name: none;
			--slots-win-claim-glow-inset: -16% -8%;
			--slots-win-title-display: block;
			--slots-win-copy-display: block;
			--slots-win-logo-display: none;
			--slots-win-logo-top: 78.8%;
			--slots-win-logo-width: 37%;
			--slots-win-logo-z-index: 4;
			--slots-win-side-decor-display: none;
			--slots-win-side-decor-top: 30%;
			--slots-win-side-decor-left: 0%;
			--slots-win-side-decor-right: 0%;
			--slots-win-side-decor-width: 28%;
			--slots-win-side-decor-z-index: 3;
			--slots-win-side-decor-left-scale: none;
			--slots-win-side-decor-scale: none;
			--slots-win-bottom-decor-display: none;
			--slots-win-bottom-decor-top: 60%;
			--slots-win-bottom-decor-left: 0%;
			--slots-win-bottom-decor-right: 0%;
			--slots-win-bottom-decor-width: 28%;
			--slots-win-bottom-decor-z-index: 3;
			--slots-win-bg-video-object-fit: cover;
			--slots-win-bg-video-object-fit-desktop: var(--slots-win-bg-video-object-fit);
			--gold: #ffdd23;
			--purple: #5a00f0;
			--shadow: rgba(36, 8, 78, 0.58);
		}

		html, body {
			width: 100%;
			height: 100%;
			margin: 0;
			font-family: var(--slots-font-family);
			background-color: var(--slots-page-background-color, #09090b);
			background-image: var(--slots-body-background-image);
			background-position: center;
			background-size: cover;
			background-repeat: no-repeat;
			color: #fff;
			overflow: hidden;
			touch-action: manipulation;
		}

		/* INTENTIONAL: removed the weird tinting from the background */

		#game-container {
			position: relative;
			z-index: 1;
			width: 100%;
			height: 100%;
			overflow: hidden;
		}

		.game-bg-video,
		.win-bg-video {
			position: absolute;
			inset: 0;
			width: 100%;
			height: 100%;
			display: none;
			object-fit: cover;
			pointer-events: none;
			user-select: none;
		}

		.game-bg-video {
			z-index: 0;
		}

		.win-bg-video {
			z-index: 0;
			object-fit: var(--slots-win-bg-video-object-fit);
		}

		.game-stage {
			position: absolute;
			top: 0;
			left: 50%;
			width: calc(100dvh * var(--canvas-ratio));
			height: 100dvh;
			min-width: 375px;
			transform: translateX(-50%);
		}

		.logo-backdrop {
			position: absolute;
			z-index: 4;
			top: var(--slots-logo-backdrop-top);
			left: 50%;
			width: var(--slots-logo-backdrop-width);
			display: var(--slots-logo-backdrop-display);
			transform: translateX(-50%);
			pointer-events: none;
			filter: drop-shadow(0 14px 28px rgba(26, 0, 60, 0.42));
			object-fit: contain;
			height: auto;
		}

		.machine-backdrop {
			position: absolute;
			z-index: var(--slots-machine-backdrop-z-index);
			top: var(--slots-machine-backdrop-top);
			left: 50%;
			width: var(--slots-machine-backdrop-width);
			display: var(--slots-machine-backdrop-display);
			transform: translateX(-50%);
			pointer-events: none;
			filter: drop-shadow(0 14px 28px rgba(26, 0, 60, 0.42));
			object-fit: contain;
			height: auto;
		}

		/* Opt-in backdrop rotation (e.g. Coin Volcano 2's fire ring), scoped to
		   <img> so it only animates the still that low-tier devices render in
		   place of the backdrop video — the video already has baked-in motion. */
		img.machine-backdrop {
			animation: var(--slots-machine-backdrop-spin-name) var(--slots-machine-backdrop-spin-duration) linear infinite;
		}

		.logo-card-left,
		.logo-card-right {
			position: absolute;
			z-index: var(--slots-logo-card-z-index);
			top: var(--slots-logo-card-top);
			width: var(--slots-logo-card-width);
			display: var(--slots-logo-card-display);
			pointer-events: none;
			filter: drop-shadow(0 12px 22px rgba(71, 0, 128, 0.5));
			animation: logoEnter 650ms cubic-bezier(.2, .8, .2, 1) both;
			object-fit: contain;
			height: auto;
		}

		.logo-card-left {
			left: var(--slots-logo-card-left);
			transform: translateX(-50%);
			/* Idle float after the entrance; the right card starts mid-cycle
			   (negative delay) so paired cards bob out of phase. */
			animation:
				logoEnter 650ms cubic-bezier(.2, .8, .2, 1) both,
				var(--slots-logo-card-idle-name) 4.6s ease-in-out 0.9s infinite;
		}

		.logo-card-right {
			right: var(--slots-logo-card-right);
			transform: translateX(50%);
			animation:
				logoEnterRight 650ms cubic-bezier(.2, .8, .2, 1) both,
				var(--slots-logo-card-idle-right-name) 4.6s ease-in-out -1.4s infinite;
		}

		/* Static radial underlay that separates the logo from a busy
		   background without a filter: drop-shadow — that would re-rasterize
		   every frame under the logo's idle float on low-tier devices. Sized
		   off the logo box (square logo canvases; a banner-logo theme would
		   need an aspect override before enabling it). */
		.logo-shadow {
			position: absolute;
			z-index: calc(var(--slots-logo-z-index) - 1);
			top: var(--slots-logo-top);
			left: 50%;
			width: var(--slots-logo-width);
			aspect-ratio: 1 / 1;
			display: var(--slots-logo-shadow-display);
			transform: translate3d(-50%, -6%, 0) scale(1.35);
			background: radial-gradient(ellipse 50% 42% at center, var(--slots-logo-shadow-color) 0%, transparent 70%);
			pointer-events: none;
		}

		.brand-logo {
			position: absolute;
			z-index: var(--slots-logo-z-index);
			top: var(--slots-logo-top);
			left: 50%;
			width: var(--slots-logo-width);
			transform: translateX(-50%);
			filter: drop-shadow(0 12px 28px rgba(71, 0, 128, 0.52));
			/* The idle float starts after logoEnter finishes; listed second so
			   its transform wins once its delay elapses. */
			animation:
				logoEnter 650ms cubic-bezier(.2, .8, .2, 1) both,
				var(--slots-logo-idle-name) 5.4s ease-in-out 1.1s infinite;
			object-fit: contain;
			height: auto;
		}

		.machine-frame {
			position: absolute;
			z-index: 4;
			top: var(--slots-machine-top);
			left: 50%;
			width: var(--slots-machine-width);
			aspect-ratio: var(--slots-machine-aspect-ratio);
			transform: translateX(-50%);
			background-color: transparent;
			background-image: var(--slots-machine-fill-background);
			background-size: var(--slots-machine-fill-width) var(--slots-machine-fill-height);
			background-position: var(--slots-machine-fill-left) var(--slots-machine-fill-top);
			background-repeat: no-repeat;
			filter: drop-shadow(0 20px 38px rgba(40, 0, 70, 0.46));
			animation: machineEnter 720ms cubic-bezier(.18, .82, .18, 1) 80ms both;
		}

		.machine-frame::before {
			content: "";
			position: absolute;
			inset: 0;
			z-index: var(--slots-machine-frame-z-index);
			background: var(--slots-machine-frame-image) center / contain no-repeat;
			pointer-events: none;
		}

		.machine-frame::after {
			content: "";
			position: absolute;
			left: var(--slots-win-line-left);
			top: var(--slots-win-line-top);
			z-index: 4;
			width: var(--slots-win-line-width);
			height: var(--slots-win-line-height);
			border-radius: 8px;
			background:
				linear-gradient(100deg, transparent 0%, rgba(255, 248, 178, 0) 28%, rgba(255, 248, 178, 0.5) 46%, rgba(255, 248, 178, 0) 64%, transparent 100%),
				radial-gradient(ellipse at center, rgba(255, 220, 50, 0.22), transparent 72%);
			box-shadow:
				0 0 24px rgba(255, 229, 78, 0.45),
				inset 0 0 18px rgba(255, 249, 190, 0.36);
			opacity: 0;
			pointer-events: none;
			transform: scaleX(0.74);
		}

		#game-container.has-win .machine-frame::after {
			animation: winLineSweep 920ms cubic-bezier(.16, .9, .22, 1) both;
		}

		.machine-frame-foreground {
			position: absolute;
			z-index: var(--slots-machine-foreground-z-index);
			top: var(--slots-machine-foreground-top);
			left: var(--slots-machine-foreground-left);
			width: var(--slots-machine-foreground-width);
			height: var(--slots-machine-foreground-height);
			display: var(--slots-machine-foreground-display);
			object-fit: var(--slots-machine-foreground-object-fit);
			pointer-events: none;
		}

		.window {
			position: absolute;
			top: var(--slots-window-top);
			left: var(--slots-window-left);
			z-index: 1;
			width: var(--slots-window-width);
			height: var(--slots-window-height);
			overflow: hidden;
			display: flex;
			border-radius: 1.1%;
			background: var(--slots-window-background);
		}

		.window::before,
		.window::after {
			content: "";
			position: absolute;
			left: 0;
			width: 100%;
			height: 15%;
			display: var(--slots-window-mask-display);
			z-index: 20;
			pointer-events: none;
		}

		.window::before {
			top: 0;
			background: linear-gradient(to bottom, rgba(58, 0, 90, 0.2), transparent);
		}

		.window::after {
			bottom: 0;
			background: linear-gradient(to top, rgba(58, 0, 90, 0.18), transparent);
		}

		/* No transform here: it would create a stacking context per column,
		   which the has-win state relies on NOT having so winning symbols can
		   rise above neighboring columns. */
		.outer-col {
			position: relative;
			overflow: hidden;
			width: var(--reel-width);
			height: 100%;
			flex: 0 0 var(--reel-width);
			/* Opt-in one-shot column cascade after machineEnter; the animation
			   lives on the wrapper because reel JS owns .col's transform. */
			animation: var(--slots-reel-enter-name) 480ms cubic-bezier(.18, .82, .18, 1) 260ms both;
		}

		.outer-col:nth-child(1) {
			--slots-reel-col-background: var(--slots-reel-background-1);
			--symbol-lite-col-delay: 0s;
		}

		.outer-col:nth-child(2) {
			--slots-reel-col-background: var(--slots-reel-background-2);
			--symbol-lite-col-delay: -1.3s;
			animation-delay: 360ms;
		}

		.outer-col:nth-child(3) {
			--slots-reel-col-background: var(--slots-reel-background-3);
			--symbol-lite-col-delay: -2.6s;
			animation-delay: 460ms;
		}

		.col {
			position: relative;
			height: auto;
			will-change: transform;
			transform: translate3d(0, 0, 0);
			background-image: var(--slots-reel-col-background, none);
			background-size: 100% var(--item-height);
			background-repeat: repeat-y;
			background-position: 0 0;
		}

		/* Vertical column separators. Drawn on the moving strip too (half a line
		   at each column edge, so inner seams add up to a full centered line and
		   land exactly on the flex boundary), but vertical motion cannot change
		   how a vertical line looks — they read as static and never blur. */
		.col::before {
			content: "";
			position: absolute;
			inset: 0;
			z-index: 2;
			pointer-events: none;
			background-image: linear-gradient(
				to right,
				var(--slots-reel-col-line-color) 0 calc(var(--slots-reel-col-line-thickness) / 2),
				transparent calc(var(--slots-reel-col-line-thickness) / 2) calc(100% - var(--slots-reel-col-line-thickness) / 2),
				var(--slots-reel-col-line-color) calc(100% - var(--slots-reel-col-line-thickness) / 2) 100%
			);
		}

		/* Horizontal cell separators printed on the drum: they ride along with
		   the reel, unlike the vertical frame lines. */
		.col::after {
			content: "";
			position: absolute;
			inset: 0;
			z-index: 2;
			pointer-events: none;
			background-image: linear-gradient(
				to bottom,
				var(--slots-reel-row-line-color) 0 calc(var(--slots-reel-row-line-thickness) / 2),
				transparent calc(var(--slots-reel-row-line-thickness) / 2) calc(100% - var(--slots-reel-row-line-thickness) / 2),
				var(--slots-reel-row-line-color) calc(100% - var(--slots-reel-row-line-thickness) / 2) 100%
			);
			background-size: 100% var(--item-height);
			background-repeat: repeat-y;
			background-position: 0 0;
			transition: filter 140ms linear;
		}

		#game-container.spinning .col.is-spinning-reel::after {
			filter: blur(var(--slots-reel-row-line-spin-blur));
		}

		.icon {
			position: relative;
			width: 100%;
			height: var(--item-height);
			display: flex;
			align-items: center;
			justify-content: center;
			isolation: isolate;
		}

		.icon .symbol-media-shell {
			position: relative;
			width: var(--slots-symbol-size);
			height: var(--slots-symbol-size);
			flex: 0 0 auto;
		}

		.icon .symbol-media {
			position: relative;
			z-index: 2;
			display: block;
			width: 100%;
			height: 100%;
			object-fit: contain;
			filter: drop-shadow(0 5px 9px rgba(73, 14, 78, 0.32));
			pointer-events: none;
			user-select: none;
		}

		/* Video symbols skip the shell, so they get their own scale knob for
			clips authored with wide transparent margins. */
		.icon .symbol-media--video {
			width: var(--slots-video-symbol-size);
			height: var(--slots-video-symbol-size);
			flex: 0 0 auto;
		}

		/* Lite renders each clip's packaged still, and that still shares the
			clip's canvas — same transparent margins — so it needs the same zoom
			the clip gets. Sizing it with --slots-symbol-size instead shrank it
			against the animated original (Joker's Jewels: 100% vs a 195% clip,
			barely half) and knocked padded tile art out of its cell (Buffalo
			King 2, whose tiles carry margins so they can float). */
		.icon.is-video-still-symbol .symbol-media-shell {
			width: var(--slots-video-symbol-size);
			height: var(--slots-video-symbol-size);
		}

		/* Recycled reel cells toggle between image and video media with the
		   hidden attribute; keep it authoritative over the display rules above
		   so a stale sibling never occupies flex space and pushes the visible
		   symbol off-center. */
		.icon .symbol-media-shell[hidden],
		.icon .symbol-media[hidden] {
			display: none;
		}

		.icon.is-static-symbol:not(.is-winning)::before {
			content: "";
			display: var(--slots-symbol-idle-fx-display);
			position: absolute;
			inset: 13% 11% 9%;
			z-index: 1;
			border-radius: 999px;
			pointer-events: none;
			background:
				radial-gradient(ellipse at 50% 58%, rgba(0, 0, 0, 0.36) 0%, rgba(0, 0, 0, 0.2) 44%, transparent 72%);
			filter: blur(4px);
			opacity: 0.34;
			animation: staticSymbolShadow 3s ease-in-out var(--symbol-tilt-delay, 0s) infinite;
		}

		.icon.is-static-symbol:not(.is-winning) .symbol-glint {
			display: var(--slots-symbol-idle-fx-display);
			position: absolute;
			inset: 0;
			z-index: 3;
			pointer-events: none;
			-webkit-mask-image: var(--symbol-mask-image);
			mask-image: var(--symbol-mask-image);
			-webkit-mask-size: 100% 100%;
			mask-size: 100% 100%;
			-webkit-mask-repeat: no-repeat;
			mask-repeat: no-repeat;
			-webkit-mask-position: center;
			mask-position: center;
			background:
				radial-gradient(circle at var(--symbol-glint-x, 66%) 28%, rgba(255, 255, 232, 0.72) 0%, rgba(255, 229, 92, 0.28) 20%, transparent 43%),
				linear-gradient(110deg, transparent 27%, rgba(255, 247, 178, 0.34) 46%, transparent 64%),
				radial-gradient(ellipse at var(--symbol-rim-x, 24%) 50%, rgba(255, 217, 68, 0.3) 0%, transparent 42%);
			background-size: 100% 100%, 240% 100%, 100% 100%;
			opacity: 0.62;
			mix-blend-mode: screen;
			animation: staticSymbolLight 3s ease-in-out var(--symbol-tilt-delay, 0s) infinite;
		}

		.icon.is-static-symbol:not(.is-winning) .symbol-media--image {
			transform-origin: center;
			will-change: transform;
			backface-visibility: hidden;
			animation: staticSymbolTilt 3s ease-in-out var(--symbol-tilt-delay, 0s) infinite;
			animation-name: var(--slots-symbol-idle-tilt-name);
		}

		.icon.is-winning {
			z-index: 5;
		}

		/* While the win is showing, the reels are at rest, so per-column
		   clipping is only needed mid-spin. Drop it — and flatten the
		   per-column stacking contexts (transform/will-change) — so winning
		   symbols and their glow rise above every reel layer and never get cut
		   at cell or column seams. The window keeps overflow: hidden: a resting
		   reel still holds the full recycler strip (extra items hang below the
		   visible 3), and the window edge is what masks it. */
		#game-container.has-win .outer-col {
			overflow: visible;
		}

		#game-container.has-win .col {
			/* The resting transform is an inline translate3d(0, 0, 0). */
			transform: none !important;
			will-change: auto;
		}

		.icon.is-winning::before {
			content: "";
			position: absolute;
			inset: 8%;
			z-index: 0;
			border-radius: 999px;
			background:
				radial-gradient(circle, rgba(255, 251, 180, 0.95) 0%, rgba(255, 213, 54, 0.48) 42%, transparent 72%);
			opacity: 0;
			transform: scale(0.58);
			filter: blur(2px);
		}

		.icon.is-winning::after {
			content: "";
			position: absolute;
			inset: 1%;
			z-index: 4;
			border-radius: 999px;
			border: 3px solid rgba(255, 247, 184, 0.94);
			background: linear-gradient(115deg, transparent 34%, rgba(255, 255, 255, 0.72) 48%, transparent 62%);
			background-size: 220% 100%;
			box-shadow:
				0 0 24px rgba(255, 237, 96, 0.86),
				inset 0 0 18px rgba(255, 237, 96, 0.48);
			opacity: 0;
			transform: scale(0.72);
		}

		.win-spark {
			position: absolute;
			z-index: 3;
			top: 50%;
			left: 50%;
			width: 7px;
			height: 7px;
			border-radius: 999px;
			background: #fff7a8;
			box-shadow: 0 0 14px rgba(255, 238, 92, 0.9);
			opacity: 0;
			pointer-events: none;
		}

		#game-container.has-win .icon.is-winning::before {
			animation: winHaloIn 760ms cubic-bezier(.18, .82, .18, 1) both, winHaloPulse 1.5s ease-in-out 760ms infinite;
		}

		#game-container.has-win .icon.is-winning::after {
			animation: winRingIn 680ms cubic-bezier(.16, .9, .22, 1) both, winRingPulse 1.45s ease-in-out 680ms infinite, winSheen 1.15s ease-out 90ms both;
		}

		#game-container.has-win .icon.is-winning .symbol-media {
			animation: winIconPop 720ms cubic-bezier(.16, .9, .22, 1) both, winIconFloat 1.65s ease-in-out 720ms infinite;
			filter:
				drop-shadow(0 5px 9px rgba(73, 14, 78, 0.32))
				drop-shadow(0 0 20px rgba(255, 239, 92, 0.86));
		}

		#game-container.has-win .icon.is-winning .win-spark {
			animation: winSpark 620ms cubic-bezier(.16, .9, .22, 1) both;
		}

		#game-container.has-win .outer-col:nth-child(2) .icon.is-winning {
			animation-delay: 70ms;
		}

		#game-container.has-win .outer-col:nth-child(2) .icon.is-winning::before,
		#game-container.has-win .outer-col:nth-child(2) .icon.is-winning::after,
		#game-container.has-win .outer-col:nth-child(2) .icon.is-winning .symbol-media {
			animation-delay: 70ms, 830ms, 160ms;
		}

		#game-container.has-win .outer-col:nth-child(3) .icon.is-winning::before,
		#game-container.has-win .outer-col:nth-child(3) .icon.is-winning::after,
		#game-container.has-win .outer-col:nth-child(3) .icon.is-winning .symbol-media {
			animation-delay: 140ms, 900ms, 230ms;
		}

		#game-container.spinning .col {
			will-change: transform;
		}

		/* Vertical-only motion blur: a plain blur() would also smear symbols
		   sideways and soften the vertical column separators, so use the SVG
		   filter defined in the markup, which blurs along the travel axis only. */
		

		#game-container.spinning .icon.is-static-symbol:not(.is-winning)::before,
		#game-container.spinning .icon.is-static-symbol:not(.is-winning) .symbol-glint,
		#game-container.spinning .icon.is-static-symbol:not(.is-winning) .symbol-media--image {
			animation: none;
		}

		#game-container.spinning .icon.is-static-symbol:not(.is-winning)::before,
		#game-container.spinning .icon.is-static-symbol:not(.is-winning) .symbol-glint {
			display: none;
		}

		#game-container.spinning .icon .symbol-media {
			filter: none;
		}

		#game-container.spinning .icon.is-static-symbol:not(.is-winning)::before {
			transform: translate3d(0, 3%, 0) scaleX(1);
		}

		#game-container.spinning .icon.is-static-symbol:not(.is-winning) .symbol-media--image {
			transform: perspective(520px) translate3d(0, 0, 0) rotateY(0deg);
			will-change: auto;
		}

		/* Constrained devices (html.slots-perf-low, classified by app.js): drop
		   the per-frame filter and paint work. Idle glint/shadow/tilt loops
		   repaint masked, blend-moded layers at 60fps while the game sits
		   still; winning halo/ring/pop/float keep compositing after the reels
		   stop. The SVG reel motion blur is deliberately NOT dropped: it is a
		   short burst while everything else is paused, and an on-device A/B
		   (Redmi 10C, 2026-08-31) measured it at 58-61fps both with and
		   without — visually it also masks any dropped frames. */
		html.slots-perf-low .icon.is-static-symbol:not(.is-winning)::before,
		html.slots-perf-low .icon.is-static-symbol:not(.is-winning) .symbol-glint {
			display: none;
			animation: none;
		}

		/* Lite replaces the tilt/glint (masked, blend-moded repaints) with an
		   optional transform-only bob; col/row delay vars de-sync the grid so
		   the nine symbols breathe, not march. Targets every non-winning icon
		   (static, animated-webp, and the stills lite renders for video
		   symbols — e.g. Coin Strike's whole set is is-animated-symbol). */
		html.slots-perf-low .icon:not(.is-winning) .symbol-media--image {
			animation: var(--slots-symbol-lite-idle-name) 3.8s ease-in-out calc(var(--symbol-lite-col-delay, 0s) + var(--symbol-lite-row-delay, 0s)) infinite;
			will-change: auto;
		}

		/* The full-tier spinning rule above only silences static symbols;
		   this freezes the lite bob on every icon while the reels run. */
		html.slots-perf-low #game-container.spinning .icon:not(.is-winning) .symbol-media--image {
			animation: none;
		}

		/* Cycle-based phases (not plain nth-child(2)/(3)): the resting reel
		   holds ~7 recycler icons per column and any contiguous 3 can be the
		   visible window, so every row needs a phase, not just the first
		   three. */
		html.slots-perf-low .icon:nth-child(even) {
			--symbol-lite-row-delay: -0.7s;
		}

		html.slots-perf-low .icon:nth-child(3n) {
			--symbol-lite-row-delay: -2s;
		}

		html.slots-perf-low #game-container.has-win .icon.is-winning::before,
		html.slots-perf-low #game-container.has-win .icon.is-winning::after,
		html.slots-perf-low #game-container.has-win .icon.is-winning .win-spark {
			display: none;
			animation: none;
		}

		/* Low tier still marks the win: a one-shot transform-only pop cascading
		   across the columns replaces the halo/spark/filter celebration. The
		   `both` fill holds scale(1) afterwards, which keeps neutralizing the
		   full-tier static transform the old `transform: none` cancelled. */
		html.slots-perf-low #game-container.has-win .icon.is-winning .symbol-media {
			animation: winPopLite 560ms cubic-bezier(.2, 1.1, .3, 1) both;
			filter: none;
		}

		html.slots-perf-low #game-container.has-win .outer-col:nth-child(2) .icon.is-winning .symbol-media {
			animation-delay: 120ms;
		}

		html.slots-perf-low #game-container.has-win .outer-col:nth-child(3) .icon.is-winning .symbol-media {
			animation-delay: 240ms;
		}

		/* Decor floats, backdrop spin, and CTA pulses are composited transforms
		   cheap enough for low-tier devices — what isn't cheap is the
		   drop-shadow filter each element carries, which re-rasterizes every
		   animated frame. Keep the motion (it is all the life these devices
		   get once videos are swapped for stills) and drop the filters. */
		html.slots-perf-low .train-art,
		html.slots-perf-low .side-decor,
		html.slots-perf-low .side-decor-left,
		html.slots-perf-low .win-side-decor,
		html.slots-perf-low .win-side-decor-left,
		html.slots-perf-low .machine-backdrop,
		html.slots-perf-low .brand-logo,
		html.slots-perf-low .win-title-art,
		html.slots-perf-low .win-claim-btn {
			filter: none;
		}

		/* Ultra-lite (html.slots-perf-ultra, probe-measured on top of lite —
		   Mali-G57 MP1 class hardware): the three full-screen ember blends
		   and the nine de-synced symbol bobs are the costliest surfaces left
		   on lite. CTA pulse/glow and the win package stay — they carry the
		   conversion. Media is identical to lite, so the class can arrive a
		   beat later than slots-perf-low without any asset flip. */
		html.slots-perf-ultra .ember-field {
			display: none;
		}

		html.slots-perf-ultra .icon:not(.is-winning) .symbol-media--image {
			animation: none;
		}

		/* While the win overlay covers the game screen its videos are already
		   released (releaseGameScreenMedia). Pausing the covered animations was
		   not enough: the screen's layers still composited under the overlay
		   every frame, and on the moto e13 that showed as periodic dropped
		   frames during the win phase (p95 33ms on Coin Volcano 2 and Joker's
		   Jewels at ultra). Taking the container out of rendering entirely
		   holds a flat 60Hz there (p95 17ms, avg +3-5 fps). The class lands
		   700ms after the overlay starts fading in, so the switch is never
		   visible, and the preview's reset path removes it before rebuilding
		   the reels. */
		#game-container.is-covered {
			display: none;
		}

		.spin-btn {
			position: absolute;
			z-index: 8;
			top: var(--slots-button-top);
			left: 50%;
			width: var(--slots-button-width);
			aspect-ratio: var(--slots-button-aspect-ratio);
			padding: 0;
			border: 0;
			background: transparent;
			color: #fff5ba;
			cursor: pointer;
			touch-action: manipulation;
			-webkit-tap-highlight-color: transparent;
			transform: translateX(-50%);
			transition: transform 140ms ease, filter 180ms ease;
			/* The CTA pulse waits out buttonEnter (ends at 800ms); listed second
			   so its transform wins once its delay elapses. */
			animation:
				buttonEnter 640ms cubic-bezier(.18, .82, .18, 1) 160ms both,
				var(--slots-button-pulse-name) 1.6s ease-in-out 900ms infinite;
		}

		.spin-btn img,
		.spin-btn video,
		.win-claim-btn img,
		.win-claim-btn video {
			position: absolute;
			inset: 0;
			width: 100%;
			height: 100%;
			object-fit: contain;
			pointer-events: none;
			user-select: none;
		}

		/* The always-on CTA halo: a static drop-shadow of the art's own alpha,
		   so it hugs sun rays and straight bars alike and fades gaussian to
		   zero — the masked ::before layer above it can only bloom as far as
		   its stretched silhouette and cuts hard at that edge. It lives on a
		   TWIN <img> of the button art placed outside the button, because a
		   filtered layer under ctaPulse's animated scale re-rasterizes every
		   frame — measured 10.7fps on the moto e13 (Adreno-class GPU) versus
		   57fps with the pulse detached from the filter. The twin's filter
		   value never changes, so it rasterizes once; earlier-DOM + equal
		   z-index keeps it painted under its button, whose opaque art hides
		   the duplicate (ctaPulse only scales UP from 1). Images only —
		   app.js leaves the twin transparent for <video> button art. */
		.spin-btn-halo,
		.win-claim-halo {
			position: absolute;
			left: 50%;
			transform: translateX(-50%);
			object-fit: contain;
			pointer-events: none;
			user-select: none;
		}

		.spin-btn-halo {
			z-index: 8;
			top: var(--slots-button-top);
			width: var(--slots-button-width);
			aspect-ratio: var(--slots-button-aspect-ratio);
			filter: var(--slots-button-halo-filter);
			/* Opacity-only entrance: buttonEnter's transform would re-raster
			   the filtered layer for its whole run. */
			animation: fadeIn 640ms cubic-bezier(.18, .82, .18, 1) 160ms both;
		}

		.win-claim-halo {
			z-index: var(--slots-win-claim-z-index);
			top: var(--slots-win-claim-top);
			width: var(--slots-win-claim-width);
			aspect-ratio: var(--slots-win-claim-aspect-ratio);
			filter: var(--slots-win-claim-halo-filter);
			animation: fadeIn 420ms cubic-bezier(.18, .82, .18, 1) 220ms both;
		}

		.spin-btn-label,
		.win-claim-label {
			position: absolute;
			inset: 0 8%;
			z-index: 1;
			display: flex;
			align-items: center;
			justify-content: center;
			text-align: center;
			font-size: var(--slots-spin-button-font-size);
			font-family: var(--slots-spin-button-font-family);
			line-height: 0.96;
			letter-spacing: 0;
			text-transform: uppercase;
			text-shadow:
				0 2px 0 #5a1f00,
				0 5px 12px rgba(55, 0, 71, 0.65);
			-webkit-text-stroke: 1px rgba(92, 34, 0, 0.8);
		}

		.spin-btn:hover:not(:disabled) {
			filter: brightness(1.08) saturate(1.08);
			transform: translateX(-50%) translateY(-2px);
		}

		.spin-btn:active:not(:disabled) {
			transform: translateX(-50%) translateY(2px) scale(0.99);
		}

		.spin-btn:disabled {
			cursor: wait;
			filter: saturate(0.85) brightness(0.82);
			/* A dimmed, non-clickable button must not keep begging for taps. */
			--slots-button-pulse-name: none;
		}

		/* ...and must not keep glowing either (the halo twin sits before the
		   button in the DOM, so reach it via :has; browsers without :has
		   merely keep the glow through the spin). */
		#game-container:has(.spin-btn:disabled) .spin-btn-halo {
			/* fadeIn's fill would out-cascade the plain opacity. */
			animation: none;
			opacity: 0;
		}

		.spin-btn.glow {
			animation: claimGlow 1.45s ease-in-out infinite;
		}

		/* Opt-in breathing halo behind the spin CTA — same compositor-only
		   opacity pulse as the claim button's glow, sharing ctaPulse's
		   1.6s/900ms timing so the halo swells with the scale pulse.
		   The halo is cut to the button art's own silhouette (see the
		   @supports block below): an ellipse inscribed in the button box only
		   fits a round plate — on a wide pill like Joker's Jewels it bulges
		   past the plate mid-edge and pinches at the corners, reading as the
		   whole container glowing, and on a plate with transparent margins
		   (coin-strike and mixed carry ~15% at the sides) it blooms over empty
		   art. Masking costs nothing per frame: the layer rasterizes once and
		   claimGlowPulse only fades that texture on the compositor — unlike
		   .symbol-glint, which repaints INSIDE its mask every frame and is
		   what the low tier drops. Without mask support there is no halo at
		   all; the unmasked rectangle would be worse than none — with
		   coin-strike's -40% -16% inset it spans the whole stage width.
		   mask-size stretches the silhouette over the inflated box rather than
		   dilating it, so the spread is non-uniform where the inset is; the
		   gradient fades it out well before the edges, and the per-theme inset
		   already encodes how far each halo should bloom. */
		.spin-btn::before {
			content: "";
			position: absolute;
			/* Themes with trimmed (glow-free) button art widen this halo. */
			inset: var(--slots-button-glow-inset);
			z-index: -1;
			background: radial-gradient(ellipse 50% 50% at center, var(--slots-button-glow-color) 0%, transparent 72%);
			opacity: 0.35;
			pointer-events: none;
			display: none;
			animation: claimGlowPulse 1.6s ease-in-out 900ms infinite;
		}

		/* Unprefixed mask-image only landed in Chrome 120, so the prefixed
		   property has to be tested (and set) alongside it for the Android 10
		   fleet. A gradient, not a url(), keeps the test about mask support
		   rather than URL parsing. */
		@supports (-webkit-mask-image: linear-gradient(#000, #000)) or (mask-image: linear-gradient(#000, #000)) {
			.spin-btn::before {
				display: var(--slots-button-glow-display);
				-webkit-mask-image: var(--slots-button-silhouette-mask-image);
				mask-image: var(--slots-button-silhouette-mask-image);
				-webkit-mask-size: 100% 100%;
				mask-size: 100% 100%;
				-webkit-mask-repeat: no-repeat;
				mask-repeat: no-repeat;
				-webkit-mask-position: center;
				mask-position: center;
			}

			.win-claim-btn::before {
				display: block;
				-webkit-mask-image: var(--slots-win-claim-silhouette-mask-image);
				mask-image: var(--slots-win-claim-silhouette-mask-image);
				-webkit-mask-size: 100% 100%;
				mask-size: 100% 100%;
				-webkit-mask-repeat: no-repeat;
				mask-repeat: no-repeat;
				-webkit-mask-position: center;
				mask-position: center;
			}

		}

		.spin-btn:disabled::before {
			opacity: 0;
			animation: none;
		}

		.train-art {
			position: absolute;
			z-index: var(--slots-foreground-z-index);
			top: var(--slots-foreground-top);
			left: 50%;
			width: var(--slots-foreground-width);
			display: var(--slots-foreground-display);
			transform: translateX(-50%);
			pointer-events: none;
			filter: drop-shadow(0 20px 36px rgba(62, 20, 0, 0.42));
			animation: trainFloat 5.4s ease-in-out infinite;
			object-fit: contain;
			height: auto;
		}

		.bottom-gold {
			position: absolute;
			z-index: var(--slots-bottom-z-index);
			top: var(--slots-bottom-top);
			left: 50%;
			bottom: var(--slots-bottom-bottom);
			width: var(--slots-bottom-width);
			display: var(--slots-bottom-display);
			transform: translateX(-50%);
			pointer-events: none;
			object-fit: contain;
			height: auto;
		}

		.side-decor,
		.side-decor-left {
			position: absolute;
			z-index: var(--slots-side-decor-z-index);
			top: var(--slots-side-decor-top);
			width: var(--slots-side-decor-width);
			pointer-events: none;
			filter: drop-shadow(0 20px 36px rgba(62, 20, 0, 0.46));
			animation: var(--slots-side-decor-float-name) 5.4s ease-in-out infinite;
			object-fit: contain;
			height: auto;
		}

		.side-decor {
			top: var(--slots-side-decor-right-top);
			right: var(--slots-side-decor-right);
			display: var(--slots-side-decor-display);
			/* Mirror lives on whichever side the design flips; Mix 2 flips the
			   right-hand warrior, Mixed flips the left. */
			scale: var(--slots-side-decor-scale);
		}

		.side-decor-left {
			top: var(--slots-side-decor-left-top);
			left: var(--slots-side-decor-left);
			display: var(--slots-side-decor-left-display);
			/* Standalone scale so a shared decor video can mirror without
			   fighting the sideDecorFloat transform animation. */
			scale: var(--slots-side-decor-left-scale);
			/* Themes can desync the left decor's float phase so paired props
			   (e.g. two lava bowls) don't bob in mechanical lockstep. */
			animation-delay: var(--slots-side-decor-left-float-delay);
		}

		/* Ambient ember field: gradient dots drifting up on compositor-only
		   transforms. No images, no filters — this is the layer that keeps the
		   low-tier still-image background alive. The dots are painted INTO
		   three parallax layers (multiple radial-gradient backgrounds per
		   layer) instead of one element per dot: on budget GPUs (Adreno 610)
		   the compositor holds ~12 concurrent animated layers before falling
		   off a cliff, so the ember budget is 3 layers, not 8 elements.
		   Sits above the background (z 2) but below the ring/board/decor. */
		.ember-field {
			position: absolute;
			/* Only the top half: the field sits behind the machine (z 2), so
			   mid-screen dots are hidden anyway, and every ember layer costs a
			   full blend of its area per frame on fill-rate-bound GPUs —
			   halving the field bought Coin Volcano ~12fps on the Redmi. */
			inset: 0 0 46%;
			z-index: 2;
			display: var(--slots-ember-display);
			overflow: hidden;
			pointer-events: none;
			contain: layout style;
		}

		/* Each layer is 2× stage height with its dot pattern tiled twice
		   (background-size 100% 50% + repeat-y); rising by -50% of its own
		   height is exactly one tile, so the loop wraps seamlessly. */
		.ember-layer {
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			height: 200%;
			background-repeat: repeat-y;
			background-size: 100% 50%;
			animation: emberLayerRise var(--ember-duration, 16s) linear infinite;
		}

		.ember-layer:nth-child(1) {
			--ember-duration: 23s;
			opacity: 0.65;
			background-image:
				radial-gradient(circle 2px at 11% 7%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 3px at 39% 31%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 2px at 55% 44%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 2px at 67% 15%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 3px at 76% 36%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 3px at 88% 43%, var(--slots-ember-color), transparent 70%);
		}

		.ember-layer:nth-child(2) {
			--ember-duration: 15s;
			opacity: 0.85;
			background-image:
				radial-gradient(circle 3px at 21% 21%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 4px at 33% 41%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 4px at 52% 45%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 3px at 63% 5%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 3px at 79% 9%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 2px at 94% 33%, var(--slots-ember-color), transparent 70%);
		}

		.ember-layer:nth-child(3) {
			--ember-duration: 9.5s;
			opacity: 1;
			background-image:
				radial-gradient(circle 2px at 7% 37%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 4px at 27% 19%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 3px at 45% 11%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 2px at 60% 41%, var(--slots-ember-color), transparent 70%),
				radial-gradient(circle 5px at 85% 24%, var(--slots-ember-color), transparent 70%);
		}

		.confetti-container {
			position: fixed;
			inset: 0;
			z-index: 120;
			overflow: hidden;
			pointer-events: none;
			contain: layout paint style;
		}

		.confetti {
			position: absolute;
			top: 0;
			left: 0;
			opacity: 0;
			transform: translate3d(var(--confetti-x, 50vw), var(--confetti-launch-y, 76dvh), 0) rotate(0deg) scale(0.85);
			transform-origin: center;
			backface-visibility: hidden;
			will-change: transform, opacity;
			animation: confettiFall forwards;
		}

		.congrats-overlay {
			position: fixed;
			inset: 0;
			z-index: 110;
			display: none;
			overflow: hidden;
			background-color: rgba(34, 0, 66, 0.2);
			background-image: var(--slots-win-overlay-background-image);
			background-position: center;
			background-size: cover;
			background-repeat: no-repeat;
			/* No backdrop-filter here: a full-screen backdrop blur forces a
			   per-frame readback on mobile GPUs for a barely visible 1px blur. */
		}

		.congrats-overlay.visible {
			display: block;
			animation: fadeIn 280ms ease-out;
		}

		.win-stage {
			position: absolute;
			z-index: 1;
			top: 0;
			left: 50%;
			width: calc(100dvh * var(--canvas-ratio));
			height: 100dvh;
			min-width: 375px;
			transform: translateX(-50%);
			background-image: var(--slots-win-stage-background-image);
			background-position: center;
			background-size: cover;
			background-repeat: no-repeat;
		}

		.win-title-art {
			padding: 0 8px;
			position: absolute;
			z-index: 4;
			top: var(--slots-win-title-art-top);
			left: 50%;
			width: var(--slots-win-title-art-width);
			display: var(--slots-win-title-art-display);
			transform: translateX(-50%);
			pointer-events: none;
			filter: drop-shadow(0 18px 28px rgba(62, 20, 0, 0.42));
			/* Entrance is theme-selectable (winDrop default, winTitleStamp for
			   the punch-in look); the opt-in idle rock takes over after it. */
			animation:
				var(--slots-win-title-enter-name) var(--slots-win-title-enter-duration) cubic-bezier(.22, .88, .22, 1) both,
				var(--slots-win-title-idle-name) 6.4s ease-in-out 1.2s infinite;
		}

		.win-train {
			position: absolute;
			z-index: var(--slots-win-hero-z-index);
			top: var(--slots-win-hero-top);
			left: 50%;
			width: var(--slots-win-hero-width);
			display: var(--slots-win-hero-display);
			transform: translateX(-50%);
			pointer-events: none;
			filter: drop-shadow(0 22px 34px rgba(62, 20, 0, 0.38));
			animation: winDrop 500ms cubic-bezier(.22, .88, .22, 1) both;
		}

		.win-side-decor,
		.win-side-decor-left,
		.win-bottom-decor,
		.win-bottom-decor-left {
			position: absolute;
			pointer-events: none;
			filter: drop-shadow(0 18px 30px rgba(62, 20, 0, 0.42));
		}

		.win-side-decor,
		.win-side-decor-left {
			z-index: var(--slots-win-side-decor-z-index);
			top: var(--slots-win-side-decor-top);
			width: var(--slots-win-side-decor-width);
			display: var(--slots-win-side-decor-display);
			animation: sideDecorFloat 5.4s ease-in-out infinite;
		}

		.win-side-decor-left {
			left: var(--slots-win-side-decor-left);
			/* Standalone scale so a shared decor asset can mirror without
			   fighting the sideDecorFloat transform animation. */
			scale: var(--slots-win-side-decor-left-scale);
		}

		.win-side-decor {
			right: var(--slots-win-side-decor-right);
			scale: var(--slots-win-side-decor-scale);
		}

		.win-bottom-decor,
		.win-bottom-decor-left {
			z-index: var(--slots-win-bottom-decor-z-index);
			top: var(--slots-win-bottom-decor-top);
			width: var(--slots-win-bottom-decor-width);
			display: var(--slots-win-bottom-decor-display);
		}

		.win-bottom-decor-left {
			left: var(--slots-win-bottom-decor-left);
		}

		.win-bottom-decor {
			right: var(--slots-win-bottom-decor-right);
		}

		.win-panel {
			position: absolute;
			z-index: var(--slots-win-panel-z-index);
			top: var(--slots-win-panel-top);
			left: 50%;
			width: var(--slots-win-panel-width);
			aspect-ratio: var(--slots-win-panel-aspect-ratio);
			transform: translateX(-50%);
			background: var(--slots-win-panel-image) center / contain no-repeat;
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: center;
			padding: 8% 9%;
			text-align: center;
			animation: popIn 420ms cubic-bezier(.2, 1.2, .25, 1) 120ms both;
			isolation: isolate;
		}

		.win-panel::before {
			content: "";
			position: absolute;
			z-index: 0;
			top: var(--slots-win-panel-inner-top);
			left: var(--slots-win-panel-inner-left);
			width: var(--slots-win-panel-inner-width);
			height: var(--slots-win-panel-inner-height);
			display: var(--slots-win-panel-inner-display);
			border-radius: var(--slots-win-panel-inner-radius);
			background: var(--slots-win-panel-inner-background);
			pointer-events: none;
		}

		.win-title {
			position: relative;
			z-index: 1;
			display: var(--slots-win-title-display);
			margin: 0;
			max-width: 100%;
			font-size: var(--slots-win-title-font-size);
			font-family: var(--slots-win-title-font-family);
			line-height: 0.92;
			text-transform: uppercase;
			white-space: nowrap;
			color: #fff8bf;
			text-shadow:
				0 2px 0 #812300,
				0 7px 16px rgba(37, 0, 67, 0.65);
			-webkit-text-stroke: 1px rgba(105, 32, 0, 0.72);
		}

		.win-copy {
			position: relative;
			z-index: 1;
			display: var(--slots-win-copy-display);
			margin: 0.45em 0 0;
			max-width: 90%;
			font-family: var(--slots-win-text-font-family);
			font-size: var(--slots-win-text-font-size);
			line-height: 1.2;
			font-weight: 800;
			color: #fff;
			text-shadow: 0 3px 10px rgba(29, 0, 63, 0.78);
		}

		.win-claim-btn {
			position: absolute;
			z-index: var(--slots-win-claim-z-index);
			top: var(--slots-win-claim-top);
			left: 50%;
			width: var(--slots-win-claim-width);
			aspect-ratio: var(--slots-win-claim-aspect-ratio);
			padding: 0;
			border: 0;
			background: transparent;
			color: #fff5ba;
			cursor: pointer;
			transform: translateX(-50%);
			filter: drop-shadow(0 12px 24px rgba(61, 0, 81, 0.48));
			/* The opt-in scale pulse shares claimGlowPulse's 1.6s/700ms timing so
			   the button swells in phase with its glow layer. */
			animation:
				buttonEnter 420ms cubic-bezier(.18, .82, .18, 1) 220ms both,
				var(--slots-win-claim-pulse-name) 1.6s ease-in-out 700ms infinite;
		}

		/* The old claimGlow animated filter: brightness() + drop-shadow every
		   frame, re-rasterizing the button texture continuously. A glow layer
		   pulsed via opacity stays on the compositor instead. */
		/* Silhouette-masked like the spin halo above, and hidden outright
		   without mask support. */
		.win-claim-btn::before {
			content: "";
			position: absolute;
			/* Themes with trimmed (glow-free) button art widen this halo. */
			inset: var(--slots-win-claim-glow-inset);
			z-index: -1;
			background: radial-gradient(ellipse 50% 50% at center, rgba(255, 222, 35, 0.62) 0%, rgba(255, 222, 35, 0) 72%);
			opacity: 0.35;
			pointer-events: none;
			display: none;
			animation: claimGlowPulse 1.6s ease-in-out 700ms infinite;
		}

		.win-claim-label {
			font-size: var(--slots-claim-button-font-size);
			font-family: var(--slots-claim-button-font-family);
			/* Trimmed button art leaves the label no padding headroom — give
			   the claim copy a wider box than the shared 8% inset. */
			inset: 0 4%;
		}

		.win-logo {
			position: absolute;
			z-index: var(--slots-win-logo-z-index);
			top: var(--slots-win-logo-top);
			left: 50%;
			width: var(--slots-win-logo-width);
			display: var(--slots-win-logo-display);
			transform: translateX(-50%);
			pointer-events: none;
			filter: drop-shadow(0 16px 28px rgba(71, 0, 128, 0.52));
			animation: logoEnter 460ms cubic-bezier(.18, .82, .18, 1) 300ms both;
		}

		@media (min-aspect-ratio: 1/1) {
			html,
			body {
				background-image: var(--slots-body-background-image-desktop);
			}

			.win-stage {
				background-image: var(--slots-win-stage-background-image-desktop);
			}

			.congrats-overlay {
				background-image: var(--slots-win-overlay-background-image-desktop);
			}

			.win-bg-video {
				object-fit: var(--slots-win-bg-video-object-fit-desktop);
			}

			.game-stage,
			.win-stage {
				max-width: calc(100dvh * var(--canvas-ratio));
			}
		}

		@media (max-width: 420px) {
			.spin-btn-label {
				font-size: clamp(1rem, 5.1dvh, 2.6rem);
			}
		}

		@keyframes logoEnter {
			from { opacity: 0; transform: translate3d(-50%, -18px, 0) scale(0.94); }
			to { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
		}

		/* The right logo card anchors with translateX(50%); the shared
			logoEnter keyframes would bake in -50% (fill-mode: both). */
		@keyframes logoEnterRight {
			from { opacity: 0; transform: translate3d(50%, -18px, 0) scale(0.94); }
			to { opacity: 1; transform: translate3d(50%, 0, 0) scale(1); }
		}

		@keyframes machineEnter {
			from { opacity: 0; transform: translate3d(-50%, 18px, 0) scale(0.94); }
			to { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
		}

		@keyframes buttonEnter {
			from { opacity: 0; transform: translate3d(-50%, 18px, 0) scale(0.96); }
			to { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
		}

		@keyframes slotsReelLoop {
			from { transform: translate3d(0, 0, 0); }
			to { transform: translate3d(0, var(--slots-reel-loop-distance, -480px), 0); }
		}

		@keyframes trainFloat {
			0%, 100% { transform: translate3d(-50%, 0, 0); }
			50% { transform: translate3d(-50%, -1.1%, 0); }
		}

		@keyframes sideDecorFloat {
			0%, 100% { transform: translate3d(0, 0, 0); }
			50% { transform: translate3d(0, -1.1%, 0); }
		}

		/* Float variant with a gentle tilt: rises leaning one way, descends
		   leaning the other, so paired props rock like they sit on heat haze. */
		@keyframes sideDecorSway {
			0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
			25% { transform: translate3d(0, -0.7%, 0) rotate(1.7deg); }
			50% { transform: translate3d(0, -1.3%, 0) rotate(0deg); }
			75% { transform: translate3d(0, -0.7%, 0) rotate(-1.7deg); }
		}

		@keyframes emberLayerRise {
			from { transform: translate3d(0, 0, 0); }
			to { transform: translate3d(0, -50%, 0); }
		}

		/* Stamp entrance: winds up slowly from nothing, accelerates into a
		   modest 112% overshoot (per-keyframe ease-in below overrides the
		   shorthand easing) and settles smoothly — no rebound dip, which read
		   as too bouncy on phones. Transform and opacity only. */
		@keyframes winTitleStamp {
			0% {
				opacity: 0;
				transform: translate3d(-50%, 0, 0) scale(0);
				animation-timing-function: cubic-bezier(.7, 0, 1, .55);
			}
			35% { opacity: 1; }
			66% {
				opacity: 1;
				transform: translate3d(-50%, 0, 0) scale(1.12);
				animation-timing-function: cubic-bezier(.25, .6, .35, 1);
			}
			100% { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
		}

		/* Soft variant of logoIdleFloat for logos anchored near the stage top
		   (e.g. Coin Strike's recomposed coin logo at top 0%), where the full
		   -15% rise would leave the viewport. */
		@keyframes logoIdleFloatSoft {
			0%, 100% { transform: translate3d(-50%, 0, 0); }
			50% { transform: translate3d(-50%, -3.5%, 0); }
		}

		/* Logo side cards (translateX(∓50%) anchors baked in per side). */
		@keyframes logoCardFloat {
			0%, 100% { transform: translate3d(-50%, 0, 0); }
			50% { transform: translate3d(-50%, -4%, 0); }
		}

		@keyframes logoCardFloatRight {
			0%, 100% { transform: translate3d(50%, 0, 0); }
			50% { transform: translate3d(50%, -4%, 0); }
		}

		@keyframes winTitleSway {
			0%, 100% { transform: translate3d(-50%, 0, 0) rotate(0deg); }
			25% { transform: translate3d(-50%, -0.8%, 0) rotate(1.1deg); }
			75% { transform: translate3d(-50%, 0.4%, 0) rotate(-1.1deg); }
		}

		@keyframes colEnter {
			from { opacity: 0; transform: translate3d(0, 5%, 0); }
			to { opacity: 1; transform: translate3d(0, 0, 0); }
		}

		@keyframes symbolLiteBob {
			0%, 100% { transform: translate3d(0, 0, 0); }
			50% { transform: translate3d(0, -3.5%, 0); }
		}

		@keyframes winPopLite {
			0% { transform: scale(1); }
			40% { transform: scale(1.16); }
			100% { transform: scale(1); }
		}

		/* Idle animations below are transform-only on purpose: they must stay
		   composited on the low-tier devices they were built for. The baked-in
		   translate3d(-50%, …) mirrors each element's static centering. */
		@keyframes machineBackdropSpin {
			from { transform: translate3d(-50%, 0, 0) rotate(0deg); }
			to { transform: translate3d(-50%, 0, 0) rotate(360deg); }
		}

		@keyframes ctaPulse {
			0%, 100% { transform: translate3d(-50%, 0, 0) scale(1); }
			50% { transform: translate3d(-50%, 0, 0) scale(1.05); }
		}

		/* Rise tuned for Coin Volcano 2's geometry (logo top 5.21%, square
		   768px asset with ~6% transparent top padding): -15% of own height
		   lifts the visible art to ~3.4% from the stage top (-27% would touch
		   the edge but reads as too much motion). The 0% baseline is the
		   resting pose — the logo never drops below it. */
		@keyframes logoIdleFloat {
			0%, 100% { transform: translate3d(-50%, 0, 0); }
			50% { transform: translate3d(-50%, -15%, 0); }
		}

		@keyframes claimGlow {
			0%, 100% { filter: brightness(1) drop-shadow(0 0 18px rgba(255, 222, 35, 0.42)); }
			50% { filter: brightness(1.16) drop-shadow(0 0 34px rgba(255, 222, 35, 0.76)); }
		}

		@keyframes claimGlowPulse {
			0%, 100% { opacity: 0.3; }
			50% { opacity: 0.85; }
		}

		@keyframes staticSymbolTilt {
			0%, 100% {
				transform: perspective(520px) translate3d(0, 0, 0) rotateY(-24deg);
			}
			25% {
				transform: perspective(520px) translate3d(0, 0, 0) rotateY(0deg);
			}
			50% {
				transform: perspective(520px) translate3d(0, 0, 0) rotateY(24deg);
			}
			75% {
				transform: perspective(520px) translate3d(0, 0, 0) rotateY(0deg);
			}
		}

		@keyframes staticSymbolShadow {
			0%, 100% { transform: translate3d(-4%, 5%, 0) scaleX(0.82); }
			25%, 75% { transform: translate3d(0, 3%, 0) scaleX(1); }
			50% { transform: translate3d(4%, 5%, 0) scaleX(0.82); }
		}

		@keyframes staticSymbolLight {
			0%, 100% {
				--symbol-glint-x: 70%;
				--symbol-rim-x: 18%;
				background-position: 0 0, 130% 0, 0 0;
			}
			25%, 75% {
				--symbol-glint-x: 50%;
				--symbol-rim-x: 50%;
			}
			50% {
				--symbol-glint-x: 30%;
				--symbol-rim-x: 82%;
				background-position: 0 0, -95% 0, 0 0;
			}
		}

		@keyframes winHaloIn {
			0% { opacity: 0; transform: translate3d(0, 0, 0) scale(0.58); }
			62% { opacity: 0.95; transform: translate3d(0, 0, 0) scale(1.12); }
			100% { opacity: 0.72; transform: translate3d(0, 0, 0) scale(1); }
		}

		@keyframes winHaloPulse {
			0%, 100% { opacity: 0.58; transform: translate3d(0, 0, 0) scale(0.96); }
			50% { opacity: 0.88; transform: translate3d(0, 0, 0) scale(1.08); }
		}

		@keyframes winRingIn {
			0% { opacity: 0; transform: translate3d(0, 0, 0) scale(0.72) rotate(-18deg); }
			58% { opacity: 0.95; transform: translate3d(0, 0, 0) scale(1.15) rotate(8deg); }
			100% { opacity: 0.76; transform: translate3d(0, 0, 0) scale(1) rotate(0deg); }
		}

		@keyframes winRingPulse {
			0%, 100% { opacity: 0.56; transform: translate3d(0, 0, 0) scale(0.98); }
			50% { opacity: 0.9; transform: translate3d(0, 0, 0) scale(1.08); }
		}

		@keyframes winSheen {
			from { background-position: 140% 0; }
			to { background-position: -120% 0; }
		}

		@keyframes winLineSweep {
			0% { opacity: 0; transform: translate3d(0, 0, 0) scaleX(0.74); filter: blur(2px); }
			34% { opacity: 0.92; transform: translate3d(0, 0, 0) scaleX(1.04); filter: blur(0); }
			100% { opacity: 0; transform: translate3d(0, 0, 0) scaleX(1.14); filter: blur(4px); }
		}

		@keyframes winIconPop {
			0% { transform: translate3d(0, 0, 0) scale(0.88) rotate(-4deg); }
			52% { transform: translate3d(0, 0, 0) scale(1.2) rotate(3deg); }
			76% { transform: translate3d(0, 0, 0) scale(0.98) rotate(-1deg); }
			100% { transform: translate3d(0, 0, 0) scale(1.08) rotate(0deg); }
		}

		@keyframes winIconFloat {
			0%, 100% { transform: translate3d(0, 0, 0) scale(1.08); }
			50% { transform: translate3d(0, -4px, 0) scale(1.12); }
		}

		@keyframes winSpark {
			0% {
				opacity: 0;
				transform: translate3d(-50%, -50%, 0) scale(0.5);
			}
			24% {
				opacity: 1;
			}
			100% {
				opacity: 0;
				transform: translate3d(calc(-50% + var(--spark-x)), calc(-50% + var(--spark-y)), 0) scale(0.2);
			}
		}

		@keyframes winDrop {
			from { opacity: 0; transform: translate3d(-50%, -4%, 0) scale(1.02); }
			to { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
		}

		@keyframes confettiFall {
			0% {
				opacity: 0;
				transform: translate3d(var(--confetti-x, 50vw), var(--confetti-launch-y, 76dvh), 0) rotate(0deg) scale(0.85);
				animation-timing-function: cubic-bezier(.18, .82, .2, 1);
			}
			8% {
				opacity: 1;
			}
			28% {
				opacity: 1;
				transform: translate3d(calc(var(--confetti-x, 50vw) + var(--confetti-burst-x, 0px)), var(--confetti-peak-y, 18dvh), 0) rotate(var(--confetti-peak-rotation, 260deg)) scale(1);
				animation-timing-function: cubic-bezier(.22, 0, .84, .98);
			}
			100% {
				opacity: 0;
				transform: translate3d(calc(var(--confetti-x, 50vw) + var(--confetti-drift, 0px)), 110dvh, 0) rotate(var(--confetti-rotation, 720deg)) scale(0.92);
			}
		}

		@media (prefers-reduced-motion: reduce) {
			.confetti {
				display: none;
				animation: none;
			}

			/* Decorative idle loops stop for reduced-motion users (the one-shot
			   winPopLite stays — it is state feedback, not decoration). */
			.ember-field {
				display: none;
			}

			img.machine-backdrop,
			.machine-backdrop,
			.brand-logo,
			.logo-card-left,
			.logo-card-right,
			.train-art,
			.side-decor,
			.side-decor-left,
			.win-side-decor,
			.win-side-decor-left,
			.win-title-art,
			.spin-btn,
			.spin-btn::before,
			.win-claim-btn,
			.win-claim-btn::before,
			html.slots-perf-low .icon:not(.is-winning) .symbol-media--image {
				animation: none;
			}
		}

		@keyframes fadeIn {
			from { opacity: 0; }
			to { opacity: 1; }
		}

		@keyframes popIn {
			from { opacity: 0; transform: translateX(-50%) scale(0.84); }
			to { opacity: 1; transform: translateX(-50%) scale(1); }
		}
	