/* Eighty Minutes — matchday programme structure, modern tool mechanics.
   Tokens and rationale: DESIGN.md at the repo root.

   The shell is a fixed-height application frame, not a scrolling page: the
   footage has to stay on screen while the commentary runs past it, so the
   two columns scroll independently and the body does not scroll at all. */

/* ---------------------------------------------------------------- tokens */

:root {
  color-scheme: light;

  --paper:         oklch(0.963 0.010 85);
  --paper-sunk:    oklch(0.934 0.013 83);
  --paper-raised:  oklch(0.982 0.007 85);
  --ink:           oklch(0.215 0.013 62);
  --ink-muted:     oklch(0.468 0.012 66);
  --ink-faint:     oklch(0.625 0.011 70);
  --rule:          oklch(0.848 0.013 80);
  --rule-strong:   oklch(0.742 0.015 78);
  --stamp:         oklch(0.545 0.191 29);
  --stamp-ink:     oklch(0.452 0.168 29);
  --stamp-wash:    oklch(0.930 0.042 32);

  --nzl:           oklch(0.300 0.010 250);
  --fra:           oklch(0.480 0.132 256);
  --warn:          oklch(0.560 0.132 62);
  --ok:            oklch(0.480 0.108 152);
  --rsa:           oklch(0.430 0.098 150);
  --screen:        oklch(0.178 0.010 68);
  --screen-ink:    oklch(0.900 0.008 82);

  --t-label:  0.6875rem;
  --t-xs:     0.75rem;
  --t-sm:     0.8125rem;
  --t-base:   0.9375rem;
  --t-md:     1.0625rem;
  --t-lg:     1.3125rem;
  --t-xl:     1.75rem;

  --sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "Spline Sans Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --ease: cubic-bezier(0.165, 0.84, 0.44, 1);
  --leading: 1.55;

  /* The matchbar, the timeline, the rail and the page's own edges: everything
     in the stage column that is not the picture. The picture is what is left. */
  --stage-chrome: 21rem;

  /* Cards are named by their colour, so they are drawn in it rather than in
     a palette colour that stands for it. */
  --card-yellow: oklch(0.82 0.17 92);
  --card-red: oklch(0.55 0.22 27);

  --stream-w: clamp(21rem, 25vw, 27rem);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper:        oklch(0.212 0.011 68);
    --paper-sunk:   oklch(0.178 0.010 68);
    --paper-raised: oklch(0.262 0.012 70);
    --ink:          oklch(0.922 0.009 82);
    --ink-muted:    oklch(0.702 0.011 78);
    --ink-faint:    oklch(0.560 0.011 74);
    --rule:         oklch(0.318 0.012 72);
    --rule-strong:  oklch(0.408 0.014 72);
    --stamp:        oklch(0.648 0.176 32);
    --stamp-ink:    oklch(0.720 0.150 34);
    --stamp-wash:   oklch(0.300 0.058 32);
    --nzl:          oklch(0.880 0.008 250);
    --fra:          oklch(0.700 0.118 256);
    --warn:         oklch(0.760 0.120 68);
    --ok:           oklch(0.720 0.110 152);
    --rsa:          oklch(0.760 0.098 150);
    --screen:       oklch(0.148 0.010 68);
    --leading:      1.62;
  }
}

[data-theme="dark"] {
  color-scheme: dark;
  --paper:        oklch(0.212 0.011 68);
  --paper-sunk:   oklch(0.178 0.010 68);
  --paper-raised: oklch(0.262 0.012 70);
  --ink:          oklch(0.922 0.009 82);
  --ink-muted:    oklch(0.702 0.011 78);
  --ink-faint:    oklch(0.560 0.011 74);
  --rule:         oklch(0.318 0.012 72);
  --rule-strong:  oklch(0.408 0.014 72);
  --stamp:        oklch(0.648 0.176 32);
  --stamp-ink:    oklch(0.720 0.150 34);
  --stamp-wash:   oklch(0.300 0.058 32);
  --nzl:          oklch(0.880 0.008 250);
  --fra:          oklch(0.700 0.118 256);
  --warn:         oklch(0.760 0.120 68);
  --ok:           oklch(0.720 0.110 152);
  --rsa:          oklch(0.760 0.098 150);
  --screen:       oklch(0.148 0.010 68);
  --leading:      1.62;
}

/* ----------------------------------------------------------------- reset */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; height: 100%; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-base);
  line-height: var(--leading);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;

  height: 100dvh;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden;
}

/* Nothing is resized to go full screen: the shell is measured in dvh and dvh
   is the screen now, so the gain lands in the picture by the arithmetic that
   was already there. What is not free is what the platform paints underneath
   -- a fullscreen root is backed in white, which a page set in the dark theme
   wears as a flash on the way in. Two rules and not one selector list: an
   unknown prefixed selector invalidates every selector written beside it. */
:root:fullscreen { background: var(--paper); }
:root:-webkit-full-screen { background: var(--paper); }

button, input { font: inherit; color: inherit; }

/* [hidden] is a user-agent rule at the very bottom of the cascade, so any
   display: grid|flex in this file silently outranks it. Two elements shipped
   hidden and visible at the same time before this line existed. */
[hidden] { display: none !important; }
ol, ul { list-style: none; padding: 0; }
img { display: block; max-width: 100%; }

:where(a, button, input, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--stamp);
  outline-offset: 2px;
  border-radius: 1px;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--paper-raised); color: var(--ink);
  padding: 0.75rem 1rem; z-index: 20;
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------- shared */

.label {
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

mark {
  background: var(--stamp-wash);
  color: var(--stamp-ink);
  font-weight: 600;
  padding: 0 0.0625rem;
}

/* The page's small controls: Follow, Legal, the theme, the colophon. They
   were defined with the top row they used to sit on and went out with it. */
.ghost-button {
  background: none;
  border: 1px solid var(--rule-strong);
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
  white-space: nowrap;
}
a.ghost-button { display: inline-block; text-decoration: none; }
.ghost-button:hover { color: var(--ink); border-color: var(--ink-faint); }
.ghost-button--tiny { padding: 0.1875rem 0.4375rem; }
.ghost-button[aria-pressed="true"] { color: var(--stamp-ink); border-color: var(--stamp); }

/* ------------------------------------------------------------- wordmark

   In the drawer now. The page's top row was one line of chrome carrying a
   name, two meta links and a theme toggle, all of which are things you look
   for rather than things you read -- and it was costing the picture 3rem on
   every screen. The menu was already the place you go when you are not
   watching. */

.wordmark {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  text-decoration: none;
  color: inherit;
}

.wordmark__name {
  font-weight: 800;
  font-stretch: 74%;
  letter-spacing: 0.01em;
  font-size: var(--t-md);
  text-transform: uppercase;
}

.wordmark__role {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stamp-ink);
}

/* ------------------------------------------------------------- fixtures
   A row of stamps across the top was a fixture list that could only ever hold
   a handful. The archive grows and the chrome must not, so the list moved to a
   drawer -- and then everything else on that row followed it, because a name,
   two meta links and a theme toggle are things you go looking for rather than
   things you read, and the row they sat on was 3rem of picture on every
   screen. The drawer is the page's chrome now. What is left on the page is the
   match.

   The control that opens it is a glyph alone, which nothing else here is: it
   is the only control on the match row and the row is the page's masthead, so
   it is read as the menu without being told. Three rules, which is what the
   list behind it is. */

.matchbar .fixtures-open { align-self: center; }

.fixtures-open {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: none;
  border: 1px solid var(--rule-strong);
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
  white-space: nowrap;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.fixtures-open:hover { color: var(--ink); border-color: var(--ink-faint); }
.fixtures-open[aria-expanded="true"] {
  color: var(--stamp-ink);
  border-color: var(--stamp);
}

/* Three 1px rules in 9px: a hamburger that is also a fixture list. */
.fixtures-open__rules {
  width: 0.875rem;
  height: 9px;
  background: repeating-linear-gradient(
    to bottom, currentColor 0 1px, transparent 1px 4px);
}

.scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: oklch(0.215 0.013 62 / 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 220ms var(--ease), visibility 0s linear 220ms;
}
.scrim[data-open] {
  opacity: 1;
  visibility: visible;
  transition: opacity 220ms var(--ease), visibility 0s;
}

.fixtures {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 50;
  width: min(23rem, 86vw);
  display: grid;
  /* The name, the fixtures label, the list, the controls. The list is the one
     that grows: four children and three rows put it wherever the implicit row
     landed, which was under everything else. */
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  background: var(--paper-sunk);
  border-right: 1px solid var(--rule-strong);
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform 220ms var(--ease), visibility 0s linear 220ms;
}
.fixtures[data-open] {
  transform: none;
  visibility: visible;
  transition: transform 220ms var(--ease), visibility 0s;
}

.fixtures__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  padding: 1rem 1rem 0.625rem;
  border-bottom: 1px solid var(--rule-strong);
}
.fixtures__title { grid-column: 1; margin: 0; }
.fixtures__count {
  grid-row: 2;
  grid-column: 1;
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink-muted);
}
.fixtures__close {
  grid-row: 1 / span 2;
  grid-column: 2;
  align-self: center;
  background: none;
  border: 1px solid transparent;
  color: var(--ink-faint);
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.125rem 0.4375rem;
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.fixtures__close:hover { color: var(--ink); border-color: var(--rule-strong); }

.fixtures__list { overflow-y: auto; overscroll-behavior: contain; }

.fixture {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "year  teams score"
    "where where moments";
  align-items: baseline;
  gap: 0.1875rem 0.5rem;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  padding: 0.6875rem 1rem;
  cursor: pointer;
  color: var(--ink-muted);
  transition: background-color 160ms var(--ease), color 160ms var(--ease);
}
.fixture:hover { background: var(--paper-raised); color: var(--ink); }
.fixture[aria-current="true"] { background: var(--stamp-wash); color: var(--ink); }

.fixture__year {
  grid-area: year;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--stamp-ink);
}
.fixture__teams {
  grid-area: teams;
  font-weight: 700;
  font-size: var(--t-sm);
  letter-spacing: 0.01em;
}
.fixture__teams i { font-style: normal; font-weight: 400; color: var(--ink-faint); }
.fixture__score {
  grid-area: score;
  font-family: var(--mono);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
.fixture__where {
  grid-area: where;
  font-size: var(--t-xs);
  color: var(--ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fixture__moments {
  grid-area: moments;
  font-family: var(--mono);
  font-size: var(--t-label);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

/* The match you are on is marked by a word as well as a wash. Colour alone
   would be the only state on this page that a reader has to see in colour to
   know, and the team codes next to it already refuse that bargain. */
.fixture__now {
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stamp-ink);
  border: 1px solid var(--stamp);
  padding: 0.0625rem 0.3125rem;
  margin-left: 0.4375rem;
}

.fixtures__note {
  grid-column: 1 / -1;
  margin: 0.375rem 0 0;
  font-size: var(--t-xs);
  line-height: 1.45;
  color: var(--ink-faint);
}

.fixtures__band {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem clamp(0.875rem, 1.8vw, 1.25rem) 0.375rem;
  border-bottom: 1px solid var(--rule);
}

.fixtures__foot {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0;
  padding: 0.625rem clamp(0.875rem, 1.8vw, 1.25rem);
  border-top: 1px solid var(--rule-strong);
}

@media (prefers-reduced-motion: reduce) {
  .fixtures, .fixtures[data-open], .scrim, .scrim[data-open] { transition: none; }
}

/* ---------------------------------------------------------------- shell */

.shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--stream-w);
  min-height: 0;
}

.stage {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
  border-right: 1px solid var(--rule-strong);
}

/* The picture, the timeline and the rail, and nothing underneath to scroll
   towards. The column is the match: it is as tall as the window and gives its
   height to the picture, which is why the moments are a row and not a list. */
.stage__scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) transparent;
  display: grid;
  grid-template-rows: auto auto auto;
  align-content: start;
  min-height: 0;
}


/* -------------------------------------------------------------- matchbar
   The programme masthead, set as a band. It gives up height to the picture:
   the identity is condensed type and a stamped score, not a big headline. */

/* One row, and it cannot be allowed to become two. It was a wrapping flex, so
   the moment the kicker and the score could not share a line the score
   dropped onto a row of its own: 58px became 111px at 1280, nearly all of it
   empty and all of it taken off the picture. A grid with one flexible column
   cannot do that. What gives instead is the kicker, which is the one thing on
   this row nobody reads twice -- the fixture list in the menu says the same
   competition, stage and date for every match it holds. */
.matchbar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 1rem;
  padding: 0.625rem clamp(0.875rem, 1.8vw, 1.5rem) 0.5rem;
  border-bottom: 2px solid var(--ink);
}

.matchbar__id { min-width: 0; }

/* Both lines shorten rather than wrap: a masthead that reflows is a masthead
   that moves the picture every time the window does. */
.matchbar__kicker,
.matchbar__title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.matchbar__kicker {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.3;
}

.matchbar__title {
  font-size: var(--t-lg);
  font-variation-settings: "wdth" 74;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  margin-top: 0.125rem;
}

/* The band has a bracket at each end -- the index on the left, the room on the
   right -- and the score keeps the button company so the row still has three
   things in it, which is what the three columns above are. (This arrived while
   the row was a wrapping flex and said "space-between still means what it
   meant"; it is a grid now, for the reason given up there, and the pairing
   matters more under a grid than it did under the flex.) */
.matchbar__end { display: flex; align-items: center; gap: 1rem; }

.fullscreen { display: inline-flex; align-items: center; padding-inline: 0.5rem; }

/* Printer's crop marks: where the sheet is to be cut, which is the same
   question as how much room the picture is allowed. Eight strips because a
   corner is two strokes, drawn in currentColor the way the hamburger is, so
   it takes the hover and the pressed colour without being told about either.
   The state is the inset -- pressed, the marks have come back in. */
.fullscreen__crop {
  --crop-arm: 5px;
  --crop-rule: 1px;
  --crop-in: 0px;
  width: 0.875rem;
  aspect-ratio: 1;
  background:
    linear-gradient(currentColor 0 0) left  var(--crop-in) top    var(--crop-in) / var(--crop-arm)  var(--crop-rule),
    linear-gradient(currentColor 0 0) left  var(--crop-in) top    var(--crop-in) / var(--crop-rule) var(--crop-arm),
    linear-gradient(currentColor 0 0) right var(--crop-in) top    var(--crop-in) / var(--crop-arm)  var(--crop-rule),
    linear-gradient(currentColor 0 0) right var(--crop-in) top    var(--crop-in) / var(--crop-rule) var(--crop-arm),
    linear-gradient(currentColor 0 0) left  var(--crop-in) bottom var(--crop-in) / var(--crop-arm)  var(--crop-rule),
    linear-gradient(currentColor 0 0) left  var(--crop-in) bottom var(--crop-in) / var(--crop-rule) var(--crop-arm),
    linear-gradient(currentColor 0 0) right var(--crop-in) bottom var(--crop-in) / var(--crop-arm)  var(--crop-rule),
    linear-gradient(currentColor 0 0) right var(--crop-in) bottom var(--crop-in) / var(--crop-rule) var(--crop-arm);
  background-repeat: no-repeat;
  transition: background-position 160ms var(--ease), background-size 160ms var(--ease);
}
/* Pulled in, and shortened with it: the inset alone runs the four corners
   into each other long before the move is legible, and four corners that
   meet are a box rather than a mark. */
.fullscreen[aria-pressed="true"] .fullscreen__crop { --crop-in: 2px; --crop-arm: 3px; }

.scoreline { display: flex; align-items: center; gap: 1rem; white-space: nowrap; }
.scoreline__side { display: flex; align-items: baseline; gap: 0.4375rem; }
.scoreline__abbr {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  color: var(--ink-muted);
}
.scoreline__abbr--RSA { color: var(--rsa); }
.scoreline__abbr--NZL { color: var(--nzl); }
.scoreline__abbr--FRA { color: var(--fra); }
.scoreline__n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xl);
  font-weight: 600;
  line-height: 1;
}
/* Where the playhead is, when the scoreline is the running one. The stamp is
   reserved for the result, which is a different kind of claim. */
/* A card in effect, drawn as one: a small upright rectangle in the colour it
   is called, with what is left of the bin printed beside it. It sits with the
   points because for ten minutes it explains them. */
.scoreline__card {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: 0.25rem;
}
.scoreline__card::before {
  content: "";
  width: 0.5rem;
  height: 0.6875rem;
  border-radius: 1px;
  background: var(--card-yellow);
}
.scoreline__card--red::before { background: var(--card-red); }

.scoreline__bin {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-label);
  color: var(--ink-muted);
}

.scoreline__at {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  color: var(--stamp-ink);
  border: 1px solid var(--rule-strong);
  padding: 0.1875rem 0.4375rem;
}

/* Points arrive whole, so the change is a beat rather than a count-up. */
.scoreline__n--struck { animation: struck 320ms var(--ease); }
@keyframes struck {
  from { opacity: 0.3; transform: translateY(-2px); }
  to   { opacity: 1; transform: none; }
}

.scoreline__stamp {
  border: 2px solid var(--stamp);
  color: var(--stamp-ink);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.1875rem 0.4375rem;
  transform: rotate(-2.5deg);
}

/* ---------------------------------------------------------------- stage */

/* The wrapper exists for the narrow layout, where picture and scrubber have
   to stick together. On the two-column layout it gets out of the way so the
   scrubber can stick to the top of the scroll on its own: the picture may
   scroll off while you read the accounts, but you can always still seek. */
.stage__fixed { display: contents; }

.stage__frame { margin: 0.875rem clamp(0.875rem, 1.8vw, 1.5rem) 0; }

.stage__frame {
  background: var(--screen);
  border: 1px solid var(--rule-strong);
  padding: 0.375rem;
  display: grid;
  place-items: center;
}

/* Height-first, and it has to be: the rail sits under the timeline and both of
   them are the picture's own instruments. A picture sized by the width of the
   column pushes them off a short window, and a rail you have to scroll to is a
   list again. So the picture takes what is left of the window after the chrome
   above it and the two bands below, and gives up width to do it. */
.player {
  position: relative;
  width: min(100%, calc((100dvh - var(--stage-chrome)) * 16 / 9));
  aspect-ratio: 16 / 9;
  background: var(--screen);
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* The embed and the library's own copy get the same frame. This rule named
   `iframe` alone, so the `<video>` that stands in for it on 80.const fell
   back to intrinsic sizing: a small box with default chrome, adrift in a
   black field, on a page whose whole premise is that the footage stays on
   screen. `contain` because a match is 16:9 and a letterbox is honest;
   cropping to fill would cut the score bug off the top. */
.player iframe,
.player video { width: 100%; height: 100%; border: 0; display: block; }
.player video { object-fit: contain; background: var(--screen); }

.player__poster { position: absolute; inset: 0; }
.player__poster img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.22;
  filter: saturate(0.35) contrast(0.9);
}

.player__idle {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 0.875rem;
  text-align: center;
  padding: 1.25rem;
}
.player__idle-text {
  color: var(--screen-ink);
  font-size: var(--t-sm);
  opacity: 0.8;
  max-width: 44ch;
  text-shadow: 0 1px 3px oklch(0.148 0.010 68 / 0.9);
}
.player__start {
  background: oklch(0.148 0.010 68 / 0.55);
  border: 1px solid oklch(0.900 0.008 82 / 0.4);
  color: var(--screen-ink);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.5625rem 1rem;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 160ms var(--ease), background-color 160ms var(--ease);
}
.player__start:hover {
  border-color: var(--screen-ink);
  background: oklch(0.900 0.008 82 / 0.14);
}

/* Arriving at a moment (SPEC 6.5). The clock stamped large, the description
   under it, and a control sized as though it is the only thing on the page,
   because it is. Everything here sits on the poster, so it carries the same
   shadow the idle copy does. */
.player__idle--moment { gap: 0.5rem; }
.player__arrive-clock {
  margin: 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2rem, 6vw, 3rem);
  line-height: 1;
  color: var(--screen-ink);
  text-shadow: 0 1px 4px oklch(0.148 0.010 68 / 0.9);
}
.player__arrive-what {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--screen-ink);
  opacity: 0.85;
  text-shadow: 0 1px 3px oklch(0.148 0.010 68 / 0.9);
}
.player__arrive-text {
  font-family: var(--sans);
  font-size: var(--t-base);
  opacity: 0.95;
  max-width: 48ch;
}
.player__start--moment {
  margin-top: 0.5rem;
  font-size: var(--t-sm);
  padding: 0.875rem 1.75rem;
  background: var(--stamp);
  border-color: transparent;
  color: oklch(0.98 0.005 85);
}
.player__start--moment:hover { background: var(--stamp-ink); border-color: transparent; }
.player__start--moment:focus-visible { outline: 2px solid var(--screen-ink); outline-offset: 3px; }
.player__arrive-note {
  margin: 0;
  font-size: var(--t-xs);
  color: var(--screen-ink);
  opacity: 0.65;
  text-shadow: 0 1px 3px oklch(0.148 0.010 68 / 0.9);
}
/* A phone's frame is 16:9 of 390px: room for the clock, the line and the
   button, and no more. The description is on the card directly beneath,
   already in the reading position, so nothing is lost by not saying it twice. */
@media (max-width: 40rem) {
  .player__idle--moment { gap: 0.375rem; padding: 0.75rem; }
  .player__arrive-clock { font-size: 1.75rem; }
  .player__arrive-text, .player__arrive-note { display: none; }
  .player__start--moment { margin-top: 0.25rem; padding: 0.625rem 1.25rem; }
}

.player__fallback {
  color: var(--screen-ink);
  font-size: var(--t-xs);
  text-align: center;
  padding: 2rem;
  max-width: 40ch;
}
.player__fallback a { color: inherit; }

/* ------------------------------------------------------------- timeline */

.timeline {
  position: sticky;
  top: 0;
  z-index: 4;
  background: var(--paper);
  padding: 0.625rem clamp(0.875rem, 1.8vw, 1.5rem) 0.5rem;
  border-bottom: 1px solid var(--rule);
}

.timeline__track {
  position: relative;
  height: 1.75rem;
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
  cursor: crosshair;
}

.timeline__indexed {
  position: absolute;
  inset-block: 0;
  left: 0;
  background: var(--paper-raised);
  border-right: 1px dashed var(--rule-strong);
}

.tick {
  position: absolute;
  inset-block: 0.25rem;
  width: 2px;
  background: var(--ink-faint);
  border: 0;
  padding: 0;
  cursor: pointer;
  transform: translateX(-1px);
  transition: background-color 160ms var(--ease), transform 160ms var(--ease);
}
.tick:hover, .tick:focus-visible { transform: translateX(-1px) scaleX(2.5); }
.tick--scoring { background: var(--stamp); inset-block: 0.125rem; width: 3px; }
.tick--dim { opacity: 0.3; }

.timeline__playhead {
  position: absolute;
  inset-block: -3px;
  width: 1px;
  background: var(--stamp);
  transition: left 200ms linear;
  pointer-events: none;
}
.timeline__playhead::before {
  content: "";
  position: absolute;
  top: -3px; left: -3px;
  width: 7px; height: 7px;
  background: var(--stamp);
}

/* Where the punditry is. It is what the chat cannot say about its own future:
   you can see the stretches of the match nobody reviewed before you reach them
   and find the column sitting still. */
.timeline__sources {
  position: relative;
  height: 0.5rem;
  margin-top: 2px;
  border-bottom: 1px solid var(--rule);
}
.source-mark {
  position: absolute;
  bottom: 0;
  height: 100%;
  min-width: 3px;
  background: var(--stamp);
  opacity: 0.3;
  transform: translateX(-50%);
  transition: opacity 160ms var(--ease);
}
.source-mark--on { opacity: 1; }

.timeline__axis {
  position: relative;
  height: 1rem;
  margin-top: 0.25rem;
  font-family: var(--mono);
  font-size: var(--t-label);
  color: var(--ink-faint);
}
.timeline__axis span {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
}
.timeline__axis span:first-child { transform: none; }
.timeline__axis span:last-child { transform: translateX(-100%); }
.timeline__axis .axis__indexed { color: var(--ink-muted); }

/* ------------------------------------------------------------ colophon */

/* Read once, by the curious. It had a band across the bottom of the match
   column on every other visit, and that band was a band of picture. */
.colophon .watch__box { padding-bottom: 0.75rem; }
.colophon__summary-unused {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  cursor: pointer;
  padding: 0.5rem clamp(0.875rem, 1.8vw, 1.5rem);
  list-style: none;
  transition: color 160ms var(--ease);
}
.colophon__summary::-webkit-details-marker { display: none; }
.colophon__summary::before { content: "+ "; color: var(--ink-faint); }
.colophon[open] .colophon__summary::before { content: "\2013 "; }
.colophon__summary:hover { color: var(--ink); }

.colophon__body {
  display: grid;
  gap: 0.5rem;
  padding: 0 clamp(0.875rem, 1.8vw, 1.5rem) 1rem;
  max-width: 74ch;
  line-height: 1.55;
  max-height: 34vh;
  overflow-y: auto;
}
.colophon b { color: var(--ink-muted); font-weight: 600; }
.colophon a { color: var(--stamp-ink); }

/* --------------------------------------------------------------- stream
   The reading column. Its head stays put; only the list moves. */

.stream {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
  background: var(--paper-sunk);
}

.stream__head {
  padding: 0.875rem clamp(0.75rem, 1.2vw, 1.125rem) 0;
  border-bottom: 1px solid var(--rule-strong);
  display: grid;
  gap: 0.625rem;
}

.input { position: relative; display: flex; align-items: center; }

.input input {
  width: 100%;
  background: var(--paper-raised);
  border: 1px solid var(--rule-strong);
  padding: 0.5625rem 2.25rem 0.5625rem 0.75rem;
  font-size: var(--t-base);
  transition: border-color 160ms var(--ease);
  appearance: none;
}
.input input::placeholder { color: var(--ink-faint); }
.input input:hover { border-color: var(--ink-faint); }

.input__hint {
  position: absolute;
  right: 0.625rem;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink-faint);
  border: 1px solid var(--rule-strong);
  padding: 0 0.3125rem;
  pointer-events: none;
}
.input input:focus ~ .input__hint { opacity: 0; }

/* ------------------------------------------------------------- drawers */

.drawers { display: flex; gap: 0.375rem; flex-wrap: wrap; }

.drawer {
  flex: 1 1 8rem;
  border: 1px solid var(--rule);
  background: var(--paper);
}
.drawer[open] { flex-basis: 100%; }

.drawer__summary {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  cursor: pointer;
  padding: 0.3125rem 0.5rem;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  transition: color 160ms var(--ease);
}
.drawer__summary::-webkit-details-marker { display: none; }
.drawer__summary::before { content: "+"; color: var(--ink-faint); }
.drawer[open] .drawer__summary::before { content: "\2013"; }
.drawer__summary:hover { color: var(--ink); }
.drawer__n { color: var(--stamp-ink); font-weight: 600; }

.kinds {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  padding: 0.375rem 0.5rem 0.625rem;
  border-top: 1px solid var(--rule);
}

.kind {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  background: none;
  border: 1px solid var(--rule);
  padding: 0.1875rem 0.4375rem;
  cursor: pointer;
  font-size: var(--t-xs);
  color: var(--ink-muted);
  transition: color 160ms var(--ease), border-color 160ms var(--ease),
              background-color 160ms var(--ease);
}
.kind:hover { color: var(--ink); border-color: var(--ink-faint); }
.kind--on {
  color: var(--ink);
  font-weight: 600;
  border-color: var(--stamp);
  background: var(--stamp-wash);
}
.kind--scoring { border-style: solid; border-color: var(--rule-strong); }
.kind__n { font-family: var(--mono); font-size: var(--t-label); color: var(--ink-faint); }

.sheet {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  padding: 0.5rem 0.5rem 0.75rem;
  border-top: 1px solid var(--rule);
}
.sheet__team { display: grid; gap: 0.125rem; align-content: start; }
.sheet__name {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  padding-bottom: 0.1875rem;
  border-bottom: 1px solid var(--rule-strong);
  margin-bottom: 0.1875rem;
  color: var(--ink-muted);
}
.sheet__name--rsa { color: var(--rsa); }
.sheet__name--nzl { color: var(--nzl); }
.sheet__name--fra { color: var(--fra); }

.sheet__row {
  display: grid;
  grid-template-columns: 1.25rem 1fr;
  gap: 0.4375rem;
  font-size: var(--t-xs);
  align-items: baseline;
  padding: 0.0625rem 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: var(--ink-muted);
  width: 100%;
  transition: color 160ms var(--ease);
}
.sheet__row:hover { color: var(--stamp-ink); }
.sheet__num { font-family: var(--mono); color: var(--ink-faint); text-align: right; }
.sheet__row[data-scored="true"] .sheet__player { color: var(--ink); font-weight: 600; }

/* Two things to do with a name, and they are different: the button searches
   this match for them, the arrow leaves for everything the index holds. */
.sheet__line { display: flex; align-items: baseline; gap: 0.25rem; }

.sheet__page {
  flex: none;
  padding: 0 0.125rem;
  font-size: var(--t-xs);
  line-height: 1;
  text-decoration: none;
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity 160ms var(--ease), color 160ms var(--ease);
}
.sheet__line:hover .sheet__page,
.sheet__page:focus-visible { opacity: 1; }
.sheet__page:hover { color: var(--stamp-ink); }

/* A link that only appears on hover is a link a keyboard cannot find, so it
   is revealed by focus as well -- and never hidden from a pointer that is
   not there at all. */
@media (hover: none) {
  .sheet__page { opacity: 1; }
}

/* ----------------------------------------------------------------- tabs */

.tabs {
  display: flex;
  align-items: center;
  gap: 1.125rem;
}

.tab {
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 0 0 0.4375rem;
  margin-bottom: -1px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.tab:hover { color: var(--ink-muted); }
.tab--on { color: var(--ink); border-bottom-color: var(--stamp); }
.tab__count { color: var(--ink-faint); }
.tabs .ghost-button { margin-left: auto; margin-bottom: 0.4375rem; }

.stream__scroll {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden;
  min-height: 0;
}

/* Placed rather than flowed. An answer block that is not there is display:
   none and takes no row with it, and a panel that falls into the first row is
   as tall as its own content -- which for a log that grows upwards from the
   floor means an empty column with one line stranded at the top of it. */
.answer { grid-row: 1; }
#panel-chat, #panel-lines { grid-row: 2; }

/* And again inside the panel, for the same reason: the query strip is only
   there while there is a query. */
#chat-matches { grid-row: 1; }
.chat__scroll { grid-row: 2; }

#panel-chat {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
}

#panel-lines {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) transparent;
  padding: 0.75rem clamp(0.75rem, 1.2vw, 1.125rem) 3rem;
  min-height: 0;
}

.answer { margin: 0.75rem clamp(0.75rem, 1.2vw, 1.125rem); }

/* ----------------------------------------------------------------- chat

   What was said about the match, arriving as the match reaches it. A log with
   a live edge: it grows from the bottom, the newest thing is the thing you are
   looking at, and older accounts leave upwards the way a conversation does.

   Two camps, set as two sides. An account leaning to the home side speaks from
   the left, the away side from the right, and one that leans nowhere sits
   across the middle, which is also what a neutral account is. */

.chat__scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) transparent;
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
  padding: 0.625rem 0.75rem 0.25rem;
  min-height: 0;
}

/* The log is short before the match has been played, and a log that starts
   halfway up the column looks like something failed to load. So it sits on the
   floor and grows up from it -- but by an auto margin, never by aligning the
   container's content to its end. That second way is a trap: once the log is
   taller than the column, the overflow goes off the TOP edge, where a scroll
   container cannot reach it. The bubbles are still in the document, painted
   above the box, clipped, unscrollable and unclickable, and the first half of
   the conversation is simply gone. An auto margin collapses to nothing the
   moment there is no spare room, which is exactly the behaviour wanted. */
.chat { display: grid; gap: 0.4375rem; margin-top: auto; }

.bubble {
  position: relative;
  max-width: 92%;
  background: var(--paper);
  border: 1px solid var(--rule);
  transition: border-color 160ms var(--ease);
}
.bubble:hover { border-color: var(--ink-faint); }

/* The sides. Two thirds of the column each, so a run of one camp reads as a
   run and the two are never mistaken for one voice. */
.bubble--home { justify-self: start; }
.bubble--away { justify-self: end; }
.bubble--neutral { justify-self: stretch; max-width: none; opacity: 0.86; }

.bubble__hit {
  display: grid;
  gap: 0.25rem;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0.4375rem 0.5rem;
  cursor: pointer;
}

.bubble__who {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  flex-wrap: wrap;
  padding-right: 4.5rem;        /* the Watch control sits over this corner */
}

.bubble__channel {
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 11rem;
}

.bubble__at {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

/* What kind of account this is, which is a different question from whose
   channel it is. A referee breakdown and a fan reaction are read differently
   and should not arrive looking identical. Set as a stamp rather than a chip:
   it is a classification, not a button. */
.bubble__source {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom: 1px solid currentColor;
}
.bubble__source--referee-official { color: var(--stamp-ink); }
.bubble__source--reaction-partisan,
.bubble__source--analysis-contrarian { color: var(--ink-muted); font-style: italic; }
.bubble:hover .bubble__at { color: var(--stamp-ink); }

.bubble__said {
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--ink-muted);
}
.bubble__said::before, .bubble__said::after { content: "\201c"; }
.bubble__said::after { content: "\201d"; }

/* The one glyph on a bubble. Small, because playing the passage is the second
   thing you might want from it and going to the moment is the first. */
/* The second act on a bubble, and the one nobody finds if it is drawn as a
   hint: the body plays the match from this moment, this plays the passage
   itself. It says which it is in words, because a bare glyph in the corner of
   a quote reads as decoration and was duly ignored. */
.bubble__watch {
  position: absolute;
  top: 0.3125rem;
  right: 0.3125rem;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.3125rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--stamp-ink);
  background: var(--paper-raised);
  border: 1px solid var(--stamp);
  cursor: pointer;
  transition: color 160ms var(--ease), background-color 160ms var(--ease);
}
.bubble__watch:hover { color: var(--ink); background: var(--stamp-wash); }
.bubble__watch-word { line-height: 1; }

/* Arriving. A span of accounts about one moment is due at the same second, and
   they come in one after another rather than as a block, because that is what
   the difference between two people talking looks like. */
.bubble--new { animation: rise 240ms var(--ease) backwards; }
.bubble--new { animation-delay: calc(var(--i, 0) * 90ms); }

@keyframes rise {
  from { opacity: 0; transform: translateY(0.5rem); }
  to   { opacity: 1; transform: none; }
}

/* The live edge. A column that will sit still for four minutes says so. */
.chat__edge {
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  padding: 0.375rem 0.25rem 0.5rem;
  text-align: center;
}

/* What arrived while they were reading three minutes back. */
.chat__new {
  position: absolute;
  left: 50%;
  bottom: 0.75rem;
  transform: translateX(-50%);
  z-index: 4;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--stamp-ink);
  background: var(--paper-raised);
  border: 1px solid var(--stamp);
  padding: 0.25rem 0.625rem;
  cursor: pointer;
  box-shadow: 0 2px 10px oklch(0 0 0 / 0.14);
}
.chat__new:hover { color: var(--ink); }

/* A query used to land in a list of moment cards. There is no list now, so it
   lands in the one thing the list was good for: the seconds it matched. */
/* Two classes deep on purpose: `.stamps` sets its own padding further down,
   where the call's index uses it inside a panel that already has some. This
   one is the panel. */
.stamps.stamps--query {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--rule);
  background: var(--paper-sunk);
}

/* ------------------------------------------------------ the moment's parts

   A moment was a card in a column and is a card on the rail. These are the
   parts that survived the move: the kind, the player, the team, how many
   people talked about it, how sure the reading was, and the way out to the
   footage itself. */

.moment__kind {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.rail__card--scoring .moment__kind { color: var(--ink); font-weight: 600; }

.moment__player { font-size: var(--t-xs); font-weight: 600; }
.moment__team {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  padding: 0 0.25rem;
  border: 1px solid currentColor;
  color: var(--ink-faint);
}
.moment__team--RSA { color: var(--rsa); }
.moment__team--NZL { color: var(--nzl); }
.moment__team--FRA { color: var(--fra); }

.moment__accounts {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--stamp-ink);
  border-bottom: 1px solid currentColor;
  flex: none;
}
.moment__accounts--contested { color: var(--stamp); font-weight: 600; }

/* Only when the model was unsure. Certainty needs no chrome, and a number is
   less ambiguous than a bar at this size: the page rests on precision being
   legible, so it prints the figure. */
.confidence {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-label);
  color: var(--ink-faint);
  flex: none;
  cursor: help;
}
.confidence--low { color: var(--warn); }

.moment__out {
  margin-left: auto;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  text-decoration: none;
  flex: none;
  transition: color 160ms var(--ease);
}
.moment__out:hover { color: var(--stamp-ink); }

/* The share control (SPEC 6.9). Set like the deep link beside it -- mono,
   letterspaced, faint until wanted -- but a button, because it acts on this
   page rather than leaving it. Always visible: a hover-only affordance is no
   affordance on a phone, which is where most sharing happens. */
.share {
  appearance: none;
  background: none;
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  padding: 0.0625rem 0.4375rem;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  color: var(--ink-muted);
  cursor: pointer;
  flex: none;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.share:hover { color: var(--ink); border-color: var(--ink-faint); }
.share:focus-visible { outline: 2px solid var(--stamp); outline-offset: 2px; }

/* The fallback that is the whole control on plain HTTP: the link, selected,
   with the keystroke named. Sits under the foot, inside the card. */
.share__field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.625rem 0.5rem;
  border-top: 1px dashed var(--rule);
  font-size: var(--t-label);
}
.share__input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.125rem 0.375rem;
}
.share__input:focus-visible { outline: 2px solid var(--stamp); outline-offset: 1px; }
.share__key { flex: none; font-family: var(--mono); color: var(--ink-faint); }

/* How many times a short link to this moment was opened. Library only. */
.opened {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-label);
  color: var(--ink-faint);
  flex: none;
}

/* ---------------------------------------------------------------- lines */

.line {
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr);
  gap: 0.625rem;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  padding: 0.4375rem 0.25rem;
  cursor: pointer;
  align-items: baseline;
  transition: background-color 160ms var(--ease);
}
.line:hover { background: var(--paper-raised); }
.line--on { background: var(--stamp-wash); }

.line__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-label);
  color: var(--ink-faint);
}
.line--on .line__clock { color: var(--stamp-ink); }
.line__text { font-size: var(--t-xs); line-height: 1.5; }

/* ---------------------------------------------------------- empty state */

.empty {
  border: 1px dashed var(--rule-strong);
  padding: 1.25rem 1rem;
}
.empty p { font-size: var(--t-xs); color: var(--ink-muted); }
.empty p + p { margin-top: 0.625rem; }
.empty b { color: var(--ink); font-weight: 600; }
.empty code {
  font-family: var(--mono);
  font-size: var(--t-label);
  background: var(--paper-sunk);
  padding: 0.0625rem 0.3125rem;
  border: 1px solid var(--rule);
}

/* ------------------------------------------------------------------ doc
   A standing page rather than the app: the back of the programme, where the
   small print is set. body is a fixed-height grid for the match view, so a
   document has to opt back into ordinary flow. */

.doc .topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem clamp(0.875rem, 1.8vw, 1.5rem);
  border-bottom: 1px solid var(--rule-strong);
  background: var(--paper);
}

body.doc {
  height: auto;
  display: block;
  overflow: visible;
}

.doc__back { margin-left: auto; text-decoration: none; }

.doc__sheet {
  max-width: 34rem;
  margin: 0 auto;
  padding: clamp(2rem, 6vw, 4rem) clamp(1rem, 4vw, 1.5rem) 5rem;
}

.doc__title {
  font-variation-settings: "wdth" 82;
  font-weight: 800;
  font-size: var(--t-xl);
  letter-spacing: 0.01em;
  margin: 0;
}

.doc__standfirst {
  margin: 0.5rem 0 0;
  padding-bottom: 1.25rem;
  border-bottom: 2px solid var(--ink);
  color: var(--ink-muted);
}

.doc__section {
  padding: 1.5rem 0;
  border-bottom: 1px solid var(--rule);
}

.doc__head {
  margin: 0 0 0.625rem;
  color: var(--stamp-ink);
}

.doc__section p {
  margin: 0 0 0.75rem;
  max-width: 68ch;
}
.doc__section p:last-child { margin-bottom: 0; }

.doc__section a {
  color: var(--stamp-ink);
  text-underline-offset: 0.2em;
}

.doc__stamp {
  margin: 1.5rem 0 0;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------- people
   The player and channel pages. A doc sheet rather than the app shell: these
   are read top to bottom and nothing on them follows a playhead.

   Wider than legal.html, because the entries are a two-column list and the
   prose measure that suits small print is too narrow for a timecode beside a
   sentence. */

.doc__sheet--people { max-width: 46rem; }

/* The figures. Tabular, mono, and set large: the credibility of every claim
   on the page rests on these being read as measurements. */
.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 1px;
  margin: 1.25rem 0 0;
  background: var(--rule);
  border: 1px solid var(--rule);
}

.figures__row {
  background: var(--paper);
  padding: 0.75rem 0.875rem;
}

.figures__value {
  margin: 0.25rem 0 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-lg);
  font-weight: 500;
  color: var(--ink);
}

/* The sentence that defines every figure above it.
   Stamped rather than whispered: it is not small print, it is the unit. */
.caveat {
  margin: 1rem 0 0;
  padding: 0.75rem 0.875rem;
  border-left: 3px solid var(--stamp);
  background: var(--paper-sunk);
  color: var(--ink-muted);
  font-size: var(--t-sm);
  line-height: var(--leading);
}

.chip {
  display: inline-block;
  margin: 0 0.375rem 0.375rem 0;
  padding: 0.1875rem 0.5rem;
  border: 1px solid var(--rule-strong);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  white-space: nowrap;
}

.chip--team {
  border-color: var(--ink);
  color: var(--ink);
  font-weight: 600;
}

/* Shirt numbers, in the order they were worn. */
.shirts {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

.tally { margin: 0; }

/* The entries: a timecode column and a description beside it. The clock is
   the link, because the clock is what the site is for. */
.entries {
  list-style: none;
  margin: 0;
  padding: 0;
}

.entry {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 0 1rem;
  padding: 0.75rem 0;
  border-top: 1px solid var(--rule);
}
.entry:first-child { border-top: 0; }

.entry__clock {
  display: block;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  color: var(--stamp-ink);
}
.entry__clock:hover .entry__time { text-decoration: underline; }

.entry__match {
  display: block;
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.entry__time {
  display: block;
  font-size: var(--t-md);
  text-underline-offset: 0.2em;
}

.entry__kind {
  margin: 0 0 0.25rem;
  color: var(--ink-faint);
}

/* Who said it. The only text on this site that is somebody else's, so the
   credit is set at the weight of a name rather than of a caption. */
.entry__channel {
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
}
.entry__channel:hover { text-decoration: underline; }

.chip--lean { margin: 0 0 0 0.25rem; }

.entry__text { margin: 0; }

/* A pundit's own sentence. Marked as a quotation because it is one: theirs,
   not ours, and the page has to look like it knows the difference. */
.entry__quote {
  margin: 0;
  padding-left: 0.75rem;
  border-left: 2px solid var(--rule-strong);
  color: var(--ink-muted);
  font-size: var(--t-sm);
}

/* The directory, for arriving without a name. */
.directory {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  columns: 2;
  column-gap: 2rem;
}

.directory__link {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.3125rem 0;
  text-decoration: none;
  color: var(--ink);
  break-inside: avoid;
}
.directory__link:hover .directory__name { text-decoration: underline; }

.directory__count {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-sm);
  color: var(--ink-faint);
}

@media (max-width: 40rem) {
  .entry { grid-template-columns: 1fr; gap: 0.25rem; }
  .entry__clock { display: flex; gap: 0.5rem; align-items: baseline; }
  .entry__match, .entry__time { display: inline; }
  .directory { columns: 1; }
}

/* ----------------------------------------------------------- responsive
   Structural, not fluid. Below the two-column threshold the page becomes a
   scrolling document again, with the picture pinned to the top of it. */

@media (max-width: 75rem) {
  .rail { --rail-card-w: min(18rem, 44vw); }
}

@media (max-width: 62rem) {
  body { height: auto; overflow: visible; display: block; }

  /* One column, and the picture has to stay pinned past the end of its own
     section. Sticky is bounded by its containing block, so the stage stops
     being a box at all and the whole shell becomes the frame it sticks in. */
  .shell { display: block; }
  .stage { display: contents; }

  /* A wrapping row again. The grid above exists because the match row is
     permanent chrome in the fixed shell, where every pixel of it is a pixel
     off the picture; here the row scrolls away and the picture is sized off
     36vh regardless, so wrapping costs nothing -- and a title squeezed to
     nothing to save a row that is free is a bad trade. */
  .matchbar {
    display: flex;
    justify-content: space-between;
    gap: 1.5rem;
    flex-wrap: wrap;
  }
  .matchbar__kicker, .matchbar__title { white-space: normal; overflow: visible; }

  .stage__scroll { overflow: visible; display: block; }
  /* Sticks to the top of the window now rather than under a row of chrome
     that no longer exists. The match row scrolls away; the picture does not. */
  .stage__fixed {
    display: block;
    position: sticky;
    top: 0;
    z-index: 6;
    background: var(--paper);
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--rule-strong);
  }
  .stage__frame { margin: 0; }
  .timeline { position: static; border-bottom: 0; padding-inline: 0; }
  .player { width: min(100%, calc(36vh * 16 / 9)); }

  /* The rail is pinned with the picture here: they are one object, and a row
     of moments that scrolls away from the match it indexes is a list again. */
  .rail {
    --rail-card-w: min(20rem, 62vw);
    border-top: 1px solid var(--rule);
    margin-top: 0.5rem;
  }

  .stream {
    display: grid;
    border-top: 1px solid var(--rule-strong);
  }
  .stream__head { position: static; }

  /* A document, not a frame: the column cannot be as tall as the window
     because the window is full of everything above it. The log keeps a
     bottom to stick to by being given a height of its own. */
  .stream__scroll { overflow: visible; display: block; }
  #panel-chat { display: block; }
  .chat__scroll { max-height: 68dvh; }
  #panel-lines { overflow: visible; }

  .colophon__body { max-height: none; }
}

/* ------------------------------------------------------------- the rail

   The moments, on their side under the picture, pushed along by the playhead.
   It was the phone's arrangement, where one column left no room for a list
   beside the footage. It is every width's now for a better reason: a hundred
   moment cards in a column are read instead of the match, and a row the match
   pushes along is read with it.

   The card width is a variable because it is the only thing that changes with
   the viewport. Everything else -- the snap, the centring, the dimming of the
   neighbours -- is the same rule at every size. */

.rail {
  position: relative;
  /* A grid item is as wide as its min-content unless told otherwise, and the
     min-content here is a hundred cards laid end to end. The track's own
     overflow does not save its parent: without this the rail is 36,000px wide,
     the page scrolls sideways, and everything positioned against the rail --
     the steps, the pill -- is dragged off with it. */
  min-width: 0;
  --rail-card-w: min(21rem, 38vw);
}

.rail__track {
  /* The offsetParent for the cards, which is the whole reason it is here.
     `.rail` is positioned (the pill sits over it), so without this a card's
     offsetLeft is measured from the rail and carries the step button's width
     with it -- a third of a card of skew, silently, in every sum that decides
     where the rail is and where it should scroll to. Measured from the track,
     offsetLeft is the scroll coordinate, which is what every one of those sums
     already believed it was reading. */
  position: relative;
  display: flex;
  gap: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* Half the track either side, so the first and last card can sit in the
     reading position like every other one. */
  padding-inline: calc(50% - var(--rail-card-w) / 2);
  scrollbar-width: none;
  height: clamp(7.5rem, 15dvh, 10.5rem);
}
.rail__track::-webkit-scrollbar { display: none; }

.rail__card {
  flex: none;
  width: var(--rail-card-w);
  margin-inline: 0.3125rem;
  scroll-snap-align: center;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  background: var(--paper);
  border: 1px solid var(--rule);
  /* One card is legible at a time. The neighbours are there to say which way
     the match runs, not to be read. */
  opacity: 0.62;
  transition: opacity 160ms var(--ease), border-color 160ms var(--ease);
}
.rail__card--on { opacity: 1; border-color: var(--stamp); }

/* A query dims what it does not match, and never filters: the rail is where
   the match is, and a moment that moved out of it would be a lie about when
   the thing happened. Same figure the timeline's ticks use. */
.rail__card--dim { opacity: 0.3; }
.rail__card--dim.rail__card--on { opacity: 0.55; }

.rail__hit {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 0.3125rem;
  text-align: left;
  background: none;
  border: 0;
  padding: 0.5rem 0.625rem 0.375rem;
  cursor: pointer;
  min-height: 0;
}

.rail__head { display: flex; align-items: baseline; gap: 0.4375rem; flex-wrap: wrap; }

.rail__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-md);
  color: var(--stamp-ink);
  font-weight: 500;
}

.rail__text {
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--ink);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  min-height: 0;
  /* Selectable: a long press is a selection on touch, never a tap. */
  user-select: text;
}
.rail__text--none { color: var(--ink-muted); font-style: italic; }

.rail__foot {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border-top: 1px solid var(--rule);
  padding: 0.3125rem 0.625rem 0.375rem;
  font-size: var(--t-label);
}

/* The lead-in card. Quieter than a moment and shaped like one, so it reads as
   part of the row rather than a banner over it: no border, no clock, and it
   never takes the reading position's mark. */
.rail__intro {
  flex: none;
  width: var(--rail-card-w);
  margin-inline: 0.3125rem;
  scroll-snap-align: center;
  display: grid;
  align-content: center;
  gap: 0.375rem;
  padding: 0.5rem 0.75rem;
  background: var(--paper-sunk);
  border: 1px dashed var(--rule);
  opacity: 0.9;
}

.rail__intro-label { color: var(--ink-muted); }

.rail__intro-text {
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--ink-muted);
}
.rail__intro-text b { color: var(--ink); font-weight: 600; }

/* The step. A thumb flicks and a trackpad scrolls; a mouse does neither well,
   so there is a way to walk the rail by pointer. It browses like every other
   gesture that is not a tap on a card.

   Small and over the track's edge rather than a full-height slab beside it:
   the slab was a band of empty paper with an arrow adrift in the middle of it,
   which read as a rendering fault rather than a control. */
.rail__step {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  font-size: var(--t-sm);
  line-height: 1;
  color: var(--ink-muted);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease),
              opacity 160ms var(--ease);
}
.rail__step--back { left: 0.375rem; }
.rail__step--next { right: 0.375rem; }
.rail__step:hover { color: var(--ink); border-color: var(--stamp); }

/* At either end of the match there is nothing that way to read. */
.rail__step[disabled] { opacity: 0; pointer-events: none; }

/* Where the match is, while a thumb is somewhere else. It is the only way back
   that does not move the match, so it sits over the rail rather than in it. */
.rail__pill {
  position: absolute;
  top: 0.4375rem;
  right: 2.375rem;
  z-index: 3;
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  padding: 0.3125rem 0.5625rem;
  background: var(--stamp);
  color: var(--paper);
  border: 0;
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.rail__pill-clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-transform: none;
  letter-spacing: 0;
}

/* A short phone is a real phone. The card is a fixed shape, so what gives is
   the description: the clock, the kind and the accounts are the parts that
   have to survive at 640px, because they are what the card is for. */
@media (max-height: 46rem) {
  .rail__text { -webkit-line-clamp: 2; }
}

/* The phone's bottom edge. */
.railbar {
  display: none;
  position: fixed;
  inset: auto 0 0 0;
  z-index: 8;
  background: var(--paper);
  border-top: 1px solid var(--rule-strong);
  padding-bottom: env(safe-area-inset-bottom);
}

.railbar__list, .railbar__search {
  background: none;
  border: 0;
  padding: 0.75rem 0.875rem;
  font-family: var(--sans);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
}
.railbar__list { flex: 1; text-align: left; }
.railbar__count {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  letter-spacing: 0;
}
.railbar__search {
  border-left: 1px solid var(--rule);
  font-size: var(--t-md);
}

@media (max-width: 40rem) {
  /* Every rem the chrome takes is a rem the picture loses, and the picture is
     the reason this arrangement exists. The role chip goes; the controls keep
     their words, because a bare glyph is not what anything else here does. */
  .wordmark__role { display: none; }
  .ghost-button, .fixtures-open { padding: 0.3125rem 0.5rem; }
  .stream__head { position: static; }
  .matchbar { padding-bottom: 0.625rem; }
  .sheet { grid-template-columns: 1fr; }

  /* The picture stays pinned and the moments come to it. A card is most of
     the width here, so its neighbours peek rather than sit beside it, and the
     steps go: a thumb has the whole track to flick. */
  .rail { --rail-card-w: 84vw; }
  .rail__step { display: none; }
  .rail__track { height: clamp(8.5rem, 20dvh, 12rem); }
  .railbar { display: flex; }
  .player { width: 100%; }
  .colophon { margin-bottom: 4rem; }

  /* The list, for what a rail is bad at: a sheet over the picture, with the
     match still audible behind it. */
  .stream {
    position: fixed;
    inset: auto 0 0 0;
    /* Over the scrim, under the fixtures drawer: the sheet is lit and the
       page behind it is not. */
    z-index: 45;
    height: 88dvh;
    grid-template-rows: auto minmax(0, 1fr);
    border-top: 1px solid var(--rule-strong);
    transform: translateY(101%);
    transition: transform 220ms var(--ease);
  }
  .stream[data-open] { transform: translateY(0); }
  .stream__scroll { overflow: hidden; display: grid; }
  #panel-chat { display: grid; }
  .chat__scroll { max-height: none; }
  #panel-lines { overflow-y: auto; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------------- the answer

   A typed question is a different act from a typed filter, so its result is a
   different object: one block above the list, dismissible, saying what it was
   asked as well as what it found. */

.answer {
  border: 1px solid var(--stamp);
  background: var(--paper);
}

.answer__working,
.answer__none,
.answer__caveat {
  font-size: var(--t-xs);
  color: var(--ink-muted);
  padding: 0.625rem 0.6875rem;
}
.answer__caveat { color: var(--ink-faint); padding-top: 0; }

.answer__head {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  font-size: var(--t-xs);
  color: var(--ink);
  padding: 0.5rem 0.6875rem;
  border-bottom: 1px solid var(--rule);
}
.answer__head .label { flex: none; }

.answer__close {
  margin-left: auto;
  background: none;
  border: 0;
  color: var(--ink-faint);
  font-size: var(--t-md);
  line-height: 1;
  cursor: pointer;
  padding: 0 0.125rem;
  transition: color 160ms var(--ease);
}
.answer__close:hover { color: var(--ink); }

.answer__picks { display: grid; }
.answer__picks li + li { border-top: 1px solid var(--rule); }

.answer__pick {
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr);
  gap: 0.625rem;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0.5rem 0.6875rem;
  cursor: pointer;
  align-items: baseline;
  transition: background-color 160ms var(--ease);
}
.answer__pick:hover { background: var(--stamp-wash); }
.answer__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-sm);
  color: var(--stamp-ink);
}
.answer__why { font-size: var(--t-xs); line-height: 1.45; }

.input__ask {
  position: absolute;
  right: 0.3125rem;
  background: var(--paper-sunk);
  border: 1px solid var(--rule-strong);
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.25rem 0.5rem;
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.input__ask:hover { color: var(--stamp-ink); border-color: var(--stamp); }

/* ------------------------------------------------------- the pundit meter

   Two independent numbers. Regard is where the mark sits; disagreement is the
   bar under it. Ten people can agree a decision was terrible, and ten people
   can be evenly split on something nobody minded, and one gauge cannot say
   both. */

.verdict {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-left: auto;
  order: 7;
  flex: none;
  cursor: help;
}

.verdict__scale {
  position: relative;
  width: 2.75rem;
  height: 3px;
  background: var(--rule-strong);
}

.verdict__mark {
  position: absolute;
  top: -2px;
  width: 3px;
  height: 7px;
  background: currentColor;
  transform: translateX(-1px);
}

/* How much of the room was pulling the other way, drawn from the left so it
   reads as a share rather than a position. */
.verdict__spread {
  position: absolute;
  inset-block: 0;
  left: 0;
  background: var(--ink-faint);
  opacity: 0.5;
}

.verdict__word {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
}
.verdict--good  { color: var(--ok); }
.verdict--poor  { color: var(--stamp-ink); }
.verdict--level { color: var(--ink-faint); }

/* --------------------------------------------------- the call, as an index

   Seconds, not sentences. The commentary is what the index is built from, not
   what the page hands out. */

.stamps { padding: 0.25rem 0 0.75rem; }
.stamps__head {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 0.5rem;
}
.stamps__grid { display: flex; flex-wrap: wrap; gap: 0.25rem; }

.stamp-hit {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
  color: var(--ink-muted);
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: 0.1875rem 0.375rem;
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease),
              background-color 160ms var(--ease);
}
.stamp-hit:hover {
  color: var(--ink);
  border-color: var(--stamp);
  background: var(--stamp-wash);
}

/* A moment the model could not describe. Better an admission than the call. */

.stamp-hit--on { color: var(--ink); border-color: var(--stamp); background: var(--stamp-wash); }


/* -------------------------------------------------------- an account, over

   A clip is evidence for a moment and not the moment, so it never takes the
   match's frame. It opens over the page with the match paused behind it, and
   closing it puts the match back exactly where it was.

   A native dialog: the focus trap, Escape and the backdrop are three things
   the platform does properly, and doing them again by hand is how a page ends
   up with a modal you cannot get out of. */

.watch {
  margin: auto;
  padding: 0;
  width: min(44rem, 92vw);
  background: var(--paper);
  border: 1px solid var(--stamp);
  color: var(--ink);
}
.watch::backdrop { background: oklch(0.148 0.010 68 / 0.72); }

.watch__box { position: relative; display: grid; }

.watch__who {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 2.25rem 0.5rem 0.75rem;
  border-bottom: 1px solid var(--rule);
}
.watch__channel { font-size: var(--t-sm); font-weight: 600; }

.watch__frame { width: 100%; aspect-ratio: 16 / 9; background: var(--screen); }
.watch__frame iframe { width: 100%; height: 100%; border: 0; display: block; }

.watch__said {
  font-size: var(--t-xs);
  line-height: 1.55;
  color: var(--ink-muted);
  padding: 0.625rem 0.75rem 0.375rem;
}
.watch__said::before { content: "\201c"; }
.watch__said::after { content: "\201d"; }

.watch__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0 0.75rem 0.625rem;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}
.watch__out { color: var(--stamp-ink); text-decoration: none; }
.watch__out:hover { color: var(--ink); }

.watch__close {
  position: absolute;
  top: 0.25rem;
  right: 0.375rem;
  font-size: var(--t-lg);
  line-height: 1;
  color: var(--ink-faint);
  background: none;
  border: 0;
  cursor: pointer;
}
.watch__close:hover { color: var(--ink); }
/* ============================================================ find.html
 *
 * The one page on the site that scrolls. index.html is a fixed frame because
 * the footage has to stay on screen while the commentary runs past it; here
 * there is no one match and nothing to keep on screen, so a results page is a
 * document and behaves like one.
 *
 * The arrangement is Google's -- box in the middle, then box at the top with
 * the answer under it -- and one header does both jobs rather than two
 * headers swapping. Everything it is made of is the site's: the same tokens,
 * the same hairlines, the same stamp, the same two families. */

/* A column with the results taking the slack, so the footer sits on the
   bottom of the window rather than halfway up it on a short page. */
body.arch {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: auto;
  min-height: 100dvh;
  overflow: visible;
  background: var(--paper);
}

.arch__head {
  display: grid;
  gap: 1rem;
  align-items: center;
  padding: 1.25rem clamp(1rem, 4vw, 2.5rem);
  border-bottom: 1px solid var(--rule);
}

.arch__mast { text-decoration: none; color: var(--ink); display: block; }
.arch__mast-name {
  display: block;
  font-family: var(--sans);
  font-variation-settings: "wdth" 76;
  font-weight: 800;
  letter-spacing: 0.01em;
  line-height: 1;
}
.arch__mast-role {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-top: 0.25rem;
}

.arch__form { display: flex; min-width: 0; }
.arch__form input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: var(--t-base);
  font-family: var(--sans);
  color: var(--ink);
  background: var(--paper-raised);
  border: 1px solid var(--rule-strong);
  border-right: 0;
  padding: 0.5rem 0.75rem;
}
.arch__form input::placeholder { color: var(--ink-faint); }
.arch__form input:focus-visible { outline: 2px solid var(--stamp); outline-offset: 2px; }

.arch__ask {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--stamp);
  border: 1px solid var(--stamp);
  padding: 0 1rem;
  cursor: pointer;
  transition: background-color 160ms var(--ease);
}
.arch__ask:hover { background: var(--stamp-ink); border-color: var(--stamp-ink); }

.arch__tools { display: flex; gap: 0.5rem; justify-content: flex-end; }

/* At rest the head is the page: a column in the upper third, the way a search
   page has looked for twenty-five years. Not centred vertically -- the list of
   what is actually in the index goes underneath and has to be above the fold,
   because a box with nothing named behind it invites the question it cannot
   answer. */
body.arch[data-state="rest"] .arch__head {
  position: relative;
  grid-template-columns: minmax(0, 34rem);
  justify-content: center;
  justify-items: center;
  text-align: center;
  gap: 1.25rem;
  padding-top: clamp(2.5rem, 12vh, 6rem);
  padding-bottom: 2rem;
  border-bottom: 0;
}
body.arch[data-state="rest"] .arch__mast-name { font-size: 2.25rem; }
body.arch[data-state="rest"] .arch__form { width: 100%; }

/* Utility, in the corner where utility goes. Under the box they read as two
   more things to press, which is the one place on the page where there should
   only be one. */
body.arch[data-state="rest"] .arch__tools {
  position: absolute;
  top: 1.25rem;
  right: clamp(1rem, 4vw, 2.5rem);
}
/* At rest there is nothing to scope and nothing to count: the list below is
   the whole archive, which is the answer to the only question a reader who
   has not typed anything is asking. */
body.arch[data-state="rest"] .arch__scopes,
body.arch[data-state="rest"] .arch__count { display: none; }

/* With a question on the page the head becomes a rule across the top and the
   question stays in it, which is the one thing this page borrows outright. */
body.arch[data-state="results"] .arch__head {
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 1.5rem;
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--paper);
}
body.arch[data-state="results"] .arch__mast-name { font-size: var(--t-base); }
/* The list is the page in both states, so nothing hides it here. What
   changes is what is in it: at rest every match, under a question the ones
   that answered with their moments underneath. A separate results view would
   be a second place the archive lives and a place to be returned from. */
body.arch[data-state="results"] .arch__index-title { color: var(--ink-faint); }

@media (max-width: 46rem) {
  body.arch[data-state="results"] .arch__head {
    grid-template-columns: minmax(0, 1fr);
    position: static;
  }
  body.arch[data-state="results"] .arch__tools { justify-content: flex-start; }
}

/* One measure for the whole page. The scopes, the answer and the list are
   three claims about the same question and they have to share a left edge; a
   list centred inside a full-width column reads as a different document. */
.arch__body {
  max-width: 52rem;
  margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2.5rem) 3rem;
}

/* ----------------------------------------------------------- the index */


.arch__band {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  border-bottom: 1px solid var(--rule-strong);
  padding-bottom: 0.375rem;
}
.arch__index-title { border: 0; padding: 0; }

/* The fixture list.
 *
 * One grid template, declared once and worn by the column heads and by every
 * row, because heads that do not line up with what they name are decoration.
 * The rows are hairline-ruled rather than carded: DESIGN.md keeps cards for
 * moments, which are discrete objects, and a match in a fixture list is a
 * row -- which is what the reference object does with it. */
.programme,
.programme__head {
  --programme-cols: 5.5rem minmax(0, 1fr) 7.5rem 4.5rem 4rem 1rem;
  --programme-gap: 0.75rem;
}

.programme__head {
  display: grid;
  grid-template-columns: var(--programme-cols);
  gap: var(--programme-gap);
  padding: 0.5rem 0.25rem 0.375rem;
  border-bottom: 1px solid var(--rule-strong);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Measured, both themes: --ink-muted is 6.2:1 on paper and 6.7:1 in
     floodlight. --ink-faint reads as the right weight for a column head and
     is 3.2:1 and 3.8:1 -- a UI boundary, not text, and these are text. */
  color: var(--ink-muted);
}
.programme__head-score, .programme__head-n { text-align: right; }

.programme { list-style: none; margin: 0; padding: 0; }
.programme__row-group { border-bottom: 1px solid var(--rule); }

.programme__row {
  display: grid;
  grid-template-columns: var(--programme-cols);
  gap: var(--programme-gap);
  align-items: baseline;
  padding: 0.75rem 0.25rem;
  font-size: var(--t-sm);
  color: var(--ink);
  text-decoration: none;
  transition: background-color 160ms var(--ease);
}
.programme__row:hover { background: var(--paper-raised); }
.programme__row:focus-visible { outline: 2px solid var(--stamp); outline-offset: 2px; }

.programme__date, .programme__score, .programme__n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.programme__date { color: var(--ink-muted); font-size: var(--t-xs); }
.programme__where {
  color: var(--ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The teams carry their colour on the code itself and never instead of it --
   the three letters are the label, and the tint is a second reading of the
   same fact. */
.programme__teams { font-weight: 700; letter-spacing: 0.04em; }
.programme__teams i { font-style: normal; color: var(--ink-faint); font-weight: 400; }
.programme__teams b[data-team="NZL"] { color: var(--nzl); }
.programme__teams b[data-team="FRA"] { color: var(--fra); }
.programme__teams b[data-team="RSA"] { color: var(--rsa); }

/* The scoreline is the one stamped object on this page, so it is the one
   thing set larger than the row it sits in. */
.programme__score {
  text-align: right;
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.programme__n { text-align: right; color: var(--ink-muted); font-size: var(--t-xs); }

/* Held or live, on the library only.

   The public site never draws this -- `public_index` strips the flag, because
   there every row is released by definition and a badge saying so on all of
   them is noise. So these rules only ever match on 80.const, and the chip sits
   inside the description cell rather than taking a grid column of its own:
   a seventh column would change the shape of a row the site also uses. */
.programme__status {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.05rem 0.4rem;
  border: 1px solid currentColor;
  border-radius: 2px;
  font-family: var(--mono);
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  vertical-align: 0.1em;
  opacity: 0.85;
}
.programme__status--live { color: var(--stamp); }
.programme__status--held { color: var(--ink-muted); border-style: dashed; }

/* The unit, for the width where the column head is gone. Above it the head
   says MOMENTS and a second label in every row would be noise; below it a
   bare figure under a scoreline is a number with nothing to be. */
.programme__unit { display: none; }

/* The direction of travel, without a word for it. It moves on hover rather
   than appearing, because a row that grows an affordance under the cursor is
   a row that was lying about being a link. */
.programme__go {
  /* aria-hidden, and a boundary rather than text: 3.2:1 is the bar it has to
     clear and does. It is never the only thing saying the row is a link --
     the row is an <a>. */
  color: var(--ink-faint);
  transition: transform 160ms var(--ease), color 160ms var(--ease);
}
.programme__row:hover .programme__go,
.programme__row:focus-visible .programme__go {
  color: var(--stamp);
  transform: translateX(2px);
}

/* A match that answered keeps its row and grows its moments underneath. The
   wash is the same one a selected row wears everywhere else on the site. */
.programme__row-group--hit { border-bottom-color: var(--rule-strong); }
.programme__row-group--hit > .programme__row { background: var(--stamp-wash); }
.programme__row-group--hit > .programme__row:hover { background: var(--stamp-wash); }
.programme__moments {
  list-style: none;
  margin: 0 0 1.25rem 0.25rem;
  padding: 0 0 0 1rem;
  border-left: 1px solid var(--rule-strong);
}

.arch__note {
  font-size: var(--t-xs);
  color: var(--ink-muted);
  margin-top: 0.75rem;
  max-width: 46ch;
}

/* Below this the row is two lines rather than six columns. A fixture list
   that scrolls sideways is not a fixture list. */
@media (max-width: 40rem) {
  .programme__head { display: none; }
  .programme__row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: 0.25rem;
  }
  .programme__date { grid-column: 1; grid-row: 1; }
  .programme__teams { grid-column: 2; grid-row: 1; text-align: right; }
  .programme__score { grid-column: 2; grid-row: 2; }
  .programme__n { grid-column: 1; grid-row: 2; text-align: left; }
  .programme__where { grid-column: 1 / -1; grid-row: 3; white-space: normal; }
  .programme__unit { display: inline; }
  .programme__go { display: none; }
  .programme__moments { padding-left: 0.75rem; }
}

/* ----------------------------------------------------------- the scopes */

.arch__scopes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--rule);
}

.scope {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink-muted);
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: 0.25rem 0.5rem;
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease),
              background-color 160ms var(--ease);
}
.scope:hover { color: var(--ink); border-color: var(--rule-strong); }
.scope--on { color: var(--ink); border-color: var(--stamp); background: var(--stamp-wash); }
.scope__where { color: var(--ink-faint); }
.scope--on .scope__where { color: var(--stamp-ink); }

/* ----------------------------------------------------------- the answer

   The read answer sits above the list rather than in it, because it is a
   different claim: these are the ones a model says answer the question, and
   the grid underneath is everything the keywords found. When the reading step
   is rate-limited, out of budget or simply absent -- which is its usual state
   in public -- this block says so and the grid is still the page. */

.arch__answer {
  border-bottom: 1px solid var(--rule-strong);
  padding: 1rem 0;
  max-width: 52rem;
}

.arch__asked, .arch__reading {
  font-size: var(--t-sm);
  color: var(--ink);
  margin-bottom: 0.5rem;
}
.arch__reading { color: var(--ink-muted); }

.arch__picks { list-style: none; display: grid; gap: 0.25rem; }

.pick {
  display: grid;
  grid-template-columns: 4rem 7rem minmax(0, 1fr);
  gap: 0.75rem;
  align-items: baseline;
  width: 100%;
  text-align: left;
  font-size: var(--t-sm);
  color: var(--ink);
  background: none;
  border: 0;
  border-left: 2px solid var(--stamp);
  padding: 0.375rem 0.5rem;
  cursor: pointer;
  transition: background-color 160ms var(--ease);
}
.pick:hover { background: var(--stamp-wash); }
.pick__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--stamp-ink);
}
.pick__where {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink-muted);
}

.arch__none, .arch__caveat {
  font-size: var(--t-xs);
  color: var(--ink-muted);
}
.arch__caveat { margin-top: 0.5rem; font-family: var(--mono); }

/* Three columns will not fit, so the sentence goes under the two labels
   rather than into the column the clock sits in -- which is four rems wide
   and turned a paragraph into a stack of single words. */
@media (max-width: 36rem) {
  .pick {
    grid-template-columns: auto minmax(0, 1fr);
    row-gap: 0.25rem;
  }
  .pick__why { grid-column: 1 / -1; }
}

/* ------------------------------------------------------------ the tiles */

.arch__count {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* The moments of one match, under its row.
 *
 * Much narrower than a full-width gallery, and that is the whole point: these
 * are indented inside a fixture, the row above them is their heading, and a
 * picture that outgrows the row it belongs to stops reading as belonging to
 * it. Two tiles across an eight-hundred-pixel measure put the footage back in
 * charge of the page, which is the ESPN arrangement PRODUCT.md refuses. */
.programme__moments {
  display: grid;
  padding-top: 0.25rem;
}

/* A hit is a row, and the row is the whole measure.
 *
 * It was a gallery: `auto-fill` at `minmax(11.5rem, 1fr)`, which over a 52rem
 * page resolves to three columns of about fifteen rem. That reads well with
 * six answers and badly with one -- a single hit drew one narrow column, left
 * two thirds of the line empty and wrapped its description at about thirty
 * characters, so a question that had been answered looked like a question that
 * had not. Most questions here have one or two answers.
 *
 * The picture is fixed at 9rem rather than fluid, which is the same argument
 * the gallery was making and keeps: a still that grows takes the page back off
 * the sentence. Here the sentence gets the measure and the still stays a way
 * in. */
.tile {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  gap: 0 0.875rem;
  align-items: start;
  padding: 0.75rem 0;
  min-width: 0;
}
.tile + .tile { border-top: 1px solid var(--rule); }

/* Playing is a different task from scanning, so the row becomes a player: the
   picture takes the width and the words go under it. A 9rem embed is a
   thumbnail with controls, which is nobody's idea of watching. */
.tile--playing { grid-template-columns: minmax(0, 1fr); }

.tile__body {
  display: grid;
  gap: 0.25rem;
  min-width: 0;
  padding-top: 0.0625rem;
}

/* The picture: a lit rectangle in a dim frame with the timecode stamped into
   a corner, which is how DESIGN.md has every screen on this site behave.
   These rules were the gallery's until the shelf became the chat and the
   gallery went with it; they are the tile's own now rather than a second
   block's borrowed. */

.tile__play {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--rule-strong);
  background: var(--screen);
  aspect-ratio: 16 / 9;
  cursor: pointer;
  overflow: hidden;
  transition: border-color 160ms var(--ease);
}
.tile__play:hover { border-color: var(--stamp); }

.tile__play img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.88;
  transition: opacity 160ms var(--ease), transform 220ms var(--ease);
}
.tile__play:hover img { opacity: 1; transform: scale(1.03); }

.tile__cue {
  position: absolute;
  left: 0; bottom: 0;
  background: oklch(0.148 0.010 68 / 0.82);
  color: var(--screen-ink);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  padding: 0.125rem 0.3125rem;
}

.tile__glyph {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--screen-ink);
  font-size: 1.5rem;
  line-height: 1;
  opacity: 0.75;
  text-shadow: 0 2px 8px oklch(0.148 0.010 68 / 0.8);
  transition: opacity 160ms var(--ease), transform 160ms var(--ease);
  pointer-events: none;
}
.tile__play:hover .tile__glyph { opacity: 1; transform: scale(1.12); }

.tile__frame { width: 100%; aspect-ratio: 16 / 9; border: 1px solid var(--stamp); }
.tile__frame iframe { width: 100%; height: 100%; border: 0; display: block; }

/* One line of facts: the clock, then what happened, then the doubt, then the
   way out. Wraps rather than truncates -- a player's name is the thing the
   reader came with, and an ellipsis through it is worse than a second line. */
.tile__what {
  display: flex;
  flex-wrap: wrap;
  gap: 0.125rem 0.5rem;
  align-items: baseline;
  font-size: var(--t-xs);
}
.tile__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--ink);
  flex: none;
}
.tile__kind { color: var(--ink-muted); }

/* Shown only when the model was not sure, and marked when it was barely sure
   at all. High confidence gets no ink: a figure printed on everything is a
   figure nobody reads. */
.tile__sure {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  flex: none;
}
.tile__sure--low { color: var(--warn); }

.tile__said {
  font-size: var(--t-sm);
  line-height: var(--leading);
  color: var(--ink);
  max-width: 68ch;
}

/* Pushed to the end of the facts line, which is where the fixture row above
   keeps its own way out. */
.tile__out {
  margin-left: auto;
  flex: none;
  font-family: var(--mono);
  color: var(--ink-faint);
  text-decoration: none;
  transition: color 160ms var(--ease);
}
.tile__out:hover { color: var(--stamp-ink); }

/* Declared here rather than with the rest of the narrow-width rules further
   up: a media query adds no specificity, so the same selector stated before
   `.tile` loses to it on source order and the row never stacks. This block
   has to sit after the thing it overrides.

   No measure left to put a sentence beside a picture, so the still takes the
   width it is worth on a phone and the words go under it. */
@media (max-width: 40rem) {
  .tile { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
  .tile__body { padding-top: 0; }
}

/* --------------------------------------------------------- nothing found

   Teaching the vocabulary rather than saying "no results": the index is
   descriptions and the names in them, so the useful reply to a miss is what
   a hit looks like. */

.arch__empty {
  grid-column: 1 / -1;
  max-width: 46ch;
  font-size: var(--t-sm);
  color: var(--ink-muted);
  display: grid;
  gap: 0.5rem;
}

.arch__try {
  font-family: var(--mono);
  font-size: inherit;
  color: var(--stamp-ink);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  padding: 0;
  cursor: pointer;
}
.arch__try:hover { border-color: var(--stamp); }

/* Most opened (SPEC 6.8): a league table. Ruled rows, the count in tabular
   figures at the right edge, the rank at the left, and every row a link. */
.arch__shared { margin-top: 2.5rem; }
.shared__compiled {
  margin: 0.375rem 0.25rem 0.75rem;
  font-size: var(--t-xs);
  color: var(--ink-muted);
}
.shared { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule-strong); }
.shared__row-group { border-bottom: 1px solid var(--rule); }
.shared__row {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr) 4rem;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.75rem 0.25rem;
  font-size: var(--t-sm);
  color: var(--ink);
  text-decoration: none;
  transition: background-color 160ms var(--ease);
}
.shared__row:hover { background: var(--paper-raised); }
.shared__row:focus-visible { outline: 2px solid var(--stamp); outline-offset: 2px; }
.shared__rank, .shared__n, .shared__clock {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.shared__rank { color: var(--ink-faint); }
.shared__n { text-align: right; font-size: var(--t-md); }
.shared__what { display: grid; gap: 0.125rem; min-width: 0; }
.shared__head { display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; }
.shared__kind { color: var(--ink-muted); }
.shared__text { color: var(--ink); }
.shared__match { color: var(--ink-muted); font-size: var(--t-xs); }

.arch__foot {
  border-top: 1px solid var(--rule);
  padding: 1.25rem clamp(1rem, 4vw, 2.5rem);
  font-size: var(--t-xs);
  color: var(--ink-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: space-between;
}
.arch__foot a { color: var(--ink-muted); }

/* ------------------------------------------------------------- timeline
   The other page of the same programme.

   `match.html` is a fixed-height frame built around a player and it does not
   scroll on purpose: the footage has to stay on screen while the commentary
   runs past it. This is its complement, and the arrangement is the opposite
   for the same reason. There is no picture to keep, so the page is a document
   again -- the running order, ruled, read top to bottom, linkable at any line,
   and legible on a phone with no video in it at all.

   Three columns and the middle one does the reading. The clock sits in the
   left margin where a fixture list puts it, tabular and right-aligned so a
   column of minutes reads as a column; the scoreline stamps the right margin
   as it moves, which is the one thing a printed record does that the file it
   is built from cannot. Rules between the rows rather than cards: a card is
   for a discrete object you flick past, and this is a list that is meant to be
   read in order. */

body.doc--timeline { background: var(--paper); }

/* Four controls and a wordmark do not fit a 360px header, and a header that
   scrolls sideways is a header with a control nobody can reach. */
.doc--timeline .topbar { flex-wrap: wrap; row-gap: 0.5rem; }

.tl__running {
  position: sticky;
  top: 0;
  z-index: 4;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.4375rem clamp(0.875rem, 1.8vw, 1.5rem);
  border-bottom: 1px solid var(--rule-strong);
  background: var(--paper-sunk);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

/* The masthead scrolls away after two rows and a hundred more follow it, so
   the bar says which match as well as where in it. Condensed and quiet: it is
   a rule with a label on it, not a second title. */
.tl__running-match {
  font-family: var(--sans);
  font-variation-settings: "wdth" 78;
  font-weight: 700;
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tl__running-at {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex: none;
}

.tl__running-clock {
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  color: var(--ink-muted);
}

.tl__running-score {
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--stamp-ink);
}

.tl__masthead {
  max-width: 52rem;
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 3.25rem) clamp(1rem, 4vw, 1.5rem) 0;
}

.tl__kicker { margin: 0 0 0.375rem; }

/* Condensed and stamped, the way the fixture is set on a programme cover. A
   band rather than a headline: it names the match and then gets out of the
   way of the hundred lines under it. */
.tl__title {
  margin: 0;
  font-variation-settings: "wdth" 78;
  font-weight: 800;
  font-size: clamp(1.75rem, 4.5vw, 2.5rem);
  line-height: 1.04;
  letter-spacing: 0.005em;
  text-transform: uppercase;
}

.tl__where {
  margin: 0.5rem 0 0;
  color: var(--ink-muted);
  font-size: var(--t-sm);
  max-width: 60ch;
}

.tl__final {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin: 1.25rem 0 0;
  padding-top: 0.75rem;
  border-top: 2px solid var(--ink);
}

.tl__final-score {
  font-family: var(--mono);
  font-size: var(--t-xl);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--stamp-ink);
}

.doc__sheet--timeline {
  max-width: 52rem;
  padding-top: 1.5rem;
}

.tl {
  list-style: none;
  margin: 0;
  padding: 0;
}

.tl__row {
  display: grid;
  grid-template-columns: 3.75rem minmax(0, 1fr) 4.25rem;
  gap: 0 clamp(0.75rem, 2vw, 1.5rem);
  padding: 0.9375rem 0;
  border-bottom: 1px solid var(--rule);
  scroll-margin-top: 4rem;
}

/* A score is the only row that changes the state of the match, so it is the
   only one set at full strength. Everything else is context between them. */
.tl__row--scoring .tl__clock,
.tl__row--scoring .tl__kind { color: var(--ink); }
.tl__row--scoring .tl__kind { font-weight: 700; }

/* The line the address names. Not `:target`: the browser resolves a fragment
   while this page is still an empty list waiting on a fetch, and having
   failed once it never looks again -- so the hash sits in the address bar and
   nothing is marked, which is a link that appears to work and lands the
   reader with no idea which line was meant. The page keeps the mark itself. */
.tl__row--linked {
  background: var(--stamp-wash);
  box-shadow: 0 0 0 0.75rem var(--stamp-wash);
}
.tl__row--linked .tl__clock a { color: var(--stamp-ink); font-weight: 600; }

.tl__clock {
  font-family: var(--mono);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--ink-muted);
  padding-top: 0.1875rem;
}

.tl__clock a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.tl__clock a:hover { color: var(--stamp-ink); border-bottom-color: var(--stamp); }

.tl__marks {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4375rem;
  margin: 0 0 0.25rem;
}

.tl__kind {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.tl__who {
  font-size: var(--t-sm);
  font-weight: 600;
}

/* Team identity never rests on colour: the code is the label, and the colour
   is a second reading of the same three letters. */
.tl__side {
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 0.0625rem 0.3125rem;
  border: 1px solid currentColor;
  color: var(--ink-muted);
}
.tl__side[data-team="NZL"] { color: var(--nzl); }
.tl__side[data-team="RSA"] { color: var(--rsa); }
.tl__side[data-team="FRA"] { color: var(--fra); }

.tl__said {
  margin: 0;
  max-width: 62ch;
  line-height: var(--leading);
}

/* A line nobody wrote a description for. Set apart rather than dressed up:
   the reader has to be able to tell a sentence somebody wrote from a sentence
   assembled out of the two fields that were filled in. */
.tl__said--thin {
  color: var(--ink-muted);
  font-style: italic;
}

.tl__foot {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.875rem;
  margin: 0.375rem 0 0;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.tl__doubt { font-variant-numeric: tabular-nums; }
.tl__doubt--low { color: var(--warn); }

.tl__origin {
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.tl__out {
  color: var(--ink-muted);
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-bottom: 1px solid var(--rule-strong);
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.tl__out:hover { color: var(--stamp-ink); border-bottom-color: var(--stamp); }

/* The stamp. Only on the rows that moved it, so the right margin reads as a
   sparse column of scorelines rather than a repeated total. */
.tl__score {
  font-family: var(--mono);
  font-size: var(--t-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--stamp-ink);
  padding-top: 0.125rem;
}

.tl__break {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.5rem 0;
  padding: 0.5rem 0;
  border-top: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
}

.tl__break-score {
  font-family: var(--mono);
  font-size: var(--t-md);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--stamp-ink);
}

/* What the index does not have. At the foot because it is the last thing
   read, and present on every match because an artefact that only admits a gap
   when there is a bad one is an artefact nobody can calibrate. */
.tl__ledger {
  margin-top: 2.5rem;
  padding: 1.25rem 0 0;
  border-top: 1px solid var(--rule-strong);
}

.tl__ledger-head { margin: 0 0 0.75rem; color: var(--stamp-ink); }

.tl__ledger p {
  margin: 0 0 0.625rem;
  max-width: 62ch;
  font-size: var(--t-sm);
  color: var(--ink-muted);
}
.tl__ledger b { color: var(--ink); font-variant-numeric: tabular-nums; }

.tl__ledger-foot {
  margin-top: 1rem;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.tl__ledger-foot a { color: var(--stamp-ink); text-underline-offset: 0.2em; }

.tl__choose {
  list-style: none;
  margin: 0;
  padding: 0;
}

.tl__choose a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9375rem 0;
  border-bottom: 1px solid var(--rule);
  color: inherit;
  text-decoration: none;
  transition: color 160ms var(--ease);
}
.tl__choose a:hover { color: var(--stamp-ink); }

.tl__choose-title { font-weight: 600; }

.tl__choose-meta {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
}

/* The clock leaves the margin and joins the line it belongs to, and the
   scoreline goes with it. A 3.75rem gutter on a 360px screen is a tenth of the
   width spent on five characters. */
@media (max-width: 40rem) {
  .tl__row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.25rem 0.75rem;
  }
  .tl__clock {
    grid-row: 1;
    grid-column: 1;
    text-align: left;
    padding-top: 0;
  }
  .tl__score {
    grid-row: 1;
    grid-column: 2;
    padding-top: 0;
  }
  .tl__body {
    grid-row: 2;
    grid-column: 1 / -1;
  }
}
