/* ============================================================
   LineupPilot — design system

   Dark terminal + signal amber. Deliberately shares no colour with the
   Premier League / FPL palette (#37003c purple, #00ff87 green): this is an
   independent product and must not read as an official site.
   ============================================================ */

:root {
	/* Brand — deep indigo base, amber as the single signal colour */
	--mj-base:          #131C31;
	--mj-base-deep:     #0B0F1A;
	--mj-base-soft:     #1E2942;
	--mj-amber:         #FFB020;
	--mj-amber-deep:    #E08A00;
	--mj-cyan:          #4CC9E0;

	/* Surfaces */
	--mj-bg:            #0B0F1A;
	--mj-bg-elevated:   #131C31;
	--mj-bg-tint:       #18223A;
	/* Popovers sit above a card that is already --mj-bg-elevated, so they cannot
	   share it: on a dark ground a black shadow reads as nothing and the panel
	   dissolves into the page behind it. Depth here is a lighter surface plus a
	   brighter edge, the shadow only seals the gap. */
	--mj-bg-float:      #1E2942;
	--mj-divider:       #263149;
	/* The edge of a floating panel, not a divider inside a card: --mj-divider is
	   1.3:1 against --mj-bg-elevated and disappears when the two surfaces meet.
	   This clears 3:1, the non-text contrast floor, so the boundary survives. */
	--mj-divider-strong:#56688F;

	/* Text */
	--mj-ink:           #E9EDF7;
	--mj-ink-mid:       #A9B3C9;
	--mj-ink-muted:     #6F7B96;

	/* Data signals — legible on dark, distinct from the amber accent */
	--mj-pos:           #35D07F;
	--mj-neg:           #FF6B6B;

	/* Pitch — floodlit tactics board, not grass */
	--mj-pitch-light:   #17293F;
	--mj-pitch-dark:    #0D1728;
	--mj-pitch-line:    rgba(255,176,32,0.16);

	/* Effects — shadows read as depth against a dark ground */
	--mj-radius:        14px;
	--mj-radius-sm:     10px;
	--mj-radius-lg:     20px;
	--mj-shadow-sm:     0 1px 2px rgba(0,0,0,0.30), 0 1px 3px rgba(0,0,0,0.24);
	--mj-shadow:        0 4px 14px rgba(0,0,0,0.36), 0 1px 3px rgba(0,0,0,0.28);
	--mj-shadow-lg:     0 18px 48px rgba(0,0,0,0.52), 0 4px 12px rgba(0,0,0,0.36);
	--mj-shadow-brand:  0 8px 24px rgba(255,176,32,0.24);
}

/* ------------------------------------------------------------
   Base
   ------------------------------------------------------------ */
html, body {
	background: var(--mj-bg);
	color: var(--mj-ink);
	font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
	font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

body {
	min-height: 100vh;
	background:
		radial-gradient(1200px 600px at 0% -10%, rgba(255,176,32,0.07), transparent 60%),
		radial-gradient(900px 500px at 100% 0%, rgba(76,201,224,0.06), transparent 55%),
		var(--mj-bg);
	background-attachment: fixed;
}

a { color: var(--mj-amber); text-decoration: none; }
a:hover { color: var(--mj-amber-deep); text-decoration: underline; }

::selection { background: var(--mj-amber); color: var(--mj-base-deep); }

/* Branded scrollbar (WebKit) */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: rgba(233,237,247,0.14); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: rgba(233,237,247,0.26); }

/* ------------------------------------------------------------
   Branded loader (used before GWT bootstraps)
   ------------------------------------------------------------ */
.mj-boot {
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 20px;
	color: var(--mj-ink);
}
.mj-boot img { width: 64px; height: 64px; filter: drop-shadow(0 6px 18px rgba(255,176,32,0.28)); }
.mj-boot .mj-boot-title { font-weight: 800; font-size: 1.2rem; letter-spacing: -0.3px; }
.mj-boot .mj-boot-sub   { font-size: 0.85rem; color: var(--mj-ink-muted); }
.mj-boot .mj-boot-spinner {
	width: 36px; height: 36px;
	border-radius: 50%;
	border: 3px solid rgba(233,237,247,0.14);
	border-top-color: var(--mj-amber);
	animation: mj-spin 0.9s linear infinite;
}
@keyframes mj-spin { to { transform: rotate(360deg); } }

/* ------------------------------------------------------------
   Top app header
   ------------------------------------------------------------ */
.app-header {
	background:
		radial-gradient(800px 200px at 12% 120%, rgba(255,176,32,0.10), transparent 60%),
		linear-gradient(135deg, #1B2540 0%, #131C31 60%, #0B0F1A 100%);
	color: #fff;
	position: sticky;
	top: 0;
	z-index: 1030;
	border-bottom: 1px solid rgba(255,255,255,0.06);
	box-shadow: 0 6px 18px rgba(0,0,0,0.42);
}
.app-header .app-brand {
	display: flex; align-items: center; gap: 12px;
	color: #fff; text-decoration: none;
}
.app-header .app-brand:hover { color: #fff; text-decoration: none; }
.app-header .app-brand img { height: 38px; width: auto; display: block; }
.app-header .app-title { font-weight: 800; letter-spacing: -0.3px; }

.app-header .gw-pill {
	background: rgba(255,255,255,0.10);
	border: 1px solid rgba(255,255,255,0.14);
	border-radius: 999px;
	padding: 5px 12px;
	font-size: 0.8rem;
	font-weight: 600;
	letter-spacing: 0.2px;
	white-space: nowrap;
	backdrop-filter: blur(6px);
}
.app-header .gw-pill .gw-pill-label {
	color: rgba(255,255,255,0.6);
	margin-right: 6px;
	font-weight: 500;
	text-transform: uppercase;
	font-size: 0.68rem;
	letter-spacing: 1px;
}
.app-header .gw-pill.gw-pill-next .gw-pill-label { color: var(--mj-amber); }

.app-header .user-chip {
	display: inline-flex; align-items: center; gap: 8px;
	background: rgba(255,255,255,0.06);
	border: 1px solid rgba(255,255,255,0.10);
	border-radius: 999px;
	padding: 4px 4px 4px 12px;
}
.app-header .user-chip .who {
	color: rgba(255,255,255,0.92);
	font-size: 0.85rem;
	font-weight: 500;
	max-width: 160px;
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-header .user-chip .avatar {
	width: 28px; height: 28px;
	border-radius: 50%;
	background: linear-gradient(135deg, var(--mj-amber) 0%, var(--mj-amber-deep) 100%);
	color: var(--mj-base-deep);
	font-weight: 800; font-size: 0.78rem;
	display: inline-flex; align-items: center; justify-content: center;
}

.btn-ghost-light {
	background: rgba(255,255,255,0.08);
	color: #fff;
	border: 1px solid rgba(255,255,255,0.18);
	border-radius: 10px;
	padding: 5px 12px;
	font-size: 0.85rem;
	font-weight: 600;
	transition: background 0.15s ease, transform 0.1s ease;
}
.btn-ghost-light:hover { background: rgba(255,255,255,0.16); color: #fff; }
.btn-ghost-light:active { transform: translateY(1px); }

/* ------------------------------------------------------------
   Buttons (brand)
   ------------------------------------------------------------ */
.btn-mj-primary {
	background: linear-gradient(135deg, var(--mj-amber) 0%, var(--mj-amber-deep) 100%);
	color: #10182A;
	border: none;
	border-radius: 10px;
	font-weight: 600;
	padding: 8px 16px;
	box-shadow: var(--mj-shadow-brand);
	transition: transform 0.1s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.btn-mj-primary:hover { color: #10182A; filter: brightness(1.06); box-shadow: 0 10px 28px rgba(255,176,32,0.32); }
.btn-mj-primary:active { transform: translateY(1px); }
.btn-mj-primary:disabled { opacity: 0.7; filter: none; box-shadow: none; cursor: not-allowed; }

.btn-mj-accent {
	background: var(--mj-base-soft);
	color: var(--mj-amber);
	border: 1px solid rgba(255,176,32,0.42);
	border-radius: 10px;
	font-weight: 700;
	padding: 8px 16px;
	box-shadow: none;
	transition: transform 0.1s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.btn-mj-accent:hover { color: var(--mj-amber); background: #24314F; }
.btn-mj-accent:active { transform: translateY(1px); }

.btn-mj-outline {
	background: transparent;
	color: var(--mj-ink);
	border: 1.5px solid var(--mj-divider);
	border-radius: 10px;
	font-weight: 600;
	padding: 7px 14px;
	transition: background 0.15s, border-color 0.15s;
}
.btn-mj-outline:hover { background: var(--mj-bg-tint); border-color: var(--mj-amber); color: var(--mj-amber); }
.btn-mj-outline:disabled { opacity: 0.55; cursor: not-allowed; }
/* Without this the hover still lights up amber, which reads as clickable. */
.btn-mj-outline:disabled:hover { background: transparent; border-color: var(--mj-divider); color: var(--mj-ink); }

/* ------------------------------------------------------------
   Section cards
   ------------------------------------------------------------ */
.card-section {
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius);
	background: var(--mj-bg-elevated);
	box-shadow: var(--mj-shadow);
	overflow: hidden;
}
.card-section .card-header {
	background: transparent;
	font-weight: 700;
	color: var(--mj-ink);
	border-bottom: 1px solid var(--mj-divider);
	padding: 14px 18px;
	display: flex;
	align-items: center;
	gap: 10px;
}
.card-section .card-header .section-eyebrow {
	font-size: 0.7rem;
	text-transform: uppercase;
	letter-spacing: 1.2px;
	color: var(--mj-amber);
	font-weight: 700;
}
.card-section .card-body { padding: 16px 18px; }

/* Compact eyebrow for small subsections */
.eyebrow {
	font-size: 0.7rem;
	text-transform: uppercase;
	letter-spacing: 1.4px;
	font-weight: 700;
	color: var(--mj-ink-muted);
}

/* ------------------------------------------------------------
   Pitch
   ------------------------------------------------------------ */
.pitch-wrap {
	background:
		radial-gradient(120% 60% at 50% 0%, rgba(255,255,255,0.08), transparent 60%),
		linear-gradient(180deg, var(--mj-pitch-light) 0%, var(--mj-pitch-dark) 100%);
	border-radius: var(--mj-radius);
	padding: 26px 14px 22px;
	color: #fff;
	box-shadow: inset 0 0 80px rgba(0,0,0,0.30), 0 4px 14px rgba(0,0,0,0.10);
	position: relative;
	overflow: hidden;
}
.pitch-wrap::before {
	content: "";
	position: absolute; inset: 0;
	background-image:
		linear-gradient(90deg, rgba(255,255,255,0.035) 50%, transparent 50%),
		linear-gradient(0deg,  rgba(255,255,255,0.02) 50%, transparent 50%);
	background-size: 80px 80px;
	pointer-events: none;
}
.pitch-wrap::after {
	/* Centre circle */
	content: "";
	position: absolute;
	left: 50%; top: 50%;
	width: 110px; height: 110px;
	border: 1px solid var(--mj-pitch-line);
	border-radius: 50%;
	transform: translate(-50%, -50%);
	pointer-events: none;
}
.pitch-row {
	display: flex;
	justify-content: space-around;
	align-items: flex-start;
	gap: 6px;
	margin-bottom: 18px;
	position: relative;
}
.pitch-row:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------
   Player chip (on pitch)
   ------------------------------------------------------------ */
.player-chip {
	width: 88px;
	text-align: center;
	color: #fff;
	transition: transform 0.15s ease;
}
.player-chip:hover { transform: translateY(-3px); }
.player-chip .shirt {
	position: relative;
	width: 60px; height: 66px;
	margin: 0 auto 5px;
	display: flex; align-items: flex-end; justify-content: center;
	border-radius: 6px;
}
.player-chip .shirt img {
	width: 60px; height: 60px;
	object-fit: contain;
	display: block;
	filter: drop-shadow(0 4px 6px rgba(0,0,0,0.40));
}
.player-chip .shirt .shirt-fallback {
	position: absolute; inset: 0;
	background: #fff;
	border-radius: 6px 6px 22px 22px;
	box-shadow: 0 4px 8px rgba(0,0,0,0.25);
	display: flex; align-items: center; justify-content: center;
	color: var(--mj-base-deep);
	font-weight: 800;
	font-size: 0.85rem;
}
.player-chip .shirt.captain::after,
.player-chip .shirt.vice::after {
	position: absolute; top: -6px; right: -6px;
	width: 22px; height: 22px;
	font-weight: 800;
	font-size: 0.75rem;
	border-radius: 50%;
	display: flex; align-items: center; justify-content: center;
	box-shadow: 0 2px 4px rgba(0,0,0,0.40);
	border: 1.5px solid rgba(255,255,255,0.92);
}
.player-chip .shirt.captain::after {
	content: "C";
	background: linear-gradient(135deg, #FFD36B, var(--mj-amber));
	color: var(--mj-base-deep);
}
.player-chip .shirt.vice::after {
	content: "V";
	background: linear-gradient(135deg, #DCE3F0, #A8B3C9);
	color: var(--mj-base-deep);
}
/* FPL's availability flag. Top-LEFT, not the top-right the official site uses:
   the armband already owns that corner here, and a doubtful captain is exactly
   the case where both have to stay legible at once. */
.player-chip .shirt .avail {
	position: absolute; top: -5px; left: -5px;
	width: 18px; height: 18px;
	border-radius: 50%;
	display: flex; align-items: center; justify-content: center;
	font-size: 0.68rem;
	font-weight: 800;
	line-height: 1;
	color: #fff;
	border: 1.5px solid rgba(255,255,255,0.92);
	box-shadow: 0 2px 4px rgba(0,0,0,0.40);
	z-index: 2;
}
.player-chip .shirt .avail.warn { background: var(--mj-amber); color: var(--mj-base-deep); }
.player-chip .shirt .avail.bad  { background: var(--mj-neg); }
.player-chip .name {
	background: rgba(0,0,0,0.62);
	border-radius: 4px 4px 0 0;
	padding: 3px 6px;
	font-size: 0.78rem;
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* Position and club, between the name and the price. Same dark backing as the
   name so the two read as one label, but muted and smaller — it identifies the
   player rather than telling you anything about him. */
.player-chip .club {
	background: rgba(0,0,0,0.62);
	padding: 0 6px 2px;
	font-size: 0.62rem;
	font-weight: 600;
	letter-spacing: 0.3px;
	color: var(--mj-ink-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* The price/mojo line and the prediction line below it are one block: the
   rounding lives on .pred, which is always rendered. */
.player-chip .meta {
	background: var(--mj-base-soft);
	border-radius: 0;
	padding: 3px 5px;
	font-size: 0.72rem;
	font-weight: 600;
	opacity: 0.96;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 5px;
}

/* Same amber badge as .player-row .mojo, sized for the pitch chip. Mojo is the
   model's own number, so it is highlighted identically everywhere it appears. */
.player-chip .meta .mojo {
	color: var(--mj-base-deep);
	background: var(--mj-amber);
	padding: 1px 5px;
	border-radius: 4px;
	font-weight: 800;
	font-size: 0.68rem;
	line-height: 1.35;
	font-variant-numeric: tabular-nums;
	flex: 0 0 auto;
}
/* Predicted points for the coming gameweek. Cyan rather than the amber of the
   mojo badge above it: mojo is the model's standing verdict on a player, this
   is one week's points, and two amber numbers on one chip would read as the
   same measurement twice. */
.player-chip .pred {
	background: var(--mj-base-soft);
	border-top: 1px solid rgba(255,255,255,0.07);
	border-radius: 0 0 4px 4px;
	padding: 2px 5px;
	font-size: 0.7rem;
	font-weight: 700;
	color: var(--mj-cyan);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* Elite ownership — the last row of the chip's number panel, and the only one
   that is not the model talking. Price, mojo and points are this site's verdict
   on a player; this is the field's, which is what makes it worth the row: the
   two disagreeing is the whole reason to look at a chip twice.

   The share is a figure with a bar under it, not a figure written across one:
   the fill is a 3px rule along the bottom edge of the row, so it reads as a
   proportion without ever passing behind the text. A full-height fill was
   legible at 20% and unreadable at 90%, which is exactly backwards — the chips
   worth a second look are the ones at the extremes.

   Slate, not amber or cyan: those two are already spoken for by mojo and by
   predicted points, and a third signal colour here would claim this is a third
   measurement of the same kind rather than the crowd's position. */
.player-chip .eo {
	position: relative;
	overflow: hidden;
	background: var(--mj-base-soft);
	border-top: 1px solid rgba(255,255,255,0.07);
	border-radius: 0 0 4px 4px;
	padding: 2px 5px 5px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 4px;
	line-height: 1.3;
	cursor: help;
}
/* The panel's bottom rounding belongs to whichever row ends it. */
.player-chip.has-eo .pred { border-radius: 0; }
/* The track the fill is read against. Always drawn, so a player the elite have
   left alone still shows the scale he is empty on rather than nothing at all. */
.player-chip .eo::before {
	content: "";
	position: absolute;
	left: 0; right: 0; bottom: 0;
	height: 3px;
	background: rgba(0,0,0,0.45);
}
.player-chip .eo-fill {
	position: absolute;
	left: 0; bottom: 0;
	height: 3px;
	background: #7F8DAE;
}
/* Template starts at half the sample, which is where not owning a player
   becomes the active decision. Marked as a tick on the track rather than as a
   colour change: whether template is the safe side or the losing one depends on
   the rank a manager is chasing, and the chip should not answer that. Drawn
   last so it stays visible over a fill that has passed it. */
.player-chip .eo::after {
	content: "";
	position: absolute;
	left: 50%; bottom: 0;
	width: 1px; height: 6px;
	background: rgba(233,237,247,0.75);
}
/* Past the halfway tick the fill brightens, so a template pick separates from
   the pack without having to measure it against the line. */
.player-chip .eo.template .eo-fill { background: #C3CEE9; }
/* The label is what gives way when a chip is too narrow for both, never the
   figure: min-width lets it shrink at all, and a clipped "ELIT" still names the
   number beside it. */
.player-chip .eo-label {
	position: relative;
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	font-size: 0.54rem;
	font-weight: 700;
	letter-spacing: 0.6px;
	color: var(--mj-ink-muted);
}
.player-chip .eo-val {
	position: relative;
	flex: 0 0 auto;
	font-size: 0.66rem;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	color: var(--mj-ink);
	display: flex;
	align-items: center;
	gap: 2px;
}
/* A dash, not 0%: the feed only returns the elite's most-owned players, so all
   an absent player tells us is that he ranked below that cut. */
.player-chip .eo.none .eo-val { color: var(--mj-ink-muted); font-weight: 700; }
.player-chip .eo-delta { font-size: 0.54rem; font-weight: 700; }
.player-chip .eo-delta.up { color: var(--mj-pos); }
.player-chip .eo-delta.down { color: var(--mj-neg); }

/* The five gameweeks ahead, in two registers.

   The coming fixture is named, because it is the one the points above it are
   for and a manager reads it the way the official card writes it — opponent,
   venue, FPL's difficulty as the colour. The four after it are read as a run
   rather than one at a time, and there is no width for them in words: a chip is
   88px, which is not four legible opponent codes but is comfortably four
   colours. Hence the bar. */
.player-chip .fx { margin-top: 3px; }
.player-chip .fx-next {
	border-radius: 4px;
	padding: 2px 4px;
	font-size: 0.64rem;
	font-weight: 700;
	letter-spacing: 0.2px;
	line-height: 1.35;
	white-space: nowrap;
	overflow: hidden;
	cursor: help;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 3px;
}
/* min-width so it can shrink at all: a flex item defaults to its content width,
   which would push a long name past the clip edge instead of ellipsising it. */
.player-chip .fx-next .fx-op {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* A blank is the one state with no difficulty to colour, and the one the strip
   most needs to show: no fixture is no points, whatever the form. */
.player-chip .fx-next.blank {
	background: rgba(255,255,255,0.06);
	color: var(--mj-ink-muted);
	font-style: italic;
	font-weight: 600;
}
/* A double gameweek. The second opponent's name does not fit and is not the
   point; that there are two games is. */
.player-chip .fx-dbl {
	flex: 0 0 auto;
	font-size: 0.9em;
	font-weight: 800;
	padding: 0 3px;
	border-radius: 3px;
	background: rgba(0,0,0,0.28);
}
/* The bar gets a trough of its own rather than sitting straight on the grass.
   Two of the six shades are dark blue-greys — a 3, and the neutral used when FPL
   published no rating — and on the pitch gradient they were near enough
   invisible that an average run of fixtures read as missing data. The trough
   gives them the same near-black backing they have on the player card, where
   the palette was picked. */
.player-chip .fx-bar {
	display: flex;
	gap: 2px;
	margin-top: 2px;
	padding: 2px;
	border-radius: 4px;
	background: rgba(0,0,0,0.45);
}
/* One segment per gameweek, not per fixture — a blank has to hold its place in
   the run, or the games after it slide forward and the gap disappears. */
.player-chip .fx-seg {
	flex: 1 1 0;
	min-width: 0;
	height: 6px;
	border-radius: 2px;
	display: flex;
	gap: 1px;
	cursor: help;
}
/* Left as the trough showing through, so a blank is a hole in the run rather
   than another shade competing with the five that mean a difficulty. */
.player-chip .fx-seg.blank {
	background: transparent;
	box-shadow: inset 0 0 0 1px rgba(255,255,255,0.20);
}
/* Side by side rather than stacked: two 3px-tall halves would not read as two
   fixtures, two half-width ones do. */
.player-chip .fx-seg .fx-tick {
	flex: 1 1 0;
	min-width: 0;
	border-radius: 2px;
}

.player-chip .badges {
	margin-top: 4px;
	font-size: 0.8rem;
	letter-spacing: 1px;
	display: flex;
	justify-content: center;
	gap: 3px;
}
.player-chip .badges .badge-item {
	cursor: help;
	filter: drop-shadow(0 1px 2px rgba(0,0,0,0.35));
}

/* ------------------------------------------------------------
   Bench strip
   ------------------------------------------------------------ */
.bench-strip {
	background: linear-gradient(180deg, #16203A 0%, #0D1424 100%);
	border-radius: var(--mj-radius-sm);
	padding: 14px 10px;
	margin-top: 14px;
	display: flex;
	justify-content: space-around;
	gap: 6px;
	border: 1px solid rgba(255,255,255,0.04);
	box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
}

/* ------------------------------------------------------------
   Best per position list
   ------------------------------------------------------------ */
.player-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 9px 8px;
	border-radius: 8px;
	border-bottom: 1px solid var(--mj-divider);
	font-size: 0.88rem;
	transition: background 0.12s ease;
}
.player-row:hover { background: var(--mj-bg-tint); }
.player-row:last-child { border-bottom: none; }
.player-row .rank {
	width: 22px; flex: 0 0 22px;
	color: var(--mj-ink-muted);
	font-size: 0.75rem;
	font-weight: 600;
	text-align: center;
}
.player-row .name { flex: 1; font-weight: 600; color: var(--mj-ink); }
.player-row .price { color: var(--mj-ink-mid); font-variant-numeric: tabular-nums; }
.player-row .mojo {
	color: var(--mj-base-deep);
	background: var(--mj-amber);
	padding: 3px 8px;
	border-radius: 6px;
	font-weight: 700;
	font-size: 0.78rem;
	min-width: 38px;
	text-align: center;
	font-variant-numeric: tabular-nums;
}
.player-row .team {
	color: var(--mj-ink-muted);
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.5px;
	background: var(--mj-bg-tint);
	border-radius: 4px;
	padding: 2px 6px;
}
/* Position sits next to the club badge but without its chip, so the two read as
   one label rather than two competing tags. */
.player-row .pos {
	width: 30px; flex: 0 0 30px;
	color: var(--mj-ink-muted);
	font-size: 0.72rem;
	font-weight: 700;
	letter-spacing: 0.5px;
}

/* "MID · ARS · £8.5m" — the identifying line every player list carries beside
   the name, so a web name on its own is never the only thing on the row. */
.mj-pmeta {
	margin-left: 6px;
	color: var(--mj-ink-muted);
	font-size: 0.72rem;
	font-weight: 600;
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}

/* The same availability flag at list scale. It leads the meta line rather than
   trailing it: whether a player can play settles a suggestion before his price
   is worth reading. */
.mj-avail {
	display: inline-flex; align-items: center; justify-content: center;
	width: 14px; height: 14px;
	margin-left: 6px;
	border-radius: 50%;
	font-size: 0.6rem;
	font-weight: 800;
	line-height: 1;
	color: #fff;
	vertical-align: text-bottom;
	cursor: help;
}
.mj-avail.warn { background: var(--mj-amber); color: var(--mj-base-deep); }
.mj-avail.bad  { background: var(--mj-neg); }

/* ------------------------------------------------------------
   Sentiment styling
   ------------------------------------------------------------ */
.sent-up   { color: var(--mj-pos); font-weight: 700; }
.sent-down { color: var(--mj-neg); font-weight: 700; }

/* ------------------------------------------------------------
   Tabs
   ------------------------------------------------------------ */
.nav-tabs {
	border-bottom: 1px solid var(--mj-divider);
	gap: 2px;
}
.nav-tabs .nav-link {
	color: var(--mj-ink-mid);
	font-weight: 600;
	border: none;
	border-bottom: 3px solid transparent;
	padding: 8px 12px;
	border-radius: 0;
}
.nav-tabs .nav-link:hover {
	color: var(--mj-ink);
	border-bottom-color: var(--mj-divider);
	background: transparent;
}
.nav-tabs .nav-link.active {
	color: var(--mj-amber);
	font-weight: 700;
	background: transparent;
	border-bottom: 3px solid var(--mj-amber);
}

/* ------------------------------------------------------------
   Tables
   ------------------------------------------------------------ */
.table { color: var(--mj-ink); }
.table thead th {
	color: var(--mj-ink-muted);
	font-size: 0.72rem;
	letter-spacing: 1.1px;
	text-transform: uppercase;
	font-weight: 700;
	border-bottom: 1px solid var(--mj-divider);
	background: var(--mj-bg-tint);
}
.table > :not(caption) > * > * {
	padding: 8px 10px;
}
.table-striped > tbody > tr:nth-of-type(odd) > * {
	--bs-table-bg-type: var(--mj-bg-tint);
}
.table tbody td { font-variant-numeric: tabular-nums; }
/* Bootstrap paints row state as an inset shadow, so that is what fades. */
.table > tbody > tr > * { transition: box-shadow 0.12s ease; }

/* Sortable headers. Every one carries a glyph, active or not, so the column
   width does not jump as the sort moves between columns. */
.table thead th.mj-sort {
	cursor: pointer;
	user-select: none;
	white-space: nowrap;
}
.table thead th.mj-sort:hover { color: var(--mj-ink); }
.table thead th.mj-sort::after {
	content: "\2195";
	margin-left: 5px;
	font-size: 0.85em;
	opacity: 0.3;
}
.table thead th.mj-sort-asc::after,
.table thead th.mj-sort-desc::after {
	opacity: 1;
	color: var(--mj-amber);
}
.table thead th.mj-sort-asc::after { content: "\25B2"; }
.table thead th.mj-sort-desc::after { content: "\25BC"; }

/* Divider between the starting XI block and the bench block. */
.table > tbody > tr.mj-gw-bench-start > * { border-top: 2px solid var(--mj-divider); }

/* ------------------------------------------------------------
   Status footer
   ------------------------------------------------------------ */
.status-footer {
	font-size: 0.78rem;
	color: var(--mj-ink-muted);
	padding: 18px 0 6px;
}

/* ------------------------------------------------------------
   Auth (login / signup)
   ------------------------------------------------------------ */
.mj-auth-page {
	min-height: 100vh;
	display: flex; align-items: center; justify-content: center;
	padding: 24px;
	background:
		radial-gradient(900px 400px at 0% 0%, rgba(255,176,32,0.12), transparent 60%),
		radial-gradient(800px 400px at 100% 100%, rgba(76,201,224,0.10), transparent 60%),
		linear-gradient(135deg, #0B0F1A 0%, #16203A 100%);
}
.mj-auth-card {
	max-width: 440px;
	width: 100%;
	background: var(--mj-bg-elevated);
	border-radius: var(--mj-radius-lg);
	box-shadow: var(--mj-shadow-lg);
	overflow: hidden;
}
.mj-auth-card .mj-auth-brand {
	padding: 22px 28px 0;
	display: flex; align-items: center; gap: 12px;
}
.mj-auth-card .mj-auth-brand img { height: 44px; }
.mj-auth-card .mj-auth-brand .name {
	font-weight: 800; font-size: 1.05rem;
	color: var(--mj-ink); letter-spacing: -0.3px;
}
.mj-auth-card .mj-auth-brand .name .lead {
	color: var(--mj-ink-mid);
	font-weight: 600;
}
.mj-auth-card .mj-auth-body { padding: 20px 28px 28px; }
.mj-auth-card h4 {
	font-weight: 800; color: var(--mj-ink);
	margin: 8px 0 2px; letter-spacing: -0.3px;
}
.mj-auth-card .mj-auth-sub {
	color: var(--mj-ink-muted); font-size: 0.88rem; margin-bottom: 18px;
}

/* Segmented mode toggle (sign in / sign up) */
.mj-segmented {
	display: flex;
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	border-radius: 12px;
	padding: 4px;
	gap: 4px;
	margin-bottom: 18px;
}
.mj-segmented .seg {
	flex: 1;
	text-align: center;
	padding: 8px 12px;
	border-radius: 8px;
	font-size: 0.85rem;
	font-weight: 600;
	color: var(--mj-ink-mid);
	cursor: pointer;
	transition: background 0.15s, color 0.15s, box-shadow 0.15s;
	border: none; background: transparent;
}
.mj-segmented .seg:hover { color: var(--mj-ink); }
.mj-segmented .seg.active {
	background: var(--mj-base-soft);
	color: var(--mj-amber);
	box-shadow: var(--mj-shadow-sm);
}

/* Form polish */
.form-label { font-weight: 600; color: var(--mj-ink-mid); }
.form-control, .form-select {
	border-radius: 10px;
	border: 1.5px solid var(--mj-divider);
	padding: 10px 12px;
	transition: border-color 0.15s, box-shadow 0.15s;
}
.form-control:focus, .form-select:focus {
	border-color: var(--mj-amber);
	box-shadow: 0 0 0 4px rgba(255,176,32,0.16);
}
.form-control-sm { padding: 6px 10px; }

/* Alerts */
.alert {
	border-radius: 12px;
	border: 1px solid transparent;
	font-size: 0.88rem;
}
.alert-warning {
	background: rgba(255,176,32,0.10);
	border-color: rgba(255,176,32,0.34);
	color: #FFD27A;
}
.alert-danger {
	background: rgba(255,107,107,0.10);
	border-color: rgba(255,107,107,0.34);
	color: #FFA8A8;
}
.alert-success {
	background: rgba(53,208,127,0.10);
	border-color: rgba(53,208,127,0.32);
	color: #8CE9B8;
}

/* ------------------------------------------------------------
   Settings page

   Two widths off one shell. Settings is prose and single-column forms, so
   it stays near a readable line length — a "Display name" box stretched
   across a desktop screen reads worse, not better. Admin is a seven-column
   table, which wants every pixel it can get; .settings-page-wide gives it
   the room instead of making it scroll sideways.
   ------------------------------------------------------------ */
.settings-page {
	max-width: 900px;
	margin: 0 auto;
	padding: 28px 16px 60px;
}
.settings-page-wide { max-width: 1500px; }
.settings-page .page-head {
	display: flex; align-items: center; justify-content: space-between;
	margin-bottom: 20px;
}
.settings-page h1.page-title {
	font-size: 1.5rem; font-weight: 800;
	color: var(--mj-ink); margin: 0;
	letter-spacing: -0.3px;
}
.settings-page .settings-card {
	background: var(--mj-bg-elevated);
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius);
	box-shadow: var(--mj-shadow);
	padding: 22px 22px 18px;
	margin-bottom: 16px;
}
.settings-page .settings-card h5 {
	font-weight: 700; color: var(--mj-ink);
	margin: 0 0 6px; letter-spacing: -0.2px;
}
.settings-page .settings-card .help {
	color: var(--mj-ink-muted); font-size: 0.85rem; margin-bottom: 14px;
}
/* The explanatory copy above each card is prose, not layout: hold it to a
   readable measure even where the card itself is wide. */
.settings-page-wide .settings-card .help { max-width: 78ch; }

/* Admin sessions table. Every column is a short, atomic value — a date, an
   IP, "3 days ago" — so wrapping any of them costs a row of height and gains
   nothing. Only the user column, the one with two lines by design, may wrap. */
.admin-sessions th,
.admin-sessions td { white-space: nowrap; }
.admin-sessions td.session-user { white-space: normal; }

/* ------------------------------------------------------------
   My Team section header (controls)
   ------------------------------------------------------------ */
.myteam-controls {
	display: flex; align-items: center; gap: 12px;
}
.myteam-controls .myteam-status {
	color: var(--mj-ink-mid);
	font-size: 0.84rem;
}
.myteam-controls .myteam-status strong { color: var(--mj-ink); }

/* ------------------------------------------------------------
   The collapsible input panels (Squad, Parameters)
   ------------------------------------------------------------
   Both hang off the card header and push the pitch down when open, so they
   are tinted and ruled off to read as a drawer over the card rather than as
   more card. */
.mj-params {
	padding: 16px 20px 18px;
	background: var(--mj-bg-tint);
	border-bottom: 1px solid var(--mj-divider);
}

/* The run button, once an input below it has changed. The pitch still shows
   the previous run, so this and the notice beside it are the only things
   distinguishing "up to date" from "stale" — neither can afford to be
   subtle. */
.btn-mj-accent.mj-attn {
	animation: mj-attn-pulse 1.6s ease-in-out infinite;
}
@keyframes mj-attn-pulse {
	0%, 100% { box-shadow: 0 0 0 0 rgba(255, 176, 32, 0.55); }
	50%      { box-shadow: 0 0 0 7px rgba(255, 176, 32, 0); }
}
@media (prefers-reduced-motion: reduce) {
	.btn-mj-accent.mj-attn {
		animation: none;
		box-shadow: 0 0 0 3px rgba(255, 176, 32, 0.5);
	}
}

/* Which of the two sources the next run will actually read. Stated once, at
   the top, because it is the question both forms below leave open. */
.mj-squad-source {
	font-size: 0.86rem;
	line-height: 1.5;
	padding: 9px 12px;
	margin-bottom: 16px;
	border-radius: var(--mj-radius-sm);
	border-left: 3px solid var(--mj-divider);
	background: var(--mj-bg);
	color: var(--mj-ink-mid);
}
.mj-squad-source strong { color: var(--mj-ink); }
.mj-squad-source.is-manual { border-left-color: var(--mj-amber); }
.mj-squad-source.is-published { border-left-color: var(--mj-pos); }
.mj-squad-source.is-empty { border-left-color: var(--mj-ink-muted); }

/* The same answer as .mj-squad-source, in the card header, where it is on screen
   whenever the pitch it describes is. Colour-matched to the line inside the
   panel so the two read as one statement rather than two. */
.mj-squad-badge {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	font-size: 0.74rem;
	font-weight: 700;
	letter-spacing: 0.01em;
	padding: 3px 9px;
	border-radius: 999px;
	border: 1px solid var(--mj-divider);
	background: var(--mj-bg);
	color: var(--mj-ink-mid);
	cursor: pointer;
	white-space: nowrap;
}
.mj-squad-badge::before {
	content: "";
	width: 7px; height: 7px;
	border-radius: 50%;
	background: currentColor;
	flex: none;
}
.mj-squad-badge.is-manual { color: var(--mj-amber-deep); border-color: var(--mj-amber); }
.mj-squad-badge.is-published { color: var(--mj-pos); border-color: var(--mj-pos); }
.mj-squad-badge.is-empty { color: var(--mj-ink-muted); }

.mj-squad-half-title {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	font-weight: 700;
	font-size: 0.95rem;
	color: var(--mj-ink);
	margin-bottom: 4px;
}
/* Whether this half of the choice is the one that runs. "Overridden" is the
   state worth the pixels: that form is filled in and still not what is read. */
.mj-squad-use {
	font-size: 0.68rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	padding: 2px 7px;
	border-radius: 999px;
	border: 1px solid var(--mj-divider);
	color: var(--mj-ink-muted);
}
.mj-squad-use.is-on {
	color: var(--mj-pos);
	border-color: var(--mj-pos);
}
.mj-squad-help {
	font-size: 0.8rem;
	line-height: 1.5;
	color: var(--mj-ink-muted);
	margin-bottom: 10px;
	max-width: 62ch;
}
.mj-squad-help strong { color: var(--mj-ink-mid); }

/* Expandable, not expanded: the caveats matter when a suggestion looks wrong,
   not on every open of the panel. */
.mj-squad-caveats {
	font-size: 0.8rem;
	line-height: 1.5;
	color: var(--mj-ink-muted);
	margin-bottom: 12px;
}
.mj-squad-caveats > summary {
	cursor: pointer;
	font-weight: 600;
	color: var(--mj-ink-mid);
	margin-bottom: 6px;
}
.mj-squad-caveats ul { padding-left: 18px; margin-bottom: 6px; }
.mj-squad-caveats li { margin-bottom: 5px; }
.mj-squad-caveats strong { color: var(--mj-ink-mid); }

/* Two halves of one choice, so the rule between them only means anything
   while they are actually side by side. */
@media (min-width: 1200px) {
	.col-xl-7 > .mj-squad-half {
		border-left: 1px solid var(--mj-divider);
		padding-left: 24px;
	}
}

.mj-progress {
	height: 10px;
	background: var(--mj-bg-tint);
	border-radius: 999px;
	overflow: hidden;
	border: 1px solid var(--mj-divider);
}
.mj-progress .bar {
	height: 100%;
	background: linear-gradient(90deg, var(--mj-amber-deep) 0%, var(--mj-amber) 100%);
	transition: width 0.4s ease;
	background-size: 200% 100%;
	animation: mj-progress-stripe 1.6s linear infinite;
}
@keyframes mj-progress-stripe {
	0% { background-position: 0% 0; }
	100% { background-position: 200% 0; }
}

/* Transfer decision tiers — one per option, priced like a plan table */
.mj-tier {
	position: relative;
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius-sm);
	background: var(--mj-bg-elevated);
	overflow: hidden;
	margin-bottom: 10px;
}
/* The winner. Lifted rather than merely tinted: the whole point of the column
   is that one of these is the answer, and a border alone loses that at a glance
   on a dark background. */
.mj-tier.best {
	border-color: var(--mj-amber);
	box-shadow: 0 0 0 1px var(--mj-amber), 0 6px 20px rgba(0,0,0,0.35);
}
.mj-tier-badge {
	position: absolute; top: 0; right: 0;
	background: var(--mj-amber);
	color: #1a1206;
	font-size: 0.65rem;
	font-weight: 800;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	padding: 3px 8px;
	border-bottom-left-radius: var(--mj-radius-sm);
}
.mj-tier-head {
	display: flex; justify-content: space-between; align-items: center;
	gap: 8px;
	padding: 8px 12px;
	background: var(--mj-bg-tint);
	border-bottom: 1px solid var(--mj-divider);
	font-size: 0.85rem;
}
/* Room for the badge, so the cost chip never slides under it. */
.mj-tier.best .mj-tier-head { padding-right: 92px; }
.mj-tier-cost {
	font-weight: 800;
	padding: 2px 8px;
	border-radius: 6px;
	white-space: nowrap;
	/* Unqualified is the hold tier, which is spending nothing: muted and
	   unfilled, so the green and red chips below it stay meaningful. */
	color: var(--mj-ink-muted);
}
.mj-tier-cost.free { color: var(--mj-pos); background: rgba(53,208,127,0.12); }
.mj-tier-cost.hit { color: var(--mj-neg); background: rgba(255,107,107,0.12); }
.mj-tier-net {
	padding: 10px 12px 0;
	font-size: 1.5rem;
	font-weight: 800;
	line-height: 1;
}
.mj-tier-net.pos { color: var(--mj-pos); }
.mj-tier-net.neg { color: var(--mj-neg); }
.mj-tier-net .unit {
	font-size: 0.7rem;
	font-weight: 700;
	margin-left: 3px;
	color: var(--mj-ink-muted);
}
.mj-tier-sub {
	padding: 4px 12px 0;
	font-size: 0.75rem;
	color: var(--mj-ink-muted);
}
/* How reliable the tier's own figure is. Sits directly under the sum it qualifies
   and is deliberately quieter than it: the projection is the claim, this is the
   error bar on it, and styling them alike would invite reading the odds as a
   second headline number. */
.mj-tier-odds {
	padding: 2px 12px 0;
	font-size: 0.7rem;
	color: var(--mj-ink-muted);
	opacity: 0.85;
}
.mj-tier-body { padding: 10px 12px; font-size: 0.85rem; }

/* "Right move, wrong week." Amber rather than red: the tier's own numbers are
   not in dispute, it is when to act on them, so this must not read as the same
   kind of warning as a hit. */
.mj-tier-timing {
	margin-top: 8px;
	padding: 7px 9px;
	border-left: 3px solid var(--mj-amber);
	border-radius: 0 var(--mj-radius-sm) var(--mj-radius-sm) 0;
	background: rgba(240,178,60,0.08);
	color: var(--mj-ink-muted);
	font-size: 0.78rem;
	line-height: 1.45;
}
.mj-tier-timing strong { color: var(--mj-ink); }

/* Chips — offered below the transfer tiers, deliberately not competing with them.
   Flatter and quieter than .mj-tier: none of these is crowned, because a chip's
   real price is the best week left and this build only prices the coming one. */
/* The chips are four parallel options to weigh against each other, so they are
   laid out side by side rather than as a list. auto-fit collapses to fewer
   columns as the viewport narrows, down to one on a phone. */
.mj-chip-grid {
	display: grid;
	/* 1fr, so the row is always filled exactly. A fixed column maximum was tried
	   and is worse: auto-fit then lays tracks at that maximum and packs as many as
	   fit, stranding up to a full column of unused width — 340px of the 900px case.
	   1fr divides whatever is there between however many chips remain. */
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: 10px;
	/* Stretch, not start. These cards are meant to be compared, and a ragged
	   bottom edge reads as four unrelated boxes; a level one reads as a set. The
	   cost is slack inside the shorter cards, which is the cheaper problem. */
	align-items: stretch;
}
.mj-chip-card {
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius-sm);
	background: var(--mj-bg-elevated);
	padding: 8px 12px 10px;
	/* Non-binding at any realistic width — four chips across a dashboard give
	   columns well under this. It exists for the end of a season, when one chip is
	   left and a lone 1fr column would otherwise stretch that card across the
	   whole page around a chart 320px wide. */
	max-width: 620px;
	/* So the community verdict can be pinned to the bottom of a stretched card
	   rather than leaving the slack in the middle of the content. */
	display: flex;
	flex-direction: column;
}
/* Takes up whatever slack stretching created, keeping the attribution block
   flush with the bottom edge across the row. */
.mj-chip-card .mj-chip-verdict { margin-top: auto; }
/* Spent, or not yet open. Kept on screen rather than dropped: a manager knows
   they had four chips, and a silently missing card reads as a bug. */
.mj-chip-card.spent { opacity: 0.55; }
.mj-chip-head {
	display: flex; justify-content: space-between; align-items: center;
	gap: 8px;
	font-size: 0.85rem;
}
.mj-chip-state {
	font-weight: 800;
	font-size: 0.78rem;
	white-space: nowrap;
	color: var(--mj-ink-muted);
}
/* Green, like .mj-tier-cost.free, because it is the same kind of claim: points
   this option adds. Unqualified stays muted so an unpriced chip cannot be
   mistaken for one worth nothing. */
.mj-chip-state.value { color: var(--mj-pos); }
.mj-chip-detail {
	margin-top: 3px;
	font-size: 0.75rem;
	color: var(--mj-ink-muted);
}
/* Per-gameweek value. Scaled to the best week in view, so the shape reads as
   "which week" rather than inviting the heights to be measured as points — the
   figure above each bar carries the magnitude instead. */
.mj-chip-weeks {
	display: flex;
	align-items: flex-end;
	gap: 4px;
	margin-top: 8px;
	/* Capped, and left-aligned rather than stretched. This column is half the
	   dashboard wide; letting five bars span all of it produced a chart whose
	   whitespace dwarfed its data and read as a broken table. */
	max-width: 320px;
}
.mj-chip-week {
	flex: 1 1 0;
	min-width: 0;
	text-align: center;
}
.mj-chip-week-val {
	font-size: 0.65rem;
	font-weight: 700;
	color: var(--mj-ink-muted);
	line-height: 1;
	margin-bottom: 3px;
}
/* A fixed-height track so every bar shares a baseline and a ceiling; without it
   the columns size to their own content and the comparison is meaningless. */
.mj-chip-week-track {
	height: 46px;
	display: flex;
	align-items: flex-end;
}
.mj-chip-week-bar {
	width: 100%;
	background: var(--mj-ink-muted);
	border-radius: 2px 2px 0 0;
	opacity: 0.4;
	transition: opacity 0.12s ease;
}
.mj-chip-week:hover .mj-chip-week-bar { opacity: 0.75; }
/* The best week in view, which is the one the verdict is arguing about. */
.mj-chip-week.best .mj-chip-week-bar { background: var(--mj-amber); opacity: 1; }
.mj-chip-week.best .mj-chip-week-val { color: var(--mj-amber); }
.mj-chip-week-gw {
	font-size: 0.6rem;
	color: var(--mj-ink-muted);
	margin-top: 3px;
	overflow: hidden;
	white-space: nowrap;
}
.mj-chip-week.best .mj-chip-week-gw { color: var(--mj-amber); font-weight: 800; }

/* Our own timing read. Muted by default because "hold" is the quiet answer;
   green only when the model is actively arguing for spending the chip now. */
.mj-chip-timing {
	margin-top: 8px;
	padding: 6px 9px;
	border-left: 3px solid var(--mj-divider);
	border-radius: 0 var(--mj-radius-sm) var(--mj-radius-sm) 0;
	background: rgba(255,255,255,0.03);
	font-size: 0.76rem;
	line-height: 1.45;
	color: var(--mj-ink-muted);
}
.mj-chip-timing.play { border-left-color: var(--mj-pos); background: rgba(53,208,127,0.08); }
.mj-chip-timing-tag { font-weight: 800; color: var(--mj-ink); }
/* The bound on the claim. Smaller than the verdict but deliberately not hidden:
   a "hold" drawn from five gameweeks must not read as a "hold" full stop. */
.mj-chip-timing-bound {
	opacity: 0.7;
	white-space: nowrap;
}

/* Chips whose window has not opened, collapsed to one line per unlock gameweek.
   A card each would have spent four full rows saying "later". */
.mj-chip-locked {
	display: inline-block;
	padding: 5px 12px;
	margin-top: 10px;
	margin-right: 8px;
	border: 1px dashed var(--mj-divider);
	border-radius: var(--mj-radius-sm);
	font-size: 0.72rem;
	color: var(--mj-ink-muted);
}

/* Opinion, not arithmetic. Bordered like .mj-tier-timing so it reads as a note
   on the figure above rather than another figure. */
.mj-chip-verdict {
	margin-top: 7px;
	padding: 6px 9px;
	border-left: 3px solid var(--mj-ink-muted);
	border-radius: 0 var(--mj-radius-sm) var(--mj-radius-sm) 0;
	background: rgba(255,255,255,0.03);
	font-size: 0.76rem;
	line-height: 1.45;
	color: var(--mj-ink-muted);
}
.mj-chip-verdict.hold { border-left-color: var(--mj-amber); background: rgba(240,178,60,0.08); }
.mj-chip-verdict.play { border-left-color: var(--mj-pos); background: rgba(53,208,127,0.08); }
.mj-chip-verdict-tag { font-weight: 800; color: var(--mj-ink); }
.mj-chip-verdict-src {
	margin-top: 3px;
	font-size: 0.68rem;
	opacity: 0.7;
}
.mj-chip-note {
	margin-top: 10px;
	max-width: 900px;
	font-size: 0.72rem;
	line-height: 1.5;
	color: var(--mj-ink-muted);
}
.mj-chip-note strong { color: var(--mj-ink); }

/* Empty / loading state */
.mj-empty {
	display: flex; align-items: center; gap: 12px;
	color: var(--mj-ink-muted);
	padding: 14px 16px;
	background: var(--mj-bg-tint);
	border: 1px dashed var(--mj-divider);
	border-radius: var(--mj-radius-sm);
}
.mj-empty .icon {
	width: 36px; height: 36px;
	border-radius: 50%;
	background: var(--mj-base-soft);
	display: inline-flex; align-items: center; justify-content: center;
	color: var(--mj-amber);
	font-weight: 800;
	border: 1px solid var(--mj-divider);
	flex: 0 0 36px;
}

/* ------------------------------------------------------------
   Responsive tweaks

   Two steps. 768px is the tablet/phone shape: the header packs down and the
   pitch stops being a fixed grid. 480px is the phone proper, where a five-man
   row leaves ~50px per player and the chip type has to come down with it.
   ------------------------------------------------------------ */
@media (max-width: 768px) {
	/* The header is sticky, so whatever height it takes is taken from every
	   screen of the page. At full size the brand, the gameweek pill and the
	   user menu wrapped onto three rows and ate a third of a phone viewport;
	   packed down they are two rows, the pill sharing one with the menu.

	   The !important is not specificity: Bootstrap's own spacing utilities
	   (.py-3, .gap-3, .px-4) declare it, so an override has to match. */
	.app-header.py-3 { padding-top: 8px !important; padding-bottom: 8px !important; }
	.app-header .container-fluid { gap: 8px !important; }
	.app-header .app-brand { gap: 8px; }
	.app-header .app-brand img { height: 30px; }
	.app-header .app-title { font-size: 1rem !important; }
	.app-header .user-chip { padding: 3px; }
	.app-header .user-chip .who { display: none; }
	.app-header .user-chip .avatar { width: 26px; height: 26px; font-size: 0.72rem; }
	.app-header .btn-ghost-light { padding: 4px 10px; font-size: 0.8rem; }
	.app-header .gw-pill { padding: 4px 10px; font-size: 0.74rem; }
	.app-header .gw-pill .gw-pill-label { font-size: 0.62rem; margin-right: 4px; }

	/* Status line and both buttons on one row pushed "Run algorithm" off the
	   right edge of the card. The status takes a row of its own and the two
	   buttons split the one under it, so neither can be scrolled away. */
	.myteam-controls { width: 100%; flex-wrap: wrap; gap: 8px; }
	.myteam-controls .myteam-status { flex: 1 0 100%; font-size: 0.8rem; line-height: 1.4; }
	.myteam-controls > button { flex: 1 1 auto; }

	/* A fixed chip width overflowed the pitch as soon as a row held four or
	   five players — the last chip was clipped by the card edge. Chips now
	   divide whatever the row has, never growing past their desktop size. */
	.pitch-row { gap: 4px; }
	.player-chip { width: auto; flex: 1 1 0; min-width: 0; max-width: 88px; }
	.player-chip .shirt { width: 100%; max-width: 48px; height: 54px; }
	.player-chip .shirt img { width: 100%; max-width: 48px; height: 48px; }
	.bench-strip { gap: 4px; padding: 12px 8px; }
}

@media (max-width: 480px) {
	/* Page gutters and card padding handed back to the content: on a 375px
	   screen the nested paddings cost ~90px of the pitch's width. */
	.container-fluid.px-4 { padding-left: 12px !important; padding-right: 12px !important; }
	.card-section .card-header { padding: 12px 14px; }
	.card-section .card-body { padding: 14px 12px; }
	.pitch-wrap { padding: 18px 8px 16px; }
	.pitch-row { margin-bottom: 14px; }

	.player-chip .shirt { max-width: 42px; height: 46px; }
	.player-chip .shirt img { max-width: 42px; height: 42px; }
	/* Shrinks with the jersey, but not below 15px: it has to stay a legible
	   warning at the width where the chip has already dropped everything else. */
	.player-chip .shirt .avail {
		width: 15px; height: 15px;
		top: -4px; left: -4px;
		font-size: 0.58rem;
		border-width: 1px;
	}
	.player-chip .name { font-size: 0.7rem; padding: 2px 4px; }
	/* "MID · LIV" still fits a 42px-wide chip; the price below it does not, which
	   is why this line carries the club rather than repeating the price. */
	.player-chip .club { font-size: 0.56rem; padding: 0 4px 2px; }
	/* The price and the mojo badge no longer both fit on one line at this
	   width. Wrapping puts the badge under the price; the nowrap default put
	   the badge first and clipped the price mid-figure. */
	.player-chip .meta {
		font-size: 0.64rem;
		padding: 2px 3px;
		gap: 3px;
		flex-wrap: wrap;
		white-space: normal;
	}
	.player-chip .meta .mojo { font-size: 0.6rem; padding: 1px 4px; }
	.player-chip .pred { font-size: 0.64rem; padding: 2px 3px; }
	/* "ELITE 46%" fits a five-chip row on a 375px screen; the trend arrow beside
	   it does not. The arrow goes rather than the label — a percentage with
	   nothing naming it would be the one number on the chip you cannot identify,
	   and the move it made is still on the tooltip. */
	.player-chip .eo { padding: 1px 4px; gap: 3px; }
	.player-chip .eo-label { font-size: 0.5rem; letter-spacing: 0.3px; }
	.player-chip .eo-val { font-size: 0.6rem; }
	.player-chip .eo-delta { display: none; }
	/* "SUN (A)" is the longest this line ever gets and it still fits a 42px chip
	   at this size. The bar below needs no help: four colours divide any width. */
	.player-chip .fx-next { font-size: 0.55rem; padding: 1px 2px; gap: 2px; }
	.player-chip .fx-seg { height: 5px; }
	.player-chip .badges { font-size: 0.72rem; }
	.bench-strip { padding: 10px 6px; }
}

/* ------------------------------------------------------------
   Bootstrap overrides for the dark ground

   Bootstrap 5 ships a light theme; these are the utility classes the app
   actually uses whose defaults are illegible on #0B0F1A.
   ------------------------------------------------------------ */
.text-muted   { color: var(--mj-ink-muted) !important; }
.text-success { color: var(--mj-pos) !important; }
.text-danger  { color: var(--mj-neg) !important; }

.card {
	--bs-card-bg: var(--mj-bg-elevated);
	--bs-card-color: var(--mj-ink);
	--bs-card-border-color: var(--mj-divider);
	--bs-card-cap-bg: transparent;
}

.form-control, .form-select {
	background-color: var(--mj-bg-tint);
	color: var(--mj-ink);
	border-color: var(--mj-divider);
}
.form-control:focus, .form-select:focus {
	background-color: var(--mj-bg-tint);
	color: var(--mj-ink);
}
.form-control::placeholder { color: var(--mj-ink-muted); }
/* Chrome paints autofilled inputs a hard blue-white (#e8f0fe) that sits above
   background-color and resists the usual inset-shadow override. Clipping the
   UA background to the text is what actually restores the dark field; the
   inset shadow stays as a fallback for engines that honour it. */
.form-control:-webkit-autofill,
.form-control:-webkit-autofill:hover,
.form-control:-webkit-autofill:focus,
.form-control:autofill {
	-webkit-text-fill-color: var(--mj-ink);
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-box-shadow: 0 0 0 1000px var(--mj-bg-tint) inset;
	caret-color: var(--mj-ink);
	transition: background-color 5000s ease-in-out 0s;
}

.table {
	--bs-table-color: var(--mj-ink);
	--bs-table-bg: transparent;
	--bs-table-border-color: var(--mj-divider);
	--bs-table-striped-color: var(--mj-ink);
	/* Row highlight echoing .player-row:hover, for any .table-hover table. Both
	   have to be restated: Bootstrap derives them from --bs-emphasis-color,
	   which is black outside its own data-bs-theme, so the default would black
	   out the row text. The tint has to clear the striped rows, which already
	   use --mj-bg-tint, hence the lighter base-soft. */
	--bs-table-hover-color: var(--mj-ink);
	--bs-table-hover-bg: var(--mj-base-soft);
}

.btn-link { color: var(--mj-ink-mid); }
.btn-link:hover { color: var(--mj-amber); }

.spinner-border { color: var(--mj-amber); }

/* Legal footnote under the status footer */
.mj-disclaimer {
	color: var(--mj-ink-muted);
	font-size: 0.72rem;
	opacity: 0.75;
}

/* ------------------------------------------------------------
   Full player detail table (console parity)

   Deliberately dense: this is the whole ranked pool with every stat, so it
   scrolls horizontally rather than dropping columns.
   ------------------------------------------------------------ */
.mj-player-table {
	max-height: 70vh;
	overflow: auto;
}
.mj-player-table table {
	font-size: 0.78rem;
	white-space: nowrap;
}
.mj-player-table thead th {
	position: sticky;
	top: 0;
	z-index: 2;
	background: var(--mj-bg-tint);
	font-size: 0.66rem;
	letter-spacing: 0.6px;
	white-space: nowrap;
}
.mj-player-table td,
.mj-player-table th { padding: 5px 8px; }
.mj-player-table .num { text-align: right; font-variant-numeric: tabular-nums; }
/* Player name stays put while the stat columns scroll under it. */
.mj-player-table td.pname,
.mj-player-table thead th:first-child {
	position: sticky;
	left: 0;
	z-index: 1;
	background: var(--mj-bg-elevated);
}
.mj-player-table thead th:first-child { z-index: 3; background: var(--mj-bg-tint); }
.mj-player-table .mojo {
	color: var(--mj-base-deep);
	background: var(--mj-amber);
	padding: 1px 6px;
	border-radius: 4px;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}
/* Notes is the only free-text column; let it wrap instead of stretching the row. */
.mj-player-table td.notes {
	white-space: normal;
	min-width: 320px;
	max-width: 560px;
	font-size: 0.72rem;
	line-height: 1.35;
}

/* ------------------------------------------------------------
   Trust panel (FPL credentials explainer)
   ------------------------------------------------------------ */
.mj-trust-list {
	margin: 0 0 14px;
	padding-left: 18px;
}
.mj-trust-list li { margin-bottom: 3px; }

.mj-trust {
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	border-left: 3px solid var(--mj-amber);
	border-radius: var(--mj-radius-sm);
	padding: 14px 16px;
	margin-bottom: 18px;
}
.mj-trust .mj-trust-head {
	font-weight: 700;
	font-size: 0.85rem;
	color: var(--mj-ink);
	margin-bottom: 8px;
}
.mj-trust ul {
	margin: 0;
	padding-left: 18px;
	font-size: 0.82rem;
	color: var(--mj-ink-mid);
	line-height: 1.5;
}
.mj-trust li { margin-bottom: 5px; }
.mj-trust strong { color: var(--mj-ink); }
.mj-trust .mj-trust-foot {
	margin-top: 10px;
	font-size: 0.78rem;
	color: var(--mj-ink-muted);
}

/* Caveat variant of the trust panel: same shape, cautionary accent. */
.mj-trust.mj-trust-warn { border-left-color: var(--mj-ink-muted); }

/* ------------------------------------------------------------
   Landing page (signed out)
   ------------------------------------------------------------ */
.mj-hero {
	padding: 48px 32px 44px;
	border-radius: var(--mj-radius-lg);
	border: 1px solid var(--mj-divider);
	background:
		radial-gradient(900px 340px at 8% -20%, rgba(255,176,32,0.13), transparent 62%),
		radial-gradient(700px 320px at 100% 0%, rgba(76,201,224,0.08), transparent 60%),
		linear-gradient(140deg, #16203A 0%, var(--mj-bg-elevated) 55%, #0E1524 100%);
}
/* Ultrawide guard. The dashboard below is a data console and genuinely wants
   every pixel; the hero is prose beside a pitch, and past this width the two
   just drift apart with the gap between them growing to nothing useful. */
.mj-hero > .row {
	max-width: 1640px;
	margin-left: auto;
	margin-right: auto;
}
/* Once the two sit side by side they need a real channel between them: the
   default 24px leaves a paragraph of text almost touching a bordered, shadowed
   card, and the rule above the value props runs the full column width and stops
   just short of it, which points at the card rather than separating from it.
   Widened only from xl, where the split actually happens — below that this is
   a vertical gap and 24px is right.

   In two steps, because the channel has to come out of the copy column: at 1200
   a 64px gap costs the headline its line break, while by 1400 there is room for
   it and the two blocks need the separation more. */
@media (min-width: 1200px) {
	.mj-hero > .row { --bs-gutter-x: 2.5rem; }
}
@media (min-width: 1400px) {
	.mj-hero > .row { --bs-gutter-x: 4rem; }
}
.mj-hero-copy { max-width: 640px; }
.mj-hero-title {
	/* Capped lower than the full-width hero it replaces: the copy now shares the
	   row with the Dream Team, and 3rem in a half-width column wraps the two
	   deliberate lines into four accidental ones. The 2.6vw middle term is not
	   arbitrary — it is the largest that keeps "The model picks your team." on one
	   line in this column from 1200px up, which is what makes the <br> a choice
	   rather than a suggestion the browser overrides. Retuned from 2.6vw when the
	   channel between the columns widened and took 40px off the copy. */
	font-size: clamp(1.9rem, 2.4vw, 2.6rem);
	font-weight: 900;
	letter-spacing: -1.2px;
	line-height: 1.08;
	margin: 0 0 14px;
	color: var(--mj-ink);
}
.mj-hero-sub {
	font-size: 1.12rem;
	line-height: 1.65;
	color: var(--mj-ink-mid);
	margin-bottom: 20px;
}

/* The Dream Team card as the hero's right-hand side. It is already a card on a
   card, so it earns its separation with depth and a brighter edge rather than a
   third background colour. */
.mj-hero-proof .card-section {
	box-shadow: var(--mj-shadow-lg);
	border-color: var(--mj-divider-strong);
}

/* Compact pitch, for the hero only. At full size the card runs past 1000px, which
   pushes the headline beside it below the fold on a laptop — the opposite of what
   putting it here was for. Same card, same fifteen players, ~two thirds the
   height; the roomy version is what a signed-in manager gets. */
.mj-hero-proof .pitch-wrap { padding: 14px 10px 12px; }
.mj-hero-proof .pitch-row { margin-bottom: 8px; }
.mj-hero-proof .player-chip { width: 66px; }
.mj-hero-proof .player-chip .shirt { width: 38px; height: 40px; margin-bottom: 3px; }
.mj-hero-proof .player-chip .shirt img { width: 38px; height: 38px; }
.mj-hero-proof .player-chip .shirt .avail {
	width: 14px; height: 14px;
	top: -4px; left: -4px;
	font-size: 0.55rem;
	border-width: 1px;
}
.mj-hero-proof .player-chip .name { font-size: 0.7rem; padding: 2px 5px; }
.mj-hero-proof .player-chip .meta { font-size: 0.62rem; padding: 1px 4px; }
.mj-hero-proof .player-chip .pred { font-size: 0.65rem; padding: 1px 4px; }
/* 66px leaves the label and the figure without room for the trend arrow between
   them. The arrow is the part a visitor to the hero has least use for — it is a
   week-on-week move, and the share itself is the proof this pitch is here for. */
.mj-hero-proof .player-chip .eo { padding: 1px 4px; gap: 3px; }
.mj-hero-proof .player-chip .eo-label { font-size: 0.5rem; letter-spacing: 0.3px; }
.mj-hero-proof .player-chip .eo-val { font-size: 0.6rem; }
.mj-hero-proof .player-chip .eo-delta { display: none; }
.mj-hero-proof .player-chip .fx-next { font-size: 0.56rem; padding: 1px 3px; }
.mj-hero-proof .player-chip .fx-seg { height: 5px; }
/* The arrows were hidden here on the grounds that nobody hovers a hero. But the
   Sentiment card directly below names the players the news is about, and a pitch
   that says nothing about Calafiori while the card beside it does reads as the
   two disagreeing. Shown, on the same budget as everything else in this block. */
.mj-hero-proof .player-chip .badges { margin-top: 2px; font-size: 0.7rem; }

/* The squeeze above is a height budget, not a width one. Once the copy beside it
   has stopped competing for room, the pitch is ~900px across and 38px shirts
   read as lost on it — so most of the size comes back. */
@media (min-width: 1400px) {
	.mj-hero-proof .player-chip { width: 78px; }
	.mj-hero-proof .player-chip .shirt { width: 48px; height: 52px; }
	.mj-hero-proof .player-chip .shirt img { width: 48px; height: 48px; }
	.mj-hero-proof .player-chip .name { font-size: 0.74rem; }
	.mj-hero-proof .player-chip .meta { font-size: 0.66rem; }
	.mj-hero-proof .player-chip .pred { font-size: 0.7rem; }
	.mj-hero-proof .player-chip .eo-label { font-size: 0.54rem; letter-spacing: 0.6px; }
	.mj-hero-proof .player-chip .eo-val { font-size: 0.64rem; }
	.mj-hero-proof .player-chip .eo-delta { display: inline; font-size: 0.52rem; }
}

/* Countdown + snapshot age. Two readings side by side, the only part of the page
   that changes between one load and the next. */
.mj-live-strip {
	display: flex;
	flex-wrap: wrap;
	gap: 10px 28px;
	padding: 14px 16px;
	background: rgba(255,176,32,0.06);
	border: 1px solid rgba(255,176,32,0.24);
	border-radius: var(--mj-radius-sm);
}
.mj-live-item { min-width: 0; }
.mj-live-label {
	font-size: 0.68rem;
	text-transform: uppercase;
	letter-spacing: 1.2px;
	font-weight: 700;
	color: var(--mj-ink-muted);
	margin-bottom: 3px;
}
.mj-live-value {
	font-size: 1.05rem;
	font-weight: 700;
	color: var(--mj-ink);
	line-height: 1.2;
	/* The countdown redraws every second; proportional digits make the whole line
	   twitch sideways as it does. */
	font-variant-numeric: tabular-nums;
}
.mj-live-sub {
	font-size: 0.76rem;
	color: var(--mj-ink-muted);
	margin-top: 2px;
}
.mj-live-deadline .mj-live-value { color: var(--mj-amber); }
.mj-count-n { font-size: 1.25rem; font-weight: 900; letter-spacing: -0.5px; }
.mj-count-u {
	font-size: 0.8rem;
	font-weight: 700;
	color: var(--mj-ink-muted);
	margin: 0 8px 0 1px;
}
.mj-live-fresh { display: inline-flex; align-items: center; gap: 7px; font-size: 0.95rem; }
.mj-live-dot {
	width: 8px; height: 8px;
	border-radius: 50%;
	background: var(--mj-pos);
	box-shadow: 0 0 0 3px rgba(53,208,127,0.18);
	animation: mj-live-pulse 2.4s ease-in-out infinite;
}
@keyframes mj-live-pulse {
	50% { box-shadow: 0 0 0 6px rgba(53,208,127,0.05); }
}
@media (prefers-reduced-motion: reduce) {
	.mj-live-dot { animation: none; }
}

.mj-hero-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* What the dream team was solved for, under its title. */
.mj-dt-horizon {
	font-size: 0.76rem;
	color: var(--mj-ink-mid);
	margin-top: 2px;
}

/* Key for the two per-player numbers, riding the bench heading's empty right
   side. The swatches deliberately copy the chip styling rather than approximate
   it — the point is to be recognised as the same badge, not to describe one. */
.mj-dt-benchhead {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 6px 18px;
	margin: 14px 0 8px;
}
.mj-dt-legend {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 5px 16px;
}
.mj-dt-legend-item {
	display: inline-flex;
	align-items: baseline;
	gap: 7px;
	font-size: 0.72rem;
	line-height: 1.4;
	color: var(--mj-ink-muted);
	white-space: nowrap;
}
.mj-legend-mojo,
.mj-legend-pts {
	flex: 0 0 auto;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}
.mj-legend-mojo {
	background: var(--mj-amber);
	color: var(--mj-base-deep);
	padding: 1px 6px;
	border-radius: 4px;
	font-weight: 800;
	font-size: 0.68rem;
}
.mj-legend-pts {
	background: var(--mj-base-soft);
	border: 1px solid rgba(255,255,255,0.07);
	border-radius: 4px;
	padding: 1px 6px;
	color: var(--mj-cyan);
	font-weight: 700;
	font-size: 0.7rem;
}
.mj-legend-pts .unit {
	color: var(--mj-ink-muted);
	font-weight: 600;
	font-size: 0.9em;
	margin-left: 2px;
}

/* ------------------------------------------------------------
   Ranked bar list — the captain poll tally and both ownership cards
   ------------------------------------------------------------ */
/* One grid for the whole list, not one per row: the value column is sized by
   the longest value in the list, so a two-digit row and a four-digit row still
   put their tracks in the same place. */
.mj-rankbar {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 34% auto;
	align-items: center;
	row-gap: 7px;
	column-gap: 10px;
	font-size: 0.84rem;
}
.mj-rankbar-row { display: contents; }
.mj-rankbar-label { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.mj-rankbar-rank {
	color: var(--mj-ink-muted);
	font-size: 0.72rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	flex: 0 0 auto;
}
.mj-rankbar-name {
	color: var(--mj-ink);
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* The row's own gap spaces this one; it must not shrink, or the meta would be
   the first thing lost to a long name instead of the name truncating. */
.mj-rankbar-label .mj-pmeta { margin-left: 0; flex: 0 0 auto; }
.mj-rankbar-track {
	height: 7px;
	border-radius: 999px;
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	overflow: hidden;
}
.mj-rankbar-fill {
	height: 100%;
	border-radius: 999px;
	background: var(--mj-ink-muted);
	min-width: 2px;
}
/* Only the top row gets the accent. Amber is the signal colour, and ten amber
   bars would say ten things are important. */
.mj-rankbar-row.leader .mj-rankbar-fill { background: var(--mj-amber); }
.mj-rankbar-row.leader .mj-rankbar-name { font-weight: 700; }
.mj-rankbar-value {
	color: var(--mj-ink-mid);
	font-size: 0.78rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	text-align: right;
	white-space: nowrap;
	display: flex;
	align-items: baseline;
	justify-content: flex-end;
	gap: 5px;
}
.mj-rankbar-row.leader .mj-rankbar-value { color: var(--mj-amber); }
/* Week-on-week movement, where the feed carries a previous gameweek to compare
   against. Always the muted size, and never the leader's amber — it qualifies
   the percentage next to it rather than competing with it. */
.mj-rankbar-delta {
	font-size: 0.7rem;
	font-weight: 700;
	flex: 0 0 auto;
}
/* Narrow, the three columns fight over the row and the label is the one that
   loses: the track holds its 34% and the meta line never shrinks, so the name —
   the only part of the row that identifies the player — truncates to an initial.
   Below this the label takes a line of its own at full width and the track and
   value sit under it, so the name is read at its natural length and the bars
   still line up with each other.

   Measured against the card rather than the window. These cards are col-md-6, so
   a half-width card on a tablet is as cramped as a full-width one on a phone, and
   a viewport query would fix the phone while leaving the tablet truncating. The
   grid cannot query itself, hence the wrapper. */
.mj-rankbar-wrap { container-type: inline-size; }
@container (max-width: 430px) {
	.mj-rankbar {
		grid-template-columns: minmax(0, 1fr) auto;
		row-gap: 4px;
	}
	.mj-rankbar-label { grid-column: 1 / -1; }
	/* row-gap now separates a label from its own bar, so the space between one
	   entry and the next is put back here rather than by widening every gap. */
	.mj-rankbar-row + .mj-rankbar-row .mj-rankbar-label { margin-top: 8px; }
}

.mj-poll-foot {
	margin-top: 12px;
	padding-top: 10px;
	border-top: 1px solid var(--mj-divider);
	font-size: 0.74rem;
	color: var(--mj-ink-muted);
}
.btn-lg { padding: 11px 22px; font-size: 1rem; }

.mj-section-title {
	font-size: clamp(1.4rem, 2.4vw, 1.9rem);
	font-weight: 800;
	letter-spacing: -0.6px;
	color: var(--mj-ink);
	margin: 4px 0 6px;
}

/* Value propositions — a stacked list inside the hero copy column, not a band
   of cards. No card chrome: they sit on the hero's own surface, and boxing them
   would put a third border inside a bordered hero. */
.mj-props {
	margin-top: 26px;
	padding-top: 22px;
	border-top: 1px solid var(--mj-divider);
	display: flex;
	flex-direction: column;
	gap: 16px;
}
.mj-prop { display: flex; align-items: flex-start; gap: 13px; }
.mj-prop-icon {
	width: 34px; height: 34px;
	flex: 0 0 auto;
	border-radius: 9px;
	background: rgba(255,176,32,0.12);
	border: 1px solid rgba(255,176,32,0.30);
	color: var(--mj-amber);
	display: inline-flex; align-items: center; justify-content: center;
	font-size: 0.95rem;
}
.mj-prop-title {
	font-weight: 700;
	font-size: 0.94rem;
	color: var(--mj-ink);
	margin-bottom: 2px;
}
.mj-prop-desc {
	font-size: 0.82rem;
	line-height: 1.5;
	color: var(--mj-ink-mid);
}

/* Pricing */
.mj-pricing { padding: 26px 0 6px; }
/* .card-section clips overflow for its rounded corners, which would cut the
   badge in half; these cards have nothing that needs clipping. */
.mj-plan { position: relative; overflow: visible; }
.mj-plan-name {
	font-size: 0.72rem;
	text-transform: uppercase;
	letter-spacing: 1.4px;
	font-weight: 700;
	color: var(--mj-ink-muted);
	margin-bottom: 6px;
}
.mj-plan-price {
	font-size: 2.3rem;
	font-weight: 900;
	letter-spacing: -1.4px;
	color: var(--mj-ink);
	line-height: 1.1;
}
.mj-plan-price span {
	font-size: 0.85rem;
	font-weight: 600;
	letter-spacing: 0;
	color: var(--mj-ink-muted);
}
/* Stand-in for the amount while /api/billing/plan is in flight. Sized to the
   real figure so the card does not jump when the number lands. */
.mj-price-loading {
	display: inline-block;
	width: 7ch;
	height: 1em;
	vertical-align: -0.1em;
	border-radius: 6px;
	background: linear-gradient(90deg,
		rgba(233,237,247,0.08) 0%,
		rgba(233,237,247,0.20) 50%,
		rgba(233,237,247,0.08) 100%);
	background-size: 200% 100%;
	animation: mj-price-shimmer 1.2s ease-in-out infinite;
}
@keyframes mj-price-shimmer {
	from { background-position: 200% 0; }
	to   { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
	.mj-price-loading { animation: none; }
}
/* The season pass line under the monthly figure. Empty until the plan endpoint
   answers — and stays empty where no annual price is sold — so it must not
   reserve space of its own. */
/* Billing-period switch. Rendered only when the server confirms a season pass
   exists, so a deployment selling one plan shows no switch at all rather than a
   control with a single option. */
.mj-plan-toggle {
	display: flex;
	gap: 4px;
	padding: 4px;
	margin-bottom: 12px;
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	border-radius: 999px;
}
.mj-plan-tab {
	flex: 1;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 6px 10px;
	border: none;
	background: transparent;
	color: var(--mj-ink-mid);
	font-size: 0.8rem;
	font-weight: 700;
	border-radius: 999px;
	cursor: pointer;
	transition: background 0.15s ease, color 0.15s ease;
}
.mj-plan-tab:hover { color: var(--mj-ink); }
.mj-plan-tab.active {
	background: var(--mj-amber);
	color: var(--mj-base-deep);
}
.mj-plan-save {
	display: inline-block;
	background: rgba(255,176,32,0.16);
	color: var(--mj-amber);
	font-size: 0.68rem;
	font-weight: 800;
	letter-spacing: 0.4px;
	text-transform: uppercase;
	padding: 2px 7px;
	border-radius: 999px;
}
/* On the selected tab the amber pill would sit on amber, so it inverts. */
.mj-plan-tab.active .mj-plan-save {
	background: rgba(11,15,26,0.18);
	color: var(--mj-base-deep);
}
.mj-plan-list {
	list-style: none;
	padding: 0;
	margin: 0 0 4px;
	font-size: 0.86rem;
	color: var(--mj-ink-mid);
	line-height: 1.5;
}
.mj-plan-list li { position: relative; padding-left: 22px; margin-bottom: 7px; }
.mj-plan-list li::before {
	content: "✓";
	position: absolute; left: 0;
	color: var(--mj-amber);
	font-weight: 800;
}
/* The paid plan is the recommended path, so it carries the accent. */
.mj-plan-featured {
	border-color: rgba(255,176,32,0.42);
	box-shadow: 0 10px 34px rgba(255,176,32,0.10), var(--mj-shadow);
}
.mj-plan-badge {
	position: absolute;
	top: -11px; right: 16px;
	background: var(--mj-amber);
	color: var(--mj-base-deep);
	font-size: 0.66rem;
	font-weight: 800;
	letter-spacing: 0.8px;
	text-transform: uppercase;
	padding: 3px 10px;
	border-radius: 999px;
}

@media (max-width: 768px) {
	.mj-hero { padding: 30px 18px; }
	.mj-live-strip { gap: 12px 20px; padding: 12px 14px; }
}

/* Two large buttons side by side do not fit a phone, and flex-wrap leaves the
   second one stranded at half width. Below this they stack full width. */
@media (max-width: 480px) {
	.mj-hero-actions { display: grid; grid-template-columns: 1fr; }
	.mj-hero-actions > .btn-lg { width: 100%; }
	.mj-live-strip { flex-direction: column; }
}

/* ---------------------------------------------------------------------------
   Signed-out team lookup and its preview.

   The field is the first thing a stranger is asked to do, so it is sized like a
   primary action rather than a settings input, and the result below it is given
   the weight of a headline: three numbers about their own squad are the whole
   argument for going further.
   ------------------------------------------------------------------------- */
.mj-teamid-row {
	display: flex; gap: 10px; flex-wrap: wrap;
}
.mj-teamid-row .form-control {
	flex: 1 1 260px; min-width: 0;
}
.mj-teamid-row button { flex: 0 0 auto; }

.mj-preview-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: 12px;
}
.mj-preview-stat {
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius-sm);
	padding: 14px 16px;
}
.mj-preview-num {
	font-size: 2rem; font-weight: 800; line-height: 1.1;
	color: var(--mj-amber);
	font-variant-numeric: tabular-nums;
}
.mj-preview-label { font-size: 0.9rem; color: var(--mj-ink); margin-top: 2px; }
.mj-preview-sub { font-size: 0.78rem; color: var(--mj-ink-muted); margin-top: 3px; }

.mj-preview-share { color: var(--mj-ink-mid); }
.mj-preview-share-row { display: flex; gap: 8px; flex-wrap: wrap; }
.mj-preview-share-row .form-control {
	flex: 1 1 320px; min-width: 0;
	font-size: 0.85rem;
	color: var(--mj-ink-mid);
}
.mj-preview-share-row button { flex: 0 0 auto; }

.mj-preview-leagues { color: var(--mj-ink-mid); }
.mj-preview-league-row { display: flex; gap: 8px; flex-wrap: wrap; }
/* A name, not a count — it has to shrink to fit where a two-digit figure did.
   Short values such as "37%" are still numbers and keep the full size. */
.mj-preview-num-text { font-size: 1.35rem; line-height: 1.25; }
.mj-preview-num-text.mj-preview-num-short { font-size: 2rem; line-height: 1.1; }
.mj-move-up { color: var(--mj-pos); font-size: 0.78rem; font-weight: 700; }
.mj-move-down { color: var(--mj-neg); font-size: 0.78rem; font-weight: 700; }

.mj-preview-cta {
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius-sm);
	padding: 16px 18px;
	font-size: 0.9rem;
	color: var(--mj-ink-mid);
}
.mj-badge-template {
	background: rgba(255, 176, 32, 0.16);
	color: var(--mj-amber);
	font-weight: 600;
	font-size: 0.7rem;
	letter-spacing: 0.3px;
}

/* Ownership edge (paid squad analysis) */
.mj-edge-summary {
	display: flex; flex-wrap: wrap; gap: 8px 20px;
	font-size: 0.84rem;
	color: var(--mj-ink-mid);
}
.mj-edge-summary strong { color: var(--mj-amber); font-size: 1rem; }
/* A figure that opens the players behind it. The counts that name nobody are
   left as plain text, so the affordance only appears where there is a list. */
.mj-edge-stat { cursor: pointer; }
.mj-edge-stat:hover { text-decoration: underline; text-underline-offset: 3px; }
/* The minimum holds a long web name, the "MID · LEE · £6.8m" line beside it and
   the count, so the row stays on one line in the two columns a modal-lg fits.
   Narrower than that and the meta wraps under the name, which is legible but
   makes the rows uneven. */
.mj-edge-list {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
	gap: 4px 14px;
}
.mj-edge-row {
	display: flex; justify-content: space-between; align-items: baseline;
	gap: 10px;
	font-size: 0.85rem;
	padding: 5px 8px;
	border-radius: 6px;
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
}
.mj-edge-count { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------------------------------------------------------------------------
   Tom Select (the squad slots, and the must get/keep/sell/avoid lists)

   The library ships a light theme, so every surface is restated against the
   design tokens. Values mirror .form-control above rather than inventing new
   ones — the widget replaces a form control and should be indistinguishable
   from the fields beside it.
   --------------------------------------------------------------------------- */
.ts-wrapper { flex: 1; min-width: 0; }
/* Tom Select copies the original select's classes onto its wrapper, so the
   wrapper arrives carrying the field class and its border and padding land a
   second time around the control that already has them — 62px tall beside a
   46px input. Strip the box from the wrapper; .ts-control below is the field.

   Both field classes, not just .form-control. A <select> takes .form-select as
   naturally as .form-control — it is the more correct of the two for a plain
   one, since it carries the chevron — and keying this on one of them made the
   rule depend on which the author reached for rather than on what Tom Select
   does, which is copy whatever is there. The contributor page picked the other
   one and landed exactly the doubled box described above. The background reset
   also drops .form-select's chevron image, which would otherwise sit on the
   wrapper beside the control's own. */
.ts-wrapper.form-control,
.ts-wrapper.form-select {
	padding: 0;
	border: 0;
	background: transparent;
	height: auto;
	box-shadow: none;
}
.ts-wrapper.single .ts-control,
.ts-control {
	background: var(--mj-bg-tint);
	border: 1.5px solid var(--mj-divider);
	border-radius: 10px;
	padding: 10px 12px;
	color: var(--mj-ink);
	box-shadow: none;
	transition: border-color 0.15s, box-shadow 0.15s;
}
.ts-wrapper.single.input-active .ts-control,
.ts-wrapper.focus .ts-control {
	background: var(--mj-bg-tint);
	border-color: var(--mj-amber);
	box-shadow: 0 0 0 4px rgba(255,176,32,0.16);
}
/* Tom Select sets 13px/18px on the control, against this app's 16px/24px. With
   identical padding and border that alone left the picker 40px tall beside a
   46px input. Matching the type matches the height, and keeps doing so if the
   padding above is ever retuned. */
.ts-control,
.ts-control input,
.ts-control .item {
	font-size: 1rem;
	line-height: 1.5;
}
.ts-control input { color: var(--mj-ink); }
/* "Pick a player" is a prompt, not a value: muted like every other placeholder on
   the page, so a filled slot and an empty one are told apart at a glance. */
.ts-control input::placeholder { color: var(--mj-ink-muted); }
.ts-control .item { color: var(--mj-ink); }

/* The ✕ that empties a filled slot. It only appears on hover or focus, so it has
   to read against the tint the moment it shows. */
.plugin-clear_button .clear-button { color: var(--mj-ink-muted); }
.plugin-clear_button .clear-button:hover { color: var(--mj-ink); }

/* The dropdown floats over a card that is itself --mj-bg-elevated, and over the
   sibling pickers of the other ten slots. It has to read as a layer in front of
   the page, so it takes the float surface — a step lighter than the control it
   drops from — a brighter edge than a normal divider, and a shadow deep enough
   to darken the row it covers. The gap keeps the control's amber focus ring
   visible, so the panel is anchored to the field it belongs to. */
.ts-dropdown {
	background: var(--mj-bg-float);
	border: 1px solid var(--mj-divider-strong);
	border-radius: 10px;
	color: var(--mj-ink);
	box-shadow: 0 16px 40px rgba(0,0,0,0.65), 0 2px 8px rgba(0,0,0,0.5);
	margin-top: 6px;
	padding: 4px;
	overflow: hidden;
}
/* Players are the foreground of this panel, not secondary text: full ink, so a
   name in the list is never dimmer than the page showing through beside it. */
.ts-dropdown .option {
	color: var(--mj-ink);
	padding: 9px 10px;
	border-radius: 7px;
	cursor: pointer;
}
/* The row under the cursor or keyboard caret. A tint one step up was a 5-point
   lightness change and invisible; amber is the app's single signal colour and
   the only thing on this panel that cannot be mistaken for the page. */
.ts-dropdown .active {
	background: rgba(255,176,32,0.18);
	color: var(--mj-ink);
	box-shadow: inset 2px 0 0 var(--mj-amber);
}
/* The value already in the slot, when it survives the current filter. Distinct
   from .active above, which is where the cursor is — the two used to share the
   amber wash, so an open picker showed two rows that looked equally chosen. */
.ts-dropdown .selected { background: rgba(255,255,255,0.06); color: var(--mj-ink); }
.ts-dropdown .active.selected {
	background: rgba(255,176,32,0.18);
	box-shadow: inset 2px 0 0 var(--mj-amber);
}
.ts-dropdown .no-results { color: var(--mj-ink-muted); padding: 9px 10px; }
/* Scrolling is the norm here — a position group runs to ~250 players — so the
   track cannot be the invisible default that leaves no sign there is more list. */
.ts-dropdown .ts-dropdown-content { max-height: 260px; scrollbar-color: var(--mj-divider-strong) transparent; }
.ts-dropdown .ts-dropdown-content::-webkit-scrollbar { width: 10px; }
.ts-dropdown .ts-dropdown-content::-webkit-scrollbar-thumb {
	background: var(--mj-divider-strong);
	border: 3px solid transparent;
	border-radius: 6px;
	background-clip: content-box;
}
/* Tom Select wraps the search match in <span class="highlight">; the default is
   a pale yellow that vanishes on this background. Weight and colour rather than
   a filled box: the box was a second rectangle inside a row that now carries its
   own amber wash, and on the active row the two washes cancelled out. */
.ts-dropdown .highlight {
	background: transparent;
	color: var(--mj-amber);
	font-weight: 700;
}
.ts-wrapper.single .ts-control:after { border-top-color: var(--mj-ink-muted); }

/* The multi-select lists — must get, keep, sell and avoid — where each chosen
   player is a chip inside the control. Tom Select's chips are grey on white and
   would sit on the tint as four unreadable smudges. Amber rather than a neutral
   tint because a chip here is a constraint the user has imposed on the solver,
   which is exactly what the page uses amber to say.

   Both control states are restated below because the library gives .has-items a
   padding of its own — left alone, the field shrank by 6px the moment the first
   player went in. The min-height is the single picker's 47px, so an empty list
   is the same field as the ones beside it rather than a shorter one. */
.ts-wrapper.multi .ts-control,
.ts-wrapper.multi.has-items .ts-control {
	padding: 7px 10px 4px;
	min-height: 47px;
	align-items: center;
}
.ts-wrapper.multi .ts-control > div {
	background: rgba(255,176,32,0.14);
	border: 1px solid rgba(255,176,32,0.35);
	border-radius: 999px;
	color: var(--mj-ink);
	padding: 2px 10px;
	font-size: 0.9rem;
}
/* A chip selected for deletion, by backspace or by click. It has to be louder
   than the resting chip beside it — the next backspace removes it. */
.ts-wrapper.multi .ts-control > div.active {
	background: rgba(255,176,32,0.3);
	border-color: var(--mj-amber);
	color: var(--mj-ink);
}
/* The ✕ on each chip. Its divider is the library's own light grey, invisible
   against the amber it now sits on, and its square right corners cut the corner
   off a pill — the plugin is written for the rectangular chip it ships with. */
.ts-wrapper.multi.plugin-remove_button .item .remove {
	border-left-color: rgba(255,176,32,0.35);
	border-radius: 0 999px 999px 0;
	color: var(--mj-ink-muted);
}
.ts-wrapper.multi.plugin-remove_button .item.active .remove {
	border-left-color: var(--mj-amber);
	color: var(--mj-ink);
}
.ts-wrapper.multi.plugin-remove_button .item .remove:hover {
	background: rgba(255,176,32,0.28);
	color: var(--mj-ink);
}

/* ------------------------------------------------------------
   Modal — the "show all" popups behind the cards' full lists
   ------------------------------------------------------------ */
/* Bootstrap ships the modal light. Dropped on this page it kept its own white
   panel but inherited the body's light ink, which left the list all but
   invisible — so the surface has to be restated in the cards' terms, not just
   the text colour. Deliberately mirrors .card-section: the popup is the same
   card, holding the rows the card itself had no room for. */
.modal-content {
	background: var(--mj-bg-elevated);
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius);
	box-shadow: var(--mj-shadow-lg);
	color: var(--mj-ink);
}
.modal-header {
	border-bottom: 1px solid var(--mj-divider);
	padding: 14px 18px;
}
.modal-title { font-weight: 700; color: var(--mj-ink); }
.modal-body { padding: 16px 18px; }
.modal-backdrop.show { background: var(--mj-base-deep); opacity: 0.72; }

/* The close glyph is a near-black SVG, invisible against the panel above. */
.modal-header .btn-close {
	filter: invert(1) grayscale(100%) brightness(1.7);
	opacity: 0.55;
}
.modal-header .btn-close:hover { opacity: 1; }

/* These lists are the long tail by definition — ownership runs sixty deep — so
   the track is drawn rather than left as the invisible default that gives no
   sign there is more below. Same treatment as the Tom Select dropdown, for the
   same reason. */
.modal-dialog-scrollable .modal-body { scrollbar-color: var(--mj-divider-strong) transparent; }
.modal-dialog-scrollable .modal-body::-webkit-scrollbar { width: 10px; }
.modal-dialog-scrollable .modal-body::-webkit-scrollbar-thumb {
	background: var(--mj-divider-strong);
	border: 3px solid transparent;
	border-radius: 6px;
	background-clip: content-box;
}

/* ------------------------------------------------------------
   Player card — the popup behind every name and jersey
   ------------------------------------------------------------ */
/* Deliberately quiet: names sit in dense lists sixty rows deep, and giving each
   one link colour would turn a table into a wall of amber. The cue is the
   cursor plus an underline that only appears under the pointer — enough to
   answer "is this clickable" once, without decorating every row. */
.mj-pclick { cursor: pointer; }
.mj-pclick:hover { text-decoration: underline; text-underline-offset: 2px; }
/* On the pitch chip the name already sits on its own dark backing and the
   jersey is an image, so an underline has nothing to attach to. */
.player-chip .name.mj-pclick:hover { text-decoration: none; background: rgba(0,0,0,0.80); }
.player-chip .shirt.mj-pclick:hover img { filter: drop-shadow(0 4px 10px rgba(255,176,32,0.55)); }

.mj-pp .modal-header { align-items: flex-start; gap: 12px; }
.mj-pp-id { display: flex; align-items: center; gap: 14px; min-width: 0; }

/* The initials are the element's own text and the photo is laid over them, so a
   headshot the CDN does not have leaves a filled circle rather than a gap. */
.mj-pp-photo {
	position: relative;
	flex: 0 0 auto;
	width: 66px; height: 66px;
	border-radius: 50%;
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	display: flex; align-items: center; justify-content: center;
	color: var(--mj-ink-muted);
	font-weight: 800;
	font-size: 1.05rem;
	letter-spacing: 1px;
	overflow: hidden;
}
.mj-pp-photo img {
	position: absolute;
	left: 0; bottom: 0;
	width: 100%; height: 100%;
	object-fit: cover;
	object-position: top center;
}
.mj-pp-idtext { min-width: 0; }
.mj-pp-name { font-size: 1.2rem; font-weight: 700; color: var(--mj-ink); line-height: 1.2; }
.mj-pp-meta {
	color: var(--mj-ink-mid);
	font-size: 0.82rem;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	margin-top: 2px;
}
.mj-pp-status {
	display: inline-block;
	margin-top: 6px;
	padding: 2px 8px;
	border-radius: 999px;
	font-size: 0.68rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.6px;
}
.mj-pp-status.ok   { background: rgba(53,208,127,0.16); color: var(--mj-pos); }
.mj-pp-status.warn { background: rgba(255,176,32,0.18); color: var(--mj-amber); }
.mj-pp-status.bad  { background: rgba(255,107,107,0.16); color: var(--mj-neg); }

/* The four figures the picks are actually made on, given the prominence they
   have on a chip. Mojo is accented because it is the one the model asserts. */
.mj-pp-headline {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 8px;
	margin-bottom: 18px;
}
.mj-pp-headline-cell {
	background: var(--mj-bg-tint);
	border: 1px solid var(--mj-divider);
	border-radius: var(--mj-radius-sm);
	padding: 10px 8px;
	text-align: center;
}
.mj-pp-headline-cell.accent { border-color: var(--mj-amber); background: rgba(255,176,32,0.10); }
.mj-pp-headline-value {
	font-size: 1.45rem;
	font-weight: 800;
	color: var(--mj-ink);
	line-height: 1.1;
	font-variant-numeric: tabular-nums;
}
.mj-pp-headline-cell.accent .mj-pp-headline-value { color: var(--mj-amber); }
.mj-pp-headline-label {
	margin-top: 3px;
	color: var(--mj-ink-muted);
	font-size: 0.66rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.6px;
}

.mj-pp-block { margin-bottom: 18px; }
.mj-pp-block:last-child { margin-bottom: 0; }
.mj-pp-block-title {
	color: var(--mj-ink-muted);
	font-size: 0.7rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.8px;
	margin-bottom: 8px;
	padding-bottom: 6px;
	border-bottom: 1px solid var(--mj-divider);
}

.mj-pp-gw { display: flex; align-items: center; gap: 10px; padding: 3px 0; }
.mj-pp-gw-label {
	flex: 0 0 52px;
	color: var(--mj-ink-mid);
	font-size: 0.76rem;
	font-weight: 600;
}
.mj-pp-gw-track {
	flex: 1;
	height: 8px;
	border-radius: 4px;
	background: var(--mj-bg-tint);
	overflow: hidden;
}
.mj-pp-gw-fill {
	height: 100%;
	border-radius: 4px;
	background: linear-gradient(90deg, var(--mj-amber-deep), var(--mj-amber));
}
.mj-pp-gw-value {
	flex: 0 0 44px;
	text-align: right;
	font-size: 0.8rem;
	font-weight: 700;
	color: var(--mj-ink);
	font-variant-numeric: tabular-nums;
}
/* The gameweek the run is actually for; the rest of the horizon is context. */
.mj-pp-gw.next .mj-pp-gw-label,
.mj-pp-gw.next .mj-pp-gw-value { color: var(--mj-amber); }

.mj-pp-stats {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 8px;
}
.mj-pp-stat {
	background: var(--mj-bg-tint);
	border-radius: var(--mj-radius-sm);
	padding: 8px 10px;
	min-width: 0;
}
.mj-pp-stat-label {
	color: var(--mj-ink-muted);
	font-size: 0.66rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.5px;
	line-height: 1.3;
}
.mj-pp-stat-value {
	margin-top: 2px;
	color: var(--mj-ink);
	font-size: 0.98rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}
/* Sits inline after the value it qualifies, smaller so the count still reads
   first — the move is context, not the number the tile is about. */
.mj-pp-stat-delta {
	display: inline;
	margin-left: 4px;
	font-size: 0.7rem;
	font-weight: 700;
}

/* Fixtures — the run of games behind the predictions above them.
   Fixed columns rather than flex so the strip stays aligned with the prediction
   rows: a double gameweek stacks two cells in one column and a blank leaves one
   empty, and both have to keep their gameweek over the right slot. */
.mj-pp-fx {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: 1fr;
	gap: 6px;
}
.mj-pp-fx-col { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.mj-pp-fx-gw {
	color: var(--mj-ink-muted);
	font-size: 0.66rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.5px;
	text-align: center;
}
.mj-pp-fx-gw.next { color: var(--mj-amber); }
.mj-pp-fx-cell { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.mj-pp-fx-cell.blank { opacity: 0.5; }

/* Crest over the short name, same trick as the headshot: a club the CDN has no
   badge for still says which club it is. */
.mj-pp-fx-badge {
	position: relative;
	width: 34px; height: 34px;
	display: flex; align-items: center; justify-content: center;
	color: var(--mj-ink-muted);
	font-size: 0.58rem;
	font-weight: 700;
}
.mj-pp-fx-badge img {
	position: absolute; inset: 0;
	width: 100%; height: 100%;
	object-fit: contain;
}
.mj-pp-fx-blank {
	width: 34px; height: 34px;
	display: flex; align-items: center; justify-content: center;
	border: 1px dashed var(--mj-divider-strong);
	border-radius: 50%;
	color: var(--mj-ink-muted);
}
.mj-pp-fx-name {
	color: var(--mj-ink-mid);
	font-size: 0.7rem;
	font-weight: 600;
	white-space: nowrap;
}
/* FPL's own 1..5 scale, hue for hue, but darkened for this surface — the light
   greens and greys it ships glare against a near-black card. */
.mj-pp-fx-diff {
	width: 100%;
	border-radius: 5px;
	padding: 2px 0;
	text-align: center;
	font-size: 0.72rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}
/* Shared with the pitch chips' fixture strip. One declaration for both, because
   the same 1..5 has to mean the same colour on the pitch and in the card opened
   from it — split in two they drifted the first time either was touched. */
.mj-pp-fx-diff.d1, .player-chip .fx .d1 { background: #14B85C; color: #04160C; }
.mj-pp-fx-diff.d2, .player-chip .fx .d2 { background: #3FD07F; color: #04160C; }
.mj-pp-fx-diff.d3, .player-chip .fx .d3 { background: #35415C; color: var(--mj-ink-mid); }
.mj-pp-fx-diff.d4, .player-chip .fx .d4 { background: #D6104A; color: #fff; }
.mj-pp-fx-diff.d5, .player-chip .fx .d5 { background: #8C0A31; color: #fff; }
/* No rating published, which is not the same as an easy fixture. */
.mj-pp-fx-diff.d0, .player-chip .fx .d0 { background: var(--mj-bg-tint); color: var(--mj-ink-muted); }

/* Recent points — the same column, chip and badge as the fixture strip, so the
   two blocks read as one timeline. The chip's scale is reversed against the
   difficulty one above it: there five is the hard fixture, here ten is the haul,
   and green means good in both. */
.mj-pp-rp-pts {
	width: 100%;
	border-radius: 5px;
	padding: 2px 0;
	text-align: center;
	font-size: 0.78rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}
.mj-pp-rp-pts.haul { background: #14B85C; color: #04160C; }
.mj-pp-rp-pts.good { background: #3FD07F; color: #04160C; }
.mj-pp-rp-pts.mid  { background: #35415C; color: var(--mj-ink); }
.mj-pp-rp-pts.low  { background: #4A2436; color: var(--mj-ink-mid); }
.mj-pp-rp-pts.neg  { background: #8C0A31; color: #fff; }
/* Not a bad week — no week at all. Held apart from a played zero because the two
   say opposite things about whether he starts next. */
.mj-pp-rp-pts.dnp {
	background: transparent;
	border: 1px dashed var(--mj-divider-strong);
	color: var(--mj-ink-muted);
}
.mj-pp-rp-min {
	color: var(--mj-ink-muted);
	font-size: 0.62rem;
	font-weight: 600;
	white-space: nowrap;
}

/* Where the player stands among his own position on that stat. Full always
   means the good end, so an inverted stat like xGC reads the same way. */
.mj-pp-stat-bar {
	height: 4px;
	margin-top: 6px;
	border-radius: 2px;
	background: rgba(255,255,255,0.09);
	overflow: hidden;
}
/* Holds the tile's height so a bar-less tile does not sit short of the grid row. */
.mj-pp-stat-bar.empty { background: transparent; }
.mj-pp-stat-fill {
	height: 100%;
	border-radius: 2px;
	background: linear-gradient(90deg, var(--mj-amber-deep), var(--mj-amber));
}

.mj-pp-news { color: var(--mj-neg); font-size: 0.86rem; }
.mj-pp-factors { color: var(--mj-ink-mid); font-size: 0.82rem; margin-top: 4px; }

@media (max-width: 575.98px) {
	.mj-pp-headline { grid-template-columns: repeat(2, 1fr); }
	.mj-pp-stats { grid-template-columns: repeat(2, 1fr); }
	.mj-pp-fx { gap: 3px; }
	.mj-pp-fx-badge, .mj-pp-fx-blank { width: 26px; height: 26px; }
	.mj-pp-fx-name { font-size: 0.6rem; }
	.mj-pp-fx-diff { font-size: 0.66rem; }
	.mj-pp-rp-pts { font-size: 0.7rem; }
	.mj-pp-rp-min { font-size: 0.56rem; }
	.mj-pp-photo { width: 52px; height: 52px; font-size: 0.9rem; }
	.mj-pp-name { font-size: 1.05rem; }
}
