/*
 * Reusable component base styles. Kept intentionally minimal — extend
 * per page rather than duplicating. Check here before adding a new class.
 */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	padding: var(--space-8) var(--space-16);
	border-radius: var(--radius-md);
	border: 1px solid transparent;
	font-family: var(--font-family-sans);
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-label);
	line-height: 1;
}

.btn:active {
	transform: scale(0.97);
	opacity: 0.9;
}

@media (min-width: 768px) {
	.btn:hover {
		opacity: 0.9;
	}
}

.btn--primary {
	background-color: var(--color-accent);
	color: var(--color-white);
}

.btn--outline {
	background-color: transparent;
	border-color: var(--color-border);
	color: var(--color-text-primary);
}

.btn--block {
	width: 100%;
}

.card {
	background-color: var(--color-surface);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	padding: var(--space-16);
	box-shadow: var(--shadow-elevation);
}

/* Skip link — first focusable element on the page. Off-screen until
   tabbed to, then pinned to the top-left as a normal focusable button. */

.skip-link {
	position: fixed;
	top: -100px;
	left: var(--space-16);
	z-index: 100;
	padding: var(--space-8) var(--space-16);
	border-radius: var(--radius-md);
	background-color: var(--color-accent);
	color: var(--color-white);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	transition: top 0.15s ease;
}

.skip-link:focus {
	top: var(--space-16);
}

@media (prefers-reduced-motion: reduce) {
	.skip-link {
		transition: none;
	}
}

/* Section — repeating heading + preview + "view more" pattern used by
   every home-page section. */

.section {
	margin-top: var(--space-48);
}

.section:first-child {
	margin-top: 0;
}

/* A ticker (or any other non-.section strip) placed before the first
   section can shrink the gap below its own border, since it already
   reads as a visual break. */
.ticker + .section {
	margin-top: var(--space-24);
}

.section__header {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-16);
	margin-bottom: var(--space-16);
}

.section__link {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	white-space: nowrap;
}

.section__label {
	display: block;
	margin-bottom: var(--space-8);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

/* Clearance for the sticky top bar when the More menu smooth-scrolls to
   one of these ids — otherwise the target lands partly hidden under it. */
[id^="section-"] {
	scroll-margin-top: calc(var(--topbar-height) + env(safe-area-inset-top) + var(--space-16));
}

/* Ad unit — standalone placeholder ad slots (banners). Reuses .card for
   its surface/border/radius/shadow and .section__label for the always-
   visible "Advertisement"/"Sponsored" disclosure, so this file only
   needs to add the inner banner placeholder itself. Native ads inside
   existing rows (news grid, fixture cards) don't use this class at all —
   they reuse their surrounding card type instead, so they stay visually
   identical to their neighbors apart from the same .section__label. */

.ad-unit__banner {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 50px;
	margin-top: var(--space-8);
	border-radius: var(--radius-sm);
	background-color: var(--color-neutral-fill);
}

@media (min-width: 768px) {
	.ad-unit__banner {
		min-height: 90px;
	}
}

.ad-unit__placeholder {
	padding: var(--space-8);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-align: center;
}

/* Scroll-reveal — opt-in via .js-reveal on <body> (added by js/reveal.js
   only when JS runs and the visitor hasn't asked for reduced motion). No
   JS, no class, no effect: sections are visible and static by default. */

.js-reveal .section {
	opacity: 0;
	transform: translateY(12px);
	transition: opacity 0.2s ease, transform 0.2s ease;
}

.js-reveal .section.is-visible {
	opacity: 1;
	/* none, not translateY(0): visually identical (both are zero
	   movement) but translateY(0) is still a non-none transform value,
	   which establishes a new containing block for descendants — that
	   silently breaks position:sticky for anything nested inside a
	   revealed .section (discovered via pages/squad-builder.html's
	   sticky table header not sticking; CSS transitions animate to/from
	   none just fine, so this changes nothing about the reveal itself). */
	transform: none;
}

@media (prefers-reduced-motion: reduce) {
	.js-reveal .section {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/* Badges — status pills. Live uses the accent (the one allowed use of
   accent-as-fill); upcoming/completed stay neutral and high-contrast. */

.badge {
	display: inline-flex;
	align-items: center;
	align-self: flex-start;
	gap: 5px;
	padding: 2px 8px;
	border-radius: var(--radius-full);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	line-height: 1.35;
}

.badge--live {
	background-color: var(--color-accent);
	color: var(--color-white);
	animation: badge-glow 1.5s ease-in-out infinite;
}

.badge--live::before {
	content: "";
	width: 6px;
	height: 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-white);
	animation: badge-pulse 1.6s ease-in-out infinite;
}

@keyframes badge-pulse {
	0%, 100% { opacity: 1; }
	50% { opacity: 0.35; }
}

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

@media (prefers-reduced-motion: reduce) {
	.badge--live,
	.badge--live::before {
		animation: none;
	}
}

.badge--muted {
	background-color: var(--color-border);
	color: var(--color-text-primary);
}

/* Team badge — small identity chip, replaces bare 3-letter team-code
   text wherever it appeared. Default is neutral; --win (completed-match
   winner) mirrors the existing green win-text convention, text-only to
   keep contrast safe against the neutral fill. */

.team-badge {
	display: inline-flex;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border-radius: var(--radius-full);
	background-color: var(--color-neutral-fill);
	color: var(--color-text-primary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-heading);
	letter-spacing: -0.02em;
}

.team-badge--win {
	color: var(--color-positive);
}

/* Breaking-news ticker — thin strip under the top bar. Pure CSS marquee;
   a visually-hidden list underneath carries the same headlines for
   screen readers, since the marquee track itself is aria-hidden. */

.ticker {
	overflow: hidden;
	background-color: var(--color-surface);
	border-bottom: 1px solid var(--color-border);
	padding-block: var(--space-8);
}

.ticker__track {
	display: flex;
	width: max-content;
	gap: var(--space-32);
	padding-inline: var(--space-16);
	animation: ticker-scroll 24s linear infinite;
}

.ticker:hover .ticker__track,
.ticker:focus-within .ticker__track {
	animation-play-state: paused;
}

@keyframes ticker-scroll {
	from { transform: translateX(0); }
	to { transform: translateX(-50%); }
}

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

.ticker__item {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
	white-space: nowrap;
}

/* Scroll row — horizontally scrollable strip of cards (match strip,
   fixtures). Snap-scrolls, no visible scrollbar. */

.scroll-row {
	display: flex;
	gap: var(--space-16);
	overflow-x: auto;
	scroll-snap-type: x proximity;
	padding-bottom: var(--space-8);
	scrollbar-width: none;
}

.scroll-row::-webkit-scrollbar {
	display: none;
}

/* Needed wherever a .scroll-row doubles as a toggleable tabpanel (e.g.
   Trending Picks): [hidden] and .scroll-row are equal specificity, and
   .scroll-row's own display:flex would otherwise win and keep it
   visible despite the hidden attribute. */
.scroll-row[hidden] {
	display: none;
}

/* Match card — live/upcoming/completed match summary, part of the
   home-page match strip. */

/* Combine with .card for the shared surface/border/radius/shadow —
   only the layout and color-inheritance fix below are match-card-specific. */
.match-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 340px;
	min-height: 220px;
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	color: var(--color-text-primary);
}

.match-card--live {
	border-color: var(--color-accent);
	gap: var(--space-16);
}

.match-card:active {
	transform: scale(0.98);
}

@media (min-width: 768px) {
	.match-card {
		width: 400px;
	}

	.match-card:hover {
		border-color: var(--color-text-secondary);
	}

	.match-card--live:hover {
		border-color: var(--color-accent);
	}
}

.match-card__teams {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.match-card__row {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-8);
}

/* Team info row — badge + team name, no score (upcoming matches only,
   before there's anything to score). Unlike .match-card__row, there's
   no second value to push to the far edge, so the name just grows to
   fill the remaining space next to the badge. */
.match-card__team-info {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.match-card__team-name {
	flex: 1 1 auto;
	min-width: 0;
	overflow-wrap: break-word;
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.match-card__score {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	white-space: nowrap;
}

.match-card__meta {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.match-card__result {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Live-card-only extras: this-over ball strip, commentary, toss, social proof. */

.match-card__balls {
	display: flex;
	gap: var(--space-8);
}

.match-card__ball {
	width: 22px;
	height: 22px;
	flex-shrink: 0;
	border-radius: var(--radius-full);
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: var(--font-family-mono);
	font-size: var(--font-size-xs);
	font-variant-numeric: tabular-nums;
	background-color: var(--color-border);
	color: var(--color-text-primary);
}

.match-card__ball--boundary {
	font-weight: var(--font-weight-heading);
}

.match-card__ball--wicket {
	background-color: var(--color-accent);
	color: var(--color-white);
	font-weight: var(--font-weight-heading);
}

.match-card__social {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Player of the Match / Fantasy MVP — completed-card-only extras. Shared
   .match-card__stat lifts the numeric performance to primary contrast,
   same rule as every other score on the page. */

.match-card__pom {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.match-card__mvp {
	display: flex;
	align-items: center;
	gap: 4px;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.match-card__mvp-icon {
	width: 12px;
	height: 12px;
	flex-shrink: 0;
	color: var(--crimson-text);
}

.match-card__stat {
	color: var(--color-text-primary);
	font-weight: var(--font-weight-label);
	white-space: nowrap;
}

/* Win Prediction — compact pre-filled model prediction on upcoming
   match cards. Reuses the visual language of .poll__option-fill (accent
   fill at low opacity) at a much smaller scale, but deliberately isn't
   built from .poll__option itself: this is static and non-interactive,
   not a vote. Segment widths are per-instance data (56/44, 61/39, ...),
   set via inline style="flex-grow: N" on .win-prediction__segment. */

.win-prediction {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.win-prediction__label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.win-prediction__bar {
	display: flex;
	gap: 2px;
	height: 8px;
	border-radius: var(--radius-full);
	overflow: hidden;
	background-color: var(--color-border);
}

.win-prediction__segment {
	flex-grow: 0;
	flex-basis: 0;
	background-color: var(--color-border);
}

.win-prediction__segment--leader {
	background-color: var(--color-accent);
	opacity: 0.16;
}

.win-prediction__pcts {
	display: flex;
	justify-content: space-between;
}

.win-prediction__pct {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

/* Live match-card inner elements (match-hero__*) — named for the old
   full-width .match-hero design. That block-level wrapper is gone (this
   content now lives directly inside .match-card--live, as one item in
   the shared scroll-row), but these child classes are unchanged, so the
   name stays for now rather than a same-content rename. Rows are still
   grouped with dividers (border-top + padding-top) rather than one flat
   stack, per the original label+value pattern. */

.match-hero__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
}

.match-hero__toss {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	text-align: right;
}

.match-hero__score {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.match-hero__team-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-8);
}

.match-hero__team-score {
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
	text-align: right;
	white-space: nowrap;
}

.match-hero__status {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	padding-top: var(--space-16);
	border-top: 1px solid var(--color-border);
}

.match-hero__label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.match-hero__this-over {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

/* Commentary callout — the "this just happened" moment, set apart from
   the static stats around it with an accent rule, not just color. */

.match-hero__commentary {
	padding: var(--space-8) var(--space-16);
	border-left: 3px solid var(--color-accent);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	font-style: italic;
	color: var(--color-text-primary);
	line-height: var(--line-height-dense);
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	-webkit-box-orient: vertical;
}

.match-hero__footer {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	padding-top: var(--space-16);
	border-top: 1px solid var(--color-border);
}

.match-hero__footer-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
}

/* Sponsor lockup — passive brand credit, not an ad slot: no disclosure
   label needed since "powered by" is itself the disclosure. Kept quiet
   (secondary text, neutral mark) so it never competes with the score/
   commentary content above it. */

.sponsor-lockup {
	display: flex;
	align-items: center;
	gap: 6px;
}

.sponsor-lockup__mark {
	width: 14px;
	height: 14px;
	flex-shrink: 0;
	border-radius: var(--radius-sm);
	background-color: var(--color-border);
}

.sponsor-lockup__text {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.sponsor-lockup__text strong {
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.match-hero__social {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Player photo — circular photo with a pure-CSS initials fallback for
   the mockup phase (no real images exist yet). The img's onerror adds
   --fallback, which hides the broken image and reveals the initials
   rendered from data-initials via ::after. */

.player-photo {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 48px;
	height: 48px;
	flex-shrink: 0;
	border-radius: var(--radius-full);
	background-color: var(--color-neutral-fill);
	overflow: hidden;
}

/* Larger avatar variant — profile header only. */
.player-photo--lg {
	width: 64px;
	height: 64px;
}

.player-photo__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: var(--radius-full);
}

.player-photo--fallback .player-photo__img {
	display: none;
}

.player-photo::after {
	content: attr(data-initials);
	position: absolute;
	inset: 0;
	display: none;
	align-items: center;
	justify-content: center;
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.player-photo--fallback::after {
	display: flex;
}

/* Role icon + player meta row — team badge + bat/ball role icon + role
   label, shared between performer and trending cards so the two
   sections read as one family of components. */

.role-icon {
	display: flex;
	width: 20px;
	height: 20px;
	flex-shrink: 0;
	color: var(--color-text-secondary);
}

.role-icon svg {
	width: 100%;
	height: 100%;
}

.player-meta {
	display: flex;
	align-items: center;
	gap: 6px;
}

.player-meta__role {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Poll — "Who Will Win?" fan poll. Tapping either option reveals both
   bars (js/poll.js); the leading share gets the accent fill, mirroring
   the leaderboard's top-of-table treatment. Works, inertly, without JS —
   the buttons are just unrevealed until then. */

.poll {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.poll__match {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.poll__options {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.poll__option {
	position: relative;
	overflow: hidden;
	display: flex;
	align-items: center;
	justify-content: space-between;
	min-height: 44px;
	padding: var(--space-8) var(--space-16);
	border-radius: var(--radius-md);
	border: 1px solid var(--color-border);
	background-color: var(--color-background);
	color: var(--color-text-primary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-label);
	text-align: left;
	cursor: pointer;
}

.poll__option:active {
	transform: scale(0.99);
}

@media (min-width: 768px) {
	.poll:not(.poll--voted) .poll__option:hover {
		border-color: var(--color-text-secondary);
	}
}

.poll--voted .poll__option {
	cursor: default;
}

.poll__option:disabled {
	opacity: 1;
}

.poll__option-fill {
	position: absolute;
	inset: 0;
	width: 0%;
	background-color: var(--color-border);
	transition: width 0.6s ease;
}

.poll__option--leader .poll__option-fill {
	background-color: var(--color-accent);
	opacity: 0.16;
}

.poll__option--leader {
	border-color: var(--color-accent);
}

.poll__option-name,
.poll__option-percent {
	position: relative;
	z-index: 1;
}

.poll__option-percent {
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	font-weight: var(--font-weight-subheading);
}

/* CTA banner — the fantasy hook tied to the next upcoming match. */

.cta-banner {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
	padding: var(--space-24);
	border-radius: var(--radius-lg);
	background-color: var(--color-surface);
	border: 1px solid var(--color-border);
	box-shadow: var(--shadow-elevation);
}

@media (min-width: 768px) {
	.cta-banner {
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-24);
	}
}

.cta-banner__info {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.cta-banner__eyebrow {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.cta-banner__match {
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-heading);
}

.cta-banner__meta {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.cta-banner__countdown {
	display: inline-flex;
	align-self: flex-start;
	align-items: center;
	gap: 6px;
	padding: 4px 10px;
	border-radius: var(--radius-full);
	background-color: var(--color-border);
	color: var(--color-text-primary);
	font-family: var(--font-family-mono);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
}

.cta-banner .btn {
	width: 100%;
}

@media (min-width: 768px) {
	.cta-banner .btn {
		width: auto;
		flex-shrink: 0;
	}
}

/* Quick actions — league create/join/invite, same visual weight as a
   secondary (outline) button, one row of compact icon+label tiles. */

.quick-actions {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--space-8);
}

.quick-actions__item {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--space-8);
	min-height: 44px;
	padding: var(--space-16) var(--space-8);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
	border: 1px solid var(--color-border);
	color: var(--color-text-primary);
	text-align: center;
}

.quick-actions__item:active {
	transform: scale(0.97);
}

@media (min-width: 768px) {
	.quick-actions__item:hover {
		border-color: var(--color-text-secondary);
	}
}

.quick-actions__icon {
	display: flex;
	width: 22px;
	height: 22px;
	color: var(--color-text-secondary);
}

.quick-actions__icon svg {
	width: 100%;
	height: 100%;
}

.quick-actions__label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
}

/* Team snapshot — signed-in user's fantasy status card. */

.team-snapshot__header {
	display: flex;
	flex-direction: column;
	gap: 2px;
	margin-bottom: var(--space-16);
}

.team-snapshot__label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.team-snapshot__name {
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.team-snapshot__stats {
	display: flex;
	gap: var(--space-32);
}

.team-snapshot__stat {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.team-snapshot__stat-label {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.team-snapshot__stat-row {
	display: flex;
	align-items: baseline;
	gap: var(--space-8);
}

.team-snapshot__stat-value {
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.team-snapshot__trend {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
}

.team-snapshot__trend--up {
	color: var(--color-positive);
}

/* Performer card — Top Fantasy Performers strip, live-match scope. */

.performer-card {
	position: relative;
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 160px;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-8);
}

@media (min-width: 768px) {
	.performer-card {
		width: 180px;
	}
}

.performer-card__rank {
	position: absolute;
	top: var(--space-8);
	right: var(--space-8);
	display: inline-flex;
	align-items: center;
	gap: 3px;
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-secondary);
}

.performer-card__live-dot {
	width: 5px;
	height: 5px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	animation: badge-pulse 1.6s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
	.performer-card__live-dot {
		animation: none;
	}
}

.performer-card__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
	line-height: var(--line-height-dense);
}

.performer-card__score {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.performer-card__stat-line {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.performer-card__points {
	font-size: var(--font-size-xl);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.performer-card__points-label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-body);
	font-family: var(--font-family-sans);
	font-variant-numeric: normal;
	color: var(--color-text-secondary);
	margin-left: 2px;
}

/* Trending pick card — ownership %, credits, plain-text recent form. */

.trending-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 188px;
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.trending-card__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.trending-card__stats {
	display: flex;
	gap: var(--space-16);
}

.trending-card__stat {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.trending-card__stat-label {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.trending-card__stat-value {
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.trending-card__form {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.trending-card__form-value {
	color: var(--color-text-primary);
}

.trending-card__trend {
	font-size: var(--font-size-sm);
}

.trending-card__trend--up {
	color: var(--color-positive);
}

.trending-card__trend--down {
	color: var(--color-text-secondary);
}

/* Leaderboard preview — top-ranked users, compact list inside a card. */

.leaderboard-preview__list {
	display: flex;
	flex-direction: column;
}

.leaderboard-preview__row {
	display: grid;
	grid-template-columns: 20px 32px 1fr auto;
	align-items: center;
	gap: var(--space-8);
	padding: var(--space-8) 0;
}

.leaderboard-preview__row + .leaderboard-preview__row {
	border-top: 1px solid var(--color-border);
}

/* Same [hidden] vs display:grid specificity fix as .scroll-row — needed
   now that rows can be hidden by load-more (js/leaderboard.js). Search
   uses a separate --search-hidden class (below), never the [hidden]
   attribute, so the two states can't stomp on each other: clearing the
   search box must not reveal rows Load More hasn't uncovered yet. */
.leaderboard-preview__row[hidden] {
	display: none;
}

.leaderboard-preview__row--search-hidden {
	display: none;
}

.leaderboard-preview__rank {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-secondary);
	text-align: center;
}

.leaderboard-preview__row--top .leaderboard-preview__rank {
	color: var(--crimson-text);
}

.leaderboard-preview__avatar {
	width: 32px;
	height: 32px;
	border-radius: var(--radius-full);
	background-color: var(--color-border);
	color: var(--color-text-primary);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-heading);
}

.leaderboard-preview__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.leaderboard-preview__points {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
	text-align: right;
}

/* Fixture card — upcoming-match strip, series-schedule section. */

/* Combine with .card for the shared surface/border/radius/padding. */
.fixture-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 200px;
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.fixture-card__date {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

.fixture-card__teams {
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	line-height: var(--line-height-dense);
}

.fixture-card__venue {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Standings table — points-table snapshot. */

.table-wrap {
	overflow-x: auto;
}

.standings-table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--font-size-sm);
}

.standings-table th,
.standings-table td {
	padding: var(--space-8);
	text-align: left;
	border-bottom: 1px solid var(--color-border);
}

.standings-table th {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.03em;
	white-space: nowrap;
}

.standings-table__num {
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	text-align: right;
	color: var(--color-text-primary);
}

/* Fantasy tips — expert-column teaser, distinct from the plain news
   cards below it (byline + headline + bullets, no thumbnail). */

.fantasy-tips {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.fantasy-tips__byline {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.fantasy-tips__headline {
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.fantasy-tips__list {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	margin-top: var(--space-8);
	padding-left: var(--space-16);
}

.fantasy-tips__list li {
	list-style: disc;
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	line-height: var(--line-height-body);
}

/* News card — editorial headline/summary/thumbnail preview. */

/* Combine with .card for the shared surface/border/radius/padding. */
.news-card {
	display: flex;
	gap: var(--space-16);
}

.news-card__thumb {
	position: relative;
	flex: 0 0 64px;
	width: 64px;
	height: 64px;
	border-radius: var(--radius-sm);
	background-color: var(--color-border);
}

/* Video variant — flat play-button overlay, monochrome (no accent). */

.news-card--video .news-card__thumb::before {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: 26px;
	height: 26px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
}

.news-card--video .news-card__thumb::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-40%, -50%);
	width: 0;
	height: 0;
	border-style: solid;
	border-width: 5px 0 5px 8px;
	border-color: transparent transparent transparent var(--color-text-primary);
}

.news-card__body {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.news-card__headline {
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	line-height: var(--line-height-dense);
	color: var(--color-text-primary);
}

.news-card__summary {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	line-height: var(--line-height-dense);
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	-webkit-box-orient: vertical;
}

/* Hall of Fame teaser — closing card, whole card is the link. */

.hof-teaser {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.hof-teaser__eyebrow {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.hof-teaser__title {
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.hof-teaser__desc {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.hof-teaser__cta {
	color: var(--crimson-text);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

/* Top bar utilities — search / language / theme / notifications. Each
   button keeps the full 44x44 touch target even though the icon itself
   is small, same convention as the nav items. */

.top-bar__utilities {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	flex-shrink: 0;
}

.top-bar__icon-btn {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	flex-shrink: 0;
	border-radius: var(--radius-full);
	color: var(--color-text-secondary);
}

.top-bar__icon-btn svg {
	width: 20px;
	height: 20px;
}

.top-bar__icon-btn:active {
	transform: scale(0.92);
}

@media (min-width: 768px) {
	.top-bar__icon-btn:hover {
		color: var(--color-text-primary);
	}
}

.top-bar__lang-btn {
	font-family: var(--font-family-sans);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-heading);
}

.top-bar__badge-dot {
	position: absolute;
	top: 6px;
	right: 6px;
	width: 8px;
	height: 8px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	border: 2px solid var(--color-background);
}

/* Search — expands inline below the title row, no overlay. */

.top-bar__search {
	padding-top: var(--space-8);
}

.top-bar__search[hidden] {
	display: none;
}

.top-bar__search-input {
	width: 100%;
	min-height: 44px;
	padding: var(--space-8) var(--space-16);
	border-radius: var(--radius-md);
	border: 1px solid var(--color-border);
	background-color: var(--color-surface);
	color: var(--color-text-primary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-base);
}

.top-bar__search-input::placeholder {
	color: var(--color-text-secondary);
}

.top-bar__search-input:focus {
	outline: 2px solid var(--color-accent);
	outline-offset: 1px;
	border-color: var(--color-accent);
}

/* Notifications dropdown — overlays, anchored to the sticky top bar. */

.notif-panel {
	position: absolute;
	top: calc(100% + var(--space-8));
	right: var(--space-16);
	z-index: 40;
	width: 260px;
	max-width: calc(100vw - var(--space-32));
	display: flex;
	flex-direction: column;
	padding: var(--space-8);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
	border: 1px solid var(--color-border);
	box-shadow: var(--shadow-elevation);
	transform-origin: top right;
	animation: notif-panel-in 0.15s ease;
}

.notif-panel[hidden] {
	display: none;
}

@keyframes notif-panel-in {
	from { opacity: 0; transform: translateY(-4px) scale(0.98); }
	to { opacity: 1; transform: translateY(0) scale(1); }
}

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

.notif-panel__item {
	padding: var(--space-8);
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	line-height: var(--line-height-dense);
}

.notif-panel__item + .notif-panel__item {
	border-top: 1px solid var(--color-border);
}

/* More menu — full-height slide-in drawer + backdrop. Same open/close
   rigor as .notif-panel (hidden-attribute toggle, dismiss on outside
   click/Escape) plus a focus trap, since this is a much bigger overlay.
   Backdrop color reuses the same rgb(10, 10, 11) black already used in
   --shadow-elevation, just at a higher opacity for full-screen dimming —
   not a new color. */

.drawer-backdrop {
	position: fixed;
	inset: 0;
	z-index: 90;
	background-color: rgba(10, 10, 11, 0.6);
	animation: drawer-backdrop-in 0.2s ease;
}

.drawer-backdrop[hidden] {
	display: none;
}

@keyframes drawer-backdrop-in {
	from { opacity: 0; }
	to { opacity: 1; }
}

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

.drawer {
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	z-index: 91;
	width: min(85vw, 360px);
	display: flex;
	flex-direction: column;
	background-color: var(--color-background);
	border-left: 1px solid var(--color-border);
	box-shadow: var(--shadow-elevation);
	animation: drawer-in 0.2s ease;
}

.drawer[hidden] {
	display: none;
}

@keyframes drawer-in {
	from { transform: translateX(100%); }
	to { transform: translateX(0); }
}

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

.drawer__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-shrink: 0;
	padding: var(--space-16);
	padding-top: calc(var(--space-16) + env(safe-area-inset-top));
	border-bottom: 1px solid var(--color-border);
}

.drawer__title {
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.drawer__close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	flex-shrink: 0;
	color: var(--color-text-secondary);
}

.drawer__close svg {
	width: 20px;
	height: 20px;
}

.drawer__close:active {
	transform: scale(0.92);
}

.drawer__body {
	flex: 1 1 auto;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	gap: var(--space-24);
	padding: var(--space-16);
	padding-bottom: calc(var(--space-16) + env(safe-area-inset-bottom));
}

.drawer__group {
	display: flex;
	flex-direction: column;
}

.drawer__link {
	display: flex;
	align-items: center;
	min-height: 44px;
	padding: var(--space-8) 0;
	border-top: 1px solid var(--color-border);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
}

.drawer__group .drawer__link:first-child {
	border-top: none;
}

.drawer__link:active {
	opacity: 0.7;
}

.drawer__link:focus-visible {
	outline: 2px solid var(--color-accent);
	outline-offset: -2px;
}

@media (min-width: 768px) {
	.drawer__link:hover {
		background-color: var(--color-neutral-fill);
	}
}

/* Search filtering hides performer/trending cards — needs to beat the
   .performer-card/.trending-card class's own `display: flex` (equal
   specificity, so it's a source-order tossup without this). */

.performer-card[hidden],
.trending-card[hidden] {
	display: none;
}

/* Head-to-head / pitch & weather — next-match context row. Titles reuse
   the default h3 (base.css), only the value rows need new classes. */

.h2h-card__record {
	margin-top: var(--space-8);
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.h2h-card__record-label {
	font-family: var(--font-family-sans);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-body);
	font-variant-numeric: normal;
	color: var(--color-text-secondary);
	margin-left: 4px;
}

.h2h-card__last,
.info-card__line {
	margin-top: var(--space-8);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.weather-card__row {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin-top: var(--space-8);
}

.weather-card__icon {
	display: flex;
	width: 24px;
	height: 24px;
	flex-shrink: 0;
	color: var(--color-text-secondary);
}

.weather-card__icon svg {
	width: 100%;
	height: 100%;
}

.weather-card__temp {
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.weather-card__condition {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.weather-card__pitch {
	margin-top: var(--space-8);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

/* Tabs — ARIA tabs pattern, used above Trending Picks. Active tab uses
   accent (text + underline), inactive uses secondary text, matching the
   active-state convention already used in nav/badges/leaderboard. Tab
   labels can be full match names, so the row scrolls horizontally
   instead of wrapping — same no-visible-scrollbar treatment as
   .scroll-row/.ticker. */

.tabs {
	display: flex;
	gap: var(--space-24);
	overflow-x: auto;
	scrollbar-width: none;
	border-bottom: 1px solid var(--color-border);
	margin-bottom: var(--space-16);
}

.tabs::-webkit-scrollbar {
	display: none;
}

.tabs__tab {
	position: relative;
	flex: 0 0 auto;
	min-height: 44px;
	padding: var(--space-8) 0;
	white-space: nowrap;
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

.tabs__tab::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: -1px;
	height: 2px;
	background-color: transparent;
}

.tabs__tab--active {
	color: var(--crimson-text);
	font-weight: var(--font-weight-heading);
}

.tabs__tab--active::after {
	background-color: var(--color-accent);
}

@media (min-width: 768px) {
	.tabs__tab:not(.tabs__tab--active):hover {
		color: var(--color-text-primary);
	}
}

/* Cap card — Orange Cap / Purple Cap season-stat cards. */

.cap-card__player {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin-bottom: var(--space-16);
}

.cap-card__info {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.cap-card__name {
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.cap-card__stats {
	display: flex;
	gap: var(--space-32);
	padding-top: var(--space-16);
	border-top: 1px solid var(--color-border);
}

.cap-card__stat {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.cap-card__stat-value {
	font-size: var(--font-size-xl);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.cap-card__stat-label {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Highlight card — video-highlights scroll row. Combine with .card;
   thumbnail reuses the same neutral-fill placeholder treatment as
   .news-card__thumb, just sized for a horizontal video card. */

.highlight-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 220px;
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.highlight-card__thumb {
	position: relative;
	width: 100%;
	height: 124px;
	border-radius: var(--radius-sm);
	background-color: var(--color-border);
}

.highlight-card__play {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
	color: var(--color-text-primary);
}

.highlight-card__play svg {
	width: 16px;
	height: 16px;
	margin-left: 2px;
}

.highlight-card__duration {
	position: absolute;
	bottom: var(--space-8);
	right: var(--space-8);
	padding: 2px 6px;
	border-radius: var(--radius-sm);
	background-color: var(--color-background);
	color: var(--color-text-primary);
}

.highlight-card__caption {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
	line-height: var(--line-height-dense);
}

/* Team News — confirmed-XI status per side, two blurbs in one card.
   Reuses .grid/.grid--2 for the side-by-side/stack-on-mobile layout, so
   the only new classes are the blurb itself. */

.team-news__side {
	display: flex;
	align-items: flex-start;
	gap: var(--space-8);
}

.team-news__status {
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	line-height: var(--line-height-body);
}

/* League card — My Leagues preview strip. */

.league-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 180px;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-8);
}

.league-card__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
	line-height: var(--line-height-dense);
}

.league-card__stats {
	display: flex;
	gap: var(--space-16);
	margin-top: 4px;
}

.league-card__stat {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.league-card__stat-label {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.league-card__stat-value {
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

/* Fan Buzz card — community reactions. Avatar reuses the
   .leaderboard-preview__avatar treatment (same initials-circle). */

.fan-buzz-card {
	flex: 0 0 auto;
	scroll-snap-align: start;
	width: 220px;
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.fan-buzz-card__header {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.fan-buzz-card__name {
	flex: 1 1 auto;
	min-width: 0;
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fan-buzz-card__time {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	white-space: nowrap;
}

.fan-buzz-card__text {
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	line-height: var(--line-height-body);
}

/* App footer — quiet, static, informational. Sits outside .app-content
   so it needs the same sidebar margin-left (>=768px) and bottom
   tab-bar clearance (base) that .app-content applies to itself. */

.app-footer {
	padding: var(--space-32) var(--space-16);
	padding-bottom: calc(var(--space-32) + var(--tabbar-height) + env(safe-area-inset-bottom));
	border-top: 1px solid var(--color-border);
	background-color: var(--color-background);
}

@media (min-width: 768px) {
	.app-footer {
		margin-left: var(--sidebar-width);
		padding: var(--space-32) var(--space-24);
	}
}

.app-footer__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-16);
	text-align: center;
}

.app-footer__links {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-16);
}

.app-footer__links a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
}

.app-footer__social {
	display: flex;
	justify-content: center;
	gap: var(--space-8);
}

.app-footer__social a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	color: var(--color-text-secondary);
}

.app-footer__social svg {
	width: 20px;
	height: 20px;
}

@media (min-width: 768px) {
	.app-footer__links a:hover,
	.app-footer__social a:hover {
		color: var(--color-text-primary);
	}
}

.app-footer__copyright {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Grouped 3-column variant (2026-09-27, by request) — brand blurb + three
   headed link columns (Explore / My Fantasy / About, the same grouping
   the More menu drawer already uses) instead of .app-footer__inner's
   flat centered link row. Still .app-footer for the outer chrome
   (padding, border, sidebar offset, tab-bar clearance); only the inner
   content shape is new. .app-footer__copyright is reused as-is below it. */

.app-footer__grid {
	display: flex;
	flex-direction: column;
	gap: var(--space-32);
}

@media (min-width: 768px) {
	.app-footer__grid {
		flex-direction: row;
		justify-content: space-between;
		gap: var(--space-48);
	}
}

.app-footer__brand {
	max-width: 320px;
}

.app-footer__brand strong {
	display: block;
	margin-bottom: var(--space-8);
	font-family: var(--font-family-display);
	font-size: var(--font-size-md);
	color: var(--color-text-primary);
}

.app-footer__brand small {
	display: block;
	line-height: var(--line-height-body);
}

.app-footer__groups {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-24) var(--space-32);
}

@media (min-width: 480px) {
	.app-footer__groups {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.app-footer__group-title {
	/* Sentence case, not a tracked all-caps eyebrow — system.css's own
	   design-system rule (no decorative all-caps labels outside data-table
	   headers) applies here too; this was missed when the footer's column
	   layout was added (reported 2026-09-28). */
	margin: 0 0 var(--space-8);
	font-size: var(--font-size-xs);
	font-weight: 600;
	color: var(--color-text-secondary);
}

.app-footer__group ul {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.app-footer__group a {
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
}

.app-footer__group a:hover,
.app-footer__group a:focus-visible {
	color: var(--crimson-text);
	text-decoration: underline;
}

.app-footer__grid + .app-footer__copyright {
	display: block;
	margin-top: var(--space-32);
	padding-top: var(--space-16);
	border-top: 1px solid var(--color-border);
}

/* Match Centre — Match header. A full-width variant of the .match-hero
   pattern used on the homepage's live match card: same child-class
   conventions (top row, team rows, status block, accent border) but not
   .match-card, since that class is fixed-width and scroll-snapping for
   the horizontal strip. Score is the largest text on the page per the
   visual-hierarchy requirement. */

.match-header {
	border-color: var(--color-accent);
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.match-header__meta {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.match-header__top {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--space-16);
}

.match-header__venue {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	text-align: right;
}

.match-header__teams {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.match-header__team {
	display: flex;
	align-items: center;
	gap: var(--space-16);
}

.match-header__team-name {
	flex: 1 1 auto;
	min-width: 0;
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.match-header__score {
	font-size: var(--font-size-2xl);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
	white-space: nowrap;
}

.match-header__overs {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-body);
	color: var(--color-text-secondary);
	margin-left: 4px;
}

@media (min-width: 768px) {
	.match-header__score {
		font-size: var(--font-size-3xl);
	}
}

.match-header__state {
	padding: var(--space-8) var(--space-16);
	border-left: 3px solid var(--color-accent);
	border-top: 1px solid var(--color-border);
	padding-top: var(--space-16);
	margin-top: var(--space-8);
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

/* Match Centre — two-column layout (commentary + sidebar). Distinct from
   .grid--2 because the split is asymmetric (main column wider) rather
   than two equal tracks. */

.match-layout {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-24);
	align-items: start;
}

@media (min-width: 1024px) {
	.match-layout {
		grid-template-columns: 2fr 1fr;
	}
}

.match-layout__sidebar {
	display: flex;
	flex-direction: column;
	gap: var(--space-24);
}

/* Match Centre — commentary filters. Reuses .btn / .btn--primary /
   .btn--outline for the active/inactive states, same convention as the
   rest of the site's buttons. */

.commentary-filters {
	display: flex;
	gap: var(--space-8);
	margin-bottom: var(--space-16);
	overflow-x: auto;
	scrollbar-width: none;
}

.commentary-filters::-webkit-scrollbar {
	display: none;
}

.commentary-filters__btn {
	flex: 0 0 auto;
	padding-inline: var(--space-16);
	font-size: var(--font-size-sm);
}

/* Match Centre — ball-by-ball commentary, grouped by over. */

.commentary-over {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.commentary-over + .commentary-over {
	margin-top: var(--space-16);
}

.commentary-over__header {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-16);
	padding-bottom: var(--space-8);
	border-bottom: 1px solid var(--color-border);
}

.commentary-over__title {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.commentary-over__summary {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.commentary-ball-list {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.commentary-ball {
	display: flex;
	align-items: flex-start;
	gap: var(--space-16);
}

.commentary-ball__meta {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	margin-bottom: 2px;
}

.commentary-ball__dismissal {
	font-style: italic;
}

.commentary-ball__text {
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	line-height: var(--line-height-body);
}

.commentary-empty {
	padding: var(--space-16) 0;
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	text-align: center;
}

/* Condensed over group — powerplay/quiet overs collapsed to one summary
   line instead of a full ball list (see js/match-centre.js). */

.commentary-over--condensed .commentary-ball__text {
	color: var(--color-text-secondary);
	font-style: italic;
}

/* Sortable table headers (js/table-sort.js) — a plain <th> becomes a
   button-like sort control. The current sort direction is communicated
   both visually (arrow via aria-sort) and to assistive tech (aria-sort
   is a native table-sort attribute, already announced by screen readers). */

table[data-sortable] th[data-sort-index] {
	cursor: pointer;
	user-select: none;
}

table[data-sortable] th[data-sort-index]::after {
	content: "↕";
	margin-left: 4px;
	color: var(--color-text-secondary);
	font-weight: var(--font-weight-body);
}

table[data-sortable] th[aria-sort="ascending"]::after {
	content: "↑";
	color: var(--crimson-text);
}

table[data-sortable] th[aria-sort="descending"]::after {
	content: "↓";
	color: var(--crimson-text);
}

/* Match Centre — Playing XI list (Squads tab). Reuses .role-icon as-is;
   only the row layout itself is new. */

.playing-xi {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
	margin-top: var(--space-8);
}

.playing-xi__player {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.playing-xi__name {
	flex: 1 1 auto;
	min-width: 0;
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
}

.playing-xi__role {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Match Centre — persistent match-info footer, below the tabs/layout,
   same regardless of active tab. */

.match-info-footer {
	margin-top: var(--space-24);
}

/* Match Centre — Upcoming/Completed template-state examples: stack their
   cards with the same rhythm as the Scorecard/Info panels above. */

#upcoming-example .match-header + .grid,
#upcoming-example .grid + .card,
#completed-example .match-header + .card,
#completed-example .card + .card,
#completed-example .card + .grid {
	margin-top: var(--space-24);
}

/* Team Builder — sticky header (match/countdown/credit cap), role
   summary, player rows, and the Captain/Vice-Captain step. Sticky top
   and bottom bars sit below/above the app shell's own top bar and nav
   respectively, using the same surface/border treatment as .top-bar. */

.team-builder-header {
	position: sticky;
	/* Not var(--topbar-height) — .top-bar doesn't actually stay pinned
	   once scrolled (see --sticky-chrome-top's own docblock, global.css),
	   so anchoring here to a height nothing still occupies left scrolled
	   pitch/pool content visible in the gap above this bar. */
	top: var(--sticky-chrome-top);
	z-index: 10;
	margin: calc(var(--space-16) * -1) calc(var(--space-16) * -1) var(--space-16);
	padding: var(--space-16);
	background-color: var(--color-background);
	border-bottom: 1px solid var(--color-border);
}

@media (min-width: 768px) {
	.team-builder-header {
		margin-inline: calc(var(--space-24) * -1);
		padding-inline: var(--space-24);
	}
}

.team-builder-header__top {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-16);
	margin-bottom: var(--space-8);
}

.team-builder-header__match {
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.credit-bar {
	margin-top: var(--space-8);
}

.credit-bar__track {
	height: 8px;
	border-radius: var(--radius-full);
	background-color: var(--color-border);
	overflow: hidden;
}

.credit-bar__fill {
	height: 100%;
	/* Neutral by default — crimson is kept for the at/over-limit states
	   below (squad-builder's budgetState(): normal < 80% <= near < 100% =
	   full < over). The Team Builder bar shares this: grey, then crimson
	   only via --over. */
	background-color: var(--color-text-secondary);
	border-radius: var(--radius-full);
	width: 0%;
	transition: width 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
	.credit-bar__fill {
		transition: none;
	}
}

.credit-bar__fill--over,
.credit-bar--over .credit-bar__fill {
	background-color: var(--color-accent);
	animation: badge-glow 1s ease-in-out infinite;
}

.credit-bar--near .credit-bar__fill {
	background-color: var(--color-text-primary);
}

.credit-bar--full .credit-bar__fill {
	background-color: var(--color-accent);
}

/* Small tag beside the label once the cap is reached — the only budget
   state that reads as a warning. The credits label itself stays plain. */
.credit-bar__flag {
	display: inline-block;
	margin-top: 4px;
	margin-right: var(--space-8);
	padding: 1px 8px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	color: var(--color-white);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
}

.credit-bar__flag + .credit-bar__label {
	display: inline-block;
}

.credit-bar__label {
	display: block;
	margin-top: 4px;
	font-size: var(--font-size-xs);
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	color: var(--color-text-secondary);
}

.role-summary {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	margin-top: var(--space-8);
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.role-summary__sep {
	color: var(--color-border);
}

.role-summary__item--warn {
	color: var(--crimson-text);
	font-weight: var(--font-weight-label);
}

/* One-line status pill for the sticky budget header (RoleSummary's
   `compact` mode) — "Roles OK" or "N roles need attention" instead of the
   full per-role breakdown, which moved to the non-sticky .npl-squad-detail
   below it (see budgetHeader's own comment, view.js). Overrides
   .role-summary's flex-wrap layout: this is always one short line. */
.role-summary--compact {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	margin-top: 4px;
}

.role-summary--compact svg {
	width: 1em;
	height: 1em;
}

.team-builder-note {
	margin-top: var(--space-8);
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Non-sticky home for the full role breakdown + max-per-team rule the
   sticky budget header used to always show inline (moved out 2026-09-28
   to keep that pinned strip to one compact row — see budgetHeader's own
   comment, view.js). Plain <details>/<summary>: reference material you
   check occasionally, not something worth permanent scroll space. */
.npl-squad-detail {
	margin-top: var(--space-8);
}

.npl-squad-detail__summary {
	display: inline-block;
	cursor: pointer;
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.npl-squad-detail[open] > .npl-squad-detail__summary {
	margin-bottom: var(--space-8);
}

.player-row {
	display: flex;
	align-items: center;
	gap: var(--space-16);
	padding: var(--space-8) 0;
	border-bottom: 1px solid var(--color-border);
}

.player-row:last-child {
	border-bottom: none;
}

.player-row__info {
	flex: 1 1 auto;
	min-width: 0;
}

.player-row__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
}

.player-row__meta {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin-top: 2px;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.player-row__stats {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 2px;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	white-space: nowrap;
}

.player-row__credits {
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	color: var(--color-text-primary);
	font-weight: var(--font-weight-label);
}

.player-row--selected {
	background-color: var(--color-neutral-fill);
	border-radius: var(--radius-sm);
}

.player-row__select {
	flex-shrink: 0;
	min-width: 88px;
}

.captain-step {
	margin-top: var(--space-16);
}

.captain-step__group {
	margin-top: var(--space-16);
}

.captain-step__label {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.captain-pick {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	min-height: 44px;
	padding: var(--space-8) 0;
	border-bottom: 1px solid var(--color-border);
	font-size: var(--font-size-sm);
}

.captain-pick input {
	width: 20px;
	height: 20px;
	flex-shrink: 0;
	accent-color: var(--color-accent);
}

.captain-pick__name {
	flex: 1 1 auto;
	min-width: 0;
	color: var(--color-text-primary);
}

.captain-pick__team {
	color: var(--color-text-secondary);
}

.captain-pick__ownership {
	color: var(--color-text-secondary);
	white-space: nowrap;
}

.team-builder-footer {
	position: sticky;
	bottom: 0;
	z-index: 10;
	margin: var(--space-24) calc(var(--space-16) * -1) calc(var(--space-16) * -1);
	padding: var(--space-16);
	background-color: var(--color-background);
	border-top: 1px solid var(--color-border);
}

@media (min-width: 768px) {
	.team-builder-footer {
		margin-inline: calc(var(--space-24) * -1);
		padding-inline: var(--space-24);
	}
}

.team-builder-footer .btn {
	width: 100%;
}

.team-builder-footer .btn:disabled {
	opacity: 0.4;
}

/* Global leaderboard — inline search (reuses the top bar's search-input
   look outside the top bar itself), Load More, and the sticky
   "your rank" bar pinned to the viewport bottom. */

.leaderboard-search,
.form-input {
	width: 100%;
	min-height: 44px;
	padding: var(--space-8) var(--space-16);
	margin-bottom: var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
	background-color: var(--color-surface);
	color: var(--color-text-primary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-base);
}

.form-field {
	margin-bottom: var(--space-16);
}

.form-field__label {
	display: block;
	margin-bottom: var(--space-8);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
}

.form-field .form-input {
	margin-bottom: 0;
}

/* Public/private toggle — a two-option segmented control, same active-
   accent convention as .tabs__tab--active.
   Restyled 2026-09-28 (review, first real consumer: npl/team-segments):
   the inactive option had no background of its own (transparent over a
   dark page = "dark on dark") and no text-align, so a <button>'s content
   fell back to whatever reset.css set for form controls generally
   (left-aligned here) rather than centred. The active state was a solid
   crimson fill — competes with the Live badge as the loudest thing on
   the page; a tinted background + crimson TEXT (same "crimson = active"
   convention .app-nav__item--active already uses for the sidebar,
   global.css) reads as an accent, not a block. */

.segmented {
	display: flex;
	width: 100%;
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
	overflow: hidden;
}

.segmented__option {
	flex: 1 1 0;
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	min-height: 44px;
	background-color: var(--color-surface);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

.segmented__option--active {
	background-color: color-mix( in srgb, var(--color-accent) 14%, var(--color-surface) );
	color: var(--color-accent);
	font-weight: var(--font-weight-heading);
}

/* Share row — social icon buttons alongside a generated code/link. No
   existing article page defines this pattern yet, so it's built here
   from the same icon-button + SVG convention as .app-footer__social. */

.share-row {
	display: flex;
	gap: var(--space-8);
	margin-top: var(--space-16);
}

.share-row__btn {
	width: 44px;
	height: 44px;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--color-border);
	border-radius: var(--radius-full);
	color: var(--color-text-secondary);
}

.share-row__btn svg {
	width: 20px;
	height: 20px;
}

/* Profile — My Leagues full list reuses .league-card outside its usual
   horizontal .scroll-row, so its scroll-strip sizing needs resetting to
   fill a stacked/grid layout instead. Same [hidden]-vs-flex fix as
   .scroll-row/.leaderboard-preview__row wherever a league-card can be
   dismissed (Leave League). */

.league-list {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.league-list .league-card {
	width: 100%;
}

.league-card[hidden] {
	display: none;
}

.league-card__leave {
	flex-shrink: 0;
}

/* Profile — achievement badges, unlocked (full accent-adjacent fill) vs
   locked (outline only, muted). No new color: locked just drops to
   secondary text/border, same "muted" treatment as .badge--muted. */

.achievement-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--space-16);
}

@media (min-width: 768px) {
	.achievement-grid {
		grid-template-columns: repeat(4, 1fr);
	}
}

.achievement-badge {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-8);
	text-align: center;
}

.achievement-badge__icon {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 56px;
	height: 56px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	color: var(--color-white);
}

.achievement-badge__icon svg {
	width: 28px;
	height: 28px;
}

.achievement-badge--locked .achievement-badge__icon {
	background-color: transparent;
	border: 2px solid var(--color-border);
	color: var(--color-text-secondary);
}

.achievement-badge__label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
}

.achievement-badge--locked .achievement-badge__label {
	color: var(--color-text-secondary);
}

/* Profile — Settings section rows (language/theme/notifications). */

.settings-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
	padding: var(--space-16) 0;
	border-bottom: 1px solid var(--color-border);
}

.settings-row:last-child {
	border-bottom: none;
}

.settings-row__label {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
}

.settings-row .segmented {
	width: auto;
	min-width: 140px;
}

/* Simple checkbox toggle for notification preferences — native checkbox,
   just sized up to the 44px minimum touch target with accent-color. */

.settings-row input[type="checkbox"] {
	width: 24px;
	height: 24px;
	accent-color: var(--color-accent);
}

/* Hall of Fame — stacked season cards, current season called out with
   the same accent-border convention as the live match card. */

.season-timeline {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.season-card--active {
	border-color: var(--color-accent);
}

/* Player Stats — role filter row (visually the .tabs pattern, but a
   plain button group filtering rows in one shared table rather than an
   ARIA tablist switching panels) plus the team <select>. */

.player-stats-filters {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16);
	align-items: center;
	margin-bottom: var(--space-16);
}

.player-stats-filters__roles {
	display: flex;
	gap: var(--space-24);
	overflow-x: auto;
	scrollbar-width: none;
}

.player-stats-filters__roles::-webkit-scrollbar {
	display: none;
}

.player-stats-filters__team {
	max-width: 200px;
	margin-bottom: 0;
}

.player-stats-table td {
	white-space: nowrap;
}

.player-stats-table .player-photo {
	vertical-align: middle;
	margin-right: var(--space-8);
}

/* Auth / onboarding shell — the one layout that isn't the app shell's
   nav/tab-bar pattern, since these pages exist before a user is signed
   in. Full-viewport, centered, no sidebar/bottom-bar. Reuses .card,
   .btn, .form-input for everything inside it. */

.auth-shell {
	min-height: 100vh;
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: var(--space-24) var(--space-16);
	padding-top: calc(var(--space-24) + env(safe-area-inset-top));
	padding-bottom: calc(var(--space-24) + env(safe-area-inset-bottom));
	background-color: var(--color-background);
}

.auth-shell__brand {
	margin-bottom: var(--space-32);
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.auth-shell__card {
	width: 100%;
	max-width: 400px;
}

.auth-shell__footer {
	margin-top: var(--space-24);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	text-align: center;
}

/* Social sign-in — full-width outline buttons, provider icon + label,
   placed above the email/password fields per the friction-reduction
   rule for a free product. */

.social-auth {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	margin-bottom: var(--space-24);
}

.social-auth__btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-8);
	width: 100%;
}

.social-auth__btn svg {
	width: 20px;
	height: 20px;
	flex-shrink: 0;
}

.auth-divider {
	display: flex;
	align-items: center;
	gap: var(--space-16);
	margin-bottom: var(--space-24);
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.auth-divider::before,
.auth-divider::after {
	content: "";
	flex: 1 1 auto;
	height: 1px;
	background-color: var(--color-border);
}

/* Onboarding carousel */

.carousel {
	width: 100%;
	max-width: 420px;
	overflow: hidden;
}

.carousel__track {
	display: flex;
	transition: transform 0.25s ease;
}

@media (prefers-reduced-motion: reduce) {
	.carousel__track {
		transition: none;
	}
}

.carousel__slide {
	flex: 0 0 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	padding: 0 var(--space-16);
}

.carousel__icon {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 96px;
	height: 96px;
	margin-bottom: var(--space-32);
	border-radius: var(--radius-full);
	background-color: var(--color-neutral-fill);
	color: var(--crimson-text);
}

.carousel__icon svg {
	width: 44px;
	height: 44px;
}

.carousel__title {
	font-size: var(--font-size-xl);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
	margin-bottom: var(--space-8);
}

.carousel__desc {
	font-size: var(--font-size-base);
	color: var(--color-text-secondary);
	line-height: var(--line-height-body);
}

.carousel__dots {
	display: flex;
	justify-content: center;
	gap: var(--space-8);
	margin-top: var(--space-32);
	margin-bottom: var(--space-32);
}

.carousel__dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border-radius: var(--radius-full);
	background-color: var(--color-border);
}

.carousel__dot--active {
	background-color: var(--color-accent);
	width: 24px;
}

@media (prefers-reduced-motion: reduce) {
	.carousel__dot {
		transition: none;
	}
}

.onboarding-actions {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-16);
	width: 100%;
	max-width: 420px;
}

.onboarding-actions .btn {
	width: 100%;
}

.onboarding-skip {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

/* Simple static content pages (legal/info) — prose in the existing type
   scale, no new components. */

.prose {
	max-width: 720px;
}

.prose h3 {
	margin-top: var(--space-32);
	margin-bottom: var(--space-8);
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.prose h3:first-child {
	margin-top: 0;
}

.prose p {
	margin-bottom: var(--space-16);
	color: var(--color-text-secondary);
	line-height: var(--line-height-body);
}

.prose ul {
	margin-bottom: var(--space-16);
	padding-left: var(--space-24);
}

.prose li {
	list-style: disc;
	color: var(--color-text-secondary);
	line-height: var(--line-height-body);
}

/* 404 */

.error-page {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	padding: var(--space-64) var(--space-16);
}

.error-page__code {
	font-family: var(--font-family-mono);
	font-size: var(--font-size-4xl);
	font-weight: var(--font-weight-heading);
	color: var(--color-text-primary);
}

.error-page__message {
	margin-top: var(--space-8);
	margin-bottom: var(--space-32);
	color: var(--color-text-secondary);
}

/* Series — schedule tab spacing between the live callout, upcoming
   scroll-row, and completed table. */

.schedule-live-card {
	margin-bottom: var(--space-16);
}

.schedule-upcoming-row {
	margin-bottom: var(--space-24);
}

/* Search results page */

.search-results-empty {
	padding: var(--space-32) 0;
	text-align: center;
	color: var(--color-text-secondary);
}

.search-group + .search-group {
	margin-top: var(--space-32);
}

.invite-code-display {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-8);
	padding: var(--space-16);
	margin-top: var(--space-16);
	border-radius: var(--radius-sm);
	background-color: var(--color-neutral-fill);
	font-family: var(--font-family-mono);
	font-size: var(--font-size-xl);
	font-weight: var(--font-weight-heading);
	letter-spacing: 0.08em;
	color: var(--color-text-primary);
}

.load-more-btn {
	width: 100%;
	margin-top: var(--space-16);
}

.my-rank-bar {
	position: sticky;
	bottom: 0;
	z-index: 10;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
	margin: var(--space-24) calc(var(--space-16) * -1) calc(var(--space-16) * -1);
	padding: var(--space-16);
	background-color: var(--color-surface);
	border-top: 1px solid var(--color-border);
}

@media (min-width: 768px) {
	.my-rank-bar {
		margin-inline: calc(var(--space-24) * -1);
		padding-inline: var(--space-24);
	}
}

.my-rank-bar__label {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.my-rank-bar__line {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.my-rank-bar__points {
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	color: var(--crimson-text);
	font-weight: var(--font-weight-heading);
}

/* Private league — header card (name/privacy/invite code/member count)
   above the ranked member list. */

.league-header {
	margin-bottom: var(--space-16);
}

.league-header__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
	margin-bottom: var(--space-8);
}

.league-header__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.league-header__actions {
	display: flex;
	align-items: center;
	gap: var(--space-16);
	margin-top: var(--space-16);
}

.league-header__invite {
	flex: 0 0 auto;
	flex-direction: row;
	width: auto;
	padding-inline: var(--space-16);
	border: 1px solid var(--color-border);
}

.league-header__settings {
	border: 1px solid var(--color-border);
}

/* Match Centre — delivery badge, one per ball. Distinct visual weight per
   type while keeping the monochrome-plus-accent discipline: only the
   wicket ball uses accent-as-fill, matching .match-card__ball--wicket's
   existing precedent. Boundaries get bold text; six adds a ring on top
   of that so it doesn't read identically to four. Extras (wide/no ball)
   are dashed and muted since they're the lowest-stakes outcome. */

.delivery-badge {
	flex-shrink: 0;
	width: 28px;
	height: 28px;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: var(--radius-full);
	background-color: var(--color-border);
	color: var(--color-text-primary);
	font-family: var(--font-family-mono);
	font-size: var(--font-size-xs);
	font-variant-numeric: tabular-nums;
}

.delivery-badge--dot {
	color: var(--color-text-secondary);
}

.delivery-badge--four {
	font-weight: var(--font-weight-heading);
}

.delivery-badge--six {
	font-weight: var(--font-weight-heading);
	border: 2px solid var(--color-text-primary);
}

.delivery-badge--wide,
.delivery-badge--noball {
	background-color: transparent;
	border: 1px dashed var(--color-border);
	color: var(--color-text-secondary);
	font-size: 10px;
}

.delivery-badge--wicket {
	background-color: var(--color-accent);
	color: var(--color-white);
	font-weight: var(--font-weight-heading);
}

/* Match Centre — current batting card striker indicator. */

.batting-table__striker {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--crimson-text);
	white-space: nowrap;
}

/* Match Centre — Scorecard/Info tabs stack multiple .card blocks and, per
   card, a bowling table below a batting one. Scoped to these two panels
   so the spacing rule doesn't reach for any other .card/.table-wrap pair
   on the site. */

#panel-scorecard .card + .card,
#panel-info .card + .card,
#panel-info .grid + .card {
	margin-top: var(--space-24);
}

#panel-scorecard .table-wrap + .table-wrap,
#panel-scorecard .match-card__meta + .table-wrap {
	margin-top: var(--space-16);
}

#panel-scorecard .table-wrap + .match-card__meta {
	margin-top: var(--space-8);
}

/* ==========================================================================
   Fan Buzz page (pages/fan-buzz.html) — the community feed gets its own
   page, built around fan clubs (one per NPL team) as the "group" unit:
   filter the feed by club, post as a club member, and a live club
   leaderboard tracks whose fans are loudest. Home page keeps the short
   .fan-buzz-card teaser strip above; these classes are page-only. */

.fanbuzz-intro {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

.fanbuzz-intro__eyebrow {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--crimson-text);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.fanbuzz-intro__title {
	font-size: var(--font-size-2xl);
	font-weight: var(--font-weight-heading);
	line-height: var(--line-height-heading);
}

.fanbuzz-intro__text {
	color: var(--color-text-secondary);
	line-height: var(--line-height-body);
	max-width: 56ch;
}

.fanbuzz-intro__stats {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16);
	margin-top: var(--space-8);
}

.fanbuzz-intro__stat {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.fanbuzz-intro__stat strong {
	color: var(--color-text-primary);
	font-weight: var(--font-weight-subheading);
}

/* Fan club switcher — pill chips, one per team, "All Fans" first. Reuses
   .scroll-row for the horizontal strip; chips need flex:0 0 auto so the
   row scrolls instead of squeezing. */

.fanclub-chip {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: var(--space-8);
	min-height: 44px;
	padding: var(--space-8) var(--space-16) var(--space-8) var(--space-8);
	border-radius: var(--radius-full);
	border: 1px solid var(--color-border);
	background-color: var(--color-surface);
	color: var(--color-text-primary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	white-space: nowrap;
}

.fanclub-chip:active {
	transform: scale(0.97);
}

@media (min-width: 768px) {
	.fanclub-chip:not(.fanclub-chip--active):hover {
		border-color: var(--color-text-secondary);
	}
}

.fanclub-chip--active {
	border-color: var(--color-accent);
	color: var(--crimson-text);
}

.fanclub-chip__all {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border-radius: var(--radius-full);
	background-color: var(--color-neutral-fill);
	font-size: var(--font-size-sm);
}

.fanclub-chip--active .fanclub-chip__all {
	color: var(--crimson-text);
}

/* Live Reaction Pulse — one big communal reaction bar. Tapping doesn't
   "like" anything private, it adds to a shared crowd count, so the whole
   card reads as one live object rather than four separate buttons. */

.reaction-pulse__header {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	flex-wrap: wrap;
}

.reaction-pulse__title {
	font-size: var(--font-size-md);
	font-weight: var(--font-weight-subheading);
}

.reaction-pulse__subtitle {
	margin-top: 2px;
	margin-bottom: var(--space-16);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.reaction-pulse__grid {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: var(--space-8);
}

.reaction-pulse__btn {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 4px;
	min-height: 64px;
	padding: var(--space-8);
	border-radius: var(--radius-md);
	border: 1px solid var(--color-border);
	background-color: var(--color-background);
}

.reaction-pulse__btn:active {
	transform: scale(0.95);
}

.reaction-pulse__btn.is-active {
	border-color: var(--color-accent);
	background-color: var(--color-surface);
}

.reaction-pulse__emoji {
	font-size: var(--font-size-lg);
	line-height: 1;
}

@keyframes reaction-pulse-bump {
	0% { transform: scale(1); }
	40% { transform: scale(1.35); }
	100% { transform: scale(1); }
}

.reaction-pulse__emoji.is-bumping {
	animation: reaction-pulse-bump 0.35s ease;
}

@media (prefers-reduced-motion: reduce) {
	.reaction-pulse__emoji.is-bumping {
		animation: none;
	}
}

.reaction-pulse__count {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-secondary);
}

.reaction-pulse__btn.is-active .reaction-pulse__count {
	color: var(--crimson-text);
}

/* Composer — post as your fan club. Club buttons are a compact row of
   team-badge-styled toggles; single-select, same active convention as
   the fan club switcher above. */

.fanbuzz-composer__clubs {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	margin-bottom: var(--space-16);
}

.fanbuzz-composer__club-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 36px;
	padding: 0 var(--space-8);
	border-radius: var(--radius-full);
	border: 1px solid var(--color-border);
	background-color: var(--color-background);
	color: var(--color-text-secondary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-heading);
	letter-spacing: -0.02em;
}

.fanbuzz-composer__club-btn--active {
	border-color: var(--color-accent);
	background-color: var(--color-accent);
	color: var(--color-white);
}

.fanbuzz-composer__input {
	resize: vertical;
	min-height: 80px;
	line-height: var(--line-height-body);
}

.fanbuzz-composer__footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
}

.fanbuzz-composer__counter {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Trending tags — small filter chips, single-select toggle. */

.trending-tags {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
}

.trending-tag {
	flex: 0 0 auto;
	min-height: 36px;
	padding: 0 var(--space-16);
	border-radius: var(--radius-full);
	border: 1px solid var(--color-border);
	background-color: var(--color-background);
	color: var(--crimson-text);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
}

.trending-tag--active {
	background-color: var(--color-accent);
	border-color: var(--color-accent);
	color: var(--color-white);
}

/* Feed — vertical list of full-size posts (the home page's fan-buzz-card
   teaser is a horizontal strip of short quotes; this is the real thing:
   club identity, reactions, and threaded replies). */

.fanbuzz-feed {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
}

.fanbuzz-post[hidden] {
	display: none;
}

.fanbuzz-post__header {
	display: flex;
	align-items: flex-start;
	gap: var(--space-8);
	margin-bottom: var(--space-8);
}

.fanbuzz-post__meta {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.fanbuzz-post__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.fanbuzz-post__club {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.fanbuzz-post__club .team-badge {
	width: 18px;
	height: 18px;
	font-size: 9px;
}

.fanbuzz-post__time {
	flex-shrink: 0;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	white-space: nowrap;
}

.fanbuzz-post__text {
	font-size: var(--font-size-base);
	line-height: var(--line-height-body);
	color: var(--color-text-primary);
}

.fanbuzz-post__actions {
	display: flex;
	gap: var(--space-8);
	margin-top: var(--space-16);
	padding-top: var(--space-8);
	border-top: 1px solid var(--color-border);
}

.fanbuzz-action {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 44px;
	padding: 0 var(--space-8);
	border-radius: var(--radius-sm);
	color: var(--color-text-secondary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

@media (min-width: 768px) {
	.fanbuzz-action:hover {
		color: var(--color-text-primary);
	}
}

.fanbuzz-action--active {
	color: var(--crimson-text);
}

.fanbuzz-post__replies {
	display: flex;
	flex-direction: column;
	gap: var(--space-16);
	margin-top: var(--space-16);
	padding-top: var(--space-16);
	padding-left: var(--space-16);
	border-top: 1px solid var(--color-border);
	border-left: 2px solid var(--color-border);
}

.fanbuzz-post__replies[hidden] {
	display: none;
}

.fanbuzz-reply {
	display: flex;
	gap: var(--space-8);
}

.fanbuzz-reply__body {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.fanbuzz-reply__name {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
}

.fanbuzz-reply__text {
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	line-height: var(--line-height-body);
}

.fanbuzz-reply-form {
	display: flex;
	gap: var(--space-8);
}

.fanbuzz-reply-form .form-input {
	margin-bottom: 0;
}

.fanbuzz-empty {
	padding: var(--space-32) var(--space-16);
	text-align: center;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
}

.fanbuzz-empty[hidden] {
	display: none;
}

/* Most Active Fan Clubs — group-vs-group leaderboard. Bar fill width is
   set inline per row (percent of the top club's count), same "static
   sample data" convention as .win-prediction. */

.fanclub-rank + .fanclub-rank {
	margin-top: var(--space-16);
	padding-top: var(--space-16);
	border-top: 1px solid var(--color-border);
}

.fanclub-rank__top {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin-bottom: var(--space-8);
}

.fanclub-rank__position {
	width: 1.5em;
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	text-align: center;
}

.fanclub-rank__name {
	flex: 1 1 auto;
	min-width: 0;
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fanclub-rank__count {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.fanclub-rank__bar-track {
	height: 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-border);
	overflow: hidden;
}

.fanclub-rank__bar-fill {
	display: block;
	height: 100%;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	transition: width 0.4s ease;
}

/* Match Centre — sidebar cross-link into the Fan Buzz page. */

.match-fanbuzz-promo {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-8);
	color: var(--color-text-primary);
}

.match-fanbuzz-promo__text {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.match-fanbuzz-promo__title {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-subheading);
}

.match-fanbuzz-promo__meta {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.match-fanbuzz-promo__arrow {
	flex-shrink: 0;
	font-size: var(--font-size-lg);
	color: var(--crimson-text);
}

/* ==========================================================================
   News listing (pages/news.html) — category filter reuses .tabs/.tabs__tab
   as a button group (same convention as .player-stats-filters__role), not
   true ARIA tabs, since it filters one grid rather than switching panels. */

.news-filters {
	margin-bottom: var(--space-16);
}

/* ==========================================================================
   News article detail (pages/news-detail.html; wired up to a real
   templates/single-npl_news.html on 2026-09-27 — this whole section
   predates that and was unused until then). */

.npl-article-back {
	margin-bottom: var(--space-16);
}

.npl-article-back a {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

.npl-article-back a:hover,
.npl-article-back a:focus-visible {
	color: var(--crimson-text);
}

.npl-article-category {
	display: block;
	margin-bottom: var(--space-8);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--crimson-text);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-article-category a {
	color: inherit;
}

.article-meta__dot {
	color: var(--color-text-secondary);
}

.article-hero {
	position: relative;
	aspect-ratio: 16 / 9;
	margin-bottom: var(--space-16);
	border-radius: var(--radius-lg);
	background-color: var(--color-neutral-fill);
	overflow: hidden;
}

/* The static mockup's .article-hero was a plain background-image div;
   templates/single-npl_news.html applies this class to core's real
   wp:post-featured-image block instead, which renders an <img> — cover
   the same 16:9 box instead of the image showing at its natural size. */
.article-hero img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Video variant — same flat play-button language as .news-card--video,
   scaled up for the larger hero. */

.article-hero--video::before {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: 56px;
	height: 56px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
}

.article-hero--video::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-38%, -50%);
	width: 0;
	height: 0;
	border-style: solid;
	border-width: 11px 0 11px 18px;
	border-color: transparent transparent transparent var(--color-text-primary);
}

.article-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-8);
	margin-bottom: var(--space-8);
}

.article-meta__byline {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.article-title {
	margin-bottom: var(--space-16);
	font-size: var(--font-size-2xl);
	font-weight: var(--font-weight-heading);
	line-height: var(--line-height-heading);
}

.article-scoreline {
	margin: var(--space-24) 0;
	padding: var(--space-16);
}

.article-scoreline__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-8);
}

.article-scoreline__row + .article-scoreline__row {
	margin-top: var(--space-8);
}

.article-scoreline__team {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

.article-scoreline__result {
	margin-top: var(--space-16);
	padding-top: var(--space-16);
	border-top: 1px solid var(--color-border);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.article-share {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
	margin: var(--space-32) 0;
	padding-top: var(--space-24);
	border-top: 1px solid var(--color-border);
}

.article-share .share-row {
	margin-top: 0;
}

/* ==========================================================================
   Leaderboard — "you" row highlight + skipped-ranks gap divider. Used
   wherever a current-user row sits far outside the visible top ranks
   (a public league with thousands of members, a per-match leaderboard). */

.leaderboard-preview__row--you {
	margin-inline: calc(var(--space-8) * -1);
	padding-inline: var(--space-8);
	border-radius: var(--radius-sm);
	background-color: var(--color-surface);
}

.leaderboard-preview__row--you .leaderboard-preview__rank,
.leaderboard-preview__row--you .leaderboard-preview__name {
	color: var(--crimson-text);
}

.leaderboard-preview__gap {
	padding: var(--space-8) 0;
	text-align: center;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	letter-spacing: 0.2em;
}

/* Per-match leaderboard picker (pages/leaderboard-global.html "Matches"
   tab) — a row of fixture chips (team badges + status) swaps between
   .leaderboard-match blocks, each holding its own list + Load More, same
   convention as the tab panels around it. js/leaderboard.js scopes
   search/load-more to whichever .leaderboard-match is visible instead of
   the whole tabpanel. Chips reuse .team-badge/.badge so a match reads the
   same way here as it does everywhere else on the site (home page match
   strip, Match Centre), rather than as a generic dropdown of long names. */

.match-picker {
	margin-bottom: var(--space-16);
}

.match-picker__chip {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: var(--space-8);
	min-height: 44px;
	padding: var(--space-8) var(--space-16);
	border-radius: var(--radius-full);
	border: 1px solid var(--color-border);
	background-color: var(--color-surface);
	white-space: nowrap;
}

.match-picker__chip:active {
	transform: scale(0.97);
}

@media (min-width: 768px) {
	.match-picker__chip:not(.match-picker__chip--active):hover {
		border-color: var(--color-text-secondary);
	}
}

.match-picker__chip--active {
	border-color: var(--color-accent);
	background-color: var(--color-background);
}

.match-picker__vs {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.leaderboard-match[hidden] {
	display: none;
}

/* ==========================================================================
   Browse Public Leagues (pages/leagues-public.html). */

.public-league-card {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
}

.public-league-card__info {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.public-league-card__name-row {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.public-league-card__name {
	font-size: var(--font-size-base);
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
}

.public-league-card__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8) var(--space-16);
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.public-league-card__join {
	flex-shrink: 0;
}

.public-league-card__join:disabled {
	opacity: 0.6;
}

/* ==========================================================================
   Tournament Squad (pages/squad-builder.html) — a visual, formation-style
   alternative to team-builder.html's plain list, for the season-long
   15-player squad rather than a single match's 11. Reuses
   .team-builder-header/.credit-bar/.role-summary/.player-row/
   .captain-step/.captain-pick as-is (see team-builder.html) — this block
   is only the new pieces: the pitch/formation visual and the pool's
   filter/sort controls (which themselves reuse .tabs/.segmented).

   The "ground" is flat inline SVG using --color-grass/--color-grass-alt/
   --color-pitch-strip (see variables.css) — no photographic stadium
   image, no gradients (mown-stripe look comes from alternating flat
   shapes), and it stays theme-aware since inline SVG can reference CSS
   custom properties directly in its fill/stroke attributes. */

/* Pitch (left) + Player Pool (right) — stacked at base, side by side from
   768px via the standard .grid--2 breakpoint. The two .section children
   sit inside that grid instead of the page's normal vertical flow, so
   their own margin-top would double up with the grid's gap — zeroed out
   here rather than touching the shared .section rule. */

.squad-builder-columns > .section {
	margin-top: 0;
	/* Grid items default to min-width:auto, meaning a column won't shrink
	   below its content's intrinsic width — here, the pool table's own
	   natural (unbroken) width — which silently overrides the 3fr/2fr
	   split below back toward whatever ratio the content demands. */
	min-width: 0;
}

/* Ground (left, wider) + Player Pool (right, narrower) — stacked at base,
   side by side from 768px, 3:2 rather than the default .grid--2 50/50
   split so the ground reads as the primary element and the pool as a
   secondary reference panel beside it.

   Both columns' header pieces are page-level sticky, stacked below the
   pre-existing .team-builder-header (itself independently sticky —
   position:sticky/top:var(--topbar-height)/z-index:10, ~155px tall) via
   --squad-header-clearance:
   - left: only the heading+note+role-summary+ground wrapper
     (.xi-sticky-ground) is sticky at that offset — not the whole
     .section, which also holds the bench and Captain/VC pickers now
     that Step 1/2 are merged into one continuous section. Making the
     entire (much taller) section sticky would pin it at a fixed offset
     without progressively revealing the rest as you scroll, leaving
     most of the bench/Captain/VC content unreachable until nearly the
     bottom of the pool — see the comment on .xi-sticky-ground below.
   - right: "Player Pool" heading + role filters (.pool-sticky-header)
     stick at the same offset as the left column's heading, then the
     table's own <thead> — already display:block from the fixed-header
     technique below — sticks a further --pool-header-height below THAT.
     Two independently-sticky pieces stacked, not one, since the filters
     live outside the <table> and the column headers need to stay part
     of the table itself for js/table-sort.js's th[data-sort-index]
     lookups.
   <tbody> is back in normal page flow (no bounded/internal scroll) — the
   page itself scrolls through all 79 rows, same as the ground's column
   reaching its natural end further down. Rows get scroll-margin-top (see
   .squad-pool-table tbody tr below) so a browser's/Playwright's
   scroll-into-view never lands a row hidden under the stacked sticky
   headers. */
@media (min-width: 768px) {
	.squad-builder-columns {
		grid-template-columns: 2fr 1fr;
		/* Deliberately NOT align-items:start. A sticky element only stays
		   pinned for as long as its OWN parent box still has room left below
		   it — it releases the moment the parent's bottom edge catches up.
		   The React build (src/blocks/squad-builder/view.js's pitchGround())
		   merges the bench into the same .pitch card as the ground, both
		   inside .xi-sticky-ground, with nothing else in the left .section —
		   so with align-items:start (each column sized to its own content)
		   that section's box is barely taller than .xi-sticky-ground itself,
		   and the "sticky" ground pops loose and scrolls away almost
		   immediately. Default (stretch) alignment makes the left .section's
		   box match the row height, i.e. the right column's — the standard
		   sticky-sidebar-next-to-long-content technique: the extra height
		   isn't a visible gap (nothing is being stretched to fill it, it's
		   just empty space sitting *behind* the still-pinned sticky content
		   in normal flow), and it isn't reached until the pool column has
		   also scrolled to its own end, which is exactly when the ground
		   should finally let go. */
		/* Fallback only, for the instant before JS measures the real thing
		   (or if it never runs) — src/blocks/squad-builder/view.js sets both
		   of these as inline custom properties on this same element via a
		   ResizeObserver on .team-builder-header/.pool-sticky-header, which
		   always wins over these stylesheet values. A hardcoded px guess
		   drifts for reasons a single number can't anticipate (a narrower
		   window wrapping the role-summary line onto an extra line, a
		   transfer changing the role counts' digit widths, self-hosted
		   fonts finishing their swap after first paint) and each drift
		   reopens the "a scrolling row peeks through / renders behind the
		   header" bug this whole comment block is about — see view.js's
		   own docblock on the observer for the fix (2026-09-27). */
		--squad-header-clearance: 156px;
		--pool-header-height: 96px;
		--thead-height: 44px; /* .squad-pool-table thead's own rendered height, used only for scroll-margin-top below (not a sticky-offset stack, so generous buffer is fine here) */
	}

	/* Only the heading+note+role-summary+ground wrapper is sticky — not
	   the whole left .section. The section also holds the bench and the
	   Captain/VC pickers (11 rows apiece) now that Step 1/2 are merged
	   into one continuous flow; if the *whole* section were sticky, its
	   own height would usually exceed the viewport, and sticky pins the
	   box at a fixed top offset rather than progressively revealing more
	   of it as you scroll — most of the bench/Captain/VC content would
	   stay off-screen until you'd scrolled nearly the entire player pool.
	   Keeping just the (short, viewport-sized) ground portion sticky
	   preserves the "ground stays visible while the long pool scrolls"
	   behavior without that trap; bench/Captain/VC flow normally below
	   it, same as any other in-page content. */
	.xi-sticky-ground {
		position: sticky;
		top: calc(var(--sticky-chrome-top) + var(--squad-header-clearance));
		z-index: 3;
		background-color: var(--color-background);
		/* The ground fills the visible height under the sticky headers
		   (above the bottom action bar) instead of sizing to its rows;
		   taller content still grows past this. */
		display: flex;
		flex-direction: column;
		min-height: calc(100vh - var(--sticky-chrome-top) - var(--squad-header-clearance) - 80px);
	}

	.xi-sticky-ground > .pitch {
		flex: 1 1 auto;
		margin-bottom: 0;
		display: flex;
		flex-direction: column;
	}

	/* Roomier jersey cards on the larger ground (was 58px / 6px gap, crowded). */
	.xi-sticky-ground .pitch__row-tokens {
		gap: var(--space-16) var(--space-24);
	}

	.xi-sticky-ground .pitch__token {
		width: 80px;
	}

	.xi-sticky-ground .pitch__token-jersey {
		height: 52px;
	}

	.xi-sticky-ground .pitch__token-name {
		font-size: 11px;
	}

	.xi-sticky-ground .pitch__token-stat {
		font-size: 11px;
	}

	/* Spread the Playing XI rows over the extra height; the divider and
	   bench stay pinned at the bottom of the grass. */
	.xi-sticky-ground > .pitch > .pitch__field {
		flex: 1 1 auto;
		display: flex;
		flex-direction: column;
	}

	.xi-sticky-ground .pitch__field > .pitch__rows {
		flex: 1 1 auto;
		justify-content: space-evenly;
	}

	.pool-sticky-header {
		position: sticky;
		top: calc(var(--sticky-chrome-top) + var(--squad-header-clearance));
		z-index: 5;
		padding-top: var(--space-8);
		padding-bottom: var(--space-8);
		background-color: var(--color-background);
	}

	.squad-pool-table thead {
		position: sticky;
		top: calc(var(--sticky-chrome-top) + var(--squad-header-clearance) + var(--pool-header-height));
		z-index: 4;
		background-color: var(--color-background);
	}
}

/* The ground's mowed-stripe backdrop is a plain CSS background (tiling
   repeating-linear-gradient), not a fixed-viewBox SVG — a squad's role
   rows vary a lot in height (anywhere from 11 to well over a dozen
   jersey cards wrapping across lines depending on the draft), and an
   SVG's preserveAspectRatio="meet" only ever letterboxes to fit BOTH
   dimensions of a box, so on a tall-but-narrow column the oval used to
   render as a small vertically-centered patch — real content above and
   below it, but no stripe/boundary detail there, reading as "half a
   stadium" cut off. A tiling gradient background instead fills the box
   completely at ANY height with no aspect ratio to preserve. */
.pitch {
	position: relative;
	overflow: hidden;
	border-radius: var(--radius-lg);
	padding: var(--space-16) var(--space-8);
	margin-bottom: var(--space-16);
	background-color: var(--color-grass);
	background-image: repeating-linear-gradient(
		to bottom,
		var(--color-grass-alt) 0,
		var(--color-grass-alt) 28px,
		transparent 28px,
		transparent 56px
	);
}

/* .pitch__field wraps ONLY the Playing XI rows — the boundary ring and
   batting strip below are scoped to this inner wrapper, not the whole
   .pitch card, so the ring encircles just the field of play. The bench
   (appended as a sibling of .pitch__field, after the divider) is still
   on the grass — same card, same turf background — but deliberately
   sits outside this ring, same as a real substitute waiting beyond the
   boundary rope rather than out on the field. */
.pitch__field {
	position: relative;
	padding: var(--space-8) 0;
}

/* Boundary ring — a decorative border on its own layer (not clipping
   .pitch__field's real content) sized to the wrapper's actual width/
   height, so it reads as an ellipse at any aspect ratio instead of a
   fixed-proportion graphic that would need letterboxing. */
.pitch__field::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 0;
	border: 2px solid var(--color-border);
	border-radius: 50%;
	opacity: 0.5;
	pointer-events: none;
}

/* The batting strip — a small fixed-size rectangle, always centered on
   the field regardless of how tall the Playing XI rows grow. */
.pitch__field::after {
	content: '';
	position: absolute;
	top: 50%;
	left: 50%;
	z-index: 0;
	width: 44px;
	height: 66px;
	max-height: 40%;
	transform: translate(-50%, -50%);
	border-radius: 6px;
	background-color: var(--color-pitch-strip);
	border: 1px solid var(--color-border);
	pointer-events: none;
}

.pitch__rows {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

/* Row labels/hints/names sit directly on the grass now, whose green is too
   close in luminance to --color-text-secondary for reliable contrast in
   either theme — each gets a small solid chip instead of bare text, same
   visual language as .badge, rather than depending on the grass color
   underneath for legibility. */

.pitch__row-label {
	display: block;
	width: fit-content;
	margin: 0 auto var(--space-8);
	padding: 2px 10px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	text-align: center;
	box-shadow: var(--shadow-elevation);
}

.pitch__row-hint {
	display: block;
	width: fit-content;
	margin: 0 auto;
	padding: 2px 10px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
	text-align: center;
}

.pitch__row-tokens {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-8) 6px;
}

/* Jersey-card token — a small "shirt card" per player (jersey silhouette
   + name bar + price bar, corner Captain/VC badge) rather than a floating
   circular photo pill, closer to how fantasy-sports pick-team screens
   (e.g. FPL) present a squad on the pitch. Colors stay inside the site's
   monochrome + one-accent system (docs/design-system.md) — the jersey
   fill uses the same neutral tone ramp as the default avatar monogram
   (.npl-avatar-tone-1..5, picked per real-life team so every player from
   the same team reads as visually grouped), never a rainbow of per-team
   brand colors the way a real kit would. */

.pitch__token {
	display: flex;
	flex-direction: column;
	width: 58px;
	border-radius: var(--radius-sm);
	background-color: var(--color-background);
	box-shadow: var(--shadow-elevation);
	overflow: visible;
}

.pitch__token:active {
	transform: scale(0.96);
}

.pitch__token-jersey {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	height: 38px;
	clip-path: polygon(
		20% 0%, 35% 0%, 50% 14%, 65% 0%, 80% 0%,
		100% 24%, 80% 40%, 72% 32%,
		72% 100%, 28% 100%,
		28% 32%, 20% 40%, 0% 24%
	);
}

.pitch__token-jersey img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.pitch__token-initials {
	position: relative;
	font-size: 11px;
	font-weight: var(--font-weight-heading);
	color: var(--color-white);
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}

/* Captain/Vice-Captain indicator ("C"/"VC"), shown only on a Playing XI
   token — set via the token's own popup menu (.token-menu below), not a
   separate captain/VC list. Top-left, overhanging the jersey's corner. */
.pitch__token-badge {
	position: absolute;
	top: -5px;
	left: -5px;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: 15px;
	height: 15px;
	padding: 0 3px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	color: var(--color-white);
	font-size: 8px;
	font-weight: var(--font-weight-heading);
	line-height: 1;
	border: 1.5px solid var(--color-background);
}

/* Bugfix, 2026-09-28 (review): a single-line, nowrap+ellipsis label at
   this width/font truncated a real surname ("K. Shrest…") even with the
   short-name field above — two names both truncating to the same prefix
   read as identical, defeating jerseyDisplayNames()'s whole point. Wraps
   to up to 2 lines now instead of ellipsis-truncating to 1; -webkit-
   line-clamp is the safety net for a genuinely long name rather than
   letting a 3rd line push the jersey card's height around. */
.pitch__token-name {
	padding: 2px 3px;
	background-color: var(--color-background);
	border-top: 1px solid var(--color-border);
	font-size: 8px;
	font-weight: var(--font-weight-subheading);
	color: var(--color-text-primary);
	text-align: center;
	line-height: 1.2;
	overflow: hidden;
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
}

.pitch__token-stat {
	padding: 2px 3px;
	border-radius: 0 0 var(--radius-sm) var(--radius-sm);
	background-color: var(--color-text-primary);
	color: var(--color-background);
	font-size: 8px;
	font-weight: var(--font-weight-heading);
	text-align: center;
	font-variant-numeric: tabular-nums;
}

/* Step 2 — swap-to-reorder between Playing XI and bench. An "armed" token
   (tapped once, awaiting a second tap to swap with) gets the accent ring
   around the whole card so the pending action is visible before it's
   completed. */

.pitch__token--armed {
	box-shadow: 0 0 0 2px var(--color-accent);
	border-radius: var(--radius-md);
}

/* Generic "empty slot" markers filling the gap up to a role's minimum (or
   the bench's remaining capacity) — see TokenPlaceholder in view.js. A
   dashed, unfilled version of the jersey card, same three-row structure
   (jersey/name/stat) as a real Token so it takes up exactly the same
   height rather than looking short next to real tokens — the stat row is
   just left blank (a non-breaking space keeps its line box the same
   height as a real player's price).
   A Playing XI role's placeholder renders as a real <button> (tappable —
   see handlePlaceholderClick in view.js); the bench's own leftover-
   capacity placeholders render as a plain, non-interactive <div>. Both
   share this same visual treatment. */
.pitch__token--placeholder {
	box-shadow: none;
	background-color: transparent;
}

.pitch__token-jersey--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: transparent;
	border: 2px dashed var(--color-border);
	border-radius: var(--radius-sm);
	clip-path: none;
}

.pitch__token-plus {
	font-size: 18px;
	line-height: 1;
	font-weight: var(--font-weight-heading);
	color: var(--color-text-secondary);
}

.pitch__token-name--placeholder {
	background-color: transparent;
	border-top-color: transparent;
	color: var(--color-text-secondary);
	font-weight: var(--font-weight-body);
}

.pitch__token-stat--placeholder {
	background-color: transparent;
}

/* A bench player eligible to fill an armed role's gap (see armedRole in
   view.js) — a dashed accent outline rather than .pitch__token--armed's
   solid ring, so "any of these would work" reads differently from "this
   exact player is mid-swap." outline (not box-shadow) so it doesn't
   compete visually with the jersey's own shadow. */
.pitch__token--eligible {
	outline: 2px dashed var(--color-accent);
	outline-offset: 2px;
	border-radius: var(--radius-sm);
}

/* Bench is its own card now, not overlaid on the grass (only the Playing
   XI stands on the ground itself) — same .card spacing/margin convention
   as .captain-step__group below it. */
/* Divider between the Playing XI and the bench — both now live inside
   the same .pitch card (a real substitute waits just off the field, not
   in an unrelated list elsewhere on the page), so this needs to read as
   a clear, deliberate break rather than the bench looking like it's
   still somehow part of the ground. Styled like .pitch__row-label's
   pill chip for the same on-grass legibility, flanked by two rule lines
   so it reads as a section break, not just another row. */
.pitch__divider {
	position: relative;
	z-index: 1;
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin: var(--space-8) 0;
}

.pitch__divider::before,
.pitch__divider::after {
	content: '';
	flex: 1 1 auto;
	height: 2px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
	opacity: 0.6;
}

.pitch__divider-label {
	flex: 0 0 auto;
	padding: 2px 10px;
	border-radius: var(--radius-full);
	background-color: var(--color-background);
	box-shadow: var(--shadow-elevation);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.pitch__row-tokens--bench {
	margin-top: 0;
}

/* (.bench-strip__priority removed — bench order isn't a substitution
   priority yet; see src/blocks/squad-builder/view.js's pitchGround().) */

/* Player Pool — a real table (Player / Price / Fantasy Pts), reusing
   .standings-table exactly as player-stats.html does, sorted by
   js/table-sort.js (click a column header) rather than a custom sort
   control. .player-pool-filters reuses .tabs exactly as
   js/player-stats.js's role filter does.

   .squad-pool-wrap is the bounded box (filters fixed at its top,
   .squad-pool-scroll below it the only thing that scrolls); the
   <thead> inside stays visible via position:sticky scoped to THAT
   inner scroll container — a standard, self-contained sticky-header
   pattern, unrelated to (and unaffected by) the page-level sticky
   issues noted above, since nothing here is sticky relative to the
   page itself. */

.player-pool-filters {
	margin-bottom: 0;
}

/* Just the bleed-to-card-edges wrapper now — .squad-pool-table tbody is
   the actual scrolling element (see the fixed-header comment above). */
.squad-pool-scroll {
	margin: var(--space-16) calc(var(--space-16) * -1) calc(var(--space-16) * -1);
}

/* <thead>/<tbody>/<tr> are display:block/flex (not the table's default
   table-header-group/row-group/table-row) — this doesn't touch the
   underlying table DOM, so js/table-sort.js's table.tBodies[0] /
   th[data-sort-index] lookups are unaffected. This was originally built
   as a bounded-tbody-scroll fixed-header technique specifically to avoid
   position:sticky on <thead>, after sticky failed in an earlier layout
   here (nested several levels deep inside an internally-scrolling
   ancestor — border-collapse, the scroll-reveal transform bug, and the
   grid/flex ancestor chain were all individually ruled out as the sole
   cause at the time, without a conclusive single culprit found).
   <thead> is now page-level sticky again (see the
   @media (min-width: 768px) block above) instead of <tbody> having its
   own bounded scroll — a materially different configuration (no nested
   overflow:auto ancestor between <thead> and the viewport this time,
   and the scroll-reveal transform bug is now fixed — see .is-visible
   below), verified working via headless Playwright rather than assumed
   fixed by the transform change alone. */

.squad-pool-table {
	display: block;
	width: 100%;
}

.squad-pool-table thead,
.squad-pool-table tr {
	display: block;
	width: 100%;
}

.squad-pool-table tr {
	display: flex;
	align-items: center;
	/* Moved here from .standings-table's own per-cell border-bottom (which
	   .squad-pool-table th/td below cancels): as flex items, each cell's
	   height is its OWN content's height, not the shared table-row height
	   a real <tr>/<td> pair would give it for free — the player-name cell
	   (an avatar + text, .squad-pool-table__player) is taller than a plain
	   price/points cell, so a per-cell border used to land a few px higher
	   under the numeric columns than under the name column, reading as
	   staggered rows (reported 2026-09-28). One border on the row's own
	   box, which flexbox sizes to the tallest cell regardless of
	   align-items, always lines up under every column. */
	border-bottom: 1px solid var(--color-border);
}

.squad-pool-table th,
.squad-pool-table td {
	display: block;
	box-sizing: border-box;
	border-bottom: none;
}

.squad-pool-table th:first-child,
.squad-pool-table td:first-child {
	flex: 1 1 auto;
	min-width: 0;
}

.squad-pool-table th:not(:first-child),
.squad-pool-table td:not(:first-child) {
	flex: 0 0 76px;
}

.squad-pool-table tbody {
	display: block;
	width: 100%;
}

/* Prevents a scroll-into-view (keyboard focus, Playwright's auto-scroll
   before a click) from landing a row hidden under the stacked sticky
   headers above it (.team-builder-header + .pool-sticky-header + this
   table's own sticky <thead> — see the @media (min-width: 768px) block
   near .squad-builder-columns for the exact offsets these mirror). */
@media (min-width: 768px) {
	.squad-pool-table tbody tr {
		scroll-margin-top: calc(var(--sticky-chrome-top) + var(--squad-header-clearance) + var(--pool-header-height) + var(--thead-height) + var(--space-8));
	}
}

.squad-pool-table__player {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.squad-pool-table__name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.squad-pool-table tbody tr {
	cursor: pointer;
}

@media (min-width: 768px) {
	.squad-pool-table tbody tr:hover {
		background-color: var(--color-neutral-fill);
	}
}

.squad-pool-table tbody tr:active {
	background-color: var(--color-neutral-fill);
}

.squad-pool-table__row--selected {
	background-color: var(--color-neutral-fill);
}

/* The pool can legitimately end up with nothing left to show (a role at
   its cap, or the squad already full — see poolRows' filtering in
   view.js) — this row explains why rather than leaving a blank table. */
.squad-pool-table__empty {
	padding: var(--space-24) var(--space-8);
	text-align: center;
}

.squad-pool-table__empty .pitch__row-hint {
	margin: 0 auto;
}

.squad-pool-table__row--selected .squad-pool-table__name {
	color: var(--crimson-text);
	font-weight: var(--font-weight-subheading);
}

/* Token popup menu — Captain/Vice-Captain/Swap, opened by tapping any
   Playing XI or bench token (js/squad-builder.js). One shared instance,
   repositioned per tap via getBoundingClientRect() rather than living
   inside .pitch (position:fixed so it isn't clipped by .pitch's own
   overflow:hidden), same overlay look/open-close convention as
   .notif-panel (reuses its animation) — this replaces what used to be
   two separate Captain/Vice-Captain list sections. */
.token-menu {
	position: fixed;
	z-index: 50;
	display: flex;
	flex-direction: column;
	width: 190px;
	padding: var(--space-8);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
	border: 1px solid var(--color-border);
	box-shadow: var(--shadow-elevation);
	transform-origin: top left;
	animation: notif-panel-in 0.15s ease;
}

.token-menu[hidden] {
	display: none;
}

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

.token-menu__item {
	display: block;
	width: 100%;
	padding: var(--space-8);
	border: none;
	border-radius: var(--radius-sm);
	background: none;
	text-align: left;
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	cursor: pointer;
}

.token-menu__item[hidden] {
	display: none;
}

.token-menu__item:hover,
.token-menu__item:focus-visible {
	background-color: var(--color-neutral-fill);
}

/* ---- Squad Builder: player avatar default — a monogram (initials on a
   tonal background) shown when a player has no uploaded photo, same
   default-avatar pattern most native apps use rather than a generic
   person-silhouette icon (which reads as a broken/missing image more
   than a deliberate design). See src/blocks/squad-builder/view.js's
   PlayerAvatar component, shared by the pitch/bench tokens and the
   player-pool table rows. Tones stay within the site's monochrome
   palette (varying lightness, not hue) per the design system's "one
   accent, used sparingly" rule — see docs/design-system.md — while still
   giving each player a bit of visual distinction. */

.npl-avatar-default {
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: var(--font-weight-heading);
	letter-spacing: 0.02em;
	line-height: 1;
}

.npl-avatar-tone-1 {
	background-color: var(--color-gray-200);
	color: var(--color-text-primary);
}

.npl-avatar-tone-2 {
	background-color: var(--color-gray-300);
	color: var(--color-text-primary);
}

.npl-avatar-tone-3 {
	background-color: var(--color-gray-400);
	color: var(--color-text-primary);
}

.npl-avatar-tone-4 {
	background-color: var(--color-gray-500);
	color: var(--color-white);
}

.npl-avatar-tone-5 {
	background-color: var(--color-gray-600);
	color: var(--color-white);
}

.leaderboard-preview__avatar {
	overflow: hidden;
}

.leaderboard-preview__avatar img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.leaderboard-preview__avatar .npl-avatar-default {
	width: 100%;
	height: 100%;
	font-size: var(--font-size-xs);
}

/* ---- Squad Builder: step wizard (draft flow for a first-time squad —
   once a squad is saved, npl/squad-builder switches straight to the full
   builder instead of this wizard on future visits; see view.js's
   `wizardMode`, true only while the saved squad is incomplete) ---- */

.npl-wizard-steps {
	display: flex;
	align-items: center;
	margin-bottom: var(--space-24);
}

.npl-wizard-step {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.npl-wizard-step__index {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	border-radius: var(--radius-full);
	border: 2px solid var(--color-border);
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
	background-color: var(--color-background);
}

.npl-wizard-step--done .npl-wizard-step__index {
	border-color: var(--color-positive);
	background-color: var(--color-positive);
	color: var(--color-white);
}

.npl-wizard-step--active .npl-wizard-step__index {
	border-color: var(--color-accent);
	color: var(--crimson-text);
}

.npl-wizard-step__label {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	white-space: nowrap;
}

.npl-wizard-step--active .npl-wizard-step__label {
	color: var(--color-text-primary);
	font-weight: var(--font-weight-subheading);
}

.npl-wizard-step__label,
.npl-wizard-step__index {
	transition: none;
}

@media (max-width: 480px) {
	.npl-wizard-step__label {
		display: none;
	}
}

.npl-wizard-connector {
	flex: 1 1 auto;
	height: 2px;
	min-width: 12px;
	margin: 0 var(--space-8);
	background-color: var(--color-border);
}

.npl-wizard-connector--done {
	background-color: var(--color-positive);
}

.npl-wizard-panel {
	max-width: 640px;
	margin: 0 auto;
}

.npl-wizard-panel .role-summary {
	justify-content: center;
}

.npl-wizard-rules {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	margin: var(--space-16) 0;
	padding: 0;
	list-style: none;
}

.npl-wizard-rules li {
	display: flex;
	gap: var(--space-8);
	align-items: baseline;
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.npl-wizard-rules li strong {
	color: var(--color-text-primary);
}

.npl-wizard-actions {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: var(--space-8);
	margin-top: var(--space-24);
}

.npl-wizard-actions--single {
	justify-content: flex-end;
}

.npl-wizard-review-summary {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16);
	justify-content: center;
	margin: var(--space-16) 0;
}

.npl-wizard-review-summary .league-card__stat {
	text-align: center;
}


/* ==== Squad wizard polish (src/blocks/squad-builder/view.js) ==== */

/* Which players a role summary counts — the whole 15 vs. the first 11. */
.role-summary__scope {
	font-weight: var(--font-weight-label);
	color: var(--color-text-primary);
}

/* "+1 on bench" beside a pitch row's label: the rest of that role is on
   the bench, not missing (the all-rounders report). */
.pitch__row-bench-count {
	margin-left: 6px;
	padding: 0 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-neutral-fill);
	color: var(--color-text-primary);
	text-transform: none;
	letter-spacing: 0;
	font-weight: var(--font-weight-label);
}

/* Step 3: the bench is shown for reference but can't be Captain/VC. */
.pitch__token--muted {
	opacity: 0.45;
	filter: grayscale(1);
}

.token-menu {
	width: auto;
	min-width: 190px;
	max-width: 260px;
}

.token-menu__note {
	margin: 0;
	padding: var(--space-8);
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Inline success/error line (save, login). Text plus a left rule, never
   color alone. */
.npl-inline-status {
	margin: var(--space-8) 0;
	padding: 6px 10px;
	border-left: 3px solid currentColor;
	border-radius: var(--radius-sm);
	background-color: var(--color-surface);
	font-size: var(--font-size-sm);
	line-height: var(--line-height-dense);
}

.npl-inline-status--error {
	color: var(--crimson-text);
}

.npl-inline-status--success {
	color: var(--color-positive);
}

/* First save done — confirmation + next steps above the full builder. */
.npl-saved-banner {
	margin-bottom: var(--space-24);
	border-left: 4px solid var(--color-positive);
}

.npl-saved-banner__title {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin: 0 0 var(--space-8);
	font-size: var(--font-size-lg);
}

.npl-saved-banner__tick {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	border-radius: var(--radius-full);
	background-color: var(--color-positive);
	color: var(--color-white);
	font-size: var(--font-size-sm);
}

.npl-saved-banner__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-8) var(--space-16);
	margin-top: var(--space-16);
}

/* Phones: "My Squad | Player Pool" switch (which panel is hidden lives in
   the block's own style.css, so it works under any theme). */
.npl-panel-switch {
	position: sticky;
	top: calc(var(--sticky-chrome-top) + var(--squad-header-clearance, 0px));
	/* Opaque strip above, so scrolled pool rows can't show in the seam under the header. */
	box-shadow: 0 -8px 0 var(--color-background);
	z-index: 9;
	display: flex;
	gap: 4px;
	margin: var(--space-16) 0;
	padding: 4px;
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
}

.npl-panel-switch__option {
	flex: 1 1 0;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 0 var(--space-8);
	text-align: center;
	min-height: 44px;
	border: 0;
	border-radius: var(--radius-sm);
	background: transparent;
	color: var(--color-text-secondary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

.npl-panel-switch__option[aria-pressed="true"] {
	/* Same active treatment as the Season Squad | Match XI control. */
	background-color: color-mix( in srgb, var(--color-accent) 14%, var(--color-surface) );
	color: var(--color-accent);
	font-weight: var(--font-weight-heading);
}

.npl-panel-switch__count {
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	opacity: 0.8;
}

@media (min-width: 768px) {
	.npl-panel-switch {
		display: none;
	}
}

/* Tournament Squad vs. Team Builder — first-visit note (shared). */
.npl-mode-explainer {
	display: flex;
	align-items: flex-start;
	gap: var(--space-16);
	margin-bottom: var(--space-24);
	padding: var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
}

.npl-mode-explainer__list {
	flex: 1 1 auto;
	display: grid;
	gap: var(--space-8);
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--font-size-sm);
}

.npl-mode-explainer__list li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px var(--space-8);
	color: var(--color-text-secondary);
}

.npl-mode-explainer__list li strong {
	color: var(--color-text-primary);
}

.npl-mode-explainer__here {
	padding: 0 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-neutral-fill);
	color: var(--color-text-primary);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
}

.npl-mode-explainer__text {
	flex-basis: 100%;
}

.npl-mode-explainer__dismiss {
	flex: 0 0 auto;
	min-height: 44px;
	padding: 0 var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
	background: var(--color-background);
	color: var(--color-text-primary);
	font-family: var(--font-family-sans);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

@media (max-width: 480px) {
	.npl-mode-explainer {
		flex-direction: column;
		align-items: stretch;
	}
}

/* ==== Login card (src/blocks/auth-login/view.js) — same card language
   as the squad wizard's Step 1 panel ==== */

.npl-auth-card {
	max-width: 440px;
	margin: var(--space-32) auto;
	padding: var(--space-24);
}

.npl-auth-card__header {
	margin-bottom: var(--space-24);
}

.npl-auth-card__title {
	margin: 0 0 var(--space-8);
	font-size: var(--font-size-xl);
	font-weight: var(--font-weight-heading);
	line-height: var(--line-height-heading);
}

.npl-auth-card__lede {
	margin: 0;
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	overflow-wrap: anywhere;
}

.npl-auth-card__lede strong {
	color: var(--color-text-primary);
}

.npl-auth-card__links {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: var(--space-8);
	margin: var(--space-16) 0 0;
	font-size: var(--font-size-sm);
}

.npl-auth-card .form-field {
	margin-bottom: var(--space-16);
}

.npl-auth-card .form-field .form-input {
	margin-bottom: 4px;
}

.form-input--invalid {
	border-color: var(--color-accent);
	box-shadow: 0 0 0 1px var(--color-accent);
}

.npl-field-error,
.npl-field-help {
	margin: 0;
	font-size: var(--font-size-xs);
	line-height: var(--line-height-dense);
}

.npl-field-error {
	color: var(--crimson-text);
}

.npl-field-help {
	color: var(--color-text-secondary);
}

.npl-otp-input {
	font-family: var(--font-family-mono);
	font-size: var(--font-size-xl);
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.5em;
	text-align: center;
}

/* Only ever rendered when the server's dev gate allows it (local sites). */
.npl-dev-banner {
	margin: 0 0 var(--space-16);
	padding: var(--space-8) var(--space-16);
	border: 1px dashed var(--color-text-secondary);
	border-radius: var(--radius-sm);
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.npl-dev-banner__tag {
	display: inline-block;
	margin-right: var(--space-8);
	padding: 0 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-text-primary);
	color: var(--color-background);
	font-weight: var(--font-weight-label);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-dev-banner__code {
	font-family: var(--font-family-mono);
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
	letter-spacing: 0.1em;
}

/* Pitch card legibility — surname only (view.js's shortName()), readable
   sizes; slightly wider cards on phones, where rows wrap to two lines. */
.pitch__token-name {
	font-size: 10px;
}

.pitch__token-stat {
	font-size: 9px;
}

@media (max-width: 767px) {
	.pitch__token {
		width: 62px;
	}

	.pitch__token-jersey {
		height: 34px;
	}

	.pitch__token-name {
		font-size: 11px;
	}

	.pitch__token-stat {
		font-size: 10px;
	}
}

/* Disabled primary actions must look disabled (Continue / Save Squad
   were solid crimson while unusable). */
.npl-squad-builder .btn:disabled,
.npl-auth-card .btn:disabled {
	opacity: 0.45;
	box-shadow: none;
}

/* Phones: the reason Continue is disabled gets its own full-width line,
   Continue full width under it, Back last. */
@media (max-width: 767px) {
	.npl-wizard-actions {
		flex-direction: column-reverse;
		align-items: stretch;
	}

	.npl-wizard-actions > div {
		display: flex;
		flex-direction: column;
		gap: var(--space-8);
	}

	.npl-wizard-actions .btn {
		width: 100%;
	}
}

/* Captain / Vice-Captain chip — a direct child of the card (not the
   clipped jersey), so it's always fully visible.
   Bugfix, 2026-09-28 (review): this used to read "C in the accent [crimson],
   VC in near-black" — the base .pitch__token-badge rule IS the accent
   color, and only a Captain badge ever fell through to it unstyled
   (.pitch__token-badge--captain had no rule of its own). That put a loud
   crimson badge on the pitch fighting for attention with Live/alert
   states, AND meant the pitch's C/VC treatment didn't match the round
   breakdown's own C/VC tags (.npl-round-breakdown__tag below) — a crimson
   pill there too, next to a dark/white VC pill. One neutral treatment for
   both now, everywhere: crimson stays reserved for live/alert states,
   text ("C ×2"/"VC ×1.5") is what tells the two apart, not color. */
.pitch__token {
	position: relative;
}

.pitch__token-badge {
	top: -7px;
	left: -7px;
	z-index: 2;
	min-width: 20px;
	height: 20px;
	padding: 0 4px;
	font-size: 10px;
	border-width: 2px;
	background-color: var(--color-text-primary);
	color: var(--color-background);
	white-space: nowrap;
}

.pitch__token-badge--captain,
.pitch__token-badge--vice {
	background-color: var(--color-text-primary);
	color: var(--color-background);
}


/* ==== Home page (page "Home"; blocks npl/next-match, npl/my-fantasy,
   npl/leaderboard-preview, npl/points-table, npl/season-leaders,
   npl/news-block, npl/player-stats "top") ==== */

/* One "View more →" style everywhere on the page: every section header's
   link (section__link) and the News block's own link look identical. */
.section__link,
.npl-news-block__view-more,
.npl-next-match__secondary {
	color: var(--crimson-text);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	text-decoration: none;
	white-space: nowrap;
}

.section__link:hover,
.npl-news-block__view-more:hover,
.npl-next-match__secondary:hover,
.section__link:focus-visible,
.npl-news-block__view-more:focus-visible,
.npl-next-match__secondary:focus-visible {
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Data cards (leaderboard, points table, caps, top performers): flat,
   bordered, tabular — reads as "stats". Editorial cards (news) keep the
   softer default look with bigger headlines. */
.card--stat {
	background-color: var(--color-background);
	border: 1px solid var(--color-border);
	box-shadow: none;
}

.card--stat .font-mono,
.card--stat .standings-table__num {
	font-variant-numeric: tabular-nums;
}

/* Tier spacing: a little more air above each section than inside it. */
.npl-next-match,
.npl-my-fantasy,
.npl-home-pair,
.npl-season-leaders,
.npl-news-block,
.npl-player-stats {
	margin-top: var(--space-32);
}

.npl-next-match {
	margin-top: 0;
}

.npl-home-matches-header {
	margin-top: var(--space-32);
}

.npl-home-note,
.npl-home-empty {
	margin: var(--space-8) 0 0;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* ---- Tier 1: Next Match hero ---- */
.npl-next-match__hero {
	border: 1px solid var(--color-border);
	border-bottom-left-radius: 0;
	border-bottom-right-radius: 0;
}

.npl-next-match__facts {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-8) var(--space-16);
	margin-top: 4px;
}

/* The embedded npl/countdown reads as the hero's countdown pill. */
.npl-next-match__facts .npl-countdown {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 12px;
	border-radius: var(--radius-full);
	background-color: var(--color-text-primary);
	color: var(--color-background);
	font-size: var(--font-size-sm);
}

.npl-next-match__facts .npl-countdown__value {
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
	font-weight: var(--font-weight-heading);
}

.npl-next-match__teams-created {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.npl-next-match__actions {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: var(--space-8);
	text-align: center;
}

/* Head-to-head: attached to the hero as its lower strip — it's about the
   same match, so it no longer floats as a separate section. */
.npl-next-match__h2h {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px var(--space-16);
	padding: var(--space-16) var(--space-24);
	border: 1px solid var(--color-border);
	border-top: 0;
	border-radius: 0 0 var(--radius-lg) var(--radius-lg);
	background-color: var(--color-background);
}

.npl-next-match__h2h .section__label {
	margin: 0;
}

.npl-next-match__h2h p {
	margin: 0;
	font-size: var(--font-size-sm);
}

.npl-next-match__h2h-record {
	font-weight: var(--font-weight-heading);
}

.npl-next-match__h2h-note,
.npl-next-match__h2h-last {
	color: var(--color-text-secondary);
}

@media (min-width: 768px) {
	.npl-next-match__hero {
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
	}

	.npl-next-match__actions {
		align-items: flex-end;
		text-align: right;
	}

	.cta-banner.npl-next-match__hero .cta-banner__match {
		font-size: var(--font-size-xl);
	}
}

/* ---- Tier 2: My Fantasy ---- */
.npl-my-fantasy__grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: var(--space-16);
}

.npl-my-fantasy__grid > .card {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-8);
	margin: 0;
}

.npl-my-fantasy__squad {
	border-left: 3px solid var(--color-text-primary);
}

.npl-my-fantasy__detail {
	margin: 0;
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
}

.npl-my-fantasy__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-8) var(--space-16);
	margin-top: auto;
}

a.npl-my-fantasy__league {
	color: inherit;
	text-decoration: none;
}

a.npl-my-fantasy__league:hover {
	border-color: var(--color-text-secondary);
}

.npl-my-fantasy__signin {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
}

.npl-my-fantasy__signin p {
	margin: 0;
}

/* ---- Tier 3: Leaderboard + Points Table side by side on wide screens ---- */
.npl-home-pair {
	display: grid;
	gap: var(--space-32);
}

.npl-home-pair > * {
	margin-top: 0;
	min-width: 0;
}

@media (min-width: 1024px) {
	.npl-home-pair {
		grid-template-columns: 1fr 1fr;
		align-items: start;
	}
}

.leaderboard-preview__head {
	display: flex;
	justify-content: space-between;
	padding-bottom: var(--space-8);
	margin-bottom: 4px;
	border-bottom: 1px solid var(--color-border);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-you-tag {
	margin-left: 6px;
	padding: 0 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	color: var(--color-white);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	vertical-align: 1px;
}

.leaderboard-preview__row--you {
	background-color: var(--color-surface);
	box-shadow: inset 3px 0 0 var(--color-accent);
}

.npl-leaderboard-preview__summary {
	margin: var(--space-8) 0 0;
	padding-top: var(--space-8);
	border-top: 1px solid var(--color-border);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

/* Points table, embedded: numbers right-aligned and fixed-width. */
.npl-points-table .npl-points-table__num,
.npl-points-table th.npl-points-table__num {
	text-align: right;
	font-family: var(--font-family-mono);
	font-variant-numeric: tabular-nums;
}

.npl-home-pair .npl-points-table__table-wrap {
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	overflow-x: auto;
}

@media (max-width: 480px) {
	.npl-points-table__table th,
	.npl-points-table__table td {
		padding-left: 6px;
		padding-right: 6px;
	}
}

/* ---- Season Leaders ---- */
.npl-season-leaders__scope {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.npl-season-leaders .cap-card {
	margin: 0;
}

.cap-card--orange {
	border-top: 3px solid var(--color-text-primary);
}

.cap-card--purple {
	border-top: 3px solid var(--color-text-secondary);
}

/* ---- Top performers / full player table: numbers right-aligned ---- */
.player-stats-table th.standings-table__num,
.player-stats-table td.standings-table__num {
	text-align: right;
}

.player-stats-table__rank {
	width: 2.5em;
	color: var(--color-text-secondary);
}

.player-stats-table__sortable button {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-transform: inherit;
	letter-spacing: inherit;
	cursor: pointer;
}

.player-stats-table__sortable.is-sorted button {
	color: var(--color-text-primary);
}

.player-stats-table__sort-icon {
	font-size: 10px;
	opacity: 0.6;
}

.player-stats-table__sortable.is-sorted .player-stats-table__sort-icon {
	opacity: 1;
}

.player-stats-table__empty {
	padding: var(--space-24);
	text-align: center;
	color: var(--color-text-secondary);
}

.player-stats-filters__row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	margin-top: var(--space-8);
}

.player-stats-filters__row .form-input {
	flex: 1 1 200px;
	margin-bottom: 0;
}

.player-stats-pager {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-8);
	margin-top: var(--space-16);
}

.player-stats-pager .player-stats-count {
	margin: 0;
}

.player-stats-pager__controls {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}

.player-stats-pager__page {
	min-width: 4em;
	text-align: center;
	font-size: var(--font-size-sm);
}

/* ==== Round navigator ("< Round N >") — shared by the Matches strip,
   My Team points-by-round and the Leaderboard (src/blocks/shared/
   round-navigator.js). Same rules as matches-block/style.css, available
   on every page. ==== */
.npl-matches-block__gw-nav {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var( --wp--preset--spacing--spacing-16, 1rem );
	margin-bottom: var( --wp--preset--spacing--spacing-16, 1rem );
}

.npl-matches-block__gw-arrow {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	flex: 0 0 auto;
	border: 1px solid var( --wp--custom--color--border, #E1E1E3 );
	border-radius: var( --wp--custom--radius--full, 999px );
	background-color: var( --wp--custom--color--base, #FFFFFF );
	color: var( --wp--custom--color--text-primary, #121214 );
	text-decoration: none;
}

.npl-matches-block__gw-arrow svg {
	width: 18px;
	height: 18px;
}

a.npl-matches-block__gw-arrow:hover,
a.npl-matches-block__gw-arrow:focus-visible {
	border-color: var( --wp--preset--color--accent, #E4002B );
	color: var( --wp--custom--color--crimson-text, #C8102E );
}

.npl-matches-block__gw-arrow--disabled {
	opacity: 0.35;
}

.npl-matches-block__gw-current,
.npl-matches-block__gw-select {
	min-width: 132px;
	padding: 8px 14px;
	border-radius: var( --wp--custom--radius--full, 999px );
	background-color: var( --wp--custom--color--base, #FFFFFF );
	border: 1px solid var( --wp--custom--color--border, #E1E1E3 );
	color: var( --wp--custom--color--text-primary, #121214 );
	font-size: var( --wp--preset--font-size--sm, 0.875rem );
	font-weight: 600;
	text-align: center;
}

.npl-matches-block__gw-select {
	appearance: none;
	cursor: pointer;
	background-image: url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23121214' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E" );
	background-repeat: no-repeat;
	background-position: right 10px center;
	background-size: 14px;
	padding-right: 30px;
}

.npl-matches-block__gw-select:hover,
.npl-matches-block__gw-select:focus-visible {
	border-color: var( --wp--preset--color--accent, #E4002B );
}

/* Editor-only summary placeholder */


button.npl-matches-block__gw-arrow {
	cursor: pointer;
	padding: 0;
}

button.npl-matches-block__gw-arrow:hover:not(:disabled),
button.npl-matches-block__gw-arrow:focus-visible {
	border-color: var( --wp--preset--color--accent, #E4002B );
	color: var( --wp--custom--color--crimson-text, #C8102E );
}

button.npl-matches-block__gw-arrow:disabled {
	opacity: 0.35;
	cursor: default;
}


/* ==== My Team: Points by round + Transfers (squad-builder round-points.js,
   view.js transferBar) and the Leaderboard block ==== */

.npl-myteam-tabs {
	margin-bottom: var(--space-24);
}

/* Transfer bar — open round, deadline, free transfers, live cost. */
.npl-transfer-bar {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16) var(--space-32);
	margin-bottom: var(--space-24);
	padding: var(--space-16) var(--space-24);
	border: 1px solid var(--color-border);
	border-left: 4px solid var(--color-text-primary);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
}

.npl-transfer-bar--cost {
	border-left-color: var(--color-accent);
}

.npl-transfer-bar__item {
	display: flex;
	flex-direction: column;
	gap: 2px;
	font-size: var(--font-size-sm);
}

.npl-transfer-bar__label {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-transfer-bar--cost .npl-transfer-bar__item--cost strong {
	color: var(--crimson-text);
}

.npl-transfer-bar__rule,
.npl-transfer-bar__line {
	flex-basis: 100%;
	margin: 0;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Transfers review — what's going out/in, the free captaincy/lineup
   changes, and the server-priced cost. Normal page flow (not the sticky
   footer, which is .npl-transfer-actionbar below, a separate, always-
   reachable compact bar) — reference material worth scrolling to, same
   reasoning as .npl-squad-detail. No max-height/internal scroll needed
   now that it isn't squeezed inside a small sticky box. */
.npl-transfer-review {
	margin-top: var(--space-16);
	padding: var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
}

.npl-transfer-review__summary {
	margin-bottom: var(--space-8);
}

.npl-transfer-review__title {
	margin: 0 0 var(--space-8);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
}

.npl-transfer-review__list,
.npl-transfer-review__free {
	margin: 0 0 var(--space-8);
	padding: 0;
	list-style: none;
}

.npl-transfer-review__pair {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: center;
	gap: var(--space-8);
	padding: var(--space-8) 0;
	border-bottom: 1px solid var(--color-border);
}

.npl-transfer-review__side {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.npl-transfer-review__side--in {
	text-align: right;
}

.npl-transfer-review__tag {
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-transfer-review__name {
	overflow: hidden;
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
	text-overflow: ellipsis;
	white-space: nowrap;
}

.npl-transfer-review__side--out .npl-transfer-review__name {
	color: var(--color-text-secondary);
	text-decoration: line-through;
}

.npl-transfer-review__meta {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.npl-transfer-review__arrow {
	color: var(--color-text-secondary);
}

.npl-transfer-review__free li {
	padding: 2px 0;
	font-size: var(--font-size-sm);
}

.npl-transfer-review__cost {
	margin: 0;
	padding: 6px 10px;
	border-left: 3px solid var(--color-text-primary);
	border-radius: var(--radius-sm);
	background-color: var(--color-surface);
	font-size: var(--font-size-sm);
	font-variant-numeric: tabular-nums;
}

.npl-transfer-review__cost--cost {
	border-left-color: var(--color-accent);
	color: var(--crimson-text);
	font-weight: var(--font-weight-heading);
}

.npl-transfer-review__cost--pending {
	color: var(--color-text-secondary);
}

/* Sticky bottom action bar — the pending transfer count + cost, and
   Confirm/Undo, always reachable while editing without scrolling all the
   way down (reported 2026-09-28: the old "No changes to save" box sat in
   normal flow below the pitch/pool, out of sight while editing further
   up). Uses .team-builder-footer's existing sticky/background/border
   treatment (already used by the wizard's own save footer) — just a
   compact single-row layout instead of that one's stacked, full-width
   buttons. Minimal (not hidden) with nothing pending, so there's always a
   consistent place to find Save. */
.npl-transfer-actionbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
}

.npl-transfer-actionbar .btn {
	flex: 0 0 auto;
	width: auto;
	white-space: nowrap;
}

.npl-transfer-actionbar__info {
	display: flex;
	align-items: baseline;
	gap: var(--space-8);
	min-width: 0;
	font-size: var(--font-size-sm);
}

.npl-transfer-actionbar__count {
	font-weight: var(--font-weight-heading);
}

.npl-transfer-actionbar__cost {
	font-size: var(--font-size-xs);
	font-variant-numeric: tabular-nums;
	color: var(--color-text-secondary);
}

.npl-transfer-actionbar__cost--cost {
	color: var(--crimson-text);
	font-weight: var(--font-weight-label);
}

.npl-transfer-actionbar__none {
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
}

.npl-transfer-actionbar__actions {
	display: flex;
	flex: 0 0 auto;
	gap: var(--space-8);
}

.npl-transfer-hint {
	margin: 0 0 var(--space-8);
}

/* Transfer validation feedback (view.js's squadProblems/pairProblems):
   every warning pairs the accent with a warning glyph, never color
   alone. */
.npl-icon-warning {
	flex: none;
	vertical-align: -0.125em;
}

/* The one role counter that's out of range, while edits make it so. */
.role-summary__item--warn {
	display: inline-flex;
	align-items: center;
	gap: 4px;
}

/* One line per broken rule, each saying what to do about it. */
.npl-transfer-review__problems {
	margin: 0 0 var(--space-8);
	padding: 0;
	list-style: none;
}

.npl-transfer-review__problem {
	display: flex;
	align-items: flex-start;
	gap: var(--space-8);
}

.npl-transfer-review__problem .npl-icon-warning {
	margin-top: 0.2em;
}

/* The specific Out → In row that caused a problem. */
.npl-transfer-review__pair--problem {
	padding-left: var(--space-8);
	border-left: 3px solid var(--color-accent);
}

.npl-transfer-review__pair-issue {
	display: flex;
	grid-column: 1 / -1;
	align-items: flex-start;
	gap: 6px;
	font-size: var(--font-size-xs);
	line-height: var(--line-height-dense);
	color: var(--crimson-text);
}

.npl-transfer-review__pair-issue .npl-icon-warning,
.squad-pool-table__warning .npl-icon-warning {
	margin-top: 0.15em;
}

/* Caption under a just-picked pool player whose swap unbalances the
   squad — shown at the moment of the tap, not only in the review. */
.squad-pool-table__player--warn {
	flex-wrap: wrap;
}

.squad-pool-table__warning {
	display: flex;
	flex: 1 0 100%;
	align-items: flex-start;
	gap: 6px;
	font-size: var(--font-size-xs);
	line-height: var(--line-height-dense);
	white-space: normal;
	color: var(--crimson-text);
}

/* Disabled primary action: neutral fill and text, not a paler crimson
   that reads as "the same button, slightly different" — plus a dashed
   edge so it's distinguishable without relying on color at all. */
.npl-squad-builder .btn--primary:disabled,
.npl-squad-builder .team-builder-footer .btn--primary:disabled {
	opacity: 1;
	border: 1px dashed var(--color-text-secondary);
	background-color: var(--color-neutral-fill);
	color: var(--color-text-secondary);
	box-shadow: none;
	cursor: not-allowed;
	transform: none;
}

/* Disabled because something needs fixing: glyph + label side by side. */
.npl-squad-builder .btn--blocked {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
}

/* Phones: the sticky save footer sits above the fixed bottom tab bar,
   not behind it. */
@media (max-width: 767px) {
	.team-builder-footer {
		bottom: var(--app-nav-height, calc(var(--wp--custom--tabbar-height, 60px) + env(safe-area-inset-bottom)));
	}
}

/* Round summary — this round vs. season total, clearly separate. */
.npl-round-summary {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: var(--space-16);
	margin-bottom: var(--space-24);
}

.npl-round-summary__item {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background-color: var(--color-background);
}

.npl-round-summary__item--round {
	border-color: var(--color-text-primary);
	border-width: 2px;
}

.npl-round-summary__label {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-round-summary__live {
	padding: 0 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-accent);
	color: var(--color-white);
	text-transform: none;
	letter-spacing: 0;
}

.npl-round-summary__value {
	font-size: var(--font-size-2xl);
	font-weight: var(--font-weight-heading);
	font-variant-numeric: tabular-nums;
	line-height: 1.1;
}

.npl-round-summary__value small {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

.npl-round-summary__hint {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

/* Per-player points with the admin-style equation. */
.npl-round-breakdown {
	margin-top: var(--space-24);
}

.npl-round-breakdown__head {
	display: flex;
	justify-content: space-between;
	padding-bottom: var(--space-8);
	border-bottom: 1px solid var(--color-border);
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.npl-round-breakdown ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

.npl-round-breakdown li + li {
	border-top: 1px solid var(--color-border);
}

.npl-round-breakdown__row {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	width: 100%;
	min-height: 44px;
	padding: 6px 0;
	border: 0;
	background: none;
	color: var(--color-text-primary);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.npl-round-breakdown__chevron {
	width: 12px;
	color: var(--color-text-secondary);
}

.npl-round-breakdown__name {
	flex: 1 1 auto;
	font-weight: var(--font-weight-label);
}

/* Bugfix, 2026-09-28 (review): matches .pitch__token-badge's own fix —
   one neutral treatment for both Captain and Vice-Captain, crimson
   reserved for live/alert states, text carries the distinction. */
.npl-round-breakdown__tag {
	margin-left: 6px;
	padding: 0 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-text-primary);
	color: var(--color-background);
	font-size: var(--font-size-xs);
}

.npl-round-breakdown__tag--vice {
	background-color: var(--color-text-primary);
	color: var(--color-background);
}

.npl-round-breakdown__pts {
	font-weight: var(--font-weight-heading);
	font-variant-numeric: tabular-nums;
}

.npl-round-breakdown__detail {
	padding: 0 0 var(--space-8) 20px;
}

.npl-round-breakdown__equation {
	margin: 4px 0;
	font-family: var(--font-family-mono);
	font-size: var(--font-size-xs);
	line-height: 1.6;
}

.npl-round-breakdown__equation--total {
	padding-top: 4px;
	border-top: 1px dashed var(--color-border);
}

.npl-round-breakdown__label {
	font-family: var(--font-family-sans);
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
	text-transform: uppercase;
}

.npl-round-breakdown__none,
.npl-round-breakdown__penalty {
	margin: 4px 0 var(--space-8) 20px;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.npl-round-breakdown__penalty {
	margin-left: 0;
	padding-top: var(--space-8);
	border-top: 1px solid var(--color-border);
	color: var(--crimson-text);
	font-weight: var(--font-weight-label);
}

.npl-round-transfers,
.npl-round-context {
	margin-top: var(--space-24);
}

.npl-round-transfers ul {
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--font-size-sm);
}

.npl-round-transfers__out {
	color: var(--color-text-secondary);
	text-decoration: line-through;
}

.npl-round-transfers__in {
	font-weight: var(--font-weight-label);
}

.npl-round-transfers__cost {
	color: var(--crimson-text);
	font-weight: var(--font-weight-label);
}

.npl-round-context summary {
	cursor: pointer;
	font-weight: var(--font-weight-label);
}

.npl-round-matches {
	display: grid;
	gap: var(--space-8);
	margin: var(--space-8) 0 0;
	padding: 0;
	list-style: none;
}

.npl-round-matches a {
	display: flex;
	flex-wrap: wrap;
	gap: 4px var(--space-16);
	padding: var(--space-8) var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-sm);
	color: inherit;
	text-decoration: none;
	font-size: var(--font-size-sm);
}

.npl-round-matches__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-8);
	flex-basis: 100%;
}

.npl-round-matches__score {
	font-family: var(--font-family-mono);
	color: var(--color-text-secondary);
}

.npl-round-matches__result {
	flex-basis: 100%;
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.npl-round-matches__yours {
	flex-basis: 100%;
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-label);
	color: var(--color-accent);
}

.npl-round-prejoin {
	margin-bottom: var(--space-16);
}

.npl-round-top {
	margin: 0;
	padding: 0;
	list-style: none;
}

.npl-round-top li {
	display: flex;
	justify-content: space-between;
	padding: 6px 0;
	border-bottom: 1px solid var(--color-border);
	font-size: var(--font-size-sm);
}

/* Leaderboard page */
.npl-leaderboard__controls {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-8) var(--space-16);
	margin-bottom: var(--space-16);
}

.npl-leaderboard__scope {
	max-width: 280px;
	margin: 0;
}

.npl-leaderboard__table .npl-leaderboard__rank {
	width: 3em;
	color: var(--color-text-secondary);
}

.npl-leaderboard__team {
	display: flex;
	flex-direction: column;
}

.npl-leaderboard__name {
	font-weight: var(--font-weight-label);
}

.npl-leaderboard__manager {
	font-size: var(--font-size-xs);
	color: var(--color-text-secondary);
}

.npl-leaderboard__table th.standings-table__num,
.npl-leaderboard__table td.standings-table__num {
	text-align: right;
}

.npl-leaderboard__table th.is-sorted {
	color: var(--color-text-primary);
}

.npl-leaderboard__total {
	font-weight: var(--font-weight-heading);
}

.npl-leaderboard__table tr.is-you {
	background-color: var(--color-surface);
	box-shadow: inset 3px 0 0 var(--color-accent);
}

.npl-leaderboard__gap td {
	text-align: center;
	color: var(--color-text-secondary);
}

.npl-leaderboard__summary {
	margin: var(--space-8) 0 0;
	padding-top: var(--space-8);
	border-top: 1px solid var(--color-border);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

/* Leaderboard scope tabs (Everyone + each of your leagues): scroll sideways
   instead of wrapping when there are many. */
.npl-leaderboard__scope-tabs {
	flex: 1 1 100%;
	min-width: 0;
}

/* Phones: the Season Squad / Match XI builders stack a lot of fixed chrome
   (top bar, budget header, panel switch, action bar, tab bar). Trim it. */
@media (max-width: 767px) {
	/* The disabled button already says "No changes to save". */
	.npl-transfer-actionbar:not(.npl-transfer-actionbar--pending) .npl-transfer-actionbar__info {
		display: none;
	}

	.npl-transfer-actionbar:not(.npl-transfer-actionbar--pending) {
		justify-content: stretch;
	}

	.npl-transfer-actionbar:not(.npl-transfer-actionbar--pending) .btn {
		flex: 1 1 auto;
	}

	.team-builder-footer {
		padding-block: var(--space-8);
	}
}

@media (max-width: 767px) {
	/* The Player Pool / My Squad switch above already names the panel. */
	.pool-sticky-header > .section__header {
		display: none;
	}

	.team-builder-header {
		padding-block: var(--space-8);
	}
}

/* Phones: the wizard's rules read as label-over-description rows instead of
   a squeezed two-column list where the bold label wraps word by word. */
@media (max-width: 767px) {
	.npl-wizard-rules {
		gap: var(--space-16);
	}

	.npl-wizard-rules li {
		flex-direction: column;
		align-items: flex-start;
		gap: 2px;
		padding-bottom: var(--space-8);
		border-bottom: 1px solid var(--color-border);
	}

	.npl-wizard-rules li:last-child {
		border-bottom: 0;
		padding-bottom: 0;
	}

	.npl-wizard-rules li strong {
		font-size: var(--font-size-base);
	}
}

/* Phones: less dead space between the budget header, "Squad details" and
   the My Squad | Player Pool switch. */
@media (max-width: 767px) {
	.npl-squad-detail {
		margin-top: var(--space-8);
		margin-bottom: 0;
	}

	.npl-panel-switch {
		margin-block: var(--space-8);
	}
}

/* Phones: one 16px rhythm between stacked tab rows / control groups on the
   Team page (it was 24px, 24px, 8px and ~34px depending on the control). */
@media (max-width: 767px) {
	.npl-team-segments .npl-team-segments__control,
	.npl-squad-builder .npl-myteam-tabs,
	.npl-squad-builder .npl-transfer-bar {
		margin-bottom: var(--space-16);
	}

	.squad-pool-scroll {
		margin-top: 0;
	}
}

/* The "why Save/Continue is disabled" line above the footer buttons: plain
   14px muted text, same quiet style as "Squad details". */
.npl-squad-builder .team-builder-footer__status .team-builder-note {
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
	line-height: 1.35;
	text-align: center;
}

.npl-squad-builder .team-builder-footer__status:has(.team-builder-note) {
	padding: var(--space-8) var(--space-8) var(--space-8);
}

/* First-time squad wizard: the action bars float above the tab bar in the
   same place on both panels — step 2 (Back / Continue) and step 3 (name,
   Back / Save Squad). */
.npl-wizard-actions--floating {
	position: sticky;
	bottom: var(--app-nav-height, calc(var(--wp--custom--tabbar-height, 60px) + env(safe-area-inset-bottom)));
	z-index: 10;
	margin: var(--space-24) calc(var(--space-16) * -1) calc(var(--space-16) * -1);
	padding: var(--space-8) var(--space-16);
	background-color: var(--color-background);
	border-top: 1px solid var(--color-border);
}

.team-builder-footer__buttons {
	display: flex;
	gap: var(--space-8);
}

.team-builder-footer__buttons .btn {
	flex: 1 1 0;
	width: auto;
}

@media (max-width: 767px) {
	.npl-wizard-actions.npl-wizard-actions--floating {
		flex-direction: row;
		align-items: flex-end;
	}

	.npl-wizard-actions--floating .btn--outline {
		flex: 0 0 auto;
		width: auto;
	}

	.npl-wizard-actions--floating > div {
		flex: 1 1 auto;
		min-width: 0;
	}
}

@media (min-width: 768px) {
	.npl-wizard-actions--floating {
		bottom: 0;
	}
}

/* Step 3 (Captain & Vice-Captain): two status cards instead of a sentence. */
.npl-leader-info {
	display: grid;
	gap: var(--space-8);
	margin: var(--space-8) 0 var(--space-16);
}

@media (min-width: 560px) {
	.npl-leader-info {
		grid-template-columns: 1fr 1fr;
	}
}

.npl-leader-info__item {
	display: flex;
	align-items: center;
	gap: var(--space-16);
	padding: var(--space-8) var(--space-16);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background-color: var(--color-surface);
}

.npl-leader-info__item--done {
	border-color: var(--color-text-secondary);
}

.npl-leader-info__badge {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: 36px;
	height: 36px;
	border-radius: var(--radius-full);
	background-color: var(--color-text-primary);
	color: var(--color-background);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
}

.npl-leader-info__item:not(.npl-leader-info__item--done) .npl-leader-info__badge {
	background-color: transparent;
	color: var(--color-text-secondary);
	border: 1px dashed var(--color-text-secondary);
}

.npl-leader-info__text {
	display: flex;
	flex-direction: column;
	min-width: 0;
	flex: 1 1 auto;
}

.npl-leader-info__title {
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-heading);
}

.npl-leader-info__mult {
	font-weight: var(--font-weight-label);
	color: var(--color-text-secondary);
}

.npl-leader-info__value {
	font-size: var(--font-size-sm);
	color: var(--color-text-secondary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.npl-leader-info__item--done .npl-leader-info__value {
	color: var(--color-text-primary);
}

.npl-leader-info__state {
	color: var(--color-text-primary);
	font-weight: var(--font-weight-heading);
}

/* Phones: a slimmer budget header — small season label, thin bar. */
@media (max-width: 767px) {
	.team-builder-header {
		padding-block: var(--space-8);
	}

	.team-builder-header__top {
		margin-bottom: 2px;
	}

	.team-builder-header__match {
		font-size: var(--font-size-xs);
		font-weight: var(--font-weight-label);
		letter-spacing: 0.04em;
		text-transform: uppercase;
		color: var(--color-text-secondary);
	}

	.credit-bar {
		margin-top: 2px;
	}

	.team-builder-header .credit-bar__track {
		height: 4px;
	}

	.credit-bar__label {
		margin-top: 4px;
	}
}

/* Phones: Points summary as one compact row of three tiles (it was three
   full-width cards stacked, pushing the pitch a screen and a half down). */
@media (max-width: 767px) {
	.npl-round-summary {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: var(--space-8);
		margin-bottom: var(--space-16);
	}

	.npl-round-summary__item {
		gap: 2px;
		padding: var(--space-8) var(--space-8) var(--space-8) var(--space-16);
		min-width: 0;
	}

	.npl-round-summary__label {
		gap: 4px;
		flex-wrap: wrap;
		letter-spacing: 0.02em;
	}

	.npl-round-summary__value {
		font-size: var(--font-size-lg);
	}

	.npl-round-summary__value small {
		font-size: var(--font-size-xs);
	}

	.npl-round-summary__hint {
		font-size: 11px;
		line-height: 1.25;
	}
}

@media (max-width: 767px) {
	/* The tile header ("Season total") already says what the number is. */
	.npl-round-summary__value small {
		display: none;
	}
}

/* Phones: Transfers info card as a tight 2 x 2 grid. */
@media (max-width: 767px) {
	.npl-squad-builder .npl-transfer-bar {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--space-8) var(--space-16);
		padding: var(--space-8) var(--space-16);
		border-left-width: 3px;
	}

	.npl-transfer-bar__rule,
	.npl-transfer-bar__line {
		grid-column: 1 / -1;
		line-height: 1.35;
	}
}

/* Pool rows already in your squad: a quiet tick instead of crimson text
   (crimson is for live / problem states everywhere else). */
.squad-pool-table__row--selected .squad-pool-table__name {
	color: var(--color-text-primary);
}

.squad-pool-table__row--selected .squad-pool-table__name::after {
	content: '✓';
	margin-left: 6px;
	font-weight: var(--font-weight-heading);
	color: var(--color-text-secondary);
}

/* "Replacing X — pick a player below": says what the armed swap is for. */
.npl-armed-note {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
	margin-bottom: var(--space-8);
	padding: var(--space-8) var(--space-16);
	border: 1px solid var(--color-border);
	border-left: 3px solid var(--color-accent);
	border-radius: var(--radius-sm);
	background-color: var(--color-neutral-fill);
	font-size: var(--font-size-sm);
	font-weight: var(--font-weight-label);
}

@media (max-width: 767px) {
	/* More room for names: slimmer Price / Pts columns. */
	.squad-pool-table th:nth-child(2),
	.squad-pool-table td:nth-child(2) {
		flex-basis: 52px;
	}

	.squad-pool-table th:nth-child(3),
	.squad-pool-table td:nth-child(3) {
		flex-basis: 64px;
	}
}

/* Phones: the transfers action bar is two tidy rows — "1 transfer · cost" on
   top, then Undo + Confirm — instead of squeezing four things into one line
   (the count wrapped onto two lines). */
@media (max-width: 767px) {
	.npl-transfer-actionbar--pending {
		flex-wrap: wrap;
		row-gap: var(--space-8);
	}

	.npl-transfer-actionbar--pending .npl-transfer-actionbar__info {
		flex: 1 1 100%;
		flex-wrap: wrap;
		white-space: nowrap;
	}

	.npl-transfer-actionbar--pending .npl-transfer-actionbar__actions {
		display: flex;
		flex: 1 1 100%;
		gap: var(--space-8);
	}

	.npl-transfer-actionbar--pending .npl-transfer-actionbar__actions .btn--primary {
		flex: 1 1 auto;
	}
}

@media (min-width: 768px) {
	.npl-only-phone {
		display: none;
	}
}

/* A player transferred in this session: an "IN" tag and a solid ring so the
   change is visible right on the ground. */
.pitch__token--in {
	outline: 2px solid var(--color-text-primary);
	outline-offset: 1px;
}

.pitch__token-in {
	position: absolute;
	top: -7px;
	right: -7px;
	z-index: 2;
	padding: 1px 6px;
	border-radius: var(--radius-full);
	background-color: var(--color-text-primary);
	color: var(--color-background);
	font-size: 10px;
	font-weight: var(--font-weight-heading);
	line-height: 1.4;
	letter-spacing: 0.04em;
}

/* "Transferred in" marker: a round icon badge (arrow into a tray). */
.pitch__token-in {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 20px;
	height: 20px;
	padding: 0;
	border: 2px solid var(--color-background);
	line-height: 1;
}

.pitch__token-in svg {
	width: 11px;
	height: 11px;
}

/* Pool rows that can't be added right now (role full, over budget, team cap,
   or slots needed for another role): greyed out but still listed. */
.squad-pool-table__row--disabled {
	opacity: 0.4;
	cursor: not-allowed;
}

.token-menu__item:disabled {
	opacity: 0.4;
	cursor: not-allowed;
}

/* "Change": candidates keep the dashed highlight, everyone else steps back. */
.pitch__token--dim {
	opacity: 0.35;
}

.npl-change-note {
	position: relative;
	z-index: 3;
	margin: 0 0 var(--space-8);
}

/* Points: bench player who would come in / XI player who didn't play. */
.pitch__token-sub {
	position: absolute;
	top: -9px;
	right: -9px;
	z-index: 2;
	padding: 1px 6px;
	border: 2px solid var(--color-background);
	border-radius: var(--radius-full);
	font-size: 9px;
	font-weight: 800;
	letter-spacing: 0.04em;
	line-height: 1.4;
}

.pitch__token-sub--in {
	background-color: var(--positive);
	color: #0e0f11;
}

.pitch__token-sub--out {
	background-color: var(--color-background);
	color: var(--color-text-secondary);
	border-color: var(--color-text-secondary);
}

.pitch__token[data-sub="in"] {
	outline: 2px solid var(--positive);
	outline-offset: 1px;
}

.pitch__token[data-sub="out"] {
	opacity: 0.6;
}

.npl-round-subs ul {
	margin: var(--space-8) 0 0;
	padding: 0;
	list-style: none;
}

.npl-round-subs li {
	display: flex;
	justify-content: space-between;
	gap: var(--space-16);
	padding: var(--space-8) 0;
	border-top: 1px solid var(--color-border);
	font-size: var(--font-size-sm);
}
