/* ==========================================================================
   Poster Wall — the native nominee slideshow (shining/slideshow.php).

   Written against the --sa-* tokens only. Two things carry the whole design:

   1. ONE number per device — the card height — decides how big a poster is.
      The width follows from the image's own shape, so a 4:5 nominee card and
      a 16:9 still can share a rail with nothing cropped and no black bars.
      The three device values all ship in the markup; THIS file decides which
      is live, which is what keeps the component page-cacheable.

   2. The viewport is a real scroll container with scroll-snap. The rail
      therefore works with no JavaScript at all — which matters here more than
      most places, because LiteSpeed's Delay JS holds theme scripts until the
      first interaction.

   🔴 This component lives inside `.entry-content`, so prose rules outrank
   single-class component rules (see the .entry-content leak rule). Every
   figure/ul/li/img/a reset below is load-bearing, not defensive noise.
   ========================================================================== */

.sa-ps {
	--sa-ps-h: var(--sa-ps-hd, 480px);
	--sa-ps-n: var(--sa-ps-nd, 3);
	--sa-ps-gap: 16px;
	--sa-ps-rad: 16px;
	--sa-ps-ms: 450ms;

	position: relative;
	margin: 40px 0 8px;
	clear: both;
	/* Count mode measures a card against THIS box, not the viewport — the
	   article column is narrower than the screen and the difference is the
	   whole sidebar. */
	container-type: inline-size;
	/* 🔴 Nothing this component draws may ever make the PAGE scroll sideways —
	   the deck's fan and the hover glow both reach past their box on purpose.
	   `clip`, not `hidden`: hidden would make this a scroll container and cut
	   the lift off the top of a hovered card as well. */
	overflow-x: clip;
	overflow-clip-margin: 24px;
}

@media (max-width: 991px) {
	.sa-ps { --sa-ps-h: var(--sa-ps-ht, 400px); --sa-ps-n: var(--sa-ps-nt, 2); }
}
@media (max-width: 600px) {
	.sa-ps { --sa-ps-h: var(--sa-ps-hm, 340px); --sa-ps-n: var(--sa-ps-nm, 1.15); }
}

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

/* Tools align to the TOP of the text block, level with the heading. Aligning
   them to the bottom instead puts the counter halfway down a wrapped
   sub-heading, where it reads as a stray number inside the sentence. */
.sa-ps__head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 16px;
	margin-bottom: 16px;
}

.sa-ps__headtext { min-width: 0; }

.sa-ps .sa-ps__title {
	margin: 0 !important;
	padding: 0 !important;
	font-family: var(--sa-font-display);
	font-size: clamp(19px, 2.4vw, 25px);
	font-weight: 700;
	line-height: 1.2;
	letter-spacing: -0.01em;
	color: var(--sa-text);
	border: 0 !important;
}

/* The sparkle motif, as a lead-in rather than an ornament. */
.sa-ps .sa-ps__title::before {
	content: "\2726";
	margin-right: 9px;
	font-size: 0.72em;
	vertical-align: 0.14em;
	background: var(--sa-gradient);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.sa-ps .sa-ps__sub {
	margin: 6px 0 0 !important;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--sa-text-dim);
	max-width: 58ch;
}
/* On a phone this line is introducing artwork the visitor can already see —
   two lines of it is context, three is an obstacle between them and the cards. */
@media (max-width: 600px) {
	.sa-ps .sa-ps__sub {
		display: -webkit-box;
		-webkit-line-clamp: 2;
		-webkit-box-orient: vertical;
		overflow: hidden;
	}
	.sa-ps .sa-ps__counter { padding-top: 3px; }
}

.sa-ps__tools {
	display: flex;
	align-items: center;
	gap: 8px;
	flex: 0 0 auto;
}

.sa-ps .sa-ps__counter {
	margin: 0 !important;
	display: inline-flex;
	align-items: baseline;
	gap: 3px;
	font-variant-numeric: tabular-nums;
	font-size: 12.5px;
	font-weight: 600;
	color: var(--sa-text-dim);
	padding-right: 4px;
}
.sa-ps__counter [data-sa-ps-cur] { color: var(--sa-text); }
.sa-ps__of { opacity: .5; }

/* ---------------------------------------------------------------- arrows --- */

.sa-ps .sa-ps__arrow {
	/* 🔴 skin.css gives every :focus-visible button `position: relative` at a
	   specificity single-class rules cannot beat, so this stays scoped. */
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	padding: 0;
	border: 1px solid var(--sa-border);
	border-radius: 50%;
	background: var(--sa-surface-1);
	color: var(--sa-text-muted);
	font-size: 19px;
	line-height: 1;
	cursor: pointer;
	transition: color .18s ease, border-color .18s ease, background .18s ease, opacity .2s ease, transform .18s ease;
}
.sa-ps .sa-ps__arrow:hover:not(:disabled) {
	color: var(--sa-on-accent);
	border-color: transparent;
	background: var(--sa-gradient);
	transform: translateY(-1px);
}
.sa-ps .sa-ps__arrow:disabled { opacity: .3; cursor: default; }

/* `hover` mode: chrome fades in for a pointer, and is never in the way of a
   thumb. Hidden with opacity, never display/visibility — either would make the
   buttons unfocusable and put them out of a keyboard user's reach entirely. */
@media (hover: hover) {
	.sa-ps.is-arrows-hover .sa-ps__arrow { opacity: 0; pointer-events: none; }
	.sa-ps.is-arrows-hover:hover .sa-ps__arrow,
	.sa-ps.is-arrows-hover:focus-within .sa-ps__arrow { opacity: 1; pointer-events: auto; }
}
@media (hover: none) {
	/* A touch device navigates by swiping; the arrows are pure clutter. */
	.sa-ps.is-arrows-hover .sa-ps__arrow { display: none; }
}

/* -------------------------------------------------------------- viewport --- */

.sa-ps__viewport {
	position: relative;
	overflow-x: auto;
	overflow-y: hidden;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	scroll-padding-inline: 2px;
	overscroll-behavior-x: contain;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	/* Bleed the scroll container just past the text column so a card's glow
	   and the snap edge are not clipped by the article's own padding. */
	margin-inline: -2px;
	padding: 2px 2px 4px;
}
.sa-ps__viewport::-webkit-scrollbar { display: none; }
.sa-ps__viewport:focus-visible {
	outline: 2px solid var(--sa-focus);
	outline-offset: 3px;
	border-radius: var(--sa-ps-rad);
}

.sa-ps .sa-ps__track {
	display: flex;
	gap: var(--sa-ps-gap);
	margin: 0 !important;
	padding: 0 !important;
	list-style: none !important;
	width: max-content;
	max-width: none;
}

.sa-ps .sa-ps__card {
	flex: 0 0 auto;
	margin: 0 !important;
	padding: 0 !important;
	list-style: none !important;
	/* 🔴 START, not centre. A rail shows two or three posters at once, and a
	   centred snap point for card 2 of 3 sits at a NEGATIVE scroll offset — so
	   "next" computed a position left of zero, the browser clamped it, and the
	   arrows moved nothing at all. Centring is only right where exactly one
	   card is on stage; those layouts ask for it below. */
	scroll-snap-align: start;
	scroll-snap-stop: always;
}
/* One card on stage: centre it. */
.sa-ps--spotlight .sa-ps__card,
.sa-ps--filmstrip .sa-ps__card { scroll-snap-align: center; }
.sa-ps .sa-ps__card::before,
.sa-ps .sa-ps__card::marker { content: none !important; }

/* ------------------------------------------------------------- the sizes --- */

/* Height mode: the height is the control and the width falls out of the
   image's own shape. `min(…, 92vw)` is what stops a tall setting on a narrow
   screen from asking for width the screen has not got. */
.sa-ps.is-fit-height .sa-ps__card {
	width: min(calc(var(--sa-ps-h) * var(--sa-ps-arn, var(--sa-c-ar, 0.8))), 92vw);
}

/* Count mode: how many fit is the control, so every card is one n-th of the
   column and the height follows the shared ratio. The fallback below is the
   height-mode width, so a browser without container-query units gets a
   sensible rail rather than a broken one. */
.sa-ps.is-fit-count .sa-ps__card {
	width: min(calc(var(--sa-ps-h) * var(--sa-ps-arn, var(--sa-c-ar, 0.8))), 92vw);
}
@supports (width: 1cqw) {
	.sa-ps.is-fit-count .sa-ps__card {
		width: calc((100cqw - (var(--sa-ps-gap) * (var(--sa-ps-n) - 1))) / var(--sa-ps-n));
	}
}

.sa-ps .sa-ps__frame {
	position: relative;
	display: block;
	margin: 0 !important;
	padding: 0 !important;
	overflow: hidden;
	border-radius: var(--sa-ps-rad);
	border: 1px solid var(--sa-border);
	background: var(--sa-surface-1);
	aspect-ratio: var(--sa-ps-ar, var(--sa-c-ar, 4 / 5));
	transition: border-color .22s ease, box-shadow .22s ease, transform .22s ease;
}

.sa-ps .sa-ps__img {
	position: absolute;
	inset: 0;
	display: block;
	width: 100% !important;
	height: 100% !important;
	max-width: none !important;
	margin: 0 !important;
	border-radius: 0;
	/* `contain`, never `cover` — a nominee card that has been cropped is a
	   nominee card with somebody's face cut off. */
	object-fit: contain;
	z-index: 1;
}

/* Only rendered when a card is cut to a shape that is not the image's own:
   the same file again, blurred, so the space beside the poster is a wash of
   its own colours instead of a black bar. Same URL = one request. */
.sa-ps .sa-ps__bg {
	position: absolute;
	inset: -6%;
	display: block;
	width: 112% !important;
	height: 112% !important;
	max-width: none !important;
	margin: 0 !important;
	object-fit: cover;
	filter: blur(26px) saturate(1.5) brightness(.62);
	transform: scale(1.06);
	z-index: 0;
}

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

.sa-ps.has-glow .sa-ps__card:hover .sa-ps__frame,
.sa-ps.has-glow .sa-ps__card:focus-within .sa-ps__frame {
	border-color: var(--sa-border-strong);
	box-shadow: var(--sa-shadow-card), 0 0 0 1px var(--sa-border-strong), var(--sa-glow);
	transform: translateY(-3px);
}

/* --------------------------------------------------------------- zoom --- */

.sa-ps .sa-ps__zoom {
	position: absolute;
	inset: 0;
	z-index: 2;
	width: 100%;
	height: 100%;
	padding: 0;
	border: 0;
	background: transparent;
	cursor: zoom-in;
	display: block;
	/* 🔴 The base theme rounds every <button> to a pill (`border-radius: 999px`).
	   This one is the whole poster, and a 999px radius on a 262×328 box is an
	   ELLIPSE — so focusing it drew a huge oval ring across the artwork. It has
	   to follow the frame's corners instead. */
	border-radius: inherit;
}
.sa-ps .sa-ps__zoom:focus-visible {
	outline: 2px solid var(--sa-focus);
	outline-offset: -4px;
	border-radius: inherit;
}
.sa-ps .sa-ps__zoomic {
	position: absolute;
	right: 10px;
	bottom: 10px;
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	border-radius: 50%;
	background: rgba(0, 0, 0, .5);
	backdrop-filter: blur(6px);
	color: #fff;
	font-size: 15px;
	line-height: 1;
	opacity: 0;
	transform: translateY(4px);
	transition: opacity .2s ease, transform .2s ease;
}
@media (hover: hover) {
	.sa-ps__card:hover .sa-ps__zoomic,
	.sa-ps__zoom:focus-visible .sa-ps__zoomic { opacity: 1; transform: none; }
}
@media (hover: none) {
	/* The affordance goes, the behaviour stays: tapping the card still opens it. */
	.sa-ps .sa-ps__zoomic { display: none; }
}

/* --------------------------------------------------------------- caption --- */

.sa-ps .sa-ps__cap {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	margin: 10px 2px 0 !important;
	padding: 0 !important;
	/* 🔴 `border: 0` belongs here even though single.css now excludes us from
	   the prose figcaption rule: this is a <figcaption> inside .entry-content,
	   and the next theme-side caption style to land on it would put the same
	   stray edge back. The component decides its own edges. */
	border: 0 !important;
	font-size: 13px;
	text-align: left;
	color: var(--sa-text-muted);
	min-width: 0;
}

/* 🔴 The `a:not([class*="sa-"])` prose guard in skin.css is why every anchor
   here carries an sa- class. Without it .entry-content's link colour and
   underline win. */
/* The name block carries a deliberate accent rule down its left edge — the
   same visual the leaked prose caption used to fake, but owned here, with the
   gap that makes it read as a marker instead of as a collision. It is a
   pseudo-element rather than a border so the 2px never counts against the
   name's own width when the flex row gets tight. */
.sa-ps .sa-ps__who {
	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 2px;
	min-width: 0;
	padding-left: 11px;
	text-decoration: none !important;
	border: 0 !important;
	color: inherit;
}
.sa-ps .sa-ps__who::before {
	content: "";
	position: absolute;
	left: 0;
	top: 1px;
	bottom: 1px;
	width: 3px;
	border-radius: 999px;
	background: linear-gradient(180deg, var(--sa-vote) 0%, var(--sa-vote-2) 100%);
	opacity: .85;
	transition: opacity .18s ease;
}
.sa-ps a.sa-ps__who:hover .sa-ps__name { color: var(--sa-violet-light); }
.sa-ps a.sa-ps__who:hover::before { opacity: 1; }

.sa-ps__name {
	font-weight: 700;
	font-size: 14px;
	line-height: 1.25;
	color: var(--sa-text);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	transition: color .18s ease;
}
.sa-ps__group {
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: .08em;
	line-height: 1.2;
	text-transform: uppercase;
	color: var(--sa-text-dim);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.sa-ps__acts {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	/* Shrinks before it wraps: a long nominee name and a long group name are
	   both ellipsised above, so the row stays one line at every width. */
	flex: 0 1 auto;
	min-width: 0;
	/* Holds the right edge even when it is the row's only child — with the name
	   switched off, `space-between` alone would park the button on the left. */
	margin-left: auto;
}

.sa-ps .sa-ps__act {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 5px;
	height: 30px;
	max-width: 100%;
	padding: 0 13px;
	overflow: hidden;
	border: 1px solid var(--sa-border);
	border-radius: var(--sa-radius-pill);
	background: var(--sa-surface-1);
	color: var(--sa-text-muted);
	font-family: inherit;
	font-size: 11.5px;
	font-weight: 700;
	line-height: 1;
	text-decoration: none !important;
	cursor: pointer;
	white-space: nowrap;
	transition: color .18s ease, background .18s ease, border-color .18s ease;
}
.sa-ps .sa-ps__act:hover {
	color: var(--sa-text);
	border-color: var(--sa-border-strong);
	background: var(--sa-surface-2);
}
.sa-ps .sa-ps__act--vote {
	border-color: transparent;
	background: linear-gradient(135deg, var(--sa-vote) 0%, var(--sa-vote-2) 100%);
	color: var(--sa-on-accent);
}
.sa-ps .sa-ps__act--vote:hover {
	color: var(--sa-on-accent);
	filter: brightness(1.08);
	background: linear-gradient(135deg, var(--sa-vote) 0%, var(--sa-vote-2) 100%);
}
/* The nominee's name inside the button gets the same ellipsis treatment as the
   name beside it — "Vote Hwang Minhyun" must not push the button off a phone. */
.sa-ps__act--vote b {
	font-weight: 800;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* A narrow card cannot hold a word label next to every glyph. */
@media (max-width: 600px) {
	.sa-ps__actlab { display: none; }
	.sa-ps .sa-ps__act { padding: 0 11px; }
	.sa-ps .sa-ps__cap { gap: 10px; }
	.sa-ps .sa-ps__who { padding-left: 9px; }
}

/* ----------------------------------------------------------------- pager --- */

.sa-ps__pager { margin-top: 14px; }

.sa-ps__pager--bar {
	position: relative;
	height: 3px;
	border-radius: 999px;
	background: var(--sa-surface-2);
	overflow: hidden;
}
.sa-ps__barfill {
	position: absolute;
	inset: 0 auto 0 0;
	width: 22%;
	border-radius: 999px;
	background: var(--sa-gradient);
	transition: width .2s ease;
}

/* Where the browser has scroll-driven animations, the bar tracks the rail with
   NO JavaScript at all — it is a real progress indicator even before LiteSpeed
   has let a script run. poster-wall.js checks the same support and stands down
   rather than fighting it. The bar is not inside the scroller, so the timeline
   is named on the scroller and lifted into scope by the section. */
/* 🔴 `:not(.is-live)` is load-bearing. A scroll timeline needs something that
   SCROLLS, and the one-card layouts (deck, cross-fade, zoom) turn the viewport
   into a static stack — `overflow: visible`, nothing to scroll — so the
   animation would sit frozen on its first keyframe forever while the JS, seeing
   that the browser supports scroll-driven animation, politely stood down.
   `is-live` is exactly the flag the script sets when it takes the viewport's
   scrolling away, so it is also the right flag to hand the bar back to the
   script. Before the script runs the deck IS still a scrollable rail, and this
   drives the bar correctly then too. */
@supports (animation-timeline: scroll()) and (timeline-scope: --x) {
	.sa-ps:not(.is-live) { timeline-scope: --sa-ps-rail; }
	.sa-ps:not(.is-live) .sa-ps__viewport { scroll-timeline: --sa-ps-rail inline; }
	.sa-ps:not(.is-live) .sa-ps__barfill {
		width: 100%;
		transform-origin: left center;
		animation: sa-ps-scrub linear both;
		animation-timeline: --sa-ps-rail;
	}
	@keyframes sa-ps-scrub {
		from { transform: scaleX(.14); }
		to   { transform: scaleX(1); }
	}
}

.sa-ps__pager--dots {
	display: flex;
	justify-content: center;
	gap: 7px;
	flex-wrap: wrap;
}
.sa-ps .sa-ps__dot {
	position: relative;
	width: 7px;
	height: 7px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: var(--sa-surface-2);
	cursor: pointer;
	transition: background .2s ease, width .2s ease;
}
.sa-ps .sa-ps__dot[aria-selected="true"] {
	width: 20px;
	border-radius: 999px;
	background: var(--sa-gradient);
}

/* =============================================================== layouts === */

/* --- Spotlight: the centred card at full size, its neighbours scaled back.
   Scroll-driven where supported, so it costs no JavaScript; everywhere else it
   degrades to the plain rail, which is a perfectly good component. */
.sa-ps--spotlight .sa-ps__viewport { scroll-padding-inline: 50%; }
.sa-ps--spotlight .sa-ps__track { padding-inline: 22% !important; }
@media (max-width: 600px) {
	.sa-ps--spotlight .sa-ps__track { padding-inline: 12% !important; }
}
@supports (animation-timeline: view()) {
	.sa-ps--spotlight .sa-ps__card {
		animation: sa-ps-spot linear both;
		animation-timeline: view(inline);
		animation-range: cover 12% cover 88%;
	}
	@keyframes sa-ps-spot {
		0%, 100% { transform: scale(.84); opacity: .42; filter: saturate(.55); }
		50%      { transform: scale(1);   opacity: 1;   filter: none; }
	}
}

/* --- Coverflow is Spotlight with the neighbours turned away. */
@supports (animation-timeline: view()) {
	.sa-ps.is-anim-coverflow .sa-ps__card {
		animation: sa-ps-flow linear both;
		animation-timeline: view(inline);
		animation-range: cover 8% cover 92%;
	}
	@keyframes sa-ps-flow {
		0%   { transform: perspective(1100px) rotateY(24deg) scale(.86); opacity: .5; }
		50%  { transform: perspective(1100px) rotateY(0deg) scale(1);    opacity: 1; }
		100% { transform: perspective(1100px) rotateY(-24deg) scale(.86); opacity: .5; }
	}
}

/* --- Mosaic: no rail, no motion, everything at once. The cheapest layout. */
.sa-ps--mosaic .sa-ps__viewport {
	overflow: visible;
	scroll-snap-type: none;
	margin-inline: 0;
	padding: 0;
}
.sa-ps--mosaic .sa-ps__track {
	display: grid;
	/* 🔴 never a bare 1fr here — a minmax floor is what keeps the mobile grid
	   from clipping (see the grid-minmax rule). */
	grid-template-columns: repeat(auto-fill, minmax(min(160px, 100%), 1fr));
	width: auto;
}
.sa-ps--mosaic .sa-ps__card { width: auto !important; }
.sa-ps--mosaic .sa-ps__pager { display: none; }

/* --- Filmstrip: one stage, thumbnails beneath. The thumbnail rail is built by
   the JS; with no JS this stays a full-width snap rail, which still works. */
.sa-ps--filmstrip .sa-ps__card { scroll-snap-align: center; }
.sa-ps--filmstrip.is-fit-height .sa-ps__card {
	width: min(calc(var(--sa-ps-h) * var(--sa-ps-arn, var(--sa-c-ar, 0.8))), 92vw);
}
.sa-ps__thumbs {
	display: flex;
	gap: 8px;
	margin-top: 12px;
	overflow-x: auto;
	scrollbar-width: none;
	padding-bottom: 2px;
}
.sa-ps__thumbs::-webkit-scrollbar { display: none; }
.sa-ps .sa-ps__thumb {
	flex: 0 0 auto;
	width: 46px;
	padding: 0;
	border: 1px solid var(--sa-border);
	border-radius: 8px;
	overflow: hidden;
	background: var(--sa-surface-1);
	cursor: pointer;
	opacity: .5;
	transition: opacity .18s ease, border-color .18s ease, transform .18s ease;
}
.sa-ps .sa-ps__thumb img {
	display: block;
	width: 100%;
	height: auto;
	margin: 0 !important;
}
.sa-ps .sa-ps__thumb[aria-selected="true"] {
	opacity: 1;
	border-color: var(--sa-border-strong);
	transform: translateY(-2px);
}

/* --- Deck: a fanned pile of photocards you throw off the top.
   Needs the JS to advance; without it the cards simply sit in a rail, which is
   a perfectly good component — the `is-live` class is added by the script, not
   by the server, so a visitor whose JS never runs never sees a broken stack. */
.sa-ps--deck.is-live .sa-ps__viewport {
	overflow: visible;
	scroll-snap-type: none;
	/* Room for the fan and the thrown card to leave without a scrollbar. */
	padding: 6px 0 10px;
	margin-inline: 0;
	touch-action: pan-y;
}
.sa-ps--deck.is-live {
	/* How far each card back steps out, and how far it turns. Tuned per device
	   below: a fan sized for an 868px column runs off the side of a phone, and
	   because the pile deliberately overflows its own box that turns into
	   horizontal scroll on the whole PAGE. */
	--sa-deck-x: 24px;
	--sa-deck-r: 2.6deg;
	/* The shrink per step is doing real work, not just perspective: it has to
	   out-pace the downward swing the tilt gives the bottom corner, or the
	   fan's last card ends up underneath the caption. */
	--sa-deck-s: .075;
}
@media (max-width: 600px) {
	.sa-ps--deck.is-live { --sa-deck-x: 15px; --sa-deck-r: 2deg; }
}

.sa-ps--deck.is-live .sa-ps__track {
	display: grid;
	width: 100%;
	place-items: start center;
	/* The fan only leans RIGHT, so nudge the pile left by half of it and the
	   group reads centred even though the top card is not. */
	padding-right: calc(var(--sa-deck-x) * 2.4) !important;
}

.sa-ps--deck.is-live .sa-ps__card {
	--d: var(--sa-deck-i, 0);
	grid-area: 1 / 1;
	/* Each card back is a little smaller and turned a little further, and the
	   shrink is tuned to cancel the tilt: the fan spreads sideways while every
	   card stays inside the top card's vertical band. Letting the backs hang
	   below it is what put them behind the caption. */
	transform-origin: 50% 50%;
	transform:
		translate3d(calc(var(--d) * var(--sa-deck-x)), calc(var(--d) * 5px), 0)
		rotate(calc(var(--d) * var(--sa-deck-r)))
		scale(calc(1 - var(--d) * var(--sa-deck-s)));
	z-index: calc(30 - var(--d));
	pointer-events: none;
	transition:
		transform var(--sa-ps-ms) cubic-bezier(.22, .9, .3, 1),
		opacity var(--sa-ps-ms) ease;
}

/* Only the card on top takes the pointer. */
.sa-ps--deck.is-live .sa-ps__card[data-sa-deck="0"] {
	pointer-events: auto;
	cursor: grab;
}
.sa-ps--deck.is-live .sa-ps__card.is-grabbing { cursor: grabbing; }

/* The cards behind are context, not content: dimmed with an overlay rather
   than `opacity`, so the artwork underneath keeps its own contrast and only
   one card ever competes for attention. */
.sa-ps--deck.is-live .sa-ps__frame::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 3;
	border-radius: inherit;
	pointer-events: none;
	background: #0B0710;
	opacity: calc(var(--d) * 0.16);
	transition: opacity var(--sa-ps-ms) ease;
}
.sa-ps--deck.is-live .sa-ps__card[data-sa-deck="0"] .sa-ps__frame {
	box-shadow: 0 18px 44px rgba(0, 0, 0, .5);
}
/* The artwork is dark navy on a dark page, so without a lit edge the cards
   behind dissolve into the background and the pile stops reading as a pile. */
.sa-ps--deck.is-live .sa-ps__card:not([data-sa-deck="0"]) .sa-ps__frame {
	border-color: var(--sa-border-strong);
	box-shadow: -6px 10px 22px rgba(0, 0, 0, .45);
}

/* Deeper than the fan shows: parked, unpainted, ready to come forward. */
.sa-ps--deck.is-live .sa-ps__card.is-buried { opacity: 0; }

/* Only the top card's caption is readable — the rest would stack into mush. */
.sa-ps--deck.is-live .sa-ps__cap {
	opacity: 0;
	transition: opacity var(--sa-ps-ms) ease;
	pointer-events: none;
}
.sa-ps--deck.is-live .sa-ps__card[data-sa-deck="0"] .sa-ps__cap {
	opacity: 1;
	pointer-events: auto;
	/* Above the whole pile: the fan reaches past the top card on the right, and
	   the Vote button has to stay clickable wherever a back card lands.
	   The margin is measured, not guessed — the shrink above cancels most of
	   the tilt's downward swing but not all of it, and the last card still
	   ends ~15px below the top card. 24px clears that and keeps the label
	   tied to the pile instead of floating between it and the pager. */
	position: relative;
	z-index: 40;
	margin-top: 24px !important;
}

/* The pager belongs to the pile, not to the article column — a 3px bar running
   the full text width under a narrow stack reads as an unrelated rule. */
.sa-ps--deck.is-live .sa-ps__pager {
	max-width: min(calc(var(--sa-ps-h) * var(--sa-ps-arn, 0.8)), 92vw);
	margin-inline: auto;
}

/* While a finger or cursor is on the card, it answers immediately — a
   transition here would make the drag feel like it is lagging behind. */
.sa-ps--deck.is-live .sa-ps__card.is-dragged { transition: none; }

/* Thrown: leaves in the direction it was flung, set as --tx by the script. */
.sa-ps--deck.is-live .sa-ps__card.is-thrown {
	transform: translate3d(var(--tx, 140%), 6%, 0) rotate(var(--tr, 16deg)) scale(.96);
	opacity: 0;
	transition:
		transform 380ms cubic-bezier(.32, 0, .28, 1),
		opacity 300ms ease;
}

/* A deck is a pile you flick, so the swipe hint belongs on the top card. */
.sa-ps--deck.is-live .sa-ps__card[data-sa-deck="0"] .sa-ps__frame::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
	border-radius: inherit;
	pointer-events: none;
	box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .1);
}

/* ============================================================ transitions === */

/* Fade and zoom are one-card modes: the rail becomes a stack and the JS
   cross-fades. Without JS they are still a scrollable rail. */
.sa-ps.is-anim-fade.is-live .sa-ps__viewport,
.sa-ps.is-anim-zoom.is-live .sa-ps__viewport { overflow: hidden; scroll-snap-type: none; }
.sa-ps.is-anim-fade.is-live .sa-ps__track,
.sa-ps.is-anim-zoom.is-live .sa-ps__track { display: grid; width: 100%; place-items: center; }
.sa-ps.is-anim-fade.is-live .sa-ps__card,
.sa-ps.is-anim-zoom.is-live .sa-ps__card {
	grid-area: 1 / 1;
	opacity: 0;
	transition: opacity var(--sa-ps-ms) ease, transform var(--sa-ps-ms) ease;
	pointer-events: none;
}
.sa-ps.is-anim-zoom.is-live .sa-ps__card { transform: scale(1.04); }
.sa-ps.is-anim-fade.is-live .sa-ps__card.is-active,
.sa-ps.is-anim-zoom.is-live .sa-ps__card.is-active {
	opacity: 1;
	transform: none;
	pointer-events: auto;
}

/* Ken Burns drifts the artwork itself; it never changes the frame, so nothing
   can be cropped by it that was not cropped already. */
.sa-ps.is-anim-kenburns .sa-ps__card.is-active .sa-ps__img {
	animation: sa-ps-kb 9s ease-in-out infinite alternate;
}
@keyframes sa-ps-kb {
	from { transform: scale(1) translate3d(0, 0, 0); }
	to   { transform: scale(1.06) translate3d(0, -1.5%, 0); }
}

/* ================================================================= drag === */

@media (hover: hover) {
	.sa-ps__viewport { cursor: grab; }
	.sa-ps__viewport.is-dragging { cursor: grabbing; }
}
/* While a drag is in flight the zoom triggers must not eat the pointer, or
   letting go over a card opens the lightbox on it. */
.sa-ps__viewport.is-dragging .sa-ps__zoom { pointer-events: none; }
.sa-ps__viewport.is-dragging .sa-ps__img { user-select: none; -webkit-user-drag: none; }

/* ================================================== the vote-jump flash === */

/* "Vote for X" scrolls to that nominee inside the TotalPoll widget and rings
   it for a moment. This is the ONLY thing we put on a plugin element, it is a
   class we add and remove ourselves, and it changes no layout — the plugin
   stays read-only. */
.totalpoll-wrapper .sa-ps-flash {
	animation: sa-ps-flash 2.2s ease-out 1;
	border-radius: var(--sa-radius-sm);
}
@keyframes sa-ps-flash {
	0%, 100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
	12%      { box-shadow: 0 0 0 3px var(--sa-vote), var(--sa-glow); }
	70%      { box-shadow: 0 0 0 3px var(--sa-vote), var(--sa-glow); }
}

/* ================================================================ motion === */

@media (prefers-reduced-motion: reduce) {
	.sa-ps__viewport { scroll-behavior: auto; }
	.sa-ps *,
	.sa-ps *::before,
	.sa-ps *::after {
		animation: none !important;
		transition-duration: .01ms !important;
	}
	.sa-ps .sa-ps__card { transform: none !important; opacity: 1 !important; filter: none !important; }
}

/* =========================================================== dark/light === */

/* The theme ships dark-first; a light skin only needs the two surfaces that
   were assuming a dark backdrop. */
.tie-skin-light .sa-ps .sa-ps__zoomic { background: rgba(0, 0, 0, .42); }
