[x-cloak] { display: none !important; }

/* Player profile page (templates/players/player_profile.html) -
   page-specific classes layered on static/css/base.css, same pattern as
   league_home.css. Visual language ported from
   reference/preview/profile.html onto this project's real player data -
   see player_profile.html's own template comment for what's adapted vs.
   dropped from the mockup. */

/* --hero-accent is set inline by the template from the player's current
   team color when there is one (player_profile.html) - this diagonal wash
   (also inherited as-is on mobile - see the 560px query below) keys off
   that one custom property, falling back to --bg-card (a no-op tint) when
   unset, e.g. unrostered players. */
.pl-hero {
  position: relative;
  display: flex;
  min-height: 240px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--hero-accent, var(--bg-card)) 8%, var(--bg-card)) 0%, var(--bg-card) 65%);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
  margin-bottom: var(--space-5);
}

.pl-hero-left {
  flex: 1;
  min-width: 0;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.pl-position-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.pl-position {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--primary);
}

/* Position color-coding (same slugs/colors as .hl-badge[data-position]
   in static/css/base.css) - anything other than a curated Guard/Forward/
   Center match keeps the default orange above. */
.pl-position[data-position="guard"] { color: var(--teal); }
.pl-position[data-position="forward"] { color: var(--lime); }
.pl-position[data-position="center"] { color: var(--gold); }

.pl-number {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 12px;
  color: var(--primary);
  background: rgba(var(--primary-rgb), 0.15);
  border: 1px solid rgba(var(--primary-rgb), 0.35);
  border-radius: 5px;
  padding: 1px 6px;
}

/* Optional nickname (Player.alias) - sits between the position row and the
   real name rather than competing with either; quoted like a scouting-
   report nickname, not styled as a second heading. */
.pl-alias {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 13px;
  font-style: italic;
  color: var(--text-secondary);
  margin-top: 6px;
}
.pl-alias::before { content: "\201C"; }
.pl-alias::after { content: "\201D"; }

.pl-name {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 32px;
  text-transform: uppercase;
  line-height: 0.95;
  color: var(--text);
  margin: var(--space-2) 0;
}

.pl-team-line {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: var(--space-3);
}

.pl-team-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--primary);
  white-space: nowrap;
}

.pl-team-label--unrostered {
  color: var(--text-muted);
}

/* Team-color marker: now that .pl-hero's own background wash is too faint
   to read as "this player's team is colored" (mobile in particular fades
   it out completely behind the stacked photo/motif block), this dot is
   the one place that still visibly carries --hero-accent - the divider
   line next to it stays the generic --primary orange regardless (see
   .pl-team-rule below), so the dot alone is the team-color tell. Omitted
   entirely (not just falling back) for a colorless team - see
   player_profile.html. */
.pl-team-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--hero-accent, var(--primary));
  flex: 0 0 auto;
}

.pl-team-rule {
  flex: 1;
  height: 1px;
  background: var(--primary);
  opacity: 0.6;
}

.pl-team-line:has(.pl-team-label--unrostered) .pl-team-rule {
  background: var(--text-muted);
  opacity: 0.35;
}

.pl-hero-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

.pl-hero-stat-value {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 24px;
  color: var(--text);
  line-height: 1;
}

.pl-hero-stat-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--primary);
  margin-top: 2px;
}

.pl-hero-right {
  flex: 0 0 38%;
  position: relative;
}

.pl-hero-gradient {
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, var(--bg-card) 0%, transparent 45%);
  z-index: 1;
}

.pl-hero-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}

/* No photo_url uploaded - fill the same slot the photo would occupy with
   a stylized basketball instead of leaving it blank: a shaded sphere
   (::before, a radial-gradient standing in for leather + a highlight/
   shadow) with the shared seam-line motif (templates/core/_ball_motif.html)
   laid on top of it as the ball's actual seams, rather than the old bare
   14%-opacity outline ring floating on a flat panel. --motif-accent is
   set inline by the template from the player's current team color when
   there is one (falls back to --primary here otherwise) and tints the
   leather; the seams themselves stay a fixed dark tone regardless of
   that tint; since a real ball's seams read as near-black against any
   leather color, not the leather's own hue. */
.pl-hero-right--motif {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  background: var(--bg-raised);
}

.pl-hero-right--motif::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 134px;
  height: 134px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(
    circle at 34% 30%,
    color-mix(in srgb, white 35%, var(--motif-accent, var(--primary))) 0%,
    var(--motif-accent, var(--primary)) 45%,
    color-mix(in srgb, black 35%, var(--motif-accent, var(--primary))) 78%,
    color-mix(in srgb, black 55%, var(--motif-accent, var(--primary))) 100%
  );
  box-shadow: 0 12px 26px -8px color-mix(in srgb, var(--motif-accent, var(--primary)) 55%, transparent);
}

.pl-hero-motif {
  position: relative;
  color: color-mix(in srgb, black 60%, transparent);
  opacity: 0.55;
}

/* Default-avatar initials (no avatar_url uploaded) - layered on top of the
   ball motif rather than replacing it, so a player with no photo still
   gets an identity mark (their own initials) instead of just a generic
   ball graphic. See apps.core.templatetags.badges.initials. */
.pl-hero-initials {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 40px;
  letter-spacing: 1px;
  color: color-mix(in srgb, white 85%, transparent);
  text-shadow: 0 2px 10px color-mix(in srgb, black 45%, transparent);
  pointer-events: none;
}

/* ─── Hype block (templates/players/_hype_block.html) ────────────────
   Anonymous, no-login "Hype" reaction (apps.players.views.player_hype_view)
   - a plain form/button so it degrades to a real POST+redirect without JS,
   enhanced via hx-post to swap just this block in place. Grown into a
   proper card (user request) so Hype + count + "View full leaderboards"
   read as one engagement component instead of a bare button floating on
   the page with an unrelated link elsewhere. */
.pl-hype-block {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: var(--space-4);
}

.pl-hype-row {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 10px;
}

/* Primary action in this card - filled-tint pill with a real glow (same
   rgba(var(--primary-rgb), …) box-shadow recipe used everywhere else this
   session for "polish" - league_home.css's masthead glow, the sidebar
   Roster Card, etc.) so it reads as the thing to press, not a plain
   outlined chip sitting next to a plain text link. */
.pl-hype-row form {
  display: contents;
}

.pl-hype-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 10px 18px;
  border-radius: 10px;
  background: rgba(var(--primary-rgb), 0.16);
  border: 1px solid rgba(var(--primary-rgb), 0.45);
  color: var(--primary);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 10px 24px -14px rgba(var(--primary-rgb), 0.7);
  transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.pl-hype-btn:hover:not(:disabled) {
  background: rgba(var(--primary-rgb), 0.24);
  box-shadow: 0 14px 28px -12px rgba(var(--primary-rgb), 0.85);
  transform: translateY(-1px);
}

.pl-hype-btn:active:not(:disabled) {
  transform: scale(0.97);
}

.pl-hype-btn:disabled {
  cursor: default;
}

.pl-hype-btn .icon {
  width: 17px;
  height: 17px;
  filter: drop-shadow(0 0 5px rgba(var(--primary-rgb), 0.6));
}

.pl-hype-count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Secondary action - a quiet outlined chip (not filled/glowing like the
   Hype button) so the two don't compete for the same attention, but
   still enough presence next to it that it doesn't read as an
   afterthought text link. A faint resting gold tint (the trophy icon's
   own fixed accent, not the per-league --primary) gives it some weight
   at rest instead of reading as pure outline-on-nothing; hover adds a
   lift + soft shadow on top of the existing border/color/background
   shift and arrow nudge, matching the kind of hover response
   .pl-hype-btn already has, just quieter. */
.pl-leaderboards-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-shrink: 0;
  padding: 10px 18px;
  border-radius: 10px;
  border: 1px solid var(--border-strong);
  background: rgba(245, 195, 75, 0.05);
  color: var(--text-secondary);
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.pl-leaderboards-link:hover {
  border-color: rgba(var(--primary-rgb), 0.5);
  background: rgba(var(--primary-rgb), 0.08);
  color: var(--primary);
  transform: translateY(-1px);
  box-shadow: 0 8px 18px -14px rgba(var(--primary-rgb), 0.6);
}

.pl-leaderboards-link:active {
  transform: scale(0.97);
}

.pl-leaderboards-link .icon {
  width: 13px;
  height: 13px;
  color: var(--gold);
  flex-shrink: 0;
  filter: drop-shadow(0 0 4px rgba(245, 195, 75, 0.5));
}

.pl-leaderboards-link-arrow {
  display: inline-block;
  transition: transform 0.15s ease;
}

.pl-leaderboards-link:hover .pl-leaderboards-link-arrow {
  transform: translateX(3px);
}

/* "Already hyped this week" state - stays visibly on/lit rather than
   fading to a plain disabled gray, so it still reads as "you did this",
   not "this is broken". Keeps a glow (dimmer than the hover state) but
   drops the lift/hover response - inviting another press would be
   misleading once it's disabled. */
.pl-hype-btn.is-hyped {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
  box-shadow: 0 10px 22px -14px rgba(var(--primary-rgb), 0.6);
}
.pl-hype-btn.is-hyped:hover {
  transform: none;
}

.pl-hype-note {
  font-size: 11px;
  color: var(--text-muted);
}

/* Below 560px there isn't room for both chips at their natural desktop
   width ("View full leaderboards" alone runs longer than the space left
   next to the Hype button) - so instead of letting them wrap onto two
   mismatched-width lines, split the row evenly (flex: 1 1 0 on both) and
   drop "View full " so just "Leaderboards" remains, which fits the
   half-width share comfortably. text-overflow: ellipsis is a safety net
   for the narrowest phones, not the primary fix. */
@media (max-width: 560px) {
  .pl-hype-row {
    gap: 8px;
  }

  .pl-hype-btn,
  .pl-leaderboards-link {
    flex: 1 1 0;
    min-width: 0;
    padding: 10px 10px;
    gap: 6px;
  }

  .pl-hype-count {
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .pl-leaderboards-link-full {
    display: none;
  }

  .pl-leaderboards-link-text {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}

/* ─── Full-screen Hype celebration (templates/players/_hype_celebration.html,
   fired by static/js/hype-celebration.js on the `hypeCelebrate` HX-Trigger -
   see player_hype_view's own docstring for when that fires). Every color
   below is one of the four tokens apps.leagues.theme/league_public_base.html
   overrides per league (--primary/--primary-rgb/--on-primary/--orange-deep),
   read by the JS via getComputedStyle at the moment it fires - so a themed
   league's confetti/glow matches ITS palette automatically, same as the
   plain .pl-hype-btn above already does, with no extra plumbing between the
   view and the JS. z-index:200 - above this app's whole existing scale
   (.hl-league-nav 40 in base.css, back-to-top.css 35, nav-progress.css 100,
   share.css's popover 50) since this is a deliberate full-viewport
   takeover, but below page-loader.css's 9999 in case a slow htmx nav
   somehow overlaps it. */
.hype-celebration {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 45%, rgba(var(--primary-rgb), 0.22), var(--bg) 72%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}

.hype-celebration.is-active {
  opacity: 1;
  pointer-events: auto;
}

.hype-celebration-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hype-celebration-content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.hype-celebration-icon {
  width: 76px;
  height: 76px;
  color: var(--primary);
  filter: drop-shadow(0 0 40px rgba(var(--primary-rgb), 0.85));
}

.hype-celebration-label {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: clamp(40px, 12vw, 64px);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text);
  text-shadow: 0 0 50px rgba(var(--primary-rgb), 0.65);
}

.hype-celebration-count {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 14px;
  color: var(--primary);
  letter-spacing: 0.4px;
}

.hype-celebration-dismiss {
  position: absolute;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: var(--text-muted);
  letter-spacing: 0.3px;
}

@media (prefers-reduced-motion: reduce) {
  .hype-celebration {
    transition-duration: 0.01ms;
  }
}

/* ─── Cosigned skills + picker (templates/players/_endorse_block.html) ─── */
.pl-endorse-block {
  margin-bottom: var(--space-4);
}

.pl-skills-head {
  margin-bottom: 8px;
}

.pl-skills-head h3 {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}

.pl-skills {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  margin-bottom: 10px;
}

.pl-skill-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.pl-skill-icon {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: rgba(179, 169, 156, 0.14);
  color: var(--text-muted);
}
.pl-skill-icon .icon {
  width: 12px;
  height: 12px;
}

/* Same gold/silver/bronze tier language as static/css/stat_leaders.css's
   .stl-rank[data-tier] - a #1 cosigned skill reads the same way a #1
   stat leader already does elsewhere on the site. */
.pl-skill-row[data-tier="gold"] .pl-skill-icon { background: rgba(245, 195, 75, 0.18); color: var(--gold); }
.pl-skill-row[data-tier="silver"] .pl-skill-icon { background: rgba(179, 169, 156, 0.22); color: var(--text-secondary); }
.pl-skill-row[data-tier="bronze"] .pl-skill-icon { background: rgba(156, 67, 16, 0.28); color: var(--orange-deep); }

.pl-skill-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  flex-shrink: 0;
  white-space: nowrap;
}

/* Proportional bar - each row's count relative to the #1 row
   (apps.players.views._top_endorsements' `pct`), same device the design
   review artifact used so a glance shows relative strength, not just
   three isolated numbers. */
.pl-skill-track {
  flex: 1;
  height: 6px;
  border-radius: 999px;
  background: var(--bg-raised);
  overflow: hidden;
}

.pl-skill-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--orange-deep), var(--primary));
  box-shadow: 0 0 8px rgba(var(--primary-rgb), 0.5);
}

.pl-skill-count {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  color: var(--text-secondary);
  flex-shrink: 0;
  min-width: 20px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.pl-endorse-error {
  font-size: 12px;
  color: var(--alert);
  margin: 0 0 10px;
}

/* Suggested (unvouched) slot - apps.players.views._top_endorsements pads
   out to exactly 3 rows, so this can sit alongside real bar+count rows
   (a player with one real vouch still sees two of these) rather than
   only ever appearing when the whole list is empty. Same .pl-skill-row
   layout as a real row (icon/name/track/trailing label line up in the
   same columns), but as a real <button> - reset the button-default
   chrome since this element type is otherwise styled as a plain <div>. */
.pl-skill-row--suggested {
  background: none;
  border: none;
  padding: 0;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  border-radius: 6px;
  transition: background-color 0.15s ease;
}

.pl-skill-row--suggested:hover {
  background: rgba(var(--primary-rgb), 0.08);
}

.pl-skill-icon--suggest {
  background: rgba(var(--primary-rgb), 0.12);
  color: var(--primary);
}

.pl-skill-row--suggested .pl-skill-name {
  color: var(--text-secondary);
  font-weight: 500;
}

/* Suggested rows show an empty track (0% fill) purely to keep column
   alignment with real rows - the "Vouch" label is the actual affordance. */
.pl-skill-row--suggested .pl-skill-track {
  opacity: 0.5;
}

.pl-skill-suggest-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--primary);
  flex-shrink: 0;
}

/* Collapsed-by-default toggle (apps.players.views.player_profile_view's
   show_picker=False on a plain GET; player_endorse_view sets it True
   after any vouch so the picker stays open through the next pick). */
.pl-skills-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  padding: 0 0 8px;
  color: var(--primary);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.pl-skills-toggle-chevron {
  width: 11px;
  height: 11px;
  transition: transform 0.15s ease;
}

.pl-skills-toggle-chevron.is-open {
  transform: rotate(180deg);
}

.pl-endorse-picker {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 8px;
}

.pl-endorse-note {
  font-size: 11px;
  color: var(--text-muted);
}

.pl-endorse-group-label {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 6px;
}

.pl-endorse-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.pl-endorse-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.pl-endorse-chip:hover:not(.is-given) {
  border-color: rgba(var(--primary-rgb), 0.4);
  color: var(--text);
}

.pl-endorse-chip.is-given {
  background: rgba(var(--primary-rgb), 0.14);
  border-color: rgba(var(--primary-rgb), 0.35);
  color: var(--primary);
  cursor: default;
}

.pl-endorse-chip.is-given .icon {
  width: 11px;
  height: 11px;
}


/* ─── Bio card ────────────────────────────────────────────────────── */
.pl-bio-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  display: flex;
  gap: var(--space-4);
}

.pl-bio-item {
  flex: 1;
  text-align: center;
}

.pl-bio-value {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}

.pl-bio-label {
  font-size: 10px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-top: 2px;
}

/* The "Teams" bio item doubles as a jump-link to the History tab (below).
   Unlike a hover-only cue (invisible until a visitor already has a mouse
   over it - no help on a touch device that will never "hover" at all),
   this needs to read as tappable at rest: a tinted pill background/border
   plus a small chevron next to the label, both visible before any
   interaction. */
.pl-bio-item--link {
  border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
  background: color-mix(in srgb, var(--primary) 8%, transparent);
  border-radius: 8px;
  padding: 2px 4px;
  margin: -2px -4px;
  font: inherit;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.pl-bio-item--link:hover,
.pl-bio-item--link:focus-visible {
  background: color-mix(in srgb, var(--primary) 16%, transparent);
  border-color: color-mix(in srgb, var(--primary) 50%, transparent);
}

.pl-bio-item--link .pl-bio-value {
  color: var(--primary);
}

.pl-bio-link-icon {
  transform: rotate(90deg);
  vertical-align: -1px;
  opacity: 0.8;
}

/* ─── Tabs ────────────────────────────────────────────────────────── */
/* 5 tabs (Season/Career/History/Awards/Analytics) no longer reliably fit
   a narrow phone width without either wrapping the underline mid-stack
   or squeezing labels - flex-wrap isn't a good fix here the way it is
   for .stl-tabs' pill tabs (static/css/stat_leaders.css): a wrapped
   second row would sit below this container's own shared border-bottom,
   detached from it. Scrolls horizontally instead, tabs never shrink/wrap,
   same scrollbar treatment as .pl-cardpicker-rail below. */
.pl-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-4);
  /* The baseline is an inset shadow, not a border-bottom: the active tab
     used to overlap a real border with margin-bottom:-1px, and that 1px
     overhang counted as vertical overflow - with overflow-x:auto (which
     forces overflow-y to auto too) it produced a vertical scrollbar. */
  box-shadow: inset 0 -1px 0 var(--border);
  margin-bottom: var(--space-4);
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
  /* Self-adjusting scroll shadow (the classic background-attachment:
     local/scroll pairing) - a soft fade over whichever edge still has
     more tabs to scroll to, so a tab clipped by the container (e.g.
     "Analytics" on a narrow phone) reads as "swipe for more" rather than
     as a rendering glitch. Both shadows are 0-width/invisible on their
     own when there's nothing to scroll in that direction - no JS needed
     to detect "at the start/end" the way a scroll-linked class would. */
  background:
    linear-gradient(to right, var(--bg) 30%, transparent) left,
    linear-gradient(to left, var(--bg) 30%, transparent) right,
    radial-gradient(farthest-side at 0% 50%, rgba(0, 0, 0, 0.4), transparent) left,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.4), transparent) right;
  background-repeat: no-repeat;
  background-size: 24px 100%, 24px 100%, 10px 100%, 10px 100%;
  background-attachment: local, local, scroll, scroll;
}

.pl-tabs::-webkit-scrollbar {
  height: 4px;
}

.pl-tabs::-webkit-scrollbar-track {
  background: transparent;
}

.pl-tabs::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 999px;
}

.pl-tab {
  background: none;
  border: none;
  padding: 10px 2px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-muted);
  border-bottom: 2px solid transparent;
  flex-shrink: 0;
  white-space: nowrap;
}

.pl-tab.active {
  color: var(--primary);
  border-bottom-color: var(--primary);
}

/* ─── Metric rows (Current Season / Career tabs) ─────────────────── */
.pl-metric-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}

.pl-metric-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px var(--space-4);
  border-bottom: 1px solid var(--border);
}

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

.pl-metric-name {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.pl-metric-name .label {
  font-size: 14px;
  color: var(--text);
}

.pl-metric-name .abbr {
  font-size: 11px;
  color: var(--text-muted);
  text-transform: uppercase;
}

.pl-metric-value {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  color: var(--primary);
}

/* ─── Season picker + per-season share row (Season tab) ─────────────
   .pl-season-picker: the plain GET <select> form (templates/players/
   player_profile.html) - label and select sit side by side on a wide
   viewport, wrap is set for a narrow one (max-width:560px below tightens
   it to a full-width stacked control, same breakpoint every other
   mobile fix in this file uses). .pl-season-share-row holds the "Per
   game · <season>" caption next to the season share button - wraps for
   the same reason rather than forcing the share button to shrink or
   overflow next to a long season name. */
.pl-season-picker {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}

.pl-season-share-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
}

@media (max-width: 560px) {
  .pl-season-picker .hl-select {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
  }
}

/* ─── History tab (season-by-season team history) ────────────────────
   Deliberately NOT base.css's `.hl-table--stack` - see
   players/player_profile.html's own comment on this table for why 9
   fields stacked as label:value lines per row read worse here than a
   real, shrunk-down grid. Unlike .pl-games-table below, this table's 9
   columns are narrow enough (3-letter stat abbreviations, one short
   digit column) to fit a phone width as an actual table once padding/
   font-size drop and the jersey-# column (least essential - "GP" through
   "BLK" are the actual history being asked for) is hidden, so no
   horizontal scroll or sticky column is needed at all. */
.pl-history-wrap {
  /* Safety net, not the primary mechanism - Season/Team below are
     truncated with an ellipsis specifically so this table fits without
     ever needing to scroll, but an unexpectedly long name (or a device
     narrower than any of these were tuned against) should still be
     reachable by touch-scroll rather than clipped outright. Hidden
     scrollbar (not base.css's default visible one) because a visible
     bar here would look like a mistake on a table that's designed to
     just fit - it isn't the "swipe through wider content" affordance
     .pl-games-table intentionally signals with its own visible scroll
     hint below. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.pl-history-wrap::-webkit-scrollbar {
  display: none;
}

/* Readability pass: season is the row's label (bold, never wraps), team
   names get room on desktop, PTS is the headline stat, and #/GP recede. */
.pl-history-table .pl-history-season {
  font-weight: 700;
  white-space: nowrap;
}
.pl-history-table .pl-history-team {
  min-width: 140px;
}
.pl-history-table .pl-history-muted {
  color: var(--text-muted);
}
.pl-history-table .pl-history-pts {
  font-weight: 700;
  color: var(--primary);
}
.pl-history-table tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--bg-raised) 40%, transparent);
}

/* Phones: 8 columns squeezed into ~360px forced 10-11px type and
   truncated Season/Team to a few characters. Each season row becomes a
   compact card instead - season + jersey on top, full team name under it,
   then a 6-tile stat grid (label above value, from each td's data-label).
   Bigger, readable type and nothing truncated, with no sideways scroll. */
@media (max-width: 640px) {
  .pl-history-wrap {
    border: none;
    border-radius: 0;
    overflow: visible;
  }
  .pl-history-table,
  .pl-history-table tbody {
    display: block;
    width: 100%;
  }
  .pl-history-table thead {
    display: none;
  }
  .pl-history-table tbody tr {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 0;
    margin-bottom: var(--space-3);
    padding: 12px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
  }
  .pl-history-table tbody tr:nth-child(even) {
    background: var(--bg-card);
  }
  .pl-history-table td {
    padding: 0;
    border: none;
    font-size: 15px;
  }
  .pl-history-table td:nth-child(1) {
    grid-column: 1 / 5;
    font-size: 16px;
  }
  .pl-history-table td:nth-child(3) {
    grid-column: 5 / 7;
    text-align: right;
  }
  .pl-history-table td:nth-child(3)::before {
    content: "#";
  }
  .pl-history-table td:nth-child(2) {
    grid-column: 1 / 7;
    margin: 2px 0 12px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
    font-size: 13px;
  }
  .pl-history-table td:nth-child(2) .hl-link {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
  }
  .pl-history-table td:nth-child(n+4) {
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: 3px;
    text-align: center;
  }
  .pl-history-table td:nth-child(n+4)::after {
    content: attr(data-label);
    font-family: var(--font-body);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.6px;
    color: var(--text-muted);
  }
}

/* ─── Games this season (Season tab) ─────────────────────────────────
   8-9 columns (Date/Opponent/Score/PTS/REB/AST/STL/BLK/Share) stay a
   real, horizontally-scrollable .hl-table on phones rather than
   switching to base.css's stacked-card layout (.hl-table--stack) - same
   call standings.css's .std-standings-table makes, for the same reason:
   stacking that many columns into label:value lines per game reads worse
   than a table you swipe through. The sticky Date column below is what
   makes that swipe usable - without it, scrolling right to see AST/STL/
   BLK would scroll away the one thing that tells you which game you're
   looking at; the W/L badge rides along in that same cell (see
   .pl-game-date-cell) so the result stays visible mid-scroll too. */
.pl-games-table th:first-child,
.pl-games-table td:first-child {
  position: sticky;
  left: 0;
  /* Forces both the header and every body row's sticky cell to the same
     width - set here on the actual table cell, NOT on a flex child inside
     the td (that was tried first and didn't work: table-layout:auto's
     column-width pass doesn't reliably pick up a flex descendant's
     min-width as the cell's intrinsic width, so the column was sized
     narrower than the sticky cell was actually rendering, and the
     sticky cell - painted in its own layer - visually overflowed onto
     the PTS/REB columns next to it instead of the column growing to fit). */
  min-width: 96px;
}

/* Without this, "Jul 29, 2026" wraps onto 3 cramped lines inside the
   column auto-layout assigns it, instead of the table just scrolling
   wider - same fix, same reasoning, as game_detail.css's
   `.gd-box-table td:first-child { white-space: nowrap; }` for its own
   sticky first column. */
.pl-games-table td:first-child {
  white-space: nowrap;
}

.pl-games-table th:first-child {
  z-index: 3;
}

.pl-games-table td:first-child {
  z-index: 2;
  background: var(--bg-card);
}

.pl-games-table tbody tr:hover td:first-child,
.pl-games-table tbody tr:focus-within td:first-child {
  background: var(--bg-raised);
}

/* Win/loss legibility, ported from team_detail.css's .td-games-table
   (see that file's own comment for the full reasoning: a leading W/L
   chip plus a faint full-row tint and a colored accent bar, keyed off
   `data-result` on the <tr>, reads at a glance instead of requiring the
   score to be parsed). The one difference here: this table's first
   column is the sticky Date cell (not a dedicated result column, to keep
   the column count down - see the section comment above), so the badge
   sits inside .pl-game-date-cell and the tint/accent bar target that
   same cell instead of a separate td:first-child. */
/* Flex lives on an inner span, NOT the <td> - display:flex on a td turns it
   into a block-level flex container that drops out of table layout, so its
   column stopped sizing like the others. */
.pl-game-date-inner {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.pl-game-result-badge {
  padding: 1px 6px;
  font-size: 10px;
}

.pl-games-table tbody tr[data-result="win"] {
  background: rgba(182, 226, 74, 0.06);
}

.pl-games-table tbody tr[data-result="loss"] {
  background: rgba(255, 90, 77, 0.05);
}

/* Opaque, NOT the same translucent rgba the row background above uses -
   this cell is `position: sticky` (see .pl-games-table td:first-child
   above), so during horizontal scroll the row's other cells slide
   underneath it. A translucent background lets them show through instead
   of being covered, which read as the Date column "having no background".
   color-mix against the solid --bg-card keeps the tint visible while
   staying fully opaque. */
.pl-games-table tbody tr[data-result="win"] td:first-child {
  background: color-mix(in srgb, var(--lime) 7%, var(--bg-card));
  box-shadow: inset 3px 0 0 var(--lime);
}

.pl-games-table tbody tr[data-result="loss"] td:first-child {
  background: color-mix(in srgb, var(--alert) 6%, var(--bg-card));
  box-shadow: inset 3px 0 0 var(--alert);
}

.pl-games-table tbody tr[data-result="win"]:hover td:first-child,
.pl-games-table tbody tr[data-result="win"]:focus-within td:first-child {
  background: color-mix(in srgb, var(--lime) 12%, var(--bg-raised));
}

.pl-games-table tbody tr[data-result="loss"]:hover td:first-child,
.pl-games-table tbody tr[data-result="loss"]:focus-within td:first-child {
  background: color-mix(in srgb, var(--alert) 12%, var(--bg-raised));
}

.pl-game-score-cell {
  font-weight: 700;
}

/* Long opponent names shouldn't force extra horizontal scroll on top of
   what the stat columns already need - clipped with an ellipsis (full
   name still reachable via the link's title="" attribute, see
   player_profile.html) rather than wrapping or stretching the column. */
.pl-game-opponent-cell {
  max-width: 160px;
}

.pl-game-opponent-cell .hl-link {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

@media (max-width: 640px) {
  .pl-game-opponent-cell {
    max-width: 96px;
  }
}

/* Games-this-season scrollbar: the browser default is a thin, low-contrast
   bar that's easy to miss (and hard to grab on touch) - since this table
   deliberately scrolls sideways instead of stacking, the bar is the main
   affordance, so it gets a taller, themed pill track/thumb that lights up
   on hover/drag. Firefox has no ::-webkit-scrollbar, so it gets the
   standard properties instead; Chromium 121+ ignores ::-webkit-scrollbar
   whenever scrollbar-color is set, hence the @supports split. */
@supports not selector(::-webkit-scrollbar) {
  .pl-games-wrap {
    scrollbar-width: auto;
    scrollbar-color: var(--border-strong) var(--bg-raised);
  }
}

.pl-games-wrap::-webkit-scrollbar {
  height: 10px;
}

.pl-games-wrap::-webkit-scrollbar-track {
  margin: 0 12px;
  background: var(--bg-raised);
  border-radius: 999px;
}

.pl-games-wrap::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border: 2px solid var(--bg-raised);
  border-radius: 999px;
}

.pl-games-wrap::-webkit-scrollbar-thumb:hover,
.pl-games-wrap::-webkit-scrollbar-thumb:active {
  background: var(--primary);
}

/* Same JS-measured-overflow hint as standings.css's .std-scroll-hint /
   game_detail.css's .gd-scroll-hint (static/js/scroll-table-hint.js
   toggles [data-visible] on whichever hints exist based on which
   .hl-table-wrap(s) actually overflow) - a long opponent name can force
   real overflow above 640px, and a short one may not force it below, so
   a fixed breakpoint would guess wrong either way. Sits above the table,
   not below it, for the same visibility reason standings.css's version
   does - see that file's comment. */
.pl-scroll-hint {
  display: none;
  align-items: center;
  gap: 6px;
  width: fit-content;
  margin: var(--space-2) 0 var(--space-3);
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-raised);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
}

.pl-scroll-hint[data-visible] {
  display: inline-flex;
}

/* No-JS fallback: same 640px breakpoint base.css's .hl-table--stack uses
   for its own responsive table handling. */
.no-js .pl-scroll-hint {
  display: none;
}

@media (max-width: 640px) {
  .no-js .pl-scroll-hint {
    display: inline-flex;
  }
}

/* ─── Awards tab ──────────────────────────────────────────────────────
   Was a flex column of separate bordered cards, one per award. This
   page's own .pl-metric-card (above) already made the "one bordered
   panel, hairline-divided rows inside" move for its stat rows - the
   awards tab gets the same treatment now instead of being the one list
   on the page still built from N separate boxes. */
.pl-award-panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}

.pl-award-row {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  padding: var(--space-4);
  border-bottom: 1px solid var(--border);
}

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

.pl-award-icon {
  width: 44px;
  height: 44px;
  border-radius: 10px;
  overflow: hidden;
  flex-shrink: 0;
  background: var(--bg-raised);
}

.pl-award-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pl-award-season {
  font-size: 10px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin-bottom: 3px;
}

.pl-award-title {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 16px;
  text-transform: uppercase;
  margin-bottom: 4px;
}

/* ─── Analytics tab (career efficiency rating + per-36 rates) ────────
   Same "one bordered panel of stat rows" language as .pl-metric-card,
   plus a single hero number the way stat_leaders.css's podium leads with
   its #1 - the composite rating is the one number worth a headline size,
   the per-36 bars below it are supporting detail, not co-equal rows. */
.pl-eff-hero {
  text-align: center;
  padding: var(--space-4) 0;
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--border);
}

.pl-eff-lab {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}

.pl-eff-num {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 56px;
  line-height: 1;
  color: var(--primary);
  margin: 6px 0 0;
}

.pl-eff-sub {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 10px;
}

/* Marks a subsection heading that starts a new Analytics-tab block (Last 5
   Games vs. Career Average, Team Record This Season) rather than the first
   one (Per 36 Minutes, which already gets its top gap from .pl-eff-hero's
   own border-bottom) - without this, a block ending in .pl-eff-strip (no
   bottom margin of its own - see below) runs straight into the next
   heading with zero gap between them. */
.pl-eff-section {
  margin-top: var(--space-6);
}

.pl-eff-bars {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.pl-eff-bar-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.pl-eff-bar-lab {
  width: 36px;
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-secondary);
}

.pl-eff-bar-track {
  flex: 1;
  height: 10px;
  background: var(--bg-card);
  border-radius: 3px;
  overflow: hidden;
}

.pl-eff-bar-fill {
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--orange-deep), var(--primary));
}

.pl-eff-bar-val {
  width: 44px;
  flex-shrink: 0;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  color: var(--text);
}

/* Last 5 Games vs. Career Average - a diverging (centered-zero) bar per
   stat, not a plain +/- badge: the job here is polarity + magnitude of
   CHANGE, so the bar encodes `delta`, growing right (lime, "hot") or left
   (alert, "cold") from a fixed center line - same lime/alert pair
   .hl-badge[data-status="win"/"loss"] already use elsewhere on this page,
   reused here for consistency rather than a new color pair. Bar length is
   `delta_pct`, pre-scaled server-side to the largest |delta| across all 5
   categories (apps.players.views._hot_cold_trend) so rows are comparable
   at a glance. The delta figure stays a visible signed number beside the
   bar (never color-alone), and pl-trend-caption spells out every
   category's exact career average as plain text below - no hover-only
   data, this site is mobile-first. */
.pl-trend-bars {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-bottom: var(--space-4);
}

.pl-trend-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.pl-trend-lab {
  width: 30px;
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-secondary);
}

.pl-trend-recent {
  width: 32px;
  flex-shrink: 0;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  color: var(--text);
}

.pl-trend-track {
  display: flex;
  align-items: center;
  flex: 1;
  height: 10px;
}

.pl-trend-half {
  flex: 1;
  height: 100%;
  background: var(--bg-card);
  overflow: hidden;
}

.pl-trend-half--neg {
  display: flex;
  justify-content: flex-end;
  border-radius: 3px 0 0 3px;
}

.pl-trend-half--pos {
  border-radius: 0 3px 3px 0;
}

.pl-trend-mid {
  width: 2px;
  height: 14px;
  flex-shrink: 0;
  background: var(--border-strong);
}

.pl-trend-fill {
  height: 100%;
}

.pl-trend-fill.is-up {
  background: var(--lime);
  border-radius: 0 3px 3px 0;
}

.pl-trend-fill.is-down {
  background: var(--alert);
  border-radius: 3px 0 0 3px;
}

.pl-trend-delta {
  width: 46px;
  flex-shrink: 0;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
}

.pl-trend-delta.is-up {
  color: var(--lime);
}

.pl-trend-delta.is-down {
  color: var(--alert);
}

.pl-trend-caption {
  margin: 0 0 var(--space-4);
}

/* Per-row career average: hidden on wide screens (the bar + delta already
   carry it, and the tooltip has the exact figure), shown on phones where
   the row is re-laid out below. */
.pl-trend-career {
  display: none;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
}

/* Phones: the single-line row squeezed label + value + bar + delta into
   ~300px, leaving a bar barely 100px wide at 10px tall. Two lines instead:
   stat / last-5 / career avg / delta on top, a full-width, taller bar
   underneath. The career average is now readable per row rather than a
   run-on caption. */
@media (max-width: 560px) {
  .pl-trend-bars {
    gap: 14px;
  }
  .pl-trend-row {
    display: grid;
    grid-template-columns: 36px auto 1fr auto;
    grid-template-areas:
      "lab recent career delta"
      "track track track track";
    align-items: baseline;
    column-gap: 8px;
    row-gap: 6px;
  }
  .pl-trend-lab {
    grid-area: lab;
    width: auto;
    font-size: 12px;
  }
  .pl-trend-recent {
    grid-area: recent;
    width: auto;
    font-size: 14px;
  }
  .pl-trend-career {
    grid-area: career;
    display: block;
  }
  .pl-trend-delta {
    grid-area: delta;
    width: auto;
    font-size: 13px;
  }
  .pl-trend-track {
    grid-area: track;
    height: 12px;
  }
  .pl-trend-half {
    height: 12px;
  }
  .pl-trend-mid {
    height: 18px;
  }
}

/* Few-games fallback (<= 5 career games): a compact Recent Games peek (one
   line per game) plus a Career Highs row under the True Shooting strip, shown
   in place of the Last 5 vs. Career trend
   (apps.players.views._few_games_fallback). */
.pl-fg-log {
  display: flex;
  flex-direction: column;
}

.pl-fg-row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "opp date"
    "line line";
  align-items: center;
  column-gap: 8px;
  row-gap: 2px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}

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

.pl-fg-opp {
  grid-area: opp;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.pl-fg-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-fg-date {
  grid-area: date;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
}

.pl-fg-line {
  grid-area: line;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
}

/* Wide screens: everything on one line. */
@media (min-width: 561px) {
  .pl-fg-row {
    grid-template-columns: 1fr auto auto;
    grid-template-areas: "opp line date";
    column-gap: 16px;
  }
}

.pl-fg-foot {
  margin: var(--space-2) 0 0;
}

.pl-fg-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--primary);
  text-decoration: underline;
  cursor: pointer;
}

.pl-fg-highs {
  margin-top: var(--space-3);
}

.pl-eff-strip {
  display: flex;
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* Team Record This Season reuses .pl-eff-strip for its with/without pair,
   but unlike the True Shooting/Min-per-Game strip (which follows the Per
   36 bars with no heading of its own in between, so it needs its own
   divider), this one sits directly under a .pl-eff-section heading that
   already draws the section boundary - the plain divider on top of that
   read as a redundant rule between the heading and its own content. */
.pl-eff-strip--plain {
  margin-top: var(--space-3);
  padding-top: 0;
  border-top: none;
}

.pl-eff-strip div {
  flex: 1;
  text-align: center;
}

.pl-eff-strip .v {
  display: block;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 15px;
  color: var(--text);
}

.pl-eff-strip .l {
  display: block;
  font-size: 9.5px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-top: 2px;
}

@media (max-width: 560px) {
  /* No background override here on purpose (unlike the flat, evenly-mixed
     tint this used to swap in) - stacked via column-reverse, the desktop
     gradient's 65% stop is spent behind the now-much-taller photo/motif
     block (itself opaque, .pl-hero-right--motif's own background or the
     photo) before it ever reaches the name/stats panel below, so that
     panel reads as plain --bg-card with the team color carried entirely
     by its text (position badge, jersey border, team label, divider
     line) - matching the reference mobile design, where the card itself
     stays flat and only the text/accents pick up the team color. */
  .pl-hero {
    flex-direction: column-reverse;
  }

  .pl-hero-right {
    flex: 0 0 auto;
    aspect-ratio: 4 / 5;
    min-height: 0;
  }

  .pl-hero-gradient {
    background: linear-gradient(to top, var(--bg-card) 0%, transparent 55%);
  }

  .pl-name {
    font-size: 26px;
  }

  .pl-bio-card {
    flex-wrap: wrap;
  }

  .pl-bio-item {
    flex: 1 1 40%;
  }
}

/* ─── Share buttons (templates/players/_share_button.html) - the group
   floats in .pl-hero's top-right corner, over the photo/motif. User
   request: match League Home's merged-pill share treatment
   (.lh-masthead-actions in static/css/league_home.css) - one seamless
   transparent pill with a hairline divider between the two actions,
   not two separately-bordered circular buttons. Two differences from
   that masthead version, both because this floats over an arbitrary
   uploaded player photo instead of one fixed, known gradient: the pill
   keeps a blurred/tinted glass background (backdrop-filter, same trick
   this used before) rather than going fully transparent, and each icon
   keeps a soft drop-shadow on top of that - a photo's own brightness/
   color is unpredictable in a way the masthead's gradient never was, so
   legibility gets both a background AND a per-icon fallback instead of
   relying on just one.

   Bug fix (same one fixed in .lh-masthead-actions/stat_leaders.css's
   .stl-pane-head .hl-share-group): no `overflow: hidden` on this
   container - templates/players/_share_button.html's .pl-share-popover
   is `position: absolute` and a descendant of it, so overflow: hidden
   was silently clipping the popover to this small pill's own box
   whenever the image-share button fell back to it (no navigator.share
   file support) - reproduced live: the popover computed a real size/
   position but painted nowhere, so clicking the button looked like it
   did nothing. Fixed by rounding the outer corners of the two buttons
   directly instead of clipping the whole container. ──────────────────── */
.pl-share-group {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 0;
  border: 1px solid rgba(245, 241, 236, 0.35);
  border-radius: 999px;
  padding: 3px;
  background: color-mix(in srgb, var(--bg-card) 45%, transparent);
  backdrop-filter: blur(6px);
}

.pl-share:first-child .pl-share-btn {
  border-radius: 999px 0 0 999px;
}

.pl-share:last-child .pl-share-btn {
  border-radius: 0 999px 999px 0;
}

.pl-share {
  position: relative;
}

/* One divider between EVERY adjacent pair, not just after the first -
   `:first-child::after` alone (this rule's original shape) only ever
   drew one line, which was correct back when .pl-share-group had
   exactly two buttons but left the 2nd/3rd pair unseparated the moment
   the "Card design studio" button (templates/players/_share_button.html)
   became a third .pl-share sibling. `:not(:last-child)` scales to any
   button count instead of hardcoding "2". */
.pl-share:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  width: 1px;
  height: 16px;
  background: rgba(245, 241, 236, 0.3);
}

.pl-share-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  border: none;
  background: transparent;
  color: var(--text);
  border-radius: 0;
  padding: 7px 9px;
  line-height: 0;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
  transition: background-color 0.15s ease, color 0.15s ease;
}

.pl-share-btn svg {
  width: 18px;
  height: 18px;
}

.pl-share-btn:hover {
  background: rgba(245, 241, 236, 0.16);
  color: var(--primary);
}

.pl-share-popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 250px;
  max-width: 280px;
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: 0 12px 28px -10px rgba(0, 0, 0, 0.6);
  padding: var(--space-2);
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.pl-share-option {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 8px;
  border: none;
  background: none;
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  text-align: left;
  width: 100%;
}

.pl-share-option-inner {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.pl-share-option:hover {
  background: var(--bg-raised);
  color: var(--primary);
}

.pl-share-option svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.pl-share-hint {
  flex-basis: 100%;
  padding-left: 26px;
  color: var(--text-muted);
  font-weight: 500;
  white-space: normal;
}

@media (max-width: 560px) {
  .pl-share-group {
    top: var(--space-2);
    right: var(--space-2);
  }

  /* Ported from static/css/share.css's identical fix for .hl-share-popover
     - see that file's comment for the full reasoning: `right: 0`-anchored
     under a trigger that sits near the screen edge (this group is
     top-right of .pl-hero on phones) runs the popover off the LEFT edge
     of the viewport. Fixed + centered on the viewport sidesteps it
     regardless of where the trigger ends up. */
  .pl-share-popover {
    position: fixed;
    top: 50%;
    left: 50%;
    right: auto;
    transform: translate(-50%, -50%);
    width: calc(100vw - 48px);
    max-width: 320px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    z-index: 50;
  }
}

/* ─── Card picker modal body (templates/players/_card_picker_body.html) ───
   "Choose your card" - opened by the image-share button in .pl-share-group
   above via core/_modal.html (uid="player-card-picker", size="lg"). A
   horizontally-scrolling, snap-scrolling rail of every
   apps.core.share_cards.PLAYER_CARD_TEMPLATES style shown as its own real
   1200x630 preview - the whole point of the "Fuller redesign" over the old
   popover was showing every style at once instead of one live preview you
   had to already know to swap. Selecting a card only changes which one
   feeds the Download/Share actions below - the images themselves never
   change src. ──────────────────────────────────────────────────────────── */
.pl-cardpicker {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* Portrait/Landscape tabs (templates/players/_card_picker_body.html,
   only rendered when this league's unlocked catalog actually has both) -
   deliberately mirrors static/css/player_card_studio.css's
   .pcs-orientation rule-for-rule so this picker's own orientation switch
   reads as the same control family as the Card design studio's, not a
   different widget for what's conceptually the same choice. */
.pl-cardpicker-orientation {
  /* align-self: flex-start - .pl-cardpicker (above) is a column flex
     container, so without this a plain inline-flex child gets blockified
     into a flex item and stretched by the parent's default
     align-items:stretch to the FULL width of the modal - a wide pill
     with both button labels bunched at the left and a large dead empty
     strip to the right of "Portrait". This keeps the pill sized to its
     own content (two buttons) instead, on both mobile and desktop. */
  align-self: flex-start;
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
}
.pl-cardpicker-orientation button {
  font: 600 12.5px/1 var(--font-body);
  color: var(--text-muted);
  background: transparent;
  border: none;
  border-radius: 999px;
  padding: 8px 18px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.pl-cardpicker-orientation button:hover { color: var(--text); }
.pl-cardpicker-orientation button.is-active { background: var(--bg-card); color: var(--text); box-shadow: 0 0 0 1px var(--border-strong); }

.pl-cardpicker-rail {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  /* Top/side clearance isn't cosmetic - .pl-cardpicker-card lifts
     (translateY) and grows a box-shadow on hover, both of which would get
     clipped flush against this rail's own overflow-x:auto edge without
     it. Bottom gets a bit more room for the scrollbar below. */
  padding: var(--space-2) var(--space-1) var(--space-3);
  margin: calc(var(--space-2) * -1) calc(var(--space-1) * -1) 0;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

.pl-cardpicker-rail::-webkit-scrollbar {
  height: 6px;
}

.pl-cardpicker-rail::-webkit-scrollbar-track {
  background: transparent;
}

.pl-cardpicker-rail::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 999px;
}

.pl-cardpicker-rail::-webkit-scrollbar-thumb:hover {
  background: var(--primary);
}

.pl-cardpicker-card {
  /* No explicit width - sized by its content, same as static/css/
     player_card_studio.css's own .pcs-rail-card. .pl-cardpicker-img-wrap
     below carries the actual fixed per-orientation dimensions (matching
     .pcs-rail-thumb--portrait/--landscape px-for-px) so a landscape and a
     portrait tile read as the same control family across both modals
     instead of one sharing a single width with the other and letting
     aspect-ratio alone swing the height wildly (previously ~300x157
     landscape vs ~300x533 portrait). */
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-raised);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.pl-cardpicker-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px -12px rgba(0, 0, 0, 0.55);
}

.pl-cardpicker-card:active {
  transform: translateY(-2px) scale(0.98);
}

.pl-cardpicker-card:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.pl-cardpicker-card.is-selected {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 10%, var(--bg-raised));
}

/* Entrance stagger the moment the dialog opens - same "settle, then the
   contents cascade in a beat later" choreography as static/css/
   stat_leaders.css's podium/list reveal. core/_modal.html's own
   .hl-modal-panel animation runs 0.05s-0.33s; these pick up mid-way
   through that so the cards feel like part of one continuous motion
   instead of a separate, late second animation. Re-triggers on every
   reopen because [open] toggling on/off re-inserts the animation the
   same way .stl-pane.is-active does for its own children. */
@media (prefers-reduced-motion: no-preference) {
  dialog[open] .pl-cardpicker-card {
    animation: hl-cardpicker-card-in 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
  }
  dialog[open] .pl-cardpicker-card:nth-child(1) { animation-delay: 0.12s; }
  dialog[open] .pl-cardpicker-card:nth-child(2) { animation-delay: 0.18s; }
  dialog[open] .pl-cardpicker-card:nth-child(3) { animation-delay: 0.24s; }
  dialog[open] .pl-cardpicker-card:nth-child(n+4) { animation-delay: 0.3s; }
}

@keyframes hl-cardpicker-card-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Fixed per-orientation px, same values as static/css/
   player_card_studio.css's .pcs-rail-thumb--landscape (240x126) - kept in
   lockstep so a tile reads as the same size switching between this modal
   and Card Design Studio, even though this one holds a plain <img> (a
   real server-rendered PNG) rather than a live-scaled .pcs-card. */
.pl-cardpicker-img-wrap {
  position: relative;
  display: block;
  width: 240px;
  height: 126px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-card);
}

/* Card Design Studio ports at ShareCardTemplate.orientation="portrait"
   (apps.core.share_cards' 1080x1920 renderers, same canvas as
   render_player_story_card) - _card_picker_body.html applies this
   modifier per-card off that field so a portrait tile sizes correctly
   inside the otherwise-landscape .pl-cardpicker-rail; the rail itself
   needs no other change; a taller flex item in a horizontally-scrolling
   row is not a broken layout, just a taller tile. Same modifier-class
   shape as static/css/share.css's .hl-cardpicker-img-wrap--portrait for
   the other gallery. Fixed px matches .pcs-rail-thumb--portrait
   (176x313) for the same cross-modal-consistency reason as the base
   rule above. */
.pl-cardpicker-img-wrap--portrait {
  width: 176px;
  height: 313px;
}

/* Skeleton shimmer while the PNG is still rendering server-side
   (apps.core.share_cards runs on every request, never instant) - the img
   fades in over it once loaded instead of popping in abruptly or sitting
   blank. Mirrors static/css/share.css's identical .hl-share-preview-img-wrap
   treatment for the single-image variant of this same modal pattern. */
@media (prefers-reduced-motion: no-preference) {
  .pl-cardpicker-img-wrap:has(img:not(.is-loaded))::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(100deg, var(--bg-card) 30%, var(--bg-raised) 50%, var(--bg-card) 70%);
    background-size: 200% 100%;
    animation: hl-img-skeleton 1.4s ease-in-out infinite;
  }
}

@keyframes hl-img-skeleton {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.pl-cardpicker-img-wrap img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.pl-cardpicker-img-wrap img.is-loaded {
  opacity: 1;
}

.pl-cardpicker-check {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--primary);
  color: var(--bg);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.pl-cardpicker-name {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
}

.pl-cardpicker-hint {
  font-size: 12px;
  color: var(--text-muted);
}

.pl-cardpicker-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.pl-cardpicker-actions .hl-btn {
  flex: 1 1 auto;
  justify-content: center;
}

/* ─── "For Stories" row (templates/players/_card_picker_body.html) ────
   The 1080x1920 story card's own row below the landscape rail - see that
   template's comment for why it's not a 5th rail card. */
.pl-cardpicker-story {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.pl-cardpicker-story-preview {
  display: block;
  flex-shrink: 0;
  width: 64px;
  aspect-ratio: 1080 / 1920;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-card);
  border: 1px solid var(--border);
}

.pl-cardpicker-story-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pl-cardpicker-story-info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.pl-cardpicker-story-info .hl-btn {
  margin-top: 4px;
}

/* No mobile-width override here anymore - .pl-cardpicker-card is sized
   by its fixed-px .pl-cardpicker-img-wrap content now, same as
   static/css/player_card_studio.css's .pcs-rail-card/.pcs-rail-thumb,
   which has no mobile breakpoint of its own either. */
