/*
 * The player page — the way out, the hero, and the cards that describe
 * one footballer. Structurally the competition page's layout
 * (league-details.css), which is itself the club page's: same two-layer
 * fade/rise shell, same bar, same scrolling body.
 *
 * What is new here is `.player-card`, the block every card on this page is
 * built from. It is deliberately not `.club-overview__card`: that class belongs
 * to one tab of another page and carries that tab's vertical compression, and a
 * page reaching across into another page's stylesheet is how two surfaces end
 * up unable to change independently. The values it shares with it — surface,
 * hairline, radius, card shadow — are tokens, which is the sharing that was
 * meant to happen.
 */

/*
 * One knob for the whole page. Every length in this file and in the six
 * player-*.css sheets beside it is a token multiplied by this, so the page
 * scales as a unit and the tokens stay the source of truth — the same
 * arrangement `--fixture-scale` gives the fixtures list (league-accordion.css)
 * and `--header-scale` gives the fixture page's score block.
 *
 * 0.915, settled on by eye: up from the 0.7 it first shipped at, by way of 0.9
 * and a pass at full token size that was a touch too loose. At 0.7 the type
 * came down but the cards did not — a card is always the page's width — so
 * every card read as a wide, flat box with small print in it: a hexagon 80px
 * tall floating in a 340px card, a pitch the size of a stamp. The reference
 * runs its player page at close to token size, and what makes its cards look
 * *proportioned* is that the graphics are drawn tall and the type is large
 * enough to sit against them. The knob is what makes this tuning cheap: every
 * length here is `token × scale`, so a step either way is one number rather
 * than seven files.
 *
 * What it deliberately does *not* touch: radii, hairline borders and SVG stroke
 * widths, none of which the fixtures list scales either — a 0.7px border is a
 * blurry one, and a card at a smaller radius stops matching the cards on every
 * other screen. Nor the bar at the top, which is chrome shared with the club and
 * competition pages: a back button two pixels smaller here than there would be
 * this page's density leaking out of the content it belongs to.
 *
 * The image sizes written as inline styles from JavaScript are the one set of
 * lengths outside it — `Logo()` and `Avatar()` write those boxes themselves — so
 * they are tuned by hand and move with this value, the same way BADGE_SIZE and
 * --header-scale move together on the fixture page.
 */
.player-details,
.player-card {
  --player-scale: 0.915;
}

/*
 * The page wears the club's colour wherever the rest of the app wears the
 * accent. `--club-rgb` is sampled from the crest at runtime (core/team-colours.js)
 * and falls back to the accent itself, so a player with no crest behind them
 * gets the app's own blue and every other player gets theirs — on the pressed
 * chips, the Career tabs, the position marker, the follow star, the player of
 * the match rating. Re-pointing the tokens here rather than restyling each
 * control is what keeps this to one rule: the controls keep saying `--accent`,
 * and inside this page that word means the club.
 *
 * Each derived token is restated, not just `--accent-rgb`: a custom property
 * is resolved where it is declared, so `--accent-soft` computed on `:root` has
 * already baked the root's blue in and would not follow a change made here.
 * The portrait ring, the shirt badge, the traits shape, the value line and the
 * shot markers read `--club-rgb` directly and were already the club's.
 */
.player-details {
  --accent-rgb: var(--club-rgb);
  --accent: rgb(var(--club-rgb));
  --accent-soft: rgb(var(--club-rgb) / 0.1);
  --shadow-accent: 0 6px 18px -8px rgb(var(--club-rgb) / 0.55);
  /* The focus ring too: the back button is focused the moment the page opens,
     and a blue ring beside a claret band is the one thing on screen still
     wearing the app's colour rather than the club's. */
  --ring: rgb(var(--club-rgb));
}

.player-details {
  --enter-y: 12px;

  position: fixed;
  inset: 0;
  /*
   * Above the competition page's 97, the club page's 95, the search overlay's
   * 90 and the fixture page's 80 — this opens from all four, so a surface
   * opened from any of them has to win. Below the story viewer's 100, which
   * covers the strip it opens from and so cannot be up at the same time.
   */
  z-index: 98;
  display: flex;
  max-width: var(--app-max-width);
  margin-inline: auto;
  background: var(--bg);
  opacity: 0;
  transition: opacity var(--duration) var(--easing);
}

.player-details[hidden] {
  display: none;
}

.player-details[data-open="true"] {
  --enter-y: 0px;

  opacity: 1;
}

.player-details__sheet {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding-top: var(--safe-top);
  transform: translateY(var(--enter-y));
  transition: transform var(--duration) var(--easing);
}

/* --- Bar -------------------------------------------------------------------- */

/*
 * The app bar (app-bar.css) with this page's spacing above it. The card, the
 * wordmark and the 34px controls are the bar's own; this page adds nothing to
 * them but the room at the top, which is the same room the home header gives
 * itself.
 */
.player-details__bar {
  flex: none;
  margin-top: var(--space-4);
}

/*
 * The star, filled and tinted when this player is followed. It writes the same
 * slice the Squad tab's own star does (see PlayerDetails.js), so it wears the
 * same glyph — a bell here would be a second mark for one fact. Inside the app
 * bar the chip's own fill and border are already stripped, so the pressed
 * state is the one time it earns a fill back — the same deal the home bar's
 * filter button makes when it has filters on.
 */
.player-details__follow {
  transition:
    background-color var(--duration) var(--easing),
    color var(--duration) var(--easing);
}

.player-details__follow[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--accent);
}

/* --- Body ------------------------------------------------------------------- */

.player-details__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  /* Not scaled: the gutter between cards is the page's, not the content's, and
     it matches the card's own side margin so the cards sit on a grid. */
  gap: var(--space-4);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 0 var(--space-8);
}

/* --- The card every section is built from ----------------------------------- */

.player-card {
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-4) * var(--player-scale));
  margin-inline: var(--space-4);
  padding: calc(var(--space-4) * var(--player-scale)) calc(var(--space-5) * var(--player-scale));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

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

.player-card__header {
  display: flex;
  flex-direction: column;
  gap: calc(2px * var(--player-scale));
}

.player-card__title {
  font-size: calc(var(--text-base) * var(--player-scale));
  font-weight: var(--weight-bold);
  color: var(--text);
}

.player-card__subtitle {
  font-size: calc(var(--text-sm) * var(--player-scale));
  font-weight: var(--weight-medium);
  color: var(--text-muted);
}

.player-card__subtitle[hidden] {
  display: none;
}

.player-card__empty {
  padding-block: calc(var(--space-4) * var(--player-scale));
  text-align: center;
  font-size: calc(var(--text-sm) * var(--player-scale));
  color: var(--text-muted);
}

.player-card__empty[hidden] {
  display: none;
}

/* --- The summary card ------------------------------------------------------
 *
 * The bio (hero and fact tiles) and Position in one card, divided by a
 * hairline — see the note in PlayerDetails.js for why they are one and not two.
 *
 * The card carries no padding of its own: each section pads itself, so the
 * hairline between them runs the card's full width rather than stopping at an
 * inset. `flex: none` because the card is a flex item in a scroller, and the
 * scroller must never be allowed to squash the page's first card.
 */
.player-summary {
  flex: none;
  gap: 0;
  padding: 0;
}

.player-summary > * + * {
  border-top: 1px solid var(--border);
}

/* --- Bio: the hero and the fact tiles --------------------------------------- */

/*
 * No band. The card's own surface, with the club's colour — sampled from its
 * crest at runtime (core/team-colours.js), the accent when there is no crest —
 * worn as a ring around the portrait and as the shirt badge hung on its corner.
 * A slab of sampled colour behind the name meant white type on a colour nobody
 * chose, checked against a luminance cap so that it stayed legible, and every
 * player page opening on the same shape in a different paint. Type on the
 * surface needs no such check, and the colour goes where the identity is.
 */
.player-bio {
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-4) * var(--player-scale));
  padding: calc(var(--space-5) * var(--player-scale)) calc(var(--space-5) * var(--player-scale))
    calc(var(--space-4) * var(--player-scale));
}

.player-hero {
  display: flex;
  align-items: center;
  gap: calc(var(--space-4) * var(--player-scale));
}

/*
 * The ring. A 2px stroke with a 3px gap of surface between it and the photo —
 * the story strip's own treatment, which is what a ring around a face already
 * means in this app. The gap is what keeps it a ring rather than a border: a
 * stroke flush against the photo reads as the photo's edge.
 */
.player-hero__portrait {
  position: relative;
  flex: none;
  padding: 3px;
  border: 2px solid rgb(var(--club-rgb));
  border-radius: var(--radius-full);
  transition: border-color var(--duration) var(--easing);
}

.player-hero__photo {
  display: flex;
}

.player-hero__photo .logo {
  border-radius: var(--radius-full);
  background: var(--surface-sunken);
  object-fit: cover;
}

/* The monogram a player with no photo gets is sized for a 22px disc; at 84 it
   is a speck in a plate, so the letters come up to the portrait. */
.player-hero__photo .logo--monogram {
  font-size: calc(var(--text-xl) * var(--player-scale));
}

/*
 * The shirt number, hung on the ring's bottom-right corner the way the match
 * player sheet hangs a rating on its portrait. Bordered in the surface so it
 * reads as sitting *on* the ring rather than cut into it.
 */
.player-hero__number {
  position: absolute;
  right: calc(-4px * var(--player-scale));
  bottom: calc(-4px * var(--player-scale));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: calc(28px * var(--player-scale));
  height: calc(28px * var(--player-scale));
  padding-inline: calc(var(--space-2) * var(--player-scale));
  background: rgb(var(--club-rgb));
  border: 2px solid var(--surface);
  border-radius: var(--radius-full);
  color: var(--text-inverse);
  font-size: calc(var(--text-xs) * var(--player-scale));
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  transition: background-color var(--duration) var(--easing);
}

.player-hero__number[hidden] {
  display: none;
}

.player-hero__text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--space-2) * var(--player-scale));
}

/*
 * The name is the page's title and is set like one: the largest type on the
 * page, tight, and wrapping rather than truncating — "Borussia Mönchengla…" at
 * the top of that club's own page has told you less than the crest beside it
 * did, and the same goes for a surname.
 */
.player-hero__name {
  font-size: calc(var(--text-2xl) * var(--player-scale));
  font-weight: var(--weight-bold);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text);
  text-wrap: balance;
}

/*
 * The club, as a chip: crest, name, and a chevron when there is a page to
 * open. A filled chip is what "this is a thing you can go to" looks like in
 * this app, and it is the one control in the hero, so it is the one thing
 * drawn as one. Disabled — a preview club with no page — keeps the fill and
 * loses the chevron and the press, so the hero does not change shape depending
 * on where the page was opened from.
 */
.player-hero__club {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--space-2) * var(--player-scale));
  max-width: 100%;
  min-width: 0;
  padding: calc(3px * var(--player-scale)) calc(var(--space-3) * var(--player-scale))
    calc(3px * var(--player-scale)) calc(3px * var(--player-scale));
  background: var(--surface-sunken);
  border-radius: var(--radius-full);
  color: var(--text);
  font-size: calc(var(--text-sm) * var(--player-scale));
  font-weight: var(--weight-semibold);
  text-align: left;
}

.player-hero__club:disabled {
  cursor: default;
}

.player-hero__crest {
  flex: none;
  display: flex;
}

.player-hero__arrow {
  flex: none;
  display: flex;
  margin-left: calc(-2px * var(--player-scale));
  color: var(--text-muted);
}

.player-hero__arrow[hidden] {
  display: none;
}

/*
 * Position · flag country, and an injury when there is one. Descriptive text,
 * not a control. Wraps rather than truncates: the country is the one part that
 * can be long, and it gets the second line rather than an ellipsis.
 */
.player-hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--space-2) * var(--player-scale));
  min-width: 0;
  max-width: 100%;
  color: var(--text-secondary);
  font-size: calc(var(--text-sm) * var(--player-scale));
  font-weight: var(--weight-medium);
}

.player-hero__position {
  flex: none;
}

.player-hero__dot {
  flex: none;
  width: 3px;
  height: 3px;
  background: var(--text-muted);
  border-radius: var(--radius-full);
}

.player-hero__flag {
  flex: none;
  display: flex;
}

.player-hero__flag[hidden] {
  display: none;
}

.player-hero__country {
  min-width: 0;
}

/* The one fact that comes and goes, as a pill in the injury colour rather than
   a tile that would leave a hole in the grid when it is absent. */
.player-hero__status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px calc(var(--space-2) * var(--player-scale));
  background: var(--injury-soft);
  border-radius: var(--radius-full);
  color: var(--injury);
  font-size: calc(var(--text-2xs) * var(--player-scale));
  font-weight: var(--weight-semibold);
}

.player-hero__status[hidden] {
  display: none;
}

/*
 * Six facts, three across. Sunken tiles rather than ruled rows: a rule under
 * each fact drew a table, and a table is for comparing rows against each
 * other, which these are not. A tile is a fact on its own, and six of them in
 * a grid read as a set — the same treatment the season card's figures get,
 * with a fill because these sit directly under the hero and need an edge the
 * bare figures do not.
 */
.player-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: calc(var(--space-2) * var(--player-scale));
}

.player-facts__tile {
  display: flex;
  flex-direction: column;
  gap: calc(2px * var(--player-scale));
  min-width: 0;
  padding: calc(var(--space-2) * var(--player-scale)) calc(var(--space-3) * var(--player-scale));
  background: var(--surface-sunken);
  border-radius: var(--radius-md);
}

.player-facts__value {
  font-size: calc(var(--text-md) * var(--player-scale));
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.player-facts__label {
  font-size: calc(var(--text-xs) * var(--player-scale));
  color: var(--text-muted);
}

/* --- Position --------------------------------------------------------------- */

/*
 * The text sits at the top, beside the pitch rather than centred against it.
 *
 * Centred was the right call while this was a card of its own — the leftover
 * room read as breathing rather than as a hole. Inside the summary card it is
 * not: "Position" is a section heading, and a heading that floats in the middle
 * of its section while every other title on the page sits at the top of one is
 * the odd one out. The room under the words is the pitch's, and the card
 * carries on past it either way.
 */
.player-position {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(var(--space-4) * var(--player-scale));
  padding: calc(var(--space-4) * var(--player-scale)) calc(var(--space-5) * var(--player-scale));
}

.player-position__text {
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-1) * var(--player-scale));
}

.player-position__primary {
  margin-top: calc(var(--space-2) * var(--player-scale));
  font-size: calc(var(--text-sm) * var(--player-scale));
  font-weight: var(--weight-semibold);
  color: var(--accent);
}

.player-position__role {
  font-size: calc(var(--text-md) * var(--player-scale));
  font-weight: var(--weight-medium);
  color: var(--text);
}

/*
 * The pitch. `PitchMarkings` stretches its model to whatever box it is given
 * (`preserveAspectRatio: none`), so the aspect ratio here is what decides how
 * true to scale it ends up — the same knob match-lineup.css and season-stats.css
 * turn. 2:3 is a little squarer than a real pitch's 68:105, which is what keeps
 * the marker legible in the width a card can spare.
 */
.player-position__pitch {
  position: relative;
  flex: none;
  /*
   * Sized from its *height*, not its width, and not from --player-scale — see
   * the note on --graphic-width in player-performance.css for why no graphic on
   * this page rides the scale.
   *
   * The height is what needed pinning: this card holds three short lines of type
   * beside a portrait pitch, so the pitch decides how tall the card is. 184 is
   * what the reference gives it, and the width follows from the ratio rather
   * than being chosen separately.
   *
   * 3:4 is a mild stretch on a real pitch's 68:105 — `PitchMarkings` stretches
   * its model to whatever box it is given, so the box's ratio is the only thing
   * deciding how true to scale the drawing comes out. Mild and deliberate is the
   * same call match-lineup.css and season-stats.css document making for the two
   * other pitches in the app; a true 2:3 box here is accurate and too tall to
   * put on this card.
   */
  height: 184px;
  aspect-ratio: 3 / 4;
  background: var(--pitch);
  /* The card's inner radius. This pitch is a diagram inset in a card, not a
     photograph of a ground, and a sharp-cornered block inside a rounded card is
     the one shape on the page that matches nothing around it — the heatmap
     below makes the same call. `overflow` still clips the marker when a
     position sits hard against an edge. */
  border-radius: var(--radius-md);
  overflow: hidden;
}

.player-position__pitch-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/*
 * White markings on the grey ground rather than the darker `--pitch-line`: at
 * this size a dark line is a wire drawing and a light one is chalk, and chalk is
 * what reads as a pitch. The two pitches on this page — this and the heatmap's —
 * share the treatment; the shot map keeps shot-map.css's look because it is the
 * fixture page's graphic reused, not this page's.
 */
.player-position__pitch-line {
  fill: none;
  stroke: var(--surface);
  stroke-width: 0.9;
  opacity: 0.9;
}

.player-position__pitch-spot {
  fill: var(--surface);
  stroke: none;
  opacity: 0.9;
}

.player-position__marks {
  position: absolute;
  inset: 0;
}

/*
 * The marker, placed in percentages of the pitch box so it lands in the same
 * place whatever width the card gets — the same reason the value chart's club
 * marks are HTML over the canvas rather than shapes inside it.
 */
.player-position__mark {
  position: absolute;
  left: var(--x);
  top: var(--y);
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  min-width: calc(36px * var(--player-scale));
  padding: calc(var(--space-1) * var(--player-scale)) calc(var(--space-2) * var(--player-scale));
  background: var(--accent);
  color: var(--accent-text);
  border-radius: var(--radius-full);
  font-size: calc(var(--text-sm) * var(--player-scale));
  font-weight: var(--weight-bold);
  box-shadow: var(--shadow-accent);
}

/* --- Season summary --------------------------------------------------------- */

.player-season__header {
  flex-direction: row;
  align-items: center;
  gap: calc(var(--space-2) * var(--player-scale));
}

.player-season__badge {
  flex: none;
  display: flex;
}

.player-season__figures {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: calc(var(--space-4) * var(--player-scale)) calc(var(--space-2) * var(--player-scale));
  padding-top: calc(var(--space-2) * var(--player-scale));
  border-top: 1px solid var(--border);
}

.player-season__figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(2px * var(--player-scale));
  text-align: center;
}

.player-season__value {
  font-size: calc(var(--text-lg) * var(--player-scale));
  font-weight: var(--weight-bold);
  color: var(--text);
}

.player-season__label {
  font-size: calc(var(--text-xs) * var(--player-scale));
  color: var(--text-muted);
}

/*
 * The rating is a chip where its neighbours are figures, because it is the one
 * judged number in the row — the solid treatment the squad lists use rather
 * than the pitch's tinted wash, for the reason MatchSquad.js gives: a wash is
 * for a mark sitting on a busy graphic, and this is the thing the eye is
 * travelling along the row to reach.
 */
.player-season__rating {
  padding: calc(2px * var(--player-scale)) calc(var(--space-2) * var(--player-scale));
  border-radius: var(--radius-sm);
  color: var(--text-inverse);
  font-size: calc(var(--text-md) * var(--player-scale));
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}

.player-season__rating[data-tier="high"] {
  background: var(--rating-high-solid);
}

.player-season__rating[data-tier="mid"] {
  background: var(--rating-mid-solid);
}

.player-season__rating[data-tier="low"] {
  background: var(--rating-low-solid);
}

/* --- Page behind ------------------------------------------------------------ */

body[data-player-open] {
  overflow: hidden;
}
