/*
	Lucky Sports - Scratch & Win

	The sports-themed sister of the Lucky Fruits scratch cards page
	(public/pages/scratchCards/). The mechanic, the timing and the geometry are that page's,
	unchanged; what differs is the artwork, the symbol set and the copy. The two share the
	scratchSurface and confetti components rather than each carrying a copy.

	Layout follows the Challenges page (public/pages/challenges/challenges.css): a flex grid of
	cards plus an .open state that promotes the tapped card to position:fixed and centres it over
	a dimmed overlay.

	Almost everything you see is artwork. The card face is one poster image per prize, the scratch
	panel's background is a 3x3 sprite of the supplied holographic texture, the sports symbols are
	a 3x2 sprite, and the foil is a photograph. What the stylesheet still owns is the geometry that
	drops the panel into the hole the poster leaves, and the light: the halo around a card and the
	glow on a winning line, driven by three custom properties set on the theme class.

	ARTWORK IS PLACEHOLDER. Every file under images/ is the Lucky Fruits export renamed into the
	sports slot it stands in for, so the page runs end to end while the sports artwork is drawn.
	The geometry here is measured off the Lucky Fruits composition; when a sports export lands,
	drop it in under the same filename, bump that file's own ?v= counter, and re-measure only the
	pieces whose silhouette changed. See images/README.md for the slot list.
*/

#luckySportsPage
{
	box-sizing:border-box;
	display:block;
	isolation:isolate;
	padding:0px 40px 40px 40px;
	position:relative;
	width:100%;
}

/*
	Page backdrop, same idea as the Maldives page (public/pages/winATrip/winATrip.css) - a
	.divPageBackground element behind the content with a desktop image and a portrait mobile
	swap at 800px.

	It differs from winATrip in two respects.

	1. The artwork is a frame (neon rails down both sides, a perspective floor along the bottom,
	   dark navy centre) rather than a scene, so it has to be seen whole or it reads as noise.
	   winATrip pins an absolute box to a fixed pixel height, which works because its art is a
	   header scene. Here the page length varies a lot - two card columns on mobile makes it
	   several viewports tall - so the backdrop is fixed to the viewport and the content scrolls
	   over it, keeping the rails at the screen edges and the cards in the dark centre.
	2. It sits at z-index:-1 with isolation:isolate on the page, rather than lifting every content
	   block above it. Lifting the content would make #divLuckySportsCards a stacking context and trap
	   the opened card (z-index:20) beneath #divLuckySportsCardOverlay (z-index:19). Isolating the page
	   pins the backdrop below all content while leaving those two comparable. isolation does not
	   create a containing block for position:fixed, so the open card and the overlay still pin to
	   the viewport.

	Because it is fixed it anchors to the viewport, not to #divAppBody, so it does not inherit the
	320px the open side menu pushes the content across (app.css:84 - #divAppBody is left:320px,
	left:0 under .hideMenu, transitioning over 0.25s). Left to itself the backdrop would run under
	the menu and its centre would drift off the logo every time the menu opened. So the offset is
	mirrored here, transition included, to keep the art centred on the content at every state.
	Above 1200px only: app.css:344 drops #divAppBody back to left:0 below that, where the menu
	overlays the page instead of pushing it.
*/
#luckySportsPage .divPageBackground
{
	background-color:#020226;
	background-image:url('images/background.webp');
	background-position:center center;
	background-repeat:no-repeat;
	background-size:cover;
	bottom:0px;
	left:320px;
	pointer-events:none;
	position:fixed;
	right:0px;
	top:0px;
	transition:left 0.25s;
	z-index:-1;
}

.hideMenu #luckySportsPage .divPageBackground
{
	left:0px;
}

/* ---------------------------------------------------------------- header */

#luckySportsPage #divLuckySportsHeader
{
	padding:10px 0px 26px 0px;
	text-align:center;
}

/*
	The wordmark is artwork, not type - the artwork, the bevels and the glow cannot be reproduced in
	CSS - so the h1 is image-replaced: the art is a stack of images and the heading text stays in
	the DOM for screen readers, search engines and the case where the images fail to load.

	It is a scene rather than one picture. The supplied artwork was cut into its parts, and each
	part is an absolutely positioned child of the h1, placed in percentages of it. That is what
	lets the balls drift and the lettering breathe, and it is also what makes the whole thing
	responsive for free: every position and every size below is a percentage, so the scene is the
	same drawing at 560px and at 250px, and the pieces keep their relationships to each other at
	any width without a single breakpoint.

	Sized by aspect-ratio against the scene's own bounding box - 1992x1028 in the artwork's
	coordinates, which is every piece of it, the play logo aside - so the header reserves its space
	before the images arrive and the grid below never jumps.
*/
#luckySportsPage #divLuckySportsHeader h1
{
	aspect-ratio:1992 / 1028;
	margin:0px auto;
	max-width:560px;
	position:relative;
	width:100%;
}

#luckySportsPage #divLuckySportsHeader h1 span
{
	clip-path:inset(50%);
	height:1px;
	overflow:hidden;
	position:absolute;
	white-space:nowrap;
	width:1px;
}

/*
	The handle for the Lucky Sports logo and the artwork around it - everything in the header except
	"SCRATCH & WIN", which arrives separately and so cannot be inside this.

	It is the h1's own box repeated rather than a box of its own, so the percentage positions every
	piece inside it was measured against the h1 still resolve to the same place. Nothing is drawn
	here; it exists only to be transformed.
*/
#luckySportsPage .iHeaderScene
{
	bottom:0px;
	display:block;
	left:0px;
	position:absolute;
	right:0px;
	top:0px;
	transform-origin:50% 50%;
}

/*
	One piece of the scene.

	background-size is 100% 100% rather than contain because each image was exported trimmed to its
	own artwork at exactly the ratio of the box it is given below - there is no letterboxing to
	avoid, and stretching to the box is what keeps a piece registered to the position it was
	measured at.

	transform-origin is the centre so a piece rotates about itself rather than swinging from its
	top-left corner, and will-change keeps the drift on the compositor: twenty-one elements all
	animating transform is cheap there and expensive anywhere else.

	The "?v=" on each image is not decoration and is not the app version - it is a hand-turned
	counter, the same trick home.css uses on its promotion banner. config/app.php's version only
	reaches assets the SPA links itself; an image named inside a stylesheet is fetched at a bare
	URL. nginx sends no Cache-Control on images, so the browser caches them heuristically off
	Last-Modified, and the service worker's own fetch() reads through that same HTTP cache - which
	is why a hard refresh does not shift a replaced image. Bump the counter on any piece you
	re-export.
*/
#luckySportsPage .iHeaderPiece
{
	background-position:center center;
	background-repeat:no-repeat;
	background-size:100% 100%;
	display:block;
	position:absolute;
	transform-origin:50% 50%;
	will-change:transform;
}

#luckySportsPage .iHeaderPiece.wordmark
{
	background-image:url('images/header/wordmark.webp?v=1');
	height:54.183%;
	left:19.578%;
	top:16.148%;
	width:66.064%;
}

#luckySportsPage .iHeaderPiece.subtext
{
	background-image:url('images/header/subtext.webp?v=1');
	height:25.389%;
	left:7.371%;
	top:74.611%;
	width:92.369%;
}

#luckySportsPage .iHeaderPiece.soccerBall
{
	background-image:url('images/header/soccerBall.webp?v=1');
	height:27.918%;
	left:2.010%;
	top:14.650%;
	width:12.349%;
}

#luckySportsPage .iHeaderPiece.rugbyBall
{
	background-image:url('images/header/rugbyBall.webp?v=1');
	height:21.887%;
	left:13.300%;
	top:17.980%;
	width:11.596%;
}

#luckySportsPage .iHeaderPiece.glovesLeft
{
	background-image:url('images/header/glovesLeft.webp?v=1');
	height:37.354%;
	left:-2.500%;
	top:35.960%;
	width:16.968%;
}

#luckySportsPage .iHeaderPiece.glovesRight
{
	background-image:url('images/header/glovesRight.webp?v=1');
	height:33.852%;
	left:80.550%;
	top:0.000%;
	width:12.450%;
}

/*
	The slot the Lucky Fruits artwork carried a strawberry in. The Lucky Sports composition puts a
	cricket ball here, in the left-hand cluster, at the size and position the strawberry held -
	the geometry below is measured off the flat artwork and stays as it is when the sports export
	replaces the placeholder file.
*/
#luckySportsPage .iHeaderPiece.cricketBall
{
	background-image:url('images/header/cricketBall.webp?v=1');
	height:13.911%;
	left:12.450%;
	top:40.000%;
	width:6.526%;
}

#luckySportsPage .iHeaderPiece.whistle
{
	background-image:url('images/header/whistle.webp?v=1');
	height:25.097%;
	left:85.380%;
	top:35.490%;
	width:12.952%;
}

#luckySportsPage .iHeaderPiece.tennisBall
{
	background-image:url('images/header/tennisBall.webp?v=1');
	height:25.195%;
	left:88.600%;
	top:47.940%;
	width:13.755%;
}

#luckySportsPage .iHeaderPiece.star
{
	background-image:url('images/header/star.webp?v=1');
	height:13.911%;
	left:18.860%;
	top:0.900%;
	width:6.727%;
}

#luckySportsPage .iHeaderPiece.coinRight
{
	background-image:url('images/header/coinRight.webp?v=1');
	height:12.354%;
	left:94.500%;
	top:13.000%;
	width:7.380%;
}

#luckySportsPage .iHeaderPiece.coinLeft
{
	background-image:url('images/header/coinLeft.webp?v=1');
	height:17.802%;
	left:8.580%;
	top:56.510%;
	width:8.685%;
}

/*
	The seven small gold flecks are one 3x3 sheet rather than seven files, because seven requests
	for seven pictures of a gold chip is seven requests too many. The last two cells of the sheet
	are empty - the grid is square so that the background-position figures stay round.

	Every fleck is the same square box here, which is why there is no per-fleck size rule: each one
	was composed centred in a cell sized to the largest of them, so a small fleck simply has more
	empty cell around it and still paints at its own size.
*/
#luckySportsPage .iHeaderPiece.fleck
{
	background-image:url('images/header/flecks.webp?v=1');
	background-size:300% 300%;
	height:7.782%;
	width:4.016%;
}

#luckySportsPage .iHeaderPiece.fleck1 { background-position:0% 0%; left:28.170%; top:0.850%; }
#luckySportsPage .iHeaderPiece.fleck2 { background-position:50% 0%; left:16.240%; top:9.970%; }
#luckySportsPage .iHeaderPiece.fleck3 { background-position:100% 0%; left:7.290%; top:41.520%; }
#luckySportsPage .iHeaderPiece.fleck4 { background-position:0% 50%; left:91.120%; top:24.350%; }
#luckySportsPage .iHeaderPiece.fleck5 { background-position:50% 50%; left:81.730%; top:32.650%; }
#luckySportsPage .iHeaderPiece.fleck6 { background-position:100% 50%; left:96.500%; top:30.750%; }
#luckySportsPage .iHeaderPiece.fleck7 { background-position:0% 100%; left:86.980%; top:66.010%; }

/*
	The scene in motion: the balls hang in mid-air and the lettering breathes.

	Three drift paths, not one. A single path shared by every piece reads as the whole header
	sliding about; three paths that visit different corners read as pieces that happen to be
	floating near each other. Each one has three waypoints rather than two so a piece does not
	retrace its own line - it comes back by a different route, which is what stops the motion
	looking like a bounce.

	The desynchronisation is in the durations and the delays, and it is deliberate that no two
	durations share a factor: 3.8, 4.3, 4.6, 4.7, 4.9, 5.1, 5.3, 5.6, 5.7, 5.9 and 6.3 seconds will
	not line up again inside any sitting. The delays are negative so every piece is already somewhere
	in the middle of its path on the first frame - a header that starts with everything at rest and
	drifts apart over the first few seconds announces that it is an animation.

	Travel is in percentages of each piece's own box, so a big ball moves further than a small one
	in pixels while moving the same amount relative to itself - the reading the eye expects of
	objects of different mass adrift together - and all of it scales with the header.
*/
@keyframes headerDriftA
{
	0% { transform:translate(0%, 0%) rotate(0deg); }
	35% { transform:translate(3.6%, -8.5%) rotate(3.2deg); }
	70% { transform:translate(-2.2%, -3.6%) rotate(-2deg); }
	100% { transform:translate(0%, 0%) rotate(0deg); }
}

@keyframes headerDriftB
{
	0% { transform:translate(0%, 0%) rotate(0deg); }
	33% { transform:translate(-4.3%, 6.4%) rotate(-3.7deg); }
	66% { transform:translate(2.2%, -4.3%) rotate(1.8deg); }
	100% { transform:translate(0%, 0%) rotate(0deg); }
}

@keyframes headerDriftC
{
	0% { transform:translate(0%, 0%) rotate(0deg); }
	30% { transform:translate(4.3%, 5%) rotate(-2.6deg); }
	65% { transform:translate(-3.6%, -7.2%) rotate(2.9deg); }
	100% { transform:translate(0%, 0%) rotate(0deg); }
}

/*
	The flecks get their own path with roughly three times the travel. They are a twentieth of the
	header across, so the same percentage that carries a ball a visible distance moves a fleck by
	under a pixel - and a gold chip adrift should be the liveliest thing up there, not the stillest.
*/
@keyframes headerFleckDrift
{
	0% { transform:translate(0%, 0%) rotate(0deg); }
	30% { transform:translate(13%, -20%) rotate(13deg); }
	65% { transform:translate(-10%, 14%) rotate(-10deg); }
	100% { transform:translate(0%, 0%) rotate(0deg); }
}

#luckySportsPage .iHeaderPiece
{
	animation-duration:5.1s;
	animation-iteration-count:infinite;
	animation-name:headerDriftA;
	animation-timing-function:ease-in-out;
}

#luckySportsPage .iHeaderPiece.soccerBall { animation-delay:-0.8s; animation-duration:5.3s; }
#luckySportsPage .iHeaderPiece.rugbyBall { animation-delay:-2.4s; animation-duration:4.6s; animation-name:headerDriftC; }
#luckySportsPage .iHeaderPiece.glovesLeft { animation-delay:-1.2s; animation-duration:5.9s; animation-name:headerDriftB; }
#luckySportsPage .iHeaderPiece.glovesRight { animation-delay:-3.6s; animation-duration:5.6s; animation-name:headerDriftC; }
#luckySportsPage .iHeaderPiece.cricketBall { animation-delay:-2.9s; animation-duration:4.3s; }
#luckySportsPage .iHeaderPiece.whistle { animation-delay:-0.4s; animation-duration:4.9s; animation-name:headerDriftB; }
#luckySportsPage .iHeaderPiece.tennisBall { animation-delay:-4.2s; animation-duration:6.3s; animation-name:headerDriftC; }
#luckySportsPage .iHeaderPiece.star { animation-delay:-1.7s; animation-duration:3.8s; }
#luckySportsPage .iHeaderPiece.coinRight { animation-delay:-3.1s; animation-duration:4.7s; animation-name:headerDriftB; }
#luckySportsPage .iHeaderPiece.coinLeft { animation-delay:-2.2s; animation-duration:5.7s; }

#luckySportsPage .iHeaderPiece.fleck { animation-name:headerFleckDrift; }
#luckySportsPage .iHeaderPiece.fleck1 { animation-delay:-0.3s; animation-duration:3.4s; }
#luckySportsPage .iHeaderPiece.fleck2 { animation-delay:-2.1s; animation-duration:4.2s; }
#luckySportsPage .iHeaderPiece.fleck3 { animation-delay:-3.4s; animation-duration:2.9s; }
#luckySportsPage .iHeaderPiece.fleck4 { animation-delay:-1.5s; animation-duration:4.5s; }
#luckySportsPage .iHeaderPiece.fleck5 { animation-delay:-4.0s; animation-duration:3.9s; }
#luckySportsPage .iHeaderPiece.fleck6 { animation-delay:-0.9s; animation-duration:3.1s; }
#luckySportsPage .iHeaderPiece.fleck7 { animation-delay:-2.7s; animation-duration:4.8s; }

/*
	The lettering does not drift across the header - a wordmark that wanders is a wordmark that is
	hard to read - but it does breathe, and it breathes with a little of the balls' motion in it so
	the two do not read as two separate animations sharing a box.

	So: a scale, plus a rise of well under one percent of the block's own height and a third of a
	degree of tilt. The scale is what is felt; the rise and the tilt are what make it feel adrift
	rather than mechanical. 3.4% is about two pixels of growth on a phone.

	The two lines have their own keyframes and their own periods, out of phase, so the block does
	not swell as one lump - and the subtext moves less, because it is the wider and thinner of the
	two and the same tilt on it travels much further at the ends.

	The wordmark's runs from the first frame, through the scene's arrival below and after it: the
	arrival is a transform on .iHeaderScene and this is a transform on a child of it, so the two
	compose rather than compete and the logo breathes on the way down. The subtext's cannot, because
	the subtext arrives on its own afterwards - it is chained behind the roll instead, further down.
*/
@keyframes headerPulse
{
	from { transform:translateY(0.9%) scale(1) rotate(-0.35deg); }
	to { transform:translateY(-0.9%) scale(1.034) rotate(0.35deg); }
}

@keyframes headerPulseSubtext
{
	from { transform:translateY(0.7%) scale(1) rotate(0.22deg); }
	to { transform:translateY(-0.7%) scale(1.026) rotate(-0.22deg); }
}

#luckySportsPage .iHeaderPiece.wordmark,
#luckySportsPage .iHeaderPiece.subtext
{
	animation-direction:alternate;
	animation-name:headerPulse;
}

#luckySportsPage .iHeaderPiece.wordmark { animation-duration:3.1s; }
#luckySportsPage .iHeaderPiece.subtext { animation-delay:-1.3s; animation-duration:3.7s; animation-name:headerPulseSubtext; }

/*
	The header arrives in two beats. The Lucky Sports logo and the artwork around it drop in on a cord
	first, as one object; "SCRATCH & WIN" rolls in under them afterwards. Two beats rather than one
	because they are two things - a logo and the line that explains it - and a line that explains
	something should not arrive before the thing it explains.

	This is the first beat, and it is one transform on .iHeaderScene rather than one per piece. The
	pieces are absolutely positioned children of it, so a transform on the wrapper carries the whole
	composition and their own drift and pulse keep running underneath: the balls are already adrift
	and the logo already breathing on the way down, which is what makes it read as a scene arriving
	rather than a picture being moved. It also means nothing has to hand over to anything. The drop
	ends at identity, which is exactly what the wrapper is when no animation is running, so there is
	nothing to match and nothing to jump.

	It is pure CSS and not a class the page adds. A page's script is a <script src> appended to the
	head after its markup is already in the DOM (ElementUtil.loadJs), so anything JavaScript starts
	here begins a network round trip after the header has painted - the scene would sit at rest for
	a moment, jump up, and only then fall. With no delay and no fill the drop is running from the
	first frame the stylesheet applies, and after it ends the wrapper simply has no transform again.

	The drop is a scale as much as a fall. Starting at just over half size and 26% of the scene's
	own height above its place puts the top edge of the artwork 2% of that height above the box -
	inside the header's 10px of padding, so it never reaches up over the site menu. The shrink
	gives back almost exactly what the rise takes: 0.5 - 0.5x0.52 - 0.26 = -0.02.

	The overshoot is deliberately gentler than a single piece would get. This is the full width of
	the header swinging, and a big object that bounces like a small one reads as weightless - so
	the first swing tops out at 1.075 rather than the 1.14 the wordmark alone could carry. That
	also keeps the bottom edge at 1.082 of the scene's height, inside the header's 26px of padding
	below, so the balls never swing down over the cards.

	Only the 0% keyframe carries its own timing function, and it is an accelerating one: the fall is
	gravity and everything after it is a swing. The rest inherit the ease-in-out on the animation
	itself, which is what a decaying bounce wants. The four swings shrink by roughly a third each
	time and the tilt alternates, so the scene settles rather than stops.
*/
@keyframes headerBungeeIn
{
	0% { animation-timing-function:cubic-bezier(0.5, 0, 0.85, 0.4); transform:translateY(-26%) scale(0.52) rotate(-4deg); }
	34% { transform:translateY(4.5%) scale(1.075) rotate(1.8deg); }
	49% { transform:translateY(-2.8%) scale(0.972) rotate(-1.1deg); }
	63% { transform:translateY(2%) scale(1.032) rotate(0.65deg); }
	76% { transform:translateY(-1%) scale(0.989) rotate(-0.4deg); }
	87% { transform:translateY(1.1%) scale(1.012) rotate(0.2deg); }
	94% { transform:translateY(-0.3%) scale(0.996) rotate(-0.1deg); }
	100% { transform:translateY(0%) scale(1) rotate(0deg); }
}

/*
	The fade is its own animation rather than opacity keyframes inside the drop, because a keyframe
	that declares opacity also ends the transform segment it sits in - the accelerating fall would
	be cut in two at the point the scene finished appearing. Two animations on one element, each
	touching a property the other does not, keeps the fall a single curve and lets the fade run at
	its own length: the scene is solid a quarter of the way down, well before it lands.

	The subtext's roll uses the same keyframes at its own length and delay.
*/
@keyframes headerBungeeFade
{
	from { opacity:0; }
	to { opacity:1; }
}

#luckySportsPage .iHeaderScene
{
	animation:headerBungeeIn 1.25s ease-in-out,
	          headerBungeeFade 0.3s ease-out;
}

/*
	The second beat: "SCRATCH & WIN" rolls out from under the logo.

	It rolls on the X axis, not across the header. A wide, short banner cartwheeling in from the
	side sweeps an enormous area - at any tilt its own width becomes its height - and it would swing
	right down over the cards that are dealing in at the same moment. Rolling forward keeps it
	inside its own footprint: a rotateX only ever foreshortens the box, so the line stays where the
	artwork put it and simply turns to face you. It is also the same physical idea the cards below
	are built on, which turn over on rotateY - the same trick on the other axis.

	perspective is inside the transform rather than on the h1, for the reason .dealIn gives: a
	perspective on the parent would make it the containing block for everything positioned inside
	it. 520px against a 517px-wide line is a close viewpoint, which is what makes the near edge grow
	as it tilts - about 3% at the widest. Without it the roll is a flat squash and reads as a
	window blind rather than as something turning.

	It starts at exactly -90 degrees, edge-on, so there is no frame where the back of the artwork is
	showing and backface-visibility has nothing to decide. The fade is there for the frames either
	side of that, where the line is a sliver.

	The delay is 0.95s against the drop's 1.25s, so the roll begins while the logo above is still
	settling on its last two swings. A full stop between the two beats reads as two animations that
	happen to be on the same page; an overlap reads as one arrival in two parts.

	The last keyframe is the first keyframe of headerPulseSubtext - translateY 0.7%, scale 1,
	rotate 0.22deg - because the pulse is chained behind the roll in the same list rather than
	swapped in by a class, and the handover happens on one frame. It carries a delay equal to the
	roll's full length and no fill, so it contributes nothing until the roll is over and then wins
	for being last in the list. "both" on the roll is the mirror image: it holds the line edge-on
	through the delay, and holds its landing frame for the frame between the roll ending and the
	pulse starting.
*/
@keyframes headerSubtextRollIn
{
	0% { animation-timing-function:cubic-bezier(0.32, 0, 0.6, 0.42); transform:perspective(520px) translateY(-26%) rotateX(-90deg) scale(0.95); }
	46% { transform:perspective(520px) translateY(3.6%) rotateX(22deg) scale(1.03); }
	64% { transform:perspective(520px) translateY(-1.8%) rotateX(-9deg) scale(0.99); }
	79% { transform:perspective(520px) translateY(1.6%) rotateX(4.5deg) scale(1.008); }
	90% { transform:perspective(520px) translateY(-0.4%) rotateX(-2deg) scale(0.997); }
	100% { transform:perspective(520px) translateY(0.7%) rotateX(0deg) scale(1) rotate(0.22deg); }
}

#luckySportsPage #divLuckySportsHeader .iHeaderPiece.subtext
{
	animation:headerSubtextRollIn 0.78s ease-in-out 0.95s both,
	          headerBungeeFade 0.24s ease-out 0.95s both,
	          headerPulseSubtext 3.7s ease-in-out 1.73s infinite alternate;
}

/*
	A band of light crossing "SCRATCH & WIN", the gloss a physical foil sign gives off when it
	catches a light.

	The band is masked by the lettering's own alpha rather than clipped to the element's box. The
	box is a rectangle and the words inside it are not, so a sweep clipped to the box would run
	across the transparent gaps between the letters as a visible bar - a rectangle of light passing
	over the header. Masked to the artwork, the light only exists where the letters are, which is
	what makes it read as the letters catching it.

	It moves by background-position and not by a transform, because the mask and the gradient are on
	the same element: transforming it would carry the mask along and nothing would move relative to
	anything. The gradient tile is 220% of the box so the band can start and finish fully outside it.

	The long dwell is the point. The band crosses in under a second and then the sign is still for
	three, which is how a real sweep behaves and is also what keeps it from competing with the
	drifting balls. A shine on a loop with no rest is a page that never settles.

	The band is narrow - 8% of a tile that is 2.2 times the width of the box, so a little over 3% of
	the lettering at a time. A wider, softer gradient washed the whole sign at once and read as the
	letters changing colour rather than as light passing over them.

	The "?v=" on the mask must be the counter the background above carries - it is the same file, and
	a mask that resolves to a different cache entry than the art it masks will not line up.
*/
@keyframes subtextSheen
{
	0% { background-position:110% 0%; }
	23%, 100% { background-position:-10% 0%; }
}

#luckySportsPage .iHeaderPiece.subtext:after
{
	-webkit-mask-image:url('images/header/subtext.webp?v=1');
	-webkit-mask-position:center center;
	-webkit-mask-repeat:no-repeat;
	-webkit-mask-size:100% 100%;
	background-image:linear-gradient(105deg, rgba(255, 255, 255, 0) 46%, rgba(255, 255, 255, 0.72) 50%, rgba(255, 255, 255, 0) 54%);
	background-position:110% 0%;
	background-repeat:no-repeat;
	background-size:220% 100%;
	bottom:0px;
	content:"";
	left:0px;
	mask-image:url('images/header/subtext.webp?v=1');
	mask-position:center center;
	mask-repeat:no-repeat;
	mask-size:100% 100%;
	pointer-events:none;
	position:absolute;
	right:0px;
	top:0px;
}

/*
	The class is added by luckySports.js on the frame the roll-in ends, and the delay is measured
	from there: the light crosses 1.5s after "SCRATCH & WIN" has landed and settled.

	It is keyed to the landing rather than to page load because the two are not the same moment.
	The animation clock starts when the element is rendered, but the header is nine background
	images, and on a cold cache the artwork paints seconds after that - a sweep timed from load
	crossed a header that was not on screen yet, and the player's first sight of the sign was of
	one waiting four seconds for the next pass.

	The dwell after the crossing is the same three seconds it always had. The band crosses in under
	a second and then the sign is still, which is how a real sweep behaves and is what keeps it
	from competing with the drifting balls.
*/
#luckySportsPage .iHeaderPiece.subtext.sheen:after
{
	animation:subtextSheen 4s ease-in-out 1.5s infinite;
}

/*
	A player who has asked their device to stop animating things gets the scene as a still. Every
	piece keeps its measured position, so the header is exactly the artwork it was cut from.

	The wrapper has to be named here as well as the pieces. The logo's drop lives on it, and a rule
	that only silenced the children would leave the whole scene still falling in.

	The two lettering selectors also have to be named in full, and the subtext with both ids: they
	carry more than the blanket rule below and would otherwise outrank it and keep breathing - and
	in the subtext's case keep rolling in - which is precisely the motion the setting is asking not
	to see.
*/
@media (prefers-reduced-motion:reduce)
{
	#luckySportsPage .iHeaderScene,
	#luckySportsPage .iHeaderPiece,
	#luckySportsPage .iHeaderPiece.subtext:after,
	#luckySportsPage .iHeaderPiece.subtext.sheen:after,
	#luckySportsPage .iHeaderPiece.wordmark,
	#luckySportsPage #divLuckySportsHeader .iHeaderPiece.subtext
	{
		animation:none;
	}

	/* The sheen parks off the right edge rather than wherever the keyframes left it. */
	#luckySportsPage .iHeaderPiece.subtext:after
	{
		background-position:110% 0%;
	}
}

/*
	Same control as the Lucky Streak and Sport Predictor popups (public/css/sportPredictor.css) -
	a Font Awesome speaker that greys out when muted - but anchored to the page rather than to a
	popup chrome, so it tracks #luckySportsPage's horizontal padding at each breakpoint.

	It deliberately stays below the opened-card overlay (z-index 19) so it cannot be tapped while
	a card is being scratched.

	The info icon is the same Font Awesome control the Advent Calendar, Daily Chests, Lucky Streak
	and Loyalty Rewards popups use, so it shares this rule and only moves the anchor across. It sits
	to the left of the speaker because the speaker is the one a player reaches for mid-game.
*/
#luckySportsPage #btnMuteMusic, #luckySportsPage #btnInfo
{
	color:#edc967;
	cursor:pointer;
	filter:drop-shadow(0px 0px 5px #000000) drop-shadow(0px 0px 5px #000000) drop-shadow(0px 0px 1px #000000);
	font-size:20px;
	height:40px;
	line-height:40px;
	position:absolute;
	right:40px;
	text-align:center;
	top:6px;
	transition:color 0.25s;
	width:40px;
	z-index:2;
}

#luckySportsPage #btnInfo
{
	right:82px;
}

#luckySportsPage #btnMuteMusic.muted
{
	color:#888888;
}

#luckySportsPage #btnMuteMusic:hover, #luckySportsPage #btnMuteMusic:focus,
#luckySportsPage #btnInfo:hover, #luckySportsPage #btnInfo:focus
{
	color:#ffffff;
}

/* ---------------------------------------------------------------- grid */

#luckySportsPage #divLuckySportsCards
{
	display:flex;
	flex-wrap:wrap;
	gap:18px;
	justify-content:center;
}

/*
	The card is its poster artwork and nothing else.

	Everything that used to be drawn here in CSS - the neon border, the star ornament, the "LUCKY
	FRUITS" headline, the prize band, the "TOP PRIZE!" disc and the footer - is painted into the
	supplied art, so the element is a background image with one hole cut in it. aspect-ratio holds
	that hole in place: the four posters are all 971x1392 (+/- 0.3%), the scratch panel is
	positioned in percentages against the card, and the whole lot scales as one from the 174px
	mobile tile to the opened card with no per-breakpoint geometry.
*/
#luckySportsPage .divLuckySportsCard
{
	--themeColor:#b06bff;
	--themeColorLight:#e0c2ff;
	--themeGlow:rgba(176, 107, 255, 0.6);
	--foilTop:#4e169a;
	--foilBottom:#23103f;

	aspect-ratio:971 / 1392;
	box-sizing:border-box;
	cursor:pointer;
	flex:0 0 calc((100% / 4) - 14px);
	position:relative;
	transition:transform 0.25s ease-in-out;
}

/*
	The artwork sits on a pseudo-element so the halo can be a drop-shadow instead of a box-shadow.

	Each poster carries an alpha channel and its neon frame is inset from the edge of the file, so a
	box-shadow - which is drawn around the element's border box - stood a few pixels clear of the
	frame with corners the frame does not have. It read as a rectangle floating behind the card
	rather than as the frame glowing. drop-shadow traces the artwork's own alpha, so the light comes
	off the frame itself.

	It has to be a pseudo-element and not the card: a filter applies to an element and everything
	inside it, so on the card the scratch panel, the result band and the close button would each
	pick up the same halo.

	border-radius trims the transparent bleed the poster carries outside its frame, and no more than
	that. Clipping far enough to visibly round the card means cutting through the neon band, which
	takes the halo with it - the glow traces the artwork's alpha, so a corner cut off square is a
	corner the light stops at. The frame paints its own bend and that is the one the card keeps.
	Two values because a single percentage on a 971x1392 box gives an ellipse: the vertical figure
	is the horizontal one scaled by the aspect ratio (5.8 x 971/1392 = 4.05), so the arc comes out
	circular at any card size.

	The "?v=" is a hand-turned counter, not the app version - see the note above
	#divLuckySportsHeader h1 for why an image named inside a stylesheet needs its own.
*/
#luckySportsPage .divLuckySportsCard:before
{
	background-position:center center;
	background-repeat:no-repeat;
	background-size:100% 100%;
	border-radius:5.8% / 4.05%;
	bottom:0px;
	content:"";
	left:0px;
	position:absolute;
	right:0px;
	top:0px;
	z-index:0;
}

#luckySportsPage .divLuckySportsCard.r250:before { background-image:url('images/cards/r250.webp?v=1'); }
#luckySportsPage .divLuckySportsCard.r100:before { background-image:url('images/cards/r100.webp?v=1'); }
#luckySportsPage .divLuckySportsCard.r50:before { background-image:url('images/cards/r50.webp?v=1'); }
#luckySportsPage .divLuckySportsCard.r25:before { background-image:url('images/cards/r25.webp?v=1'); }

/*
	The R50 poster is painted higher in the file than the other three, so in a row of four the blue
	card stood proud of its neighbours. The artwork cannot be re-registered from here, so the card is
	nudged down by the difference instead - see the R50 float keyframes further down, which carry the
	offset. Moving the card rather than its background keeps the scratch panel, which is positioned
	against the card, in the middle of the poster's window.
*/

/* ---------------------------------------------------------------- themes */

/*
	Per-card colour, used by the halo, the match glow, the win line and the prize text - not by the
	card face, which is artwork. The values are sampled from each poster so the glow around a card
	is the colour of the card.

	--foilTop / --foilBottom are read back by luckySports.js and handed to ScratchSurface. The
	component only falls back to them when the foil texture fails to load, but they are still worth
	keeping per theme: a card that loses its texture should degrade to its own colour rather than to
	a generic purple.
*/
#luckySportsPage .divLuckySportsCard.gold
{
	--themeColor:#ffb128;
	--themeColorLight:#ffe3a8;
	--themeGlow:rgba(255, 177, 40, 0.6);
	--foilTop:#6e4410;
	--foilBottom:#331d06;
}

#luckySportsPage .divLuckySportsCard.purple
{
	--themeColor:#b06bff;
	--themeColorLight:#e0c2ff;
	--themeGlow:rgba(176, 107, 255, 0.6);
	--foilTop:#4e169a;
	--foilBottom:#23103f;
}

#luckySportsPage .divLuckySportsCard.blue
{
	--themeColor:#35c8ff;
	--themeColorLight:#b0ecff;
	--themeGlow:rgba(53, 200, 255, 0.6);
	--foilTop:#123a6e;
	--foilBottom:#0b1c38;
}

#luckySportsPage .divLuckySportsCard.green
{
	--themeColor:#5df06a;
	--themeColorLight:#c2ffc8;
	--themeGlow:rgba(93, 240, 106, 0.6);
	--foilTop:#14562c;
	--foilBottom:#082614;
}

/* ---------------------------------------------------------------- card chrome */

#luckySportsPage .btnCloseCard
{
	display:none;
}

/* ---------------------------------------------------------------- scratch area */

/*
	The hole in the poster.

	The four frames were profiled to find the empty middle each one leaves: the inner edges of the
	border rails sit at about 4% and 95% of the card's width, and the gap between the headline and
	the prize band runs from 28% to 81% of its height. The largest square that fits is 75% of the
	card's width; 70% is used, centred in that window, which leaves a margin on all four sides so
	the panel never crowds the artwork it is dropped into.

	top:30.1% is that same centring expressed against the card's height - it is not a round number
	because the window is not centred on the card, it sits low.

	.divScratchArea is the neon rail and .divScratchPanel is everything inside it. They are two
	elements because the panel needs overflow:hidden to clip the scratch canvas and the win line to
	its own rounded corners, and a rail drawn on that same element would be clipped along with them.
*/
/*
	The rail is drawn in the card's own colour, not in one colour for all four.

	It was a fixed blue-to-magenta gradient, which read as a part of the panel rather than a part of
	the card: four posters in four colours, each with the same light-blue frame dropped into the
	middle of it. Reading --themeColor / --themeColorLight off the card puts the orange card's panel
	in orange and the green card's in green, so the panel belongs to the poster it sits in.

	The gradient still runs light to saturated and back, so the rail keeps the lit-edge look the
	design has; it is the hue that comes from the card and the shape of the light that does not.
*/
#luckySportsPage .divScratchArea
{
	aspect-ratio:1 / 1;
	background:linear-gradient(135deg, var(--themeColorLight) 0%, var(--themeColor) 48%, var(--themeColorLight) 100%);
	border-radius:6%;
	box-shadow:0px 0px 14px 0px var(--themeGlow);
	box-sizing:border-box;
	left:15%;

	/* The rail's thickness. It is the padding and nothing else, so it scales with the card. */
	padding:1.05%;
	position:absolute;
	top:30.1%;
	width:70%;
}

#luckySportsPage .divScratchPanel
{
	background-color:#1c089b;
	border-radius:4.5%;
	height:100%;
	overflow:hidden;
	position:relative;
	width:100%;
}

/*
	The dividers between cells are the panel's own background showing through the grid gap, which is
	how the supplied reveal texture is drawn. Doing it that way rather than as nine bordered cells is
	what makes the nine cells exactly square and exactly equal: the grid divides the panel into
	thirds and the gap comes out of those thirds evenly.
*/
#luckySportsPage .divSymbolGrid
{
	display:grid;
	gap:2%;
	grid-template-columns:repeat(3, 1fr);
	grid-template-rows:repeat(3, 1fr);
	height:100%;
	padding:2%;
	position:relative;
	width:100%;
	z-index:1;

	/* So the 2% padding is inside the panel rather than added to it. */
	box-sizing:border-box;
}

/*
	Each cell wears one tile of the holographic reveal texture, and the symbol rides on top of it as
	a separate element.

	The texture is a 3x3 sprite, so background-size:300% is one tile per cell and the position is
	picked below off the cell's row and column - every cell gets the tile the artwork had in that
	position, which is what preserves the panel's corner-to-corner colour gradient.
*/
#luckySportsPage .divSymbol
{
	background-image:url('images/revealTexture.webp?v=1');
	background-repeat:no-repeat;
	background-size:300% 300%;
	border-radius:4%;
	position:relative;
}

#luckySportsPage .divSymbol:nth-child(3n + 1) { background-position-x:0%; }
#luckySportsPage .divSymbol:nth-child(3n + 2) { background-position-x:50%; }
#luckySportsPage .divSymbol:nth-child(3n + 3) { background-position-x:100%; }

#luckySportsPage .divSymbol:nth-child(-n + 3) { background-position-y:0%; }
#luckySportsPage .divSymbol:nth-child(n + 4):nth-child(-n + 6) { background-position-y:50%; }
#luckySportsPage .divSymbol:nth-child(n + 7) { background-position-y:100%; }

/*
	The symbols, from a 3x2 sprite. Held slightly inside the cell so the glow below has somewhere to
	bloom into without being clipped by the next cell.

	This is a separate element from .divSymbol for the glow's sake: filter:drop-shadow traces the
	alpha of what the element paints, so on this element - whose background is a cut-out symbol - it
	follows the symbol's outline. On .divSymbol, which paints an opaque square tile, the same filter
	would draw a rectangle.
*/
#luckySportsPage .divSymbolArt
{
	background-image:url('images/symbols.webp?v=1');
	background-repeat:no-repeat;
	background-size:300% 200%;
	bottom:8%;
	left:8%;
	position:absolute;
	right:8%;
	top:8%;
	transition:transform 0.3s ease-in-out, filter 0.3s ease-in-out;
}

#luckySportsPage .divSymbolArt.soccer { background-position:0% 0%; }
#luckySportsPage .divSymbolArt.rugby { background-position:50% 0%; }
#luckySportsPage .divSymbolArt.cricket { background-position:100% 0%; }
#luckySportsPage .divSymbolArt.basketball { background-position:0% 100%; }
#luckySportsPage .divSymbolArt.tennis { background-position:50% 100%; }
#luckySportsPage .divSymbolArt.golf { background-position:100% 100%; }

/* ---------------------------------------------------------------- match glow */

/*
	The reveal is the payoff of the whole interaction, so the three matching cells are meant to be
	impossible to miss. Four things run at once on them:

	  - an inset shadow that darkens the tile's edges, which is what gives everything below it
	    something to glow against - on the untouched tile a bright bloom just washed the cell out,
	  - a ring and halo on the cell itself, which says "these three, not those six" at a glance,
	  - a radial bloom behind the symbol (:before), blended with screen so it adds light to the
	    holographic tile rather than painting over it,
	  - a drop-shadow halo on the symbol, which traces its outline rather than its cell, plus a slow
	    scale on both so the cell reads as breathing.

	The bloom is built from --themeColor rather than --themeColorLight on purpose. Screen blending
	drives everything towards white, and a near-white gradient screened over the tile took the cell
	with it - the three winning cells came out paler than the six losing ones, which is the opposite
	of the intent. A saturated colour brightens the tile while keeping its hue.

	Both animations are wound so that even their weakest frame is a clear glow. They run infinitely
	and alternate, so a player can look at the card at any moment; a keyframe that started from
	nothing would leave the win looking unmarked half the time.

	Hovering a matched cell takes all of it further - faster, wider, and with a small rock - which is
	the hover treatment the design asks for. It is on the matched cells only: an unmatched cell that
	lit up on hover would suggest it had also won.
*/
#luckySportsPage .divSymbol.match
{
	box-shadow:
		inset 0px 0px 26px 8px rgba(4, 1, 30, 0.8),
		0px 0px 0px 2px var(--themeColorLight),
		0px 0px 26px 6px var(--themeGlow);
	z-index:1;
}

#luckySportsPage .divSymbol.match:before
{
	animation:luckySportsCardMatchBloom 1.1s ease-in-out infinite alternate;
	background:radial-gradient(circle at 50% 50%, var(--themeColor) 0%, var(--themeColor) 30%, rgba(0, 0, 0, 0) 70%);
	content:"";
	inset:-10%;
	mix-blend-mode:screen;
	pointer-events:none;
	position:absolute;
	transition:inset 0.3s ease-in-out;
	z-index:0;
}

@keyframes luckySportsCardMatchBloom
{
	from { opacity:0.6; transform:scale(0.88); }
	to { opacity:1; transform:scale(1.14); }
}

#luckySportsPage .divSymbol.match .divSymbolArt
{
	animation:luckySportsCardMatchSymbol 1.1s ease-in-out infinite alternate;
	z-index:1;
}

@keyframes luckySportsCardMatchSymbol
{
	from { filter:drop-shadow(0px 0px 6px #ffffff) drop-shadow(0px 0px 16px var(--themeColor)); transform:scale(1.02); }
	to { filter:drop-shadow(0px 0px 16px #ffffff) drop-shadow(0px 0px 34px var(--themeColor)); transform:scale(1.16); }
}

#luckySportsPage .divSymbol.match:hover:before
{
	animation-duration:0.45s;
	inset:-24%;
}

#luckySportsPage .divSymbol.match:hover .divSymbolArt
{
	animation:luckySportsCardMatchSymbolHover 0.45s ease-in-out infinite alternate;
}

@keyframes luckySportsCardMatchSymbolHover
{
	from { filter:drop-shadow(0px 0px 6px #ffffff) drop-shadow(0px 0px 14px var(--themeColor)); transform:scale(1.2) rotate(-5deg); }
	to { filter:drop-shadow(0px 0px 18px #ffffff) drop-shadow(0px 0px 34px var(--themeColor)); transform:scale(1.34) rotate(5deg); }
}

/*
	The line through the three matching symbols.

	One element rather than three cell borders, because that is the only version a diagonal can be
	drawn with, and it means rows, columns and both diagonals are the same code. luckySports.js
	sets left, top, width and the rotation from the two end cells' centres; everything is a
	percentage of the panel, so the line survives the card being resized by the zoom.
*/
#luckySportsPage .divWinLine
{
	background:linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, var(--themeColorLight) 12%, #ffffff 50%, var(--themeColorLight) 88%, rgba(255, 255, 255, 0) 100%);
	border-radius:99px;
	box-shadow:0px 0px 14px 3px var(--themeGlow);
	height:4%;
	min-height:3px;
	opacity:0;
	pointer-events:none;
	position:absolute;
	transition:opacity 0.4s ease-in-out;
	z-index:2;
}

/* Short of opaque, so the three symbols the line is drawn through still read through it. */
#luckySportsPage .divWinLine.show
{
	opacity:0.85;
}

/*
	Kept for when Rive is turned on. RIVE_ENABLED in luckySports.js is false, so the canvas this
	styles is not built and nothing below applies today.

	Rive sits ABOVE the scratch canvas (z-index 3), not below it. It is a transparent effects
	layer: scratchProgress drives foil dust and sparkle as the player scratches, and at z-index 2
	all of that would have been hidden behind the very foil it is reacting to. It stays under
	.divCardResult (5) so the result band always wins, and it is pointer-events:none, so
	scratching passes straight through.
*/
#luckySportsPage .cnvRive
{
	height:100%;
	left:0px;
	pointer-events:none;
	position:absolute;
	top:0px;
	width:100%;
	z-index:4;
}

/*
	The WIN badge.

	It is centred on the panel and covers most of it on purpose. A badge tucked into a corner would
	be a sticker on the card; across the middle it is the card interrupting itself to say the thing
	the player is waiting for. It is over the nine cells, which is the one moment that is allowed -
	three of them have just matched, and the badge is gone again before the player wants to look.

	Sized by width with the artwork's own 268 / 137 ratio, so it keeps its proportion at every card
	size without a second number to maintain. The artwork is wide and shallow, so at 90% of the
	panel it lands as a band across the middle row and leaves the top and bottom rows of symbols
	showing either side of it.

	90% and not more, because the panel is overflow:hidden for the scratch canvas' sake and the
	badge grows past its own width twice in the animation below - once on the punch and once on
	the way out. 90% at rest peaks at just under 96% of the panel, so the laurels on each end
	never touch the clip.

	z-index 4 puts it over the scratch canvas (3) and under .divCardResult (5). It never fights the
	result band anyway - the band is below the panel - but the ordering holds if either ever moves.
	pointer-events:none so a player who keeps scratching through the flash is not blocked by it.

	opacity:0 in the base rule is what makes a losing card, and every card before its result, show
	nothing. The class below is the only thing that ever paints it.

	The "?v=" here is the badge's own counter, not config('app.version') - a file named inside a
	CSS url() never gets that. It must match the sweep's mask below character for character, the
	same rule FOIL_IMAGE carries: two spellings of one file is two downloads of one picture.
*/
#luckySportsPage .divWinBadge
{
	aspect-ratio:268 / 137;
	background-image:url('images/win.webp?v=2');
	background-position:center center;
	background-repeat:no-repeat;
	background-size:contain;
	left:50%;
	opacity:0;
	pointer-events:none;
	position:absolute;
	top:50%;
	transform:translate(-50%, -50%);
	width:90%;
	z-index:4;
}

/*
	The flash.

	Three things happen in order and the order is the whole effect. It snaps up from small and
	over-bright, blown out the way a lit sign is in the first frame it is photographed - that is
	the "hit". It overshoots past full size and settles back, which is what makes it land rather
	than arrive. Then it strobes twice, dipping and coming back, because a casino win is never one
	clean announcement - it is a light doing this. Then it holds long enough to be read, and leaves
	by drifting up in scale as it goes, so it recedes rather than switches off.

	2.1s total, and about a second of that is the hold. Shorter and the player has not finished
	reading it; longer and it is in the way of the matched line it is celebrating.

	The strobe finishes at 47% - about a second in - to clear the hold for the sweep below. Two
	pieces of light on the sign at once read as neither: the strobe is the whole badge changing
	brightness and the sweep is a band crossing it, and overlapped they just looked like the
	brightness was misbehaving. In sequence they are a sign switching on and then catching a light.

	The transform is repeated on every step that changes it because a keyframe that omits a
	property drops out of that property's timeline, and translate(-50%, -50%) is doing the
	centring - one keyframe without it would throw the badge a half-width off screen mid-flash.
*/
#luckySportsPage .divWinBadge.show
{
	animation:winBadgeFlash 2.1s ease-out 1 both;
}

@keyframes winBadgeFlash
{
	0%
	{
		filter:brightness(2.8) saturate(0.35);
		opacity:0;
		transform:translate(-50%, -50%) scale(0.55);
	}

	9%
	{
		filter:brightness(2) saturate(0.7);
		opacity:1;
		transform:translate(-50%, -50%) scale(1.06);
	}

	17%
	{
		filter:brightness(1) saturate(1);
		opacity:1;
		transform:translate(-50%, -50%) scale(0.97);
	}

	22%
	{
		filter:brightness(1) saturate(1);
		opacity:1;
		transform:translate(-50%, -50%) scale(1);
	}

	26% { filter:brightness(1.9) saturate(0.8); opacity:0.35; }
	33% { filter:brightness(1) saturate(1); opacity:1; }
	40% { filter:brightness(1.75) saturate(0.85); opacity:0.45; }
	47% { filter:brightness(1) saturate(1); opacity:1; }

	78%
	{
		filter:brightness(1) saturate(1);
		opacity:1;
		transform:translate(-50%, -50%) scale(1);
	}

	100%
	{
		filter:brightness(1) saturate(1);
		opacity:0;
		transform:translate(-50%, -50%) scale(1.05);
	}
}

/*
	A band of light crossing the badge, the same gloss the "SCRATCH & WIN" subtext carries, and
	built the same way and for the same reason: masked by the artwork's own alpha rather than
	clipped to the element's box, so the light exists only where the metal is. Clipped to the box
	it would be a lit rectangle sliding over the cells behind the badge.

	The one difference is that this one crosses once, not on a loop. The header sits on screen for
	as long as the player is on the page and a shine that never repeats there would be missed; the
	badge is on screen for two seconds and a loop inside that window is a flicker.

	The 1.02s delay parks the sweep in the hold: the strobe has finished, the badge is at full and
	still, and 0.72s later the band is off the other side with about 200ms to spare before the
	fade. It is the last thing that happens while the badge is up, which is what makes the badge
	read as settling rather than as waiting to leave.

	It moves by background-position and not by a transform, because the mask and the gradient are
	on the same element: transforming it would carry the mask along and nothing would move relative
	to anything. The gradient tile is 220% of the box so the band starts and finishes fully outside
	it, and the base position parks it off the right edge for the second before the delay elapses.

	The band is wider than the header's - 14% of that tile against 8%. The header's lettering is
	outlined and thin and a narrow band reads cleanly across it; the badge is a solid slab of gold
	and the same band was a scratch on it rather than a shine.
*/
@keyframes winBadgeSheen
{
	from { background-position:115% 0%; }
	to { background-position:-15% 0%; }
}

#luckySportsPage .divWinBadge:after
{
	-webkit-mask-image:url('images/win.webp?v=2');
	-webkit-mask-position:center center;
	-webkit-mask-repeat:no-repeat;
	-webkit-mask-size:contain;
	background-image:linear-gradient(108deg, rgba(255, 255, 255, 0) 43%, rgba(255, 255, 255, 0.85) 50%, rgba(255, 255, 255, 0) 57%);
	background-position:115% 0%;
	background-repeat:no-repeat;
	background-size:220% 100%;
	bottom:0px;
	content:"";
	left:0px;
	mask-image:url('images/win.webp?v=2');
	mask-position:center center;
	mask-repeat:no-repeat;
	mask-size:contain;
	pointer-events:none;
	position:absolute;
	right:0px;
	top:0px;
}

#luckySportsPage .divWinBadge.show:after
{
	animation:winBadgeSheen 0.72s ease-in-out 1.02s 1;
}

/*
	The strobe and the sweep are the parts of this that are a problem for anyone who asked not to
	be flashed at, and they are also the parts that are decoration rather than information. The
	badge still appears, still says the same word, still leaves on its own - it just does it by
	fading, with no light crossing it.
*/
@media (prefers-reduced-motion:reduce)
{
	#luckySportsPage .divWinBadge.show
	{
		animation:winBadgeFade 2.1s ease-in-out 1 both;
	}

	#luckySportsPage .divWinBadge.show:after
	{
		animation:none;
	}

	@keyframes winBadgeFade
	{
		0% { opacity:0; }
		14% { opacity:1; }
		78% { opacity:1; }
		100% { opacity:0; }
	}
}

/*
	Closed-card foil.

	The canvas foil only exists once the card has been opened, because ScratchSurface sizes itself
	from the laid-out element and that has to be the zoomed size. Without this element the grid
	showed every card's symbols face up, so a player could see which cards had won before opening
	any of them - the whole mechanic leaked.

	It is a sibling that follows the canvas in the DOM so it can be switched off by the canvas'
	own .loaded class, with no extra JS state: no foil drawn yet means cover the symbols; foil
	drawn means the real one has taken over. ScratchSurface.destroy() drops .loaded, so a card
	closed part-scratched (which re-deals) is covered again.

	It is the same texture file the canvas draws, so the swap between the two is invisible. The
	"?v=" must therefore match FOIL_IMAGE in luckySports.js character for character - a different
	counter here would make the browser fetch and cache the same texture twice.
*/
#luckySportsPage .divFoilPreview
{
	align-items:center;
	background-image:url('images/scratchFoil.webp?v=1');
	background-position:center center;
	background-size:cover;
	bottom:0px;
	display:flex;
	justify-content:center;
	left:0px;
	position:absolute;
	right:0px;
	top:0px;
	transition:opacity 0.3s ease-in-out;
	z-index:3;
}

/*
	The label rides on its own element because the foil behind it is photographic - a colour that
	reads on the dark part of the texture disappears on the bright part. A pill gives it a constant
	backdrop at any size.
*/
#luckySportsPage .divFoilPreview span
{
	background:rgba(6, 2, 22, 0.6);
	border-radius:20px;
	color:rgba(255, 255, 255, 0.95);
	font-size:9px;
	font-weight:700;
	letter-spacing:1.5px;
	padding:4px 10px;
	text-transform:uppercase;

	/* Three words now, and the panel is only ~120px across on a phone - let it run off rather
	   than wrap into a two-line block in the middle of the card. */
	white-space:nowrap;
}

/*
	The preview and the canvas must never both be part-transparent at the same time.

	ScratchSurface fades its canvas in over 0.2s when the foil is drawn, and the preview fades out
	over 0.3s the moment that happens. For about 200ms both sat mid-fade and the symbols showed
	through the pair of them - the card flashed its symbols as it opened, which gives the outcome away
	before a single scratch. Killing the canvas' fade-in means it is fully opaque on the frame
	.loaded lands, so the preview dissolves into a foil that is already there.

	:not(.completed) is load-bearing: .completed keeps .loaded and re-declares the transition to fade
	the spent foil off the revealed symbols. That fade has to survive.
*/
#luckySportsPage .cnvScratchSurface.loaded:not(.completed)
{
	transition:none;
}

#luckySportsPage .cnvScratchSurface.loaded ~ .divFoilPreview
{
	opacity:0;
	pointer-events:none;
}

#luckySportsPage .divScratchHint
{
	background:rgba(0, 0, 0, 0.55);
	border-radius:5px;
	bottom:8px;
	color:#ffffff;
	display:none;
	font-size:9px;
	font-weight:700;
	left:50%;
	letter-spacing:1.5px;
	padding:4px 10px;
	pointer-events:none;
	position:absolute;
	transform:translateX(-50%);
	transition:opacity 0.3s ease-in-out;
	z-index:4;

	/* Two words on one line or the pill wraps and stops reading as a label on a narrow phone. */
	white-space:nowrap;
}

/* Only meaningful once there is a foil to scratch - on a closed card the action is a tap. */
#luckySportsPage .divLuckySportsCard.open .divScratchHint
{
	display:block;
}

#luckySportsPage .divLuckySportsCard.revealed .divScratchHint
{
	opacity:0;
}

/*
	The result covers the poster's own "YOU COULD WIN Rxxx!" band along the bottom of the card, not
	the scratch panel. It used to be a band inside the panel, which meant the moment a player won,
	the bottom row of symbols went behind it - and on a column or a diagonal that is one of the three
	they had just matched. The panel stays entirely theirs; the card's promise is what gets replaced
	by the outcome.

	The numbers are measured off the artwork: the band runs from about 80% to 98% of the card's
	height, inside the border rails at 4% and 96%. It is opaque rather than a scrim, because the
	promise underneath is a different number on the losing cards and reading the two at once is
	worse than reading neither.
*/
#luckySportsPage .divCardResult
{
	align-items:center;
	background:linear-gradient(180deg, #0a0326 0%, #05011a 100%);
	border:1px solid var(--themeColor);
	border-radius:10px;
	bottom:2%;
	box-shadow:0px 0px 20px 2px var(--themeGlow);
	box-sizing:border-box;
	display:flex;
	flex-direction:column;
	justify-content:center;
	left:4%;
	opacity:0;
	padding:2% 4%;
	pointer-events:none;
	position:absolute;
	right:4%;
	text-align:center;
	top:79.5%;
	transition:opacity 0.4s ease-in-out;
	z-index:5;
}

#luckySportsPage .divCardResult.show
{
	opacity:1;
}

/*
	The result text, in three sizes.

	It cannot be one size. The band is a fixed share of the card, and the card runs from a 249px
	tile on a phone's grid to a 640px panel when it is open - so a size that fills the opened band
	overflows the tile a played card goes back to. The sizes below are each measured against the
	band they live in: 86px in the desktop grid, 118px opened at any width (the opened card is a
	fixed height), 46px in the mobile grid.
*/
#luckySportsPage .spanResultLabel
{
	color:#ffffff;
	font-size:13px;
	font-weight:700;
	letter-spacing:2px;
	text-transform:uppercase;
}

#luckySportsPage .spanResultPrize
{
	color:var(--themeColor);
	font-size:38px;
	font-weight:800;
	line-height:1.1;
	text-shadow:0px 0px 18px var(--themeGlow);
}

#luckySportsPage .spanResultSubtitle
{
	color:#b9a9d4;
	font-size:12px;
	margin-top:4px;
}

/*
	Opened. The band is 118px tall here and the old sizes left most of it empty - the outcome is the
	payoff of the whole interaction and was being announced in the smallest type on the card.
*/
#luckySportsPage .divLuckySportsCard.open .spanResultLabel
{
	font-size:16px;
	letter-spacing:2.5px;
}

#luckySportsPage .divLuckySportsCard.open .spanResultPrize
{
	font-size:52px;
}

#luckySportsPage .divLuckySportsCard.open .spanResultSubtitle
{
	font-size:15px;
	margin-top:6px;
}

/* ---------------------------------------------------------------- open / zoom state */

#luckySportsPage #divLuckySportsCardOverlay
{
	background-color:rgba(0, 0, 0, 0.75);
	height:100%;
	left:0px;
	opacity:0;
	pointer-events:none;
	position:fixed;
	top:0px;
	transition:opacity 0.25s ease-in-out;
	width:100%;
	z-index:19;
}

#luckySportsPage #divLuckySportsCardOverlay.show
{
	opacity:1;
	pointer-events:auto;
}

/*
	Sized by height, not width.

	The card is one fixed-proportion image, so the constraint that actually bites is the viewport's
	height - a card sized to 92vw on a phone in portrait would be a third taller than the screen.
	Height drives it and aspect-ratio derives the width.

	The width guard is a third term inside the same min() and not a max-width, which is what it was
	and which broke scratching on a phone. max-width clamps the width the aspect ratio derived
	without recomputing the height, so on a 390px screen the card came out 366x640 - ratio 0.573
	against the poster's 0.698. The poster paints with background-size:100% 100% so it simply
	stretched, while the scratch panel keeps aspect-ratio:1/1; the square panel and the window
	painted for it in the stretched poster came apart, and a finger aimed at the window landed on
	artwork. Folding the width limit into the height keeps one number driving the box, so the card
	is always the poster's own shape.

	It deliberately has no overflow-y:auto. Scrolling a card whose whole point is that you drag
	across it is a fight between the two gestures, and per spec an overflow-y of auto forces
	overflow-x to auto as well, which used to clip the corner badge. Nothing can overflow now: the
	card is an image and every layer inside it is positioned as a percentage of that image.
*/
#luckySportsPage .divLuckySportsCard.open
{
	aspect-ratio:971 / 1392;
	cursor:default;
	height:min(88vh, 640px, calc(94vw * 1392 / 971));
	left:50%;
	position:fixed;
	top:50%;
	transform:translate(-50%, -50%);
	width:auto;
	z-index:20;
}

#luckySportsPage .divLuckySportsCard.open:hover
{
	transform:translate(-50%, -50%);
}

#luckySportsPage .divLuckySportsCard.open .divScratchHint
{
	font-size:11px;
}

#luckySportsPage .divLuckySportsCard.open .divFoilPreview span
{
	font-size:14px;
	letter-spacing:2.2px;
	padding:7px 18px;
}

/*
	Sat outside the card when the card was a bordered box. It sits inside it now, in the poster's own
	top-right margin: the artwork carries a dark bleed there, so a button placed over it reads
	cleanly, and hanging it off the corner would leave it floating in the overlay.
*/
#luckySportsPage .divLuckySportsCard.open .btnCloseCard
{
	background-color:rgba(0, 0, 0, 0.6);
	border:1px solid var(--themeColor);
	border-radius:50%;
	cursor:pointer;
	display:block;
	height:34px;
	position:absolute;
	right:3%;
	top:2%;
	width:34px;
	z-index:6;
}

#luckySportsPage .divLuckySportsCard.open .btnCloseCard:before,
#luckySportsPage .divLuckySportsCard.open .btnCloseCard:after
{
	background-color:#ffffff;
	content:"";
	height:2px;
	left:50%;
	position:absolute;
	top:50%;
	width:14px;
}

#luckySportsPage .divLuckySportsCard.open .btnCloseCard:before
{
	transform:translate(-50%, -50%) rotate(45deg);
}

#luckySportsPage .divLuckySportsCard.open .btnCloseCard:after
{
	transform:translate(-50%, -50%) rotate(-45deg);
}

#luckySportsPage .divCardConfetti
{
	height:100%;
	left:0px;
	pointer-events:none;
	position:absolute;
	top:0px;
	width:100%;
	z-index:7;
}

/*
	Grid idle motion - a slow bob on the card and a matching pulse on its halo.

	Unconditional, not a Rive fallback: Rive is now only instantiated on the card the player has
	actually opened, so the four closed cards would otherwise sit dead still. The bob is what makes
	them read as tappable; the glow alone was too quiet to invite a tap.

	The two run on different elements because they animate different properties on different boxes:
	the bob is a transform on the card, so the artwork and the scratch panel move together, and the
	glow is a filter on the artwork pseudo-element - see the note on .divLuckySportsCard:before for why
	the halo cannot live on the card. They share a period so a card rises and brightens together
	rather than beating against itself.

	All four cards run in phase, with no stagger. A staggered row looks better in isolation, but the
	travel is 10px peak to peak and a card's neighbours would then sit up to 10px above or below it at
	any given moment - which is the misalignment the R50 offset further up exists to remove. The row
	breathes as one instead, and hover is what gives a single card its own response.

	A drop-shadow blur reads roughly twice as wide as the same number given to a box-shadow, so the
	glow radii are about half what the box-shadow version used.
*/
#luckySportsPage .divLuckySportsCard
{
	animation:luckySportsCardFloat 3.4s ease-in-out infinite alternate;
}

#luckySportsPage .divLuckySportsCard:before
{
	animation:luckySportsCardIdleGlow 3.4s ease-in-out infinite alternate;
}

@keyframes luckySportsCardFloat
{
	from { transform:translateY(-5px); }
	to { transform:translateY(5px); }
}

@keyframes luckySportsCardIdleGlow
{
	from { filter:drop-shadow(0px 0px 7px var(--themeGlow)); }
	to { filter:drop-shadow(0px 0px 15px var(--themeGlow)); }
}

/*
	Hover swaps the animation rather than setting transform or filter directly: a running animation
	wins over a plain declaration for the property it animates, so either one would simply be
	ignored. The card keeps bobbing under the cursor - it lifts, tightens its travel and speeds up,
	which reads as the card waking up rather than as it stopping.

	The tilt is what separates hover from the idle bob. Both are vertical travel, so at a glance the
	only difference used to be the rate; rocking the card a little over its own centre as it rises
	is a second axis of movement, and that is the part the eye reads as the card being alive rather
	than as the same loop played faster.
*/
#luckySportsPage .divLuckySportsCard:hover
{
	animation-duration:0.85s;
	animation-name:luckySportsCardFloatHover;
}

#luckySportsPage .divLuckySportsCard:hover:before
{
	animation-duration:0.85s;
	animation-name:luckySportsCardHoverGlow;
}

@keyframes luckySportsCardFloatHover
{
	from { transform:translateY(-7px) scale(1.012) rotate(-0.6deg); }
	to { transform:translateY(-15px) scale(1.032) rotate(0.6deg); }
}

/*
	R50's alignment correction, carried by its own copies of the two float keyframes.

	The shift is 1.31% of the card's height, measured off the rendered page rather than off the
	artwork: with no correction the four top rails landed on rows 376, 375, 369 and 375 of a 1440px
	screenshot, so the blue one was about 6px shy of the other three on a 467px card.

	It rides on the transform because the two obvious places to put it both fail. A percentage top
	resolves against the containing block's height, the row's height comes from its content, and a
	percentage against an indefinite height lays out as zero - getComputedStyle still reports the
	resolved pixel figure, so the rule looks like it applied. A percentage margin resolves against the
	containing block's width, which is the whole row rather than the card, so its meaning changes
	every time the column count does. A percentage inside translateY resolves against the element's
	own border-box height, which aspect-ratio makes definite, so 1.31% means the same thing at every
	breakpoint - and the transform is already animated, so nothing new is introduced to the card.

	:not(.open) because the opened card is centred on the viewport by a transform of its own and must
	not carry the grid's offset there, and :not(.dealIn) because the deal has its own keyframes for
	this card - both selectors would otherwise out-specify the deal and the blue card alone would
	skip the turn.
*/
#luckySportsPage .divLuckySportsCard.r50:not(.open):not(.dealIn) { animation-name:luckySportsCardFloatR50; }
#luckySportsPage .divLuckySportsCard.r50:not(.open):not(.dealIn):hover { animation-name:luckySportsCardFloatHoverR50; }

@keyframes luckySportsCardFloatR50
{
	from { transform:translateY(calc(1.31% - 5px)); }
	to { transform:translateY(calc(1.31% + 5px)); }
}

@keyframes luckySportsCardFloatHoverR50
{
	from { transform:translateY(calc(1.31% - 7px)) scale(1.012) rotate(-0.6deg); }
	to { transform:translateY(calc(1.31% - 15px)) scale(1.032) rotate(0.6deg); }
}

@keyframes luckySportsCardHoverGlow
{
	from { filter:drop-shadow(0px 0px 12px var(--themeGlow)); }
	to { filter:drop-shadow(0px 0px 22px var(--themeGlow)) drop-shadow(0px 0px 6px var(--themeColorLight)); }
}

/*
	The deal.

	The four cards turn face up one after another when the page opens, the way a dealer lays a row
	out - they are physical cards, so they arrive by being turned over rather than by fading in.
	Each starts edge-on and a little above its place, swings through to flat and settles.

	The turn is 0.7s with a 190ms stagger - just under a third of the turn, so each card is a
	little past halfway through its own swing as the next one starts. At the 110ms stagger it began
	on, the four overlapped so heavily that the row read as one motion rather than as four cards
	being laid out - the point of dealing them in turn is lost if you cannot see the turn. (The
	turn was 0.82s/220ms first; trimmed on request because the fall read a touch languid once it
	had the stage to itself.)

	The first card waits 1.45s, not the 0.08s it opened on. The header is a two-beat arrival - the
	logo drops in (1.25s), then the sub-text rolls up under it (finishing at 1.73s) - and cards
	falling through the middle of that stole both beats: three things moving reads as commotion,
	one after another reads as a sequence. 1.45s starts the first turn as the sub-text is settling,
	so the eye is handed from the lettering to the cards without a dead frame between them. The row
	is complete at 2.72 seconds, and DELAY_DEAL_IN in luckySports.js is the figure that has to
	follow this one.

	perspective is inside the transform, not on #divLuckySportsCards. A perspective on the container
	would make it the containing block for everything positioned inside it, and the opened card is
	position:fixed against the viewport - it would have been centred on the row instead. Per-element
	perspective gives each card its own vanishing point, which for a row of cards being turned over
	is the truer reading anyway.

	.dealIn is dropped by luckySports.js once the deal is over. It cannot simply be left on the
	card: the bob and the hover swap are animations on the same property, so whichever the card
	carries last is the one that wins, and a card that kept .dealIn would replay the turn every time
	the cursor left it. The class exists for the length of the deal and then the card is an ordinary
	card again.

	Which is why the turn does not land flat. The moment the class comes off, the idle bob starts at
	its own first keyframe - and that keyframe is translateY(-5px), the top of the bob's travel, not
	zero. A turn that ended at zero therefore snapped 5px upwards on the handover and then began to
	bob, which read as the row hitching before it settled. The turn ends where the bob begins, so
	there is nothing to hand over: the card is already at the top of its travel and simply starts
	falling. The glow does the same thing for the same reason, see luckySportsCardDealInGlow below.

	The stagger is a custom property rather than an animation-delay, because the card and its glow
	are two elements running two animations that have to stay in step, and a custom property set on
	the card is inherited by its :before.
*/
@keyframes luckySportsCardDealIn
{
	from
	{
		opacity:0;
		transform:perspective(1200px) translateY(-27px) rotateY(-102deg) scale(0.9);
	}

	55% { opacity:1; }

	to
	{
		opacity:1;
		transform:perspective(1200px) translateY(-5px) rotateY(0deg) scale(1);
	}
}

#luckySportsPage .divLuckySportsCard.dealIn,
#luckySportsPage .divLuckySportsCard.dealIn:hover
{
	animation:luckySportsCardDealIn 0.7s cubic-bezier(0.18, 0.72, 0.28, 1) var(--dealDelay, 0s) both;
	backface-visibility:hidden;
}

#luckySportsPage .divLuckySportsCard.dealIn:nth-child(1) { --dealDelay:1.45s; }
#luckySportsPage .divLuckySportsCard.dealIn:nth-child(2) { --dealDelay:1.64s; }
#luckySportsPage .divLuckySportsCard.dealIn:nth-child(3) { --dealDelay:1.83s; }
#luckySportsPage .divLuckySportsCard.dealIn:nth-child(4) { --dealDelay:2.02s; }

/*
	The blue card carries the registration offset the R50 poster needs (see the R50 float above), so
	it lands on 1.31% rather than on zero. Without its own keyframes it would finish the turn flat
	and then jump down by that offset the moment the bob took over.
*/
#luckySportsPage .divLuckySportsCard.r50.dealIn,
#luckySportsPage .divLuckySportsCard.r50.dealIn:hover
{
	animation-name:luckySportsCardDealInR50;
}

@keyframes luckySportsCardDealInR50
{
	from
	{
		opacity:0;
		transform:perspective(1200px) translateY(calc(1.31% - 27px)) rotateY(-102deg) scale(0.9);
	}

	55% { opacity:1; }

	to
	{
		opacity:1;
		transform:perspective(1200px) translateY(calc(1.31% - 5px)) rotateY(0deg) scale(1);
	}
}

/*
	The glow arrives with the card rather than after it.

	It was held off entirely while the card turned, because a halo around a card that is still
	edge-on is a halo around a sliver and it flickers. Switching it off does fix the flicker, but it
	also means the glow appears from nothing on the frame the class is removed - a pop, on the same
	frame as the card's own handover. So it is faded in over the second half of the turn instead,
	once the card is wide enough to carry it, and it ends on exactly the value the idle glow starts
	from, so that handover is invisible too.

	It reaches that value at 92% rather than at the end. The class is removed by a timer and the
	animation is started by the style engine, so the two are only approximately aligned - a glow
	that was still climbing on its last frame snapped the rest of the way when the class came off,
	which is the pop this exists to avoid. Arriving early leaves the last 65ms flat.
*/
@keyframes luckySportsCardDealInGlow
{
	0%, 55% { filter:drop-shadow(0px 0px 0px rgba(0, 0, 0, 0)); }
	92%, 100% { filter:drop-shadow(0px 0px 7px var(--themeGlow)); }
}

#luckySportsPage .divLuckySportsCard.dealIn:before,
#luckySportsPage .divLuckySportsCard.dealIn:hover:before
{
	animation:luckySportsCardDealInGlow 0.7s ease-in var(--dealDelay, 0s) both;
}

/*
	No deal for a player who has asked for no motion - the four cards are simply there. The class is
	still added and removed on the usual schedule, so nothing downstream has to know about this.
*/
@media (prefers-reduced-motion:reduce)
{
	#luckySportsPage .divLuckySportsCard.dealIn,
	#luckySportsPage .divLuckySportsCard.dealIn:hover,
	#luckySportsPage .divLuckySportsCard.r50.dealIn,
	#luckySportsPage .divLuckySportsCard.r50.dealIn:hover,
	#luckySportsPage .divLuckySportsCard.dealIn:before,
	#luckySportsPage .divLuckySportsCard.dealIn:hover:before
	{
		animation:none;
	}
}

/*
	The opened card is held still. It is positioned from the viewport centre with a transform of its
	own, which the bob would overwrite, and a card being scratched should not move under the finger.
*/
#luckySportsPage .divLuckySportsCard.open,
#luckySportsPage .divLuckySportsCard.open:hover
{
	animation:none;
}

#luckySportsPage .divLuckySportsCard.open:before,
#luckySportsPage .divLuckySportsCard.open:hover:before
{
	animation:none;
	filter:drop-shadow(0px 0px 20px var(--themeGlow));
}

/*
	The win, on the frame the symbols come up.

	The card is a physical object and it has just paid out, so it reacts: a short knock sideways
	that damps out, and two beats of light through the whole card. Both are deliberately over in
	about half a second - it is punctuation for the reveal, not a state the card sits in. The
	matched cells carry the sustained part of the win (see .divSymbol.match), and something that
	kept shaking under a player trying to read the prize would be in the way of it.

	The shake carries the centring translate because that is what positions the opened card; a
	keyframe that set only the knock would throw the card into the top-left corner for the length
	of it. Percentages inside translate resolve against the element's own box, so the pixel offsets
	are added with calc rather than folded into the percentage.

	Neither animation is filled. When they finish the properties fall back to the card's own
	declarations, which also takes the filter off - a filter left in place makes the card a
	containing block for anything fixed inside it, and there is no reason to leave that behind for
	an effect that has ended.

	:hover is spelled out because .open:hover above switches animations off, and it is the same
	weight as this rule.
*/
#luckySportsPage .divLuckySportsCard.open.win,
#luckySportsPage .divLuckySportsCard.open.win:hover
{
	animation:luckySportsCardWinShake 0.52s cubic-bezier(0.36, 0.07, 0.19, 0.97), luckySportsCardWinFlash 0.66s ease-out;
}

#luckySportsPage .divLuckySportsCard.open.win:before,
#luckySportsPage .divLuckySportsCard.open.win:hover:before
{
	animation:luckySportsCardWinHalo 0.66s ease-out;
}

@keyframes luckySportsCardWinShake
{
	0% { transform:translate(-50%, -50%) rotate(0deg); }
	12% { transform:translate(calc(-50% - 9px), -50%) rotate(-1.7deg); }
	28% { transform:translate(calc(-50% + 9px), -50%) rotate(1.7deg); }
	44% { transform:translate(calc(-50% - 6px), -50%) rotate(-1.1deg); }
	60% { transform:translate(calc(-50% + 4px), -50%) rotate(0.7deg); }
	78% { transform:translate(calc(-50% - 2px), -50%) rotate(-0.3deg); }
	100% { transform:translate(-50%, -50%) rotate(0deg); }
}

@keyframes luckySportsCardWinFlash
{
	0% { filter:brightness(1) saturate(1); }
	12% { filter:brightness(1.85) saturate(1.3); }
	30% { filter:brightness(1) saturate(1); }
	46% { filter:brightness(1.5) saturate(1.2); }
	100% { filter:brightness(1) saturate(1); }
}

@keyframes luckySportsCardWinHalo
{
	0% { filter:drop-shadow(0px 0px 20px var(--themeGlow)); }
	12% { filter:drop-shadow(0px 0px 46px var(--themeColorLight)) drop-shadow(0px 0px 14px #ffffff); }
	30% { filter:drop-shadow(0px 0px 20px var(--themeGlow)); }
	46% { filter:drop-shadow(0px 0px 38px var(--themeColorLight)) drop-shadow(0px 0px 10px #ffffff); }
	100% { filter:drop-shadow(0px 0px 20px var(--themeGlow)); }
}

/* A win that is announced without being thrown about, for a player who has asked for no motion. */
@media (prefers-reduced-motion:reduce)
{
	#luckySportsPage .divLuckySportsCard.open.win,
	#luckySportsPage .divLuckySportsCard.open.win:hover,
	#luckySportsPage .divLuckySportsCard.open.win:before,
	#luckySportsPage .divLuckySportsCard.open.win:hover:before
	{
		animation:none;
	}
}

/*
	The loss, on the same frame, and deliberately the opposite shape of movement.

	The win knocks the card sideways and floods it with light. This drops it: one heavy bump
	downwards with a slight squash, damping out, and the light goes the other way - a beat of
	near-darkness rather than a flash of white. A losing card that shook the same way as a winning
	one would read as a second kind of celebration.

	Three of the four parts are over in about two thirds of a second and leave nothing behind. The
	fourth does: the card keeps a dark veil and a collapsed halo, because a spent card should go on
	looking spent while the player decides whether to open another. The result band is at z-index 5
	and the close button at 6, so both stay at full strength over it.

	The veil is the card's own :after - :before is the poster artwork - and it borrows the poster's
	border-radius so the darkness stops where the frame does rather than squaring off the corners.
*/
#luckySportsPage .divLuckySportsCard.open.lose,
#luckySportsPage .divLuckySportsCard.open.lose:hover
{
	animation:luckySportsCardLoseBump 0.62s cubic-bezier(0.22, 1.2, 0.36, 1), luckySportsCardLoseFlash 0.66s ease-out;
}

#luckySportsPage .divLuckySportsCard.open.lose:before,
#luckySportsPage .divLuckySportsCard.open.lose:hover:before
{
	animation:luckySportsCardLoseHalo 0.66s ease-out both;
}

#luckySportsPage .divLuckySportsCard.open.lose:after
{
	animation:luckySportsCardLoseVeil 0.66s ease-out both;
	background:radial-gradient(circle at 50% 40%, rgba(6, 2, 22, 0.3) 0%, rgba(3, 1, 12, 0.72) 82%);
	border-radius:5.8% / 4.05%;
	bottom:0px;
	content:"";
	left:0px;
	pointer-events:none;
	position:absolute;
	right:0px;
	top:0px;
	z-index:4;
}

@keyframes luckySportsCardLoseBump
{
	0% { transform:translate(-50%, -50%) scale(1); }
	18% { transform:translate(-50%, calc(-50% + 11px)) scale(0.976); }
	40% { transform:translate(-50%, calc(-50% - 3px)) scale(1.006); }
	62% { transform:translate(-50%, calc(-50% + 4px)) scale(0.992); }
	82% { transform:translate(-50%, calc(-50% - 1px)) scale(1.002); }
	100% { transform:translate(-50%, -50%) scale(1); }
}

@keyframes luckySportsCardLoseFlash
{
	0% { filter:brightness(1) saturate(1); }
	16% { filter:brightness(0.4) saturate(0.3); }
	42% { filter:brightness(1.02) saturate(0.9); }
	64% { filter:brightness(0.7) saturate(0.58); }
	100% { filter:brightness(1) saturate(1); }
}

@keyframes luckySportsCardLoseHalo
{
	0% { filter:drop-shadow(0px 0px 20px var(--themeGlow)); }
	16% { filter:drop-shadow(0px 0px 3px var(--themeGlow)); }
	100% { filter:drop-shadow(0px 0px 9px rgba(4, 1, 30, 0.85)); }
}

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

/*
	No bump and no dip in the light for a player who has asked for no motion - but the veil and the
	collapsed halo stay. They are not decoration, they are the card telling the player it is spent,
	and with the animations off both are simply there from the start.
*/
@media (prefers-reduced-motion:reduce)
{
	#luckySportsPage .divLuckySportsCard.open.lose,
	#luckySportsPage .divLuckySportsCard.open.lose:hover,
	#luckySportsPage .divLuckySportsCard.open.lose:after
	{
		animation:none;
	}

	#luckySportsPage .divLuckySportsCard.open.lose:before,
	#luckySportsPage .divLuckySportsCard.open.lose:hover:before
	{
		animation:none;
		filter:drop-shadow(0px 0px 9px rgba(4, 1, 30, 0.85));
	}
}

/* ---------------------------------------------------------------- feature bar */

#luckySportsPage #divLuckySportsFeatures
{
	background:linear-gradient(180deg, rgba(78, 22, 154, 0.35) 0%, rgba(20, 8, 36, 0.35) 100%);
	border:1px solid rgba(148, 80, 236, 0.45);
	border-radius:14px;
	display:flex;
	flex-wrap:wrap;
	gap:10px;
	justify-content:space-around;
	margin-top:26px;
	padding:14px 16px;
}

/*
	Four items across the foot of the page, and the size is per breakpoint rather than one figure.

	On a desktop the bar is the full width of the content and the four sit a long way apart, so 11px
	read as fine print in a lot of space. It is 15px there now. Below 800px the row is tighter and
	below 600px it wraps to two columns at a 174px card - the type comes down with the room rather
	than pushing the labels onto a third line.

	The emoji follows the type at each step, because the gap between an icon and its label is the
	one proportion in here that has to hold.
*/
#luckySportsPage .divFeature
{
	align-items:center;
	color:#ffffff;
	display:flex;
	font-size:15px;
	font-weight:700;
	gap:10px;
	letter-spacing:1px;
	text-transform:uppercase;
	white-space:nowrap;
}

/*
	One line on a desktop, two on a phone, from the same markup.

	The break is written into the HTML and hidden here rather than the other way round, because a
	break put in by CSS cannot be put in reliably - there is no property that says "break here" for
	an inline run. Hiding a <br/> is one declaration and works everywhere.

	The space after each <br/> in the markup is what makes this safe: with the break gone the two
	words need something between them, and a space in the source is the only thing that survives
	both states. At the phone breakpoint the break comes back and that space is collapsed at the
	start of the second line, where a space costs nothing.
*/
#luckySportsPage .divFeature br
{
	display:none;
}

#luckySportsPage .divFeature:before
{
	color:#b06bff;
	font-size:21px;
	line-height:1;
}

#luckySportsPage .divFeature.instantFun:before { content:"\26A1"; }
#luckySportsPage .divFeature.excitingPrizes:before { content:"\1F3C6"; }
#luckySportsPage .divFeature.fairPlay:before { content:"\1F6E1"; }
#luckySportsPage .divFeature.safeSecure:before { content:"\1F512"; }

/* ---------------------------------------------------------------- info popup / T&Cs */

/*
	The how-it-works copy, which used to sit at the foot of the page under the feature bar - the one
	place a player who has not worked out what to do will never look.

	The layer is the dimmer and the click-away target in one: the panel is centred in it, so a tap
	anywhere outside the panel lands on the layer itself and closes. It sits above the opened card
	(z-index 20) and its overlay (19) only so the stacking is unambiguous; the two cannot both be
	open, because #btnInfo is below the card overlay and cannot be reached while a card is up.
*/
#luckySportsPage #divLuckySportsInfo
{
	align-items:center;
	background-color:rgba(4, 2, 20, 0.82);
	bottom:0px;
	display:flex;
	justify-content:center;
	left:0px;
	opacity:0;
	padding:20px;
	pointer-events:none;
	position:fixed;
	right:0px;
	top:0px;
	transition:opacity 0.25s;
	z-index:25;
}

#luckySportsPage #divLuckySportsInfo.show
{
	opacity:1;
	pointer-events:auto;
}

/*
	The panel carries the page's own backdrop rather than a flat fill, so the popup reads as a piece
	of the same scene instead of a system dialog dropped on top of it. background-color sits under
	the image for the moment before it arrives and for the edges cover does not reach.

	The gradient over the image is not decoration. The backdrop is a dark field crossed by bright
	purple streaks, and cover on a 440px box magnifies a crop of it - so a streak can land straight
	under a line of copy. The scrim holds the whole panel dark enough for white text at every crop
	while the artwork still reads through it.

	The "?v=" is a hand-turned counter, not the app version - see the note above
	#divLuckySportsHeader h1 for why an image named inside a stylesheet needs its own. It must match
	the counter on .divPageBackground, which names the same two files.
*/
#luckySportsPage #divLuckySportsInfoPopup
{
	background-color:#020226;
	background-image:linear-gradient(rgba(6, 3, 28, 0.72), rgba(6, 3, 28, 0.72)), url('images/background.webp');
	background-position:center center;
	background-repeat:no-repeat;
	background-size:cover, cover;
	border:1px solid rgba(148, 80, 236, 0.45);
	border-radius:14px;
	box-shadow:0px 0px 30px rgba(0, 0, 0, 0.6);
	box-sizing:border-box;
	color:#d5c9ea;
	font-size:14px;
	max-height:90vh;
	max-width:440px;
	overflow-y:auto;
	padding:26px 28px;
	position:relative;
	width:100%;
}

#luckySportsPage #divLuckySportsInfoPopup h4
{
	color:#ffffff;
	margin:0px 34px 12px 0px;
}

#luckySportsPage #divLuckySportsInfoPopup ol
{
	margin:0px;
	padding-left:20px;
}

#luckySportsPage #divLuckySportsInfoPopup li
{
	margin-bottom:8px;
}

#luckySportsPage #divLuckySportsInfoPopup p
{
	color:#b06bff;
	margin:14px 0px 0px 0px;
}

/* Same control as the opened card's close button, minus the card's theme variable - the popup is
   not themed, so the ring is the page purple. */
#luckySportsPage #btnCloseInfo
{
	background-color:rgba(0, 0, 0, 0.6);
	border:1px solid #b06bff;
	border-radius:50%;
	cursor:pointer;
	height:30px;
	position:absolute;
	right:16px;
	top:16px;
	width:30px;
}

#luckySportsPage #btnCloseInfo:before,
#luckySportsPage #btnCloseInfo:after
{
	background-color:#ffffff;
	content:"";
	height:2px;
	left:50%;
	position:absolute;
	top:50%;
	width:13px;
}

#luckySportsPage #btnCloseInfo:before
{
	transform:translate(-50%, -50%) rotate(45deg);
}

#luckySportsPage #btnCloseInfo:after
{
	transform:translate(-50%, -50%) rotate(-45deg);
}

#luckySportsPage #divTermsAndConditions
{
	margin-top:26px;
}

#luckySportsPage #h3TermsAndConditions
{
	color:#ffffff;
	cursor:pointer;
	position:relative;
}

#luckySportsPage #h3TermsAndConditions:after
{
	content:"\25BC";
	font-size:12px;
	margin-left:8px;
}

#luckySportsPage #divTermsAndConditions h4,
#luckySportsPage #divTermsAndConditions p,
#luckySportsPage #divTermsAndConditions hr
{
	display:none;
}

#luckySportsPage #divTermsAndConditions.show h4,
#luckySportsPage #divTermsAndConditions.show p,
#luckySportsPage #divTermsAndConditions.show hr
{
	display:block;
}

#luckySportsPage #divTermsAndConditions.show #h3TermsAndConditions:after
{
	content:"\25B2";
}

#luckySportsPage #divTermsAndConditions h4
{
	color:#ffffff;
	margin-bottom:6px;
	margin-top:16px;
}

#luckySportsPage #divTermsAndConditions p
{
	color:#d5c9ea;
	font-size:14px;
	margin:4px 0px;
}

#luckySportsPage .textPurple { color:#b06bff; }
#luckySportsPage .textBlue { color:#2f9dff; }

/* ---------------------------------------------------------------- breakpoints */

@media (max-width:1200px)
{
	#luckySportsPage
	{
		padding:0px 30px 30px 30px;
	}

	/* The menu overlays the page from here down instead of pushing it (app.css:344), so the
	   backdrop goes back to spanning the full viewport. */
	#luckySportsPage .divPageBackground
	{
		left:0px;
	}

	/* 2 x 2 from here down - this is the layout the design vision is drawn at */
	#luckySportsPage .divLuckySportsCard
	{
		flex:0 0 calc((100% / 2) - 9px);
	}
}

@media (max-width:800px)
{
	#luckySportsPage
	{
		padding:0px 20px 20px 20px;
	}

	#luckySportsPage .divPageBackground
	{
		background-image:url('images/backgroundMobile.webp');
	}

	#luckySportsPage #divLuckySportsInfoPopup
	{
		background-image:linear-gradient(rgba(6, 3, 28, 0.72), rgba(6, 3, 28, 0.72)), url('images/backgroundMobile.webp');
	}

	#luckySportsPage #divLuckySportsHeader h1
	{
		max-width:420px;
	}

	#luckySportsPage #btnMuteMusic
	{
		right:20px;
	}

	/*
		On a phone the two buttons crossed over from a pair in the top right to one on each side.
		Stacked side by side they read as a group with the artwork pushed off centre; split, they
		frame it - info left, the wordmark between them, the speaker right. The offsets match the
		page padding above so both sit the same distance from their own edge.
	*/
	#luckySportsPage #btnInfo
	{
		left:20px;
		right:auto;
	}

	/* The row tightens here, so the type steps back towards where it was. */
	#luckySportsPage .divFeature
	{
		font-size:12px;
		gap:8px;
	}

	#luckySportsPage .divFeature:before
	{
		font-size:17px;
	}
}

/*
	Mobile is the primary target - 80-90% of the player base is on a phone - so this block is a
	vertical budget, not a shrink of the desktop card. The goal is all four cards reachable in the
	first screen: two full rows of two, with the second row breaking the fold rather than sitting
	below it.

	There is far less to tune than there used to be. The card is one image with a fixed aspect
	ratio, so its height follows from the column width and nothing inside it needs a breakpoint -
	the scratch panel, the nine cells and the symbols are all percentages of the card. What is left
	is the page padding, the gap between cards, and the two labels that are real text.
*/
@media (max-width:600px)
{
	#luckySportsPage
	{
		padding:0px 15px 15px 15px;
	}

	#luckySportsPage #divLuckySportsHeader
	{
		padding:4px 0px 16px 0px;
	}

	#luckySportsPage #divLuckySportsCards
	{
		gap:12px;
	}

	#luckySportsPage .divLuckySportsCard
	{
		flex:0 0 calc((100% / 2) - 6px);
	}

	/*
		The scene is 1.94:1, so every pixel of width costs 0.52 of height. 250px keeps the wordmark
		legible while staying inside the vertical budget this block exists to protect - the four
		cards have to remain reachable in the first screen.
	*/
	#luckySportsPage #divLuckySportsHeader h1
	{
		max-width:250px;
	}

	#luckySportsPage #btnMuteMusic
	{
		right:15px;
		top:2px;
	}

	#luckySportsPage #btnInfo
	{
		left:15px;
		top:2px;
	}

	/* At a 174px card the panel is about 120px across, so the pill has to give ground. */
	#luckySportsPage .divFoilPreview span
	{
		font-size:8px;
		letter-spacing:1px;
		padding:3px 7px;
	}

	/* The bar wraps beside a 174px card down here, so the labels go back to two lines. */
	#luckySportsPage .divFeature
	{
		font-size:9px;
		white-space:normal;
	}

	#luckySportsPage .divFeature br
	{
		display:inline;
	}

	/*
		The played card goes back to a 174px tile, where the band is 47px tall and its padding
		leaves 37px of that for text - the grid sizes above need 57px and would be clipped. These
		come to 33px, which keeps a couple of pixels in hand for a taller line box on a font that
		is not Jost. The opened card is a fixed 640px at every width, so its own sizes are left
		alone here.
	*/
	#luckySportsPage .spanResultLabel
	{
		font-size:8px;
		letter-spacing:1.5px;
	}

	#luckySportsPage .spanResultPrize
	{
		font-size:21px;
	}

	#luckySportsPage .spanResultSubtitle
	{
		font-size:8px;
		margin-top:2px;
	}
}
