/*
 * The fixture page's Commentary panel — the minute-by-minute feed
 * (FixtureCommentary.js).
 *
 * One card holding hairline-separated rows, which is this app's list shape
 * already: the league accordion and the club page's Matches tab are both built
 * that way, and a commentary feed is the same object — a long run of rows that
 * belong together. The reference feed this is modelled on floats a separate
 * white card per entry on a grey page instead. That does not transfer, because
 * this page has no grey: `--bg` and `--surface` are both white here and
 * separation is carried by the `--border` hairline plus `--shadow-card` (see
 * tokens.css). A hundred individually-shadowed cards on white would be the
 * haze the neutral scale was rebuilt to get away from.
 *
 * Each row is three columns, and the middle one is the only elastic one:
 *
 *   rail    minute, and a glyph on the entries that have one. Fixed width, so
 *           every minute in the feed lines up down one edge and the column can
 *           be scanned on its own.
 *   body    the sentence, or the two names of a substitution.
 *   plate   who it happened to — a face with the club crest on its corner,
 *           borrowed whole from the pitch's own player node. An entry about a
 *           side rather than a player (a substitution) shows the crest alone;
 *           a period marker or an added-time line belongs to nobody and shows
 *           neither.
 *
 * Nothing in the feed is tappable — an entry is a reading, not a destination —
 * so there is no press state, no hover and no chevron anywhere below.
 */

.fixture-commentary {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-3);
  opacity: 1;
  transition: opacity var(--duration) var(--easing);
}

/* Both of these mirror `.fixture-details__panel-empty` — the panel this one
   replaces — because `panelVisibility` (FixtureDetails.js) drives every panel
   through the same `hidden` / `data-open` pair. A class beats the UA [hidden]
   rule, so the pair has to stay explicit. */
.fixture-commentary[hidden] {
  display: none;
}

.fixture-commentary[data-open="false"] {
  opacity: 0;
}

/* --- Filters ---------------------------------------------------------------- */

/*
 * All / Key events, on `.filter-chip` — the sheet's own pill (filter-sheet.css),
 * already loaded, and already carrying the `aria-pressed` on/off look this
 * pair wants. The reference draws its active chip as a black pill; this app
 * says "active" in exactly one colour and the chip already knows it, so the
 * selected one takes the accent fill like every other chip in the product.
 *
 * Not sticky. The tab row above it already owns `top: 0` in this same
 * scrolling body (tab-bar.css), and `.fixture-details__panels` carries
 * `zoom: 0.9` — so an offset picked here would be measured in scaled pixels
 * against an unscaled bar and would drift the moment either changed.
 */
.commentary__filters {
  display: flex;
  gap: var(--space-2);
}

/* --- The feed --------------------------------------------------------------- */

.commentary__feed {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  /* Clips the last visible row's hairline against the card's own edge — see
     the negative margin on the list below. */
  overflow: hidden;
}

/*
 * `ol`, not `ul`, so base.css's list reset does not reach it — the feed is an
 * ordered list of events and is marked up as one. The reset is repeated here
 * rather than widened there, because that rule is about `ul`s used as layout
 * and this is the only real `ol` in the app.
 *
 * The -1px is what lets every row carry a `border-bottom` unconditionally: the
 * last one's hairline is pulled under the card's own border and clipped by the
 * `overflow: hidden` above. Doing it that way rather than with `:last-child`
 * is deliberate — the Key events filter hides rows, so the last *visible* row
 * is very often not the last child, and a `:last-child` rule would leave a
 * stray line floating mid-card.
 */
.commentary__list {
  margin: 0 0 -1px;
  padding: 0;
  list-style: none;
}

.commentary__entry {
  /* The rail's width. One length, referenced twice below, so the minute column
     and the glyph above it can never drift apart. */
  --rail-width: 34px;

  display: grid;
  grid-template-columns: var(--rail-width) 1fr auto;
  align-items: start;
  column-gap: var(--space-3);
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
}

/*
 * Period boundaries — kick-off, the two half markers, full time.
 *
 * A sunken fill rather than a heavier border or a centred line: this is a
 * chip-depth surface *inside* a card, which is exactly the job README.md
 * gives `--surface-sunken`. It reads as a band across the feed, so the two
 * halves are separable at a glance while the marker itself stays an entry in
 * the list rather than becoming a section header the filter would have to
 * treat differently.
 */
.commentary__entry[data-kind="period"] {
  background: var(--surface-sunken);
}

/*
 * What the Key events filter hides. Explicit for the same reason every panel
 * in this app has to say it: the UA's own `[hidden] { display: none }` is a
 * bare attribute selector, and the `display: grid` on the class above outranks
 * it outright — so without this the filter would set the attribute, the row
 * would keep its grid box, and nothing would appear to happen.
 */
.commentary__entry[hidden] {
  display: none;
}

/* --- Rail ------------------------------------------------------------------- */

.commentary__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  /* The glyph and the minute sit against the first line of the sentence beside
     them, not against the middle of a paragraph that may be four lines long. */
  padding-top: 1px;
}

/*
 * Tabular figures, and the reason is the column: proportional digits set `11'`
 * visibly narrower than `88'`, and a rail whose whole purpose is to be scanned
 * vertically cannot have its numbers wandering left and right. Plus Jakarta
 * Sans ships `tnum`; base.css explains why every column like this has to ask.
 */
.commentary__minute {
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

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

.commentary__mark--period {
  color: var(--text-muted);
}

/*
 * A booking, drawn rather than set. 9x13 is a playing-card ratio at the size
 * the rail has room for; the radius is the corner a real card has, not the
 * app's --radius-sm, which at this size would round it into a lozenge.
 *
 * No border. Both fills are taken far enough down from a pure card yellow and
 * card red to hold their own shape against --surface (see the note on the pair
 * in tokens.css), and an outline would have meant a colour value in a
 * component file — which is the one thing README.md rules out outright.
 */
.commentary__booking {
  width: 9px;
  height: 13px;
  border-radius: 1.5px;
}

.commentary__booking[data-card="yellow"] {
  background: var(--card-yellow);
}

.commentary__booking[data-card="red"] {
  background: var(--card-red);
}

/*
 * The VAR badge. Three letters, so it is three letters rather than an icon of
 * three letters.
 *
 * `--surface-inverse` and not `--accent`: accent owns "this one is active"
 * across the whole app, and a VAR decision is not a selection. The neutral
 * dark chip is the one fill in the palette that can carry small caps without
 * claiming anything.
 *
 * 8px is below --text-2xs on purpose and is the one place in the app that goes
 * there. It is three uppercase letters in a badge, not running text, and the
 * width of the rail is what it has to fit inside.
 */
.commentary__var {
  padding: 1px 3px;
  border-radius: 3px;
  background: var(--surface-inverse);
  color: var(--text-inverse);
  font-size: 8px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.03em;
  line-height: 1.4;
}

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

.commentary__body {
  /* Without this a long unbroken sentence would push the crest column off the
     end of the row — a grid item's default `min-width: auto` refuses to shrink
     below its content. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/*
 * "GOAL!", "Yellow card", "Substitution", "VAR decision" — the classification
 * line. --text-md bold against --text-sm regular below it: one step up in size
 * and two in weight, which is enough to be found while scrolling without the
 * titled entries reading as headings for the plain ones under them.
 */
.commentary__title {
  color: var(--text);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
}

.commentary__text {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: 1.45;
}

/* A period marker is the feed talking about itself rather than about the
   match, so it is set a step quieter than an ordinary sentence — but medium
   rather than regular, because it is also a landmark someone scrolling is
   looking for. */
.commentary__entry[data-kind="period"] .commentary__text {
  color: var(--text-secondary);
  font-weight: var(--weight-medium);
}

/* --- Substitution ----------------------------------------------------------- */

.commentary__swap {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* Centred, not baseline-aligned: the row leads with a portrait now, and an
   image on a text baseline hangs below the line it is supposed to sit on. */
.commentary__swap-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

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

/*
 * (In) and (Out). The colour is the fast read and the word is the accessible
 * one — neither is doing the job alone, which is the rule anywhere in this app
 * that colour carries meaning.
 *
 * --form-win / --form-loss rather than a new pair: a player coming on and a
 * player coming off are the good and bad ends of one axis, which is exactly
 * what those two already mean in a match context.
 */
.commentary__swap-tag {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.commentary__swap-tag[data-direction="in"] {
  color: var(--form-win);
}

.commentary__swap-tag[data-direction="out"] {
  color: var(--form-loss);
}

/* --- The plate at the end of a row ------------------------------------------- */

/*
 * Both of these are aligned to the first line of the sentence, the same as the
 * rail opposite them, so a four-line entry keeps its marks at the top rather
 * than letting one drift to the middle.
 */
.commentary__crest {
  display: flex;
  padding-top: 1px;
}

/*
 * The face-plus-crest plate. Positioning is `.season-stats__avatar-wrap` and
 * `.season-stats__club` (season-stats.css) doing the work — this only places
 * the cluster in the row and retunes the overlap for the smaller photo.
 *
 * `flex: none` because the crest hangs outside the avatar's box: without it a
 * long sentence could squeeze the column and clip the corner off the crest.
 */
.commentary__player {
  flex: none;
  margin-top: 1px;
  /* The crest overhangs to the right by design. Reserving that overhang here
     keeps it off the card's padding edge rather than letting it sit tight
     against it. */
  margin-right: 3px;
}

/*
 * The offsets that file tunes for a 41px pitch photo, brought down for a 26px
 * one. Same idea, proportionally: the crest sits just off the corner rather
 * than centred on it, so it reads as a badge on the portrait and not as a
 * second disc floating beside it.
 */
.commentary__player .season-stats__club {
  bottom: -3px;
  right: -7px;
}

/*
 * The monogram under the crest, one step down from what `.logo--monogram`
 * sets for a disc this size.
 *
 * Nothing in this project has player photos yet, so the plate's portrait is
 * always two letters — and two letters centred in a 30px disc run straight
 * under the crest badging its corner. Smaller type clears it. This is a
 * fallback being made to work, not the intended end state: a feed with photos
 * in it retires the whole problem, and the crest keeps its position either
 * way.
 */
.commentary__player .logo--monogram {
  font-size: 10px;
}

/*
 * A thinner plate ring than the pitch's 1.75px. There the ring separates a
 * photo from a sunken pitch backdrop and has real work to do; here it sits on
 * a white card, where the same weight at a smaller diameter reads as a heavy
 * outline rather than as an edge — and, on the crest, eats the few pixels of
 * image that decide whether it resolves at all.
 */
.commentary__player .logo {
  border-width: 1px;
}

/* --- Substitution faces ------------------------------------------------------ */

/* The two small portraits beside the names. `Avatar` takes no class of its
   own, so the row selects them — and `flex: none` keeps a long name from
   squashing a circle into an ellipse. */
.commentary__swap-row .logo {
  flex: none;
  background: var(--surface-sunken);
}

/* --- Empty ------------------------------------------------------------------ */

.commentary__empty {
  padding: var(--space-8) var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* A refusal reads in the body colour rather than the muted one: "sign in" is
   an instruction, and an instruction printed as an aside gets skipped. */
.commentary__empty[data-state="locked"] {
  color: var(--text);
}

/* --- Loading ------------------------------------------------------------------
 *
 * The feed's shape while the first answer is on its way: rows on the entry
 * grid — rail, sentence, plate — in the shared `.skeleton` pulse
 * (interactions.css), so the card is the same card before and after.
 */
.commentary__skeleton {
  margin: 0;
  padding: 0;
  list-style: none;
}

.commentary__skeleton-row {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  align-items: center;
  column-gap: var(--space-3);
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.commentary__skeleton-row:last-child {
  border-bottom: 0;
}

.commentary__skeleton-minute {
  display: block;
  width: 22px;
  height: 12px;
  margin-inline: auto;
  border-radius: var(--radius-full);
}

.commentary__skeleton-lines {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.commentary__skeleton-lines > .skeleton {
  display: block;
}

.commentary__skeleton-plate {
  display: block;
  width: 30px;
  height: 30px;
}

/* --- Goal cards ---------------------------------------------------------------
 *
 * The one exception to "rows, not cards" at the top of this file, and worth
 * saying why it is allowed to be one. That note argues against a *feed* of
 * white cards on a white page — a hundred of them read as haze, which is the
 * failure tokens.css's neutral scale was rebuilt to avoid. One raised card
 * every twenty rows is the opposite case: it is the thing the reader opened
 * the tab to find, and the hairline rows around it are what give it something
 * to stand out from. A feed where the goals look like the throw-ins is a feed
 * that has buried its own headline.
 *
 * Everything that raises it is drawn from the scoring club's own colour, which
 * `core/team-colours.js` has already put on the panel root — so a goal card is
 * tinted by whoever scored it rather than by a house green. That is one fewer
 * invented colour and one more fact on screen.
 */

.commentary__entry[data-kind="goal"] {
  /* Which side scored, resolved once so the tint, the border and the title
     cannot disagree with the graphic below them. */
  --goal-rgb: var(--stat-home-rgb);
  --goal-colour: var(--stat-home);

  margin: var(--space-3) var(--space-2);
  padding: var(--space-4) var(--space-3);
  background: linear-gradient(
    180deg,
    rgb(var(--goal-rgb) / 0.11),
    rgb(var(--goal-rgb) / 0.03) 45%,
    var(--surface) 75%
  );
  /* Replaces the row hairline outright rather than adding to it — a bottom
     border left in place would draw a straight line across the card's own
     rounded corner. */
  border: 1px solid rgb(var(--goal-rgb) / 0.3);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

.commentary__entry[data-kind="goal"]:has(.shot-replay[data-side="away"]) {
  --goal-rgb: var(--stat-away-rgb);
  --goal-colour: var(--stat-away);
}

/* "GOAL!" leads at the size the moment deserves, in the scorer's colour. Every
   other titled entry — a booking, a VAR call — keeps the ordinary title. */
.commentary__entry[data-kind="goal"] .commentary__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  letter-spacing: 0.02em;
  color: var(--goal-colour);
}

/* --- The shot graphic ----------------------------------------------------------
 *
 * On every entry that names a shot, not only the goals: a save, a block, one
 * off the woodwork. What separates a goal from an attempt is the chrome above
 * — the tint, the border, the title — and not the graphic, which is the same
 * drawing of the same kind of event.
 *
 * Full width of the row, spanning the rail and the crest columns both. How the
 * pitch, the net and the marker vocabulary are drawn all lives in
 * `shot-map.css` — the graphic carries `.shot-map` as well as `.shot-replay`,
 * so only what changes for this context is here.
 */

.shot-replay {
  grid-column: 1 / -1;
  margin: var(--space-3) 0 0;
  gap: 0;
  overflow: hidden;
  border-radius: var(--radius-md);
}

.shot-replay[data-side="away"] {
  --shot-colour: var(--stat-away);
}

/*
 * Floodlit.
 *
 * The Statistics tab draws its goal mouth on the page's own light surface,
 * which is right there: it is one panel among seven cards of figures, and a
 * dark rectangle in the middle of that column would read as a hole. A goal
 * card is not a column of figures. It is a single moment, and a ball in a dark
 * net under lights is what that moment looks like everywhere the game is
 * shown. `--surface-inverse` is the ground the story viewer already paints on,
 * so this borrows a dark the app owns rather than inventing one.
 */
.shot-replay .shot-map__sky {
  fill: var(--surface-inverse);
}

/* White frame and white netting on the dark. One declaration flips both:
   `.shot-map__net` is a fill-opacity over whatever the frame is painted in. */
.shot-replay .shot-map__frame {
  fill: var(--text-inverse);
}

.shot-replay .shot-map__net {
  fill-opacity: 0.22;
}

/*
 * The ground goes dark with the sky, rather than taking --pitch-deep like the
 * Statistics tab's copy does. That token is a pale grey — the pitch is a
 * drawn surface now, not turf — and a pale slab under a floodlit goal mouth
 * would undo the whole point of this panel, as well as putting the white
 * footballs below onto a ground they cannot be seen against. One dark field
 * throughout; the six-yard line still draws the horizon under the frame.
 */
.shot-replay .shot-map__grass,
.shot-replay .shot-map__turf {
  fill: var(--surface-inverse);
}

/* White markings, and taken back off full strength: paint on a dark ground
   should read as paint, not as the brightest thing in the panel. --pitch-line
   is the pale-panel hairline grey, which would vanish here. */
.shot-replay .shot-map__line {
  fill: var(--text-inverse);
  fill-opacity: 0.5;
}

/*
 * The pitch carries a 12-unit bleed either side so a marker on the byline is
 * not clipped, and on a full-width card that bleed shows as a pale margin the
 * goal panel above does not have — two panels of one graphic failing to line
 * up. Painting the element's own background rather than widening the ground
 * rect fills it without moving a single coordinate.
 */
.shot-replay .shot-map__pitch {
  background: var(--surface-inverse);
}

/*
 * Both footballs are white rather than the club's colour, and that is a
 * legibility call rather than a stylistic one: the club colour is sampled from
 * a crest and can land anywhere, including a navy that would disappear into
 * dark ground and a dark net both. White reads on either. The club's colour is
 * carried by the marker ring instead, which sits on a white disc where any hue
 * holds.
 */
.shot-replay .shot-map__mouth-glyph,
.shot-replay__ball {
  fill: var(--text-inverse);
}

/*
 * A ball or a glove needs no disc behind it — it is a shape on netting, not a
 * placement dot that has to be found against a pattern. A pip does: it is
 * 2 units across on a dark, ruled ground, and it is the *position* that has to
 * read. So the disc goes only where the mark is a symbol, which is what
 * `data-mark` is on the group for.
 */
.shot-replay .shot-map__mouth-mark:not([data-mark="pip"]) .shot-map__mouth-disc {
  fill: none;
  stroke: none;
}

/* The pip and its disc, inverted for the floodlit panel — the light surface
   fill they wear on the Statistics tab would be invisible against the netting
   and the dark sky both. */
.shot-replay .shot-map__mouth-mark[data-mark="pip"] .shot-map__mouth-disc {
  fill: var(--text-inverse);
  stroke: none;
}

.shot-replay .shot-map__mouth-mark[data-mark="pip"] .shot-map__mouth-pip {
  fill: var(--surface-inverse);
}

/* The trail is drawn on dark ground here, so it inverts: the near-black it
   wears on the Statistics tab's light pitch would vanish. */
.shot-replay .shot-map__selection {
  stroke: var(--text-inverse);
}

.shot-replay .shot-map__trajectory {
  opacity: 0.75;
}

/* --- Facts strip ---------------------------------------------------------------
 *
 * Three facts on one line under the graphic. Two of them the sentence also
 * carries, which is the point: a card is read at a glance and a paragraph in
 * order, and someone scrolling past wants the number without the sentence.
 */

.shot-replay__facts {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border-top: 1px solid var(--border);
}

.shot-replay__fact {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
}

.shot-replay__fact-label {
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

.shot-replay__fact-value {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* --- The replay -----------------------------------------------------------------
 *
 * Four steps, staged: the card settles, the trail draws, the ball runs it, and
 * the net takes it. `FixtureCommentary` flips `data-animate` to `play` when the
 * card reaches the viewport — see the observer there for why it is not started
 * at build time.
 *
 * Every step sits inside `--duration-max`, which INTERACTIONS.md sets as a hard
 * ceiling and which means per animation: the ceiling is about how long a single
 * move may hold the eye, and four moves of 320ms are four 320ms moves, not one
 * of 1280. They are staggered rather than run together because the order *is*
 * the content — a ball that arrives before its own trajectory has drawn is not
 * a replay of anything.
 *
 * The resting state is the finished state, which is what makes the whole thing
 * optional: a card that never receives `play` — reduced motion, an observer
 * that never fires — is a correct, complete card that simply did not move.
 */

@keyframes replay-card-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}

@keyframes replay-trail-draw {
  from {
    opacity: 0;
    clip-path: inset(0 0 100% 0);
  }
}

@keyframes replay-ball-run {
  from {
    transform: translate(0, 0);
  }
  to {
    transform: translate(var(--travel-x), var(--travel-y));
  }
}

@keyframes replay-net-take {
  0% {
    opacity: 0;
    transform: scale(0.4);
  }
  70% {
    opacity: 1;
    transform: scale(1.15);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.shot-replay[data-animate="play"] {
  animation: replay-card-in var(--spring-duration) var(--easing) both;
}

/*
 * The trail is wiped in from the goal end rather than drawn by a dash offset.
 * A `stroke-dashoffset` animation is the usual way to draw a line and it
 * cannot be used here: the dash pattern is already carrying the dotted look,
 * and animating the offset would slide the dots along the path instead of
 * extending it. A clip is indifferent to what it reveals.
 */
.shot-replay[data-animate="play"] .shot-map__trajectory {
  animation: replay-trail-draw var(--duration-slow) var(--easing) 90ms both;
}

.shot-replay[data-animate="play"] .shot-replay__ball {
  animation: replay-ball-run var(--duration-slow) var(--easing) 140ms both;
}

/*
 * Scaled from its own centre rather than its box corner, which for an SVG
 * child needs `transform-box` saying so — without it the pop would swing in
 * from the top left of the whole panel.
 */
.shot-replay .shot-map__mouth-mark {
  transform-box: fill-box;
  transform-origin: center;
}

.shot-replay[data-animate="play"] .shot-map__mouth-mark {
  animation: replay-net-take var(--spring-duration) var(--spring) 430ms both;
}

/* Before it plays, the two things that arrive are not there yet. Held on the
   idle state rather than on the elements themselves, so the resting state
   stays the finished one — see the note above. */
.shot-replay[data-animate="idle"] .shot-replay__ball,
.shot-replay[data-animate="idle"] .shot-map__mouth-mark {
  opacity: 0;
}

/*
 * Reduced motion keeps the card and skips the replay. Every duration token is
 * already zeroed under this query, but an `animation` with a zero duration and
 * a `both` fill still snaps through its keyframes — and `replay-card-in` starts
 * at `opacity: 0` — so the guard has to remove the animations outright rather
 * than rely on the scale collapsing them.
 */
@media (prefers-reduced-motion: reduce) {
  .shot-replay[data-animate="play"],
  .shot-replay[data-animate="play"] .shot-map__trajectory,
  .shot-replay[data-animate="play"] .shot-replay__ball,
  .shot-replay[data-animate="play"] .shot-map__mouth-mark {
    animation: none;
  }

  .shot-replay[data-animate="idle"] .shot-replay__ball,
  .shot-replay[data-animate="idle"] .shot-map__mouth-mark {
    opacity: 1;
  }
}
