/* CS2:nuts, design language inspired by agentation.com */

:root {
  /* ---------- spacing scale ----------
     34 distinct spacing values were in use and a third of them sat off any
     grid: 3px, 5px, 6px, 7px, 9px, 10px, 14px, 18px. That is not a scale,
     it is accumulation, and it is why related and unrelated things ended up
     the same distance apart.

     Base 4px. The step from one rung to the next is large enough to read as
     deliberate, which is the whole point: proximity only groups things if
     the gaps differ enough to be seen.

       1  4px   inside one element, label to value
       2  8px   between elements of the same group
       3  12px  padding inside a card or row
       4  16px  between groups inside a section
       5  24px  between cards
       6  32px  between sub-sections
       7  48px  between sections
       8  64px  major page divisions
     Half steps exist for hairline work only, not for layout.

     ---------- WHICH RUNG, BY RELATIONSHIP ----------
     Pick the rung from what the gap MEANS, not by eye. Every spacing bug
     shipped in this file came from styling a new component's margins from
     scratch instead of naming the relationship:

       heading -> its own subtitle or lede      --sp-2
       heading -> its content                   --sp-3
       label -> its value                       --sp-1
       row -> row inside one list               --sp-1 (see --pad-dense)
       card -> card in a grid                   --sp-4
       block -> block inside a section          --sp-4
       section -> section, both sides equal     --sp-7

     THREE RULES THAT WOULD HAVE CAUGHT EVERY BUG SO FAR:

     1. A new component sets NO margin on anything that already has one.
        `.ev-page h2` gives every heading its top margin, so a new heading
        inside it needs nothing. .dim-title redeclared it as 0 and lost the
        section boundary.

     2. Never a negative margin to pull something closer. It reads as a fix
        and behaves as an overlap: .ev-soon-note sat 4px INSIDE its heading.
        If a gap is too big, the rung below it is the answer.

     3. Space goes on ONE side of a boundary, not both. Pick the element that
        owns the relationship, usually the lower one, and leave the other at
        zero. Two elements both claiming a gap is how 28px appears in a file
        whose scale has no 28.

     Enforced by scripts/validate.py, which counts raw rem and px values in
     margin, padding and gap and fails if the count rises. */
  --sp-1: .25rem;
  --sp-2: .5rem;
  --sp-3: .75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;

  /* ---------- interior padding ----------
     The gap INSIDE a container, as opposed to the gap between containers,
     which the rungs above cover. Two tokens rather than a value per
     component: "the cards feel tight" is then one line to change, not the
     thirteen separate declarations this replaced.

       card, a bordered block that holds several facts    --pad-card
       row, one line in a list of many                    --pad-row

     Both tighten on phones, where the horizontal rung costs reading width
     that a narrow screen does not have to give. */
  --pad-card: var(--sp-4) var(--sp-5);
  --pad-row: var(--sp-3) var(--sp-4);
  /* Dense rows inside a card, where the row is a line of data rather than a
     block. The old guidance here was "2px to --sp-1", which put the map form
     at 4px of whitespace between rows against 7px between whole series. The
     Law of Proximity is a ratio, not a pixel count: at 1.75x the groups stop
     reading as groups and the hairline ends up doing all the work. This rung
     puts it near 3x. */
  --pad-dense: var(--sp-1) 0;
  /* A 24px badge and its label. This was 4, 6, 7, 8 and 10px across eight
     components rendering the identical pair, which is what made the icon
     read as crowding the name in some places and drifting from it in
     others. Nobody chose five values; they accumulated one component at a
     time. One third of the badge is the ratio Apple and Family both hold,
     and for 24px that is 8. */
  --gap-badge: var(--sp-2);

  /* ---------- optical scaling ----------
     Reviewed against family.co, whose craft here is measurable: line height
     and tracking are functions of size rather than per component choices.
     Large text needs less leading and tighter tracking because the eye
     already has the letterforms; small text needs the opposite.

     This file had ten different line heights doing the work of four, with
     15px text set at 1.25, 1.55 and 1.6 in different places.

       display, 24px and up      --lh-tight  + --ls-display
       heading, 18 to 23px       --lh-snug   + --ls-heading
       UI text and rows          --lh-body   + no tracking
       running prose             --lh-prose  + no tracking */
  --lh-tight: 1.1;
  --lh-snug: 1.3;
  --lh-body: 1.5;
  --lh-prose: 1.65;
  --ls-display: -.02em;
  --ls-heading: -.015em;

  /* ---------- motion ----------
     Reviewed against family.co, which runs two duration tiers and one
     signature curve, cubic-bezier(.19, 1, .22, 1), rather than the browser
     default. This file had nine durations and reached for plain `ease` 72
     times, which is the default and reads as nobody having picked it.

     The curve is easeOutExpo: it leaves immediately and settles long, so a
     hover feels answered at once while still arriving softly. Same reason
     the durations are short. Anything over about .2s on a hover reads as
     lag on a page whose job is looking things up.

       --dur-micro  hover ink, small state flips
       --dur-base   the default for anything visible
       --dur-slow   panels, overlays, things that travel

     Every one of these is disabled under prefers-reduced-motion, which this
     file already honors in 14 places. */
  --dur-micro: .12s;
  --dur-base: .18s;
  --dur-slow: .28s;
  /* Entrances only, and far longer than a state flip. Measured on
     family.co: their reveals run a full 1s on this same curve, across 56
     elements. That does not feel slow because easeOutExpo is roughly 90%
     complete a third of the way in and spends the rest settling, so the
     eye reads it as immediate and smooth rather than as waiting. At .42s
     the cascade below was over before it registered. */
  --dur-enter: .9s;
  --ease-out: cubic-bezier(.19, 1, .22, 1);
  /* Layout that DISPLACES other content needs an even curve, not the
     front loaded one. easeOutExpo is 78% done a fifth of the way in, which
     is what makes an arrival feel immediate, and is exactly wrong when the
     page below is being pulled up with it: the notice collapsed 85px to
     23px in 63ms and the picker snapped rather than travelled. This curve
     spends its time in the middle, where the eye can follow it. */
  --ease-move: cubic-bezier(.4, 0, .2, 1);
  /* Exits displace, so they get their own tier. Longer than a state flip,
     well short of an entrance, which can afford .9s because nothing is
     waiting on it. */
  --dur-exit: .45s;
  /* the toggle thumb overshoots on purpose, so it keeps its own curve */
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);

  /* ---------- weight ----------
     Three decorative stops, matching family.co, which ships 400/500/600 and
     nothing between. 550 and 650 were doing the job of 500 and 600 and read
     as indecision at any size.

     450 and 700 are NOT decorative and never used for emphasis. They encode
     win and loss alongside ink level, which is how a result survives a
     colorblind reader and a black and white print. */
  --w-body: 400;
  --w-medium: 500;
  --w-strong: 600;

  /* ---------- type scale ----------
     Six sizes. Below 16px a geometric ratio is a fiction: 1.2^n rounds to
     11, 13, 15, which are steps of 1.18 and 1.15, inside rounding noise. So
     the reading band uses a fixed 2px step and only the display band uses a
     ratio. 2px is the smallest step Inter carries as a perceptible rank at
     these sizes, and the 1px steps this file used to carry are exactly why
     nine sizes were doing the work of four.

       --t-micro    11px  column heads, chips, legends, anything not a sentence
       --t-small    13px  the workhorse: dense cells, captions, mono numerals
       --t-body     15px  running prose, primary labels, form controls
       --t-lead     18px  sub-heads and the vs pivot
       --t-title    24px  page h1 and standalone figures
       --t-display  34px  home hero only

     Nothing below --t-micro. Two exemptions are marked at their rules:
     .sort-caret is a glyph and .form-axis is SVG chart furniture whose
     values are restated as text.

     Weights are NOT collapsed. All 12 uses of 450 sit on --ink-40, --ink-60
     or inverted text, with zero on full ink, so 450 is stroke compensation
     for reduced ink rather than a light weight. Thinning it would undo part
     of the contrast work through a channel WCAG does not score. */
  --t-micro: .6875rem;
  --t-small: .8125rem;
  --t-body: .9375rem;
  --t-lead: 1.125rem;
  --t-title: 1.5rem;
  --t-display: 2.125rem;

  --font-primary: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-title: "IBM Plex Serif", Georgia, "Times New Roman", serif;
  --font-mono: "SF Mono", SFMono-Regular, ui-monospace, Consolas, monospace;
  --body-bg: #fdfdfb;
  /* Counter-Strike palette: CS2 amber orange + CS:GO dark blue-black */
  --ink: #131a22;
  --ink-80: rgba(19, 26, 34, .82);
  --ink-60: rgba(19, 26, 34, .72);
  --ink-40: rgba(19, 26, 34, .60);
  /* --ink-40 clears 4.5:1 at 13px and up and does NOT at 11px, where it
     measures between 3.7 and 4.5 depending on the ground under it. The
     faintest ink available to 11px text is --ink-60. Checked on /, where
     .up-event and the .bet-head row were both sitting under the line. */
  --ink-25: rgba(19, 26, 34, .26);
  --hairline: rgba(19, 26, 34, .08);
  --hairline-strong: rgba(19, 26, 34, .13);
  --wash: rgba(19, 26, 34, .045);
  --surface: #fff;
  --line-mid: rgba(19, 26, 34, .2);
  --line-strong: rgba(19, 26, 34, .35);
  --accent: #d07c0a;        /* darkened CS2 orange, readable on white */
  /* accent as TEXT: --accent is 3.14:1 on the page ground, which fails AA
     for the small text it is used on. Fills keep --accent, text takes this. */
  --accent-text: #a35f08;
  /* the focus ring needs 3:1 against the page, which key-art orange does
     not reach on a light ground; dark mode keeps the brighter value */
  --focus: #b0690a;
  /* text sitting on an --accent fill. White fails in both themes (3.2:1
     light, 2.1:1 dark); ink clears AA on both oranges. */
  --on-accent: #131a22;
  --accent-bright: #f0a132; /* CS2 key-art orange, for fills */
  --accent-border: rgba(240, 161, 50, .55);
}

/* dark mode: same system, inverted ink over CS:GO night-blue */
[data-theme="dark"] {
  --body-bg: #11161d;
  --ink: #e8e6e0;
  --ink-80: rgba(232, 230, 224, .84);
  --ink-60: rgba(232, 230, 224, .70);
  --ink-40: rgba(232, 230, 224, .53);
  --ink-25: rgba(232, 230, 224, .28);
  --hairline: rgba(232, 230, 224, .09);
  --hairline-strong: rgba(232, 230, 224, .16);
  --wash: rgba(232, 230, 224, .06);
  --surface: #1a212a;
  --line-mid: rgba(232, 230, 224, .28);
  --line-strong: rgba(232, 230, 224, .45);
  --accent: #f0a132; /* the darkened variant lacks contrast on night-blue */
  --accent-text: #f0a132;   /* already 8.52:1 on the dark ground */
  /* Without this, --accent-bright stays :root's #f0a132, which is the value
     dark also gives --accent-text, so every hover stepping from text to
     bright changed nothing at all. */
  --accent-bright: #ffb85c;
  --focus: #f0a132;
  --on-accent: #131a22;
}
[data-theme="dark"] .logo-badge img { filter: drop-shadow(0 0 1px rgba(232, 230, 224, .3)); }
[data-theme="dark"] .logo-badge.logo-inverse { background: var(--wash); }
[data-theme="dark"] .logo-badge.logo-dark { background: rgba(232, 230, 224, .88); }
[data-theme="dark"] .share-overlay { background: rgba(0, 0, 0, .6); }

* { box-sizing: border-box; margin: 0; padding: 0; }

html {
  /* off-screen decorations (hidden tooltip bubbles) must not create
     horizontal scroll; clip avoids the scroll-container quirks of hidden */
  overflow-x: clip;
}
body {
  background: var(--body-bg);
  color: var(--ink);
  font-family: var(--font-primary);
  font-size: 15px;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

#app { max-width: 60rem; margin: 0 auto; padding: 2.5rem 1.5rem 2rem; }
@media (min-width: 48em) { #app { padding-inline: var(--sp-6); } }

a { color: inherit; text-decoration: none; }
img { display: inline-block; max-width: 100%; height: auto; }
button { cursor: pointer; font-family: var(--font-primary); border: 0; background: none; }

.brand {
  display: inline-block;
  color: var(--ink);
  margin-bottom: 2.5rem;
  transition: opacity var(--dur-base) var(--ease-out);
}
.brand:hover { opacity: .75; }
.masthead {
  display: flex;
  align-items: center;
  gap: 2rem;
  margin-bottom: 2.5rem;
}
/* The fixed theme switch sits at the viewport edge and would otherwise land
   on the nav, so the masthead reserves its lane. This used to stop at 641px
   because below that the switch dropped below the header, but the menu
   button now occupies that row and the two collided by 53px. */
@media (max-width: 1180px) {
  .masthead { padding-right: 4.5rem; }
}
.masthead .brand { margin-bottom: 0; }
/* wrap is the backstop: the row is clipped rather than scrolled when it
   overflows, so it must never overflow. Reachable at 2x text size. */
.site-nav { margin-left: auto; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 1.25rem; }
.site-nav a {
  font-size: .8125rem;
  font-weight: var(--w-medium);
  color: var(--ink-60);
  transition: color var(--dur-base) var(--ease-out);
}
.site-nav a:hover { color: var(--ink); }
.site-nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom: 2px solid var(--accent-bright);
  padding-bottom: 2px;
}
.banner {
  background: var(--wash);
  border-bottom: 1px solid var(--hairline);
  padding: .5625rem 4rem;
  text-align: center;
  font-size: .8125rem;
  font-weight: 450;
  color: var(--ink-60);
}
/* The family guidelines set a 120px floor for the horizontal lockup. Its
   ratio is 887.3:140, so 15px tall (the old mark's height) rendered only
   95px wide and broke that rule. 20px gives 127px. */
.brand-mark { height: 20px; width: auto; display: block; }
.banner-nut {
  height: 12px;
  width: auto;
  vertical-align: -1px;
  margin-right: .4375rem;
}

.footer-nut {
  height: 13px;
  width: auto;
  color: var(--ink-40);
  margin-right: .5rem;
  vertical-align: -2px;
}

/* keyboard focus ring; text inputs keep their border treatment instead */
/* :where() so the whole rule carries zero specificity. The fallback radius is
   here for controls that have none of their own, and at normal specificity it
   was overwriting each component's own corners the moment it took keyboard
   focus, so a pill lost its pill on Tab. */
:where(a:focus-visible, button:focus-visible) {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: .375rem;
}
/* the outline itself must not lose to a component rule, only the radius */
a:focus-visible, button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    /* stops infinite loops (live pulse) instead of spinning them at .01ms */
    animation-iteration-count: 1 !important;
    /* and the delay, which duration alone does not cover: the staggered
       slate rows use fill both, so an uncleared delay would hold them at
       zero opacity for up to 280ms for the one reader who asked for no
       motion at all. */
    animation-delay: 0ms !important;
  }
  .btn-primary:hover, .btn-share:hover { transform: none; }
  .up-row:hover { transform: none; }
}

footer {
  max-width: 60rem;
  margin: 3.5rem auto 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--hairline);
  color: var(--ink-60);
  font-size: .8125rem;
  display: grid;
  /* two columns, each block its own cell, rather than one column of
     run-on sentences */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem 3rem;
  align-items: start;
  /* The right column starts level with the left column's first line of text
     rather than with the lockup above it. That offset is exactly the lockup's
     height plus the gap under it, so both values live here and every rule
     that depends on them reads the same two, and they cannot drift apart. */
  --foot-mark-h: 20px;
  --foot-brand-gap: .5rem;
}
@media (max-width: 720px) { footer { grid-template-columns: 1fr; } }

.foot-mark { height: var(--foot-mark-h); width: auto; display: block; color: var(--ink); }
.foot-rg { margin-top: calc(var(--foot-mark-h) + var(--foot-brand-gap)); }
/* Must follow the rule above, not join the footer's other mobile rules:
   same specificity, so source order is what decides which one wins. One
   column means there is no lockup alongside to align to. */
@media (max-width: 720px) { .foot-rg { margin-top: 0; } }
/* align-items so the lockup keeps its own width; as a stretched flex item
   it grew to the full column on mobile */
.foot-brand { display: flex; flex-direction: column; align-items: flex-start; gap: var(--foot-brand-gap); }
.foot-brand p { margin: 0; line-height: var(--lh-body); }
.foot-parent { color: var(--ink-40); }
.foot-parent a { color: var(--ink-60); font-weight: 600; }
.foot-parent a:hover { color: var(--accent-text); }

.foot-block { display: flex; flex-direction: column; gap: .375rem; }
.foot-h {
  display: block;
  font-size: .6875rem;
  font-weight: var(--w-strong);
  color: var(--ink-40);
}
.foot-h::after { content: none; }
.foot-block p { margin: 0; line-height: var(--lh-body); }

.foot-dl { display: grid; grid-template-columns: max-content 1fr; gap: .1875rem .75rem; }
.foot-dl > div { display: contents; }
.foot-dl dt { color: var(--ink-40); }
.foot-dl dd { color: var(--ink-60); }

.foot-disclaimer p { font-size: .6875rem; font-style: italic; color: var(--ink-40); }

.foot-meta {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--hairline);
  color: var(--ink-40);
}
/* Prose links. The global reset strips every anchor's decoration, which is
   right when most anchors are whole cards and rows, but six links inside real
   sentences were left rendering identically to the text around them, which is
   the Law of Similarity's own example.
   
   The :not([class]) filter is load bearing rather than tidy. .mf-head is an
   anchor inside an li with a stretched hit area, so a bare "li a" would
   underline the opponent name, the date and the maps note in every series
   header on all 100 team pages. */
:where(p, li) > a:where(:not([class])) {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
:where(p, li) > a:where(:not([class])):hover { color: var(--accent-text); }

footer a {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
footer a:hover { color: var(--ink); }

h2 {
  display: flex;
  align-items: center;
  gap: .75rem;
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: -.005rem;
  color: var(--ink-80);
}
h2::after { content: ""; flex: 1 1; height: 1px; background: var(--hairline); }
.h2-note { font-weight: 450; color: var(--ink-40); white-space: nowrap; }

.ctx-menu {
  position: fixed;
  z-index: 120;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: .5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .14);
  padding: .25rem;
  min-width: 13rem;
}
.ctx-item {
  display: block;
  width: 100%;
  padding: .4375rem .625rem;
  border-radius: .375rem;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--ink);
  text-align: left;
  transition: background var(--dur-micro) var(--ease-out);
}
.ctx-item:hover { background: var(--wash); }

/* animated theme switch: pill track, spring-sliding thumb, sun/moon crossfade */
.theme-toggle {
  /* In the footer's meta row, in flow, pushed to the far end. The fixed pill
     this replaces needed a measured offset to dodge the banner stack and sat
     over sidebar content on narrow screens; in flow both problems vanish.
     relative is load-bearing: the thumb inside is absolutely positioned, and
     when the pill stopped being fixed it stopped being the thumb's
     containing block, so the thumb anchored to the page and rendered as a
     lone moon in the top left corner. */
  position: relative;
  margin-left: auto;
  width: 46px;
  height: 26px;
  border-radius: 999px;
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
  z-index: 60;
  transition: background var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.theme-toggle:hover { box-shadow: inset 0 0 0 1px var(--line-mid); }
.theme-toggle[aria-checked="true"] {
  background: color-mix(in srgb, var(--accent-bright) 26%, var(--surface));
}
.theme-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.theme-toggle:disabled { opacity: .5; cursor: default; }
.toggle-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .18), inset 0 0 0 1px var(--hairline);
  color: var(--ink-60);
  /* overshoot curve approximates the spring */
  transition: transform var(--dur-slow) var(--ease-spring);
}
.theme-toggle[aria-checked="true"] .toggle-thumb { transform: translateX(20px); }
.toggle-ic {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-slow) var(--ease-spring);
}
.toggle-ic svg { width: 12px; height: 12px; }
.toggle-moon { opacity: 0; transform: scale(.4) rotate(-90deg); }
.theme-toggle[aria-checked="true"] .toggle-moon { opacity: 1; transform: none; }
.theme-toggle[aria-checked="true"] .toggle-sun { opacity: 0; transform: scale(.4) rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .toggle-thumb, .toggle-ic { transition: none; }
}

/* ---------- chips ---------- */

/* CSS tooltips: [data-tip] renders a bubble above the element on hover.
   (Native title tooltips proved unreliable across viewing surfaces.) */
[data-tip] { position: relative; }
span[data-tip] { cursor: help; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: 100%;
  /* The visible bubble still floats 7px clear of the trigger, but its hit
     area now reaches down to it, so a pointer can travel into the bubble
     without crossing dead space. background-clip keeps the fill inside the
     padding box, so this changes nothing visually. */
  border-bottom: 7px solid transparent;
  left: 50%;
  /* --tip-shift is set on open by placeTip() when the centred position
     would put the bubble past a screen edge. No static value can fix this:
     an anchor near either gutter always clips a centred bubble. */
  transform: translateX(calc(-50% + var(--tip-shift, 0px)));
  width: max-content;
  max-width: 15rem;
  background: var(--ink);
  /* Must follow the background shorthand, which resets it. Declared before,
     it was silently undone one line later and the fill leaked down into the
     transparent border, so the bubble sat against the trigger. */
  background-clip: padding-box;
  color: var(--body-bg);
  font-family: var(--font-primary);
  font-size: .6875rem;
  font-weight: 450;
  letter-spacing: 0;
  line-height: var(--lh-body);
  text-align: left;
  white-space: normal;
  padding: .375rem .5625rem;
  border-radius: .375rem;
  box-shadow: 0 4px 14px rgba(19, 26, 34, .18);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-micro) var(--ease-out) var(--dur-slow);
  /* WCAG 1.4.13 Hoverable: content shown on hover has to survive the pointer
     moving onto it. Generated content counts as part of its element for hit
     testing, so allowing pointer events is all it takes; with them off the
     bubble vanished the moment anyone tried to reach it. */
  pointer-events: auto;
  z-index: 40;
}
/* Never wider than the screen, wherever it is anchored. Without the cap a
   240px bubble on an element near either edge overflows into the clip and
   the text is gone, with no scroll to recover it. */
[data-tip]::after { max-width: min(15rem, var(--tip-max, calc(100vw - 2rem))); }
[data-tip]:hover::after, [data-tip]:focus-visible::after { opacity: 1; visibility: visible; }
/* Tap-revealed on a coarse pointer. Hover and focus-visible are the only two
   ways in, and neither is available to a touch user on a plain span: a team
   page carries 309 tips and 234 of them sit on spans. Mobile browsers do
   emulate hover on tap, which makes the text technically reachable but
   undiscoverable and impossible to dismiss on purpose. Must follow the rule
   above: same specificity, so source order decides. */
[data-tip].tip-open::after { opacity: 1; visibility: visible; }
/* WCAG 1.4.13 Dismissible: Escape hides tooltips without the reader having
   to move the pointer off whatever they were looking at. Cleared again on
   the next pointer move, since that is a fresh intent. */
body.tips-off [data-tip]::after { opacity: 0; visibility: hidden; }
/* the sidebar hugs the right edge; right-anchor its bubbles so they never
   clip outside the viewport */
@media (min-width: 861px) {
  /* Only correct while .sidebar is the narrow right rail. Below this the
     grid collapses to one column (line ~1345) and the sidebar sits flush to
     the left gutter, so anchoring right sent a 240px bubble off the left
     edge, where html{overflow-x:clip} makes it unrecoverable. */
  .sidebar [data-tip]::after { left: auto; right: 0; transform: none; }
}

/* flat SVG flags */
.flag-icon {
  width: 15px;
  height: 11px;
  object-fit: cover;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgba(19, 26, 34, .08);
  vertical-align: -1px;
}
.team-meta .flag .flag-icon { width: 18px; height: 13px; vertical-align: .0625rem; }

.chip {
  display: inline-flex;
  align-items: center;
  font-size: .6875rem;
  font-weight: var(--w-medium);
  letter-spacing: .01em;
  color: var(--ink-60);
  border: 1px solid var(--hairline-strong);
  border-radius: .375rem;
  padding: var(--sp-1) var(--sp-2);
  white-space: nowrap;
}
.chip-label { font-family: var(--font-primary); font-weight: var(--w-medium); color: var(--ink-60); margin-right: .375rem; }
.chip-label:last-child { margin-right: 0; margin-left: .25rem; }
.chip-map {
  font-family: var(--font-mono);
  text-transform: none;
  letter-spacing: 0;
  background: color-mix(in srgb, var(--map-color, #8a8a8a) 12%, var(--surface));
  border-color: transparent;
  color: var(--ink-80);
}
.map-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--map-color, #8a8a8a);
  margin-right: .375rem;
  flex-shrink: 0;
}
/* .map-icon is inline-styled in app.js (pre-tinted img); both theme
   variants render and [data-theme] picks one */
.map-icon { display: inline-block; }
.mi-dark { display: none; }
[data-theme="dark"] .mi-light { display: none; }
[data-theme="dark"] .mi-dark { display: inline-block; }
.chip-upset {
  color: var(--accent-text);
  border-color: var(--accent-border);
}

/* ---------- selector page ---------- */

.hero { max-width: 36rem; margin: 3.5rem auto 0; }
.hero h1 {
  font-family: var(--font-title);
  /* 2.25rem put "Find your edge in CS2 predictions." 16px past the 36rem
     hero and wrapped one word onto its own line. Sized to hold it on one
     line with headroom, rather than to the exact fit, so a font metric
     difference across browsers does not put the wrap back. */
  font-size: 2.125rem;
  font-weight: 500;
  /* rem tracking does not scale with the type it sits under. -.02rem was
     -.0094em here and near nothing on a smaller heading using the same
     value. The token is in em so it holds its optical weight at any size. */
  letter-spacing: var(--ls-display);
  margin-bottom: 1rem;
}
.hero .lead {
  font-size: .9375rem;
  font-weight: 450;
  line-height: var(--lh-body);
  color: var(--ink-60);
}
.hero .lead + .lead { margin-top: .25rem; }
.hero .lead strong { font-weight: 600; color: var(--ink-80); }

/* hand-drawn marker stroke under a word, à la agentation */
.pen-underline {
  display: inline;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none' viewBox='0 0 100 12'%3E%3Cpath d='M2 8 Q50 4 98 7' stroke='%23f0a132' stroke-width='4' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 0 calc(100% - 1px);
  background-size: 100% 9px;
  padding-bottom: 3px;
  /* The stroke draws left to right on arrival. It is the only decorative
     motion here and it sits on a word, not on data, so nothing is waiting
     behind it. */
  animation: pen-draw var(--dur-enter) var(--ease-out) .12s both;
}
@keyframes pen-draw { from { background-size: 0 9px; } }

/* the search line is the picker's caption, not hero prose: clearly apart
   from the intro paragraph, snug against the inputs it describes */
/* The team picker is a search landmark, not a paragraph with inputs under
   it. <search> is the HTML element for a set of controls performing a search
   or filter, and it carries role=search for free; the explicit display keeps
   older engines that treat it as an unknown inline element in line. The h2
   names the landmark and puts the control in the document outline alongside
   every other section on the page. */
search { display: block; }
.picker-section { margin-top: 3rem; }
.picker-title { margin-bottom: .25rem; }
.picker {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  margin-top: .75rem;
}
.picker-row {
  display: flex;
  align-items: center;
  gap: .625rem;
  flex-wrap: wrap;
}
.combo { position: relative; }
.combo input {
  font-family: var(--font-primary);
  font-size: .9375rem;
  font-weight: 500;
  color: var(--ink);
  width: 15rem;
  padding: .5rem .75rem;
  border: 1px solid var(--hairline-strong);
  border-radius: .375rem;
  background: var(--surface);
  outline: none;
  transition: border-color var(--dur-base) var(--ease-out);
}
.combo input:hover { border-color: var(--line-mid); }
.combo input:focus { border-color: var(--line-strong); }
.combo input::placeholder { color: var(--ink-25); }

.combo-list {
  position: absolute;
  top: calc(100% + .25rem);
  left: 0;
  width: 17rem;
  max-height: 19rem;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: .5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
  padding: .25rem;
  z-index: 20;
}
.combo-item {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .375rem .5rem;
  border-radius: .375rem;
  font-family: var(--font-primary);
  font-size: .8125rem;
  font-weight: 500;
  color: var(--ink);
  text-align: left;
  transition: background var(--dur-micro) var(--ease-out);
}
.combo-item:hover, .combo-item.active { background: var(--wash); }
/* staggered entry: each row springs in slightly after the one above.
   Delay is capped so long lists don't crawl. */
@keyframes combo-in {
  from { opacity: 0; transform: translateY(5px) scale(.98); }
  to { opacity: 1; transform: none; }
}
.combo-item {
  animation: combo-in var(--dur-slow) var(--ease-spring) both;
  animation-delay: calc(min(var(--i, 0), 8) * 22ms);
}
@media (prefers-reduced-motion: reduce) {
  .combo-item { animation: none; }
}
.combo-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo-check { width: 13px; height: 13px; flex-shrink: 0; color: var(--accent-bright); }
.combo-rank { margin-left: auto; font-size: .6875rem; color: var(--ink-40); }
.combo-empty { padding: .5rem; font-size: .8125rem; color: var(--ink-40); }
.vs-label { font-size: .8125rem; color: var(--ink-40); }

.btn-primary {
  display: inline-flex;
  align-items: center;
  padding: .5rem .875rem;
  font-size: .8125rem;
  font-weight: 500;
  border-radius: .375rem;
  background: var(--ink);
  color: var(--body-bg);
  transition: background var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.btn-primary:hover {
  background: var(--accent);
  color: var(--on-accent);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(208, 124, 10, .28);
}
.btn-primary:active { transform: translateY(0); box-shadow: none; }

/* dot morph: the search glass springs into the brand acorn on hover/focus.
   The button itself holds still, only the shape changes. Gated to
   hover-capable pointers; touch devices keep the search icon. */
.btn-morph { gap: .5rem; }
.btn-morph:hover {
  /* stays "white": just a breath of tint and lift, no color swap */
  background: color-mix(in srgb, var(--ink) 90%, var(--body-bg));
  color: var(--body-bg);
  transform: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .1);
}
.dot-morph { position: relative; width: 14px; height: 14px; flex-shrink: 0; }
.dm-search {
  position: absolute;
  inset: 0;
  width: 14px;
  height: 14px;
  stroke: var(--accent-bright);
  transition: transform var(--dur-slow) var(--ease-spring), opacity var(--dur-base) var(--ease-out), filter var(--dur-base) var(--ease-out);
}
/* dark mode inverts the button to near-white; the bright orange washes out
   there, so both icons drop to the darkened text-safe orange */
[data-theme="dark"] .dm-search { stroke: #d07c0a; }
[data-theme="dark"] .dm-nut path { fill: #d07c0a; }
.dm-nut {
  position: absolute;
  left: 50%;
  top: 50%;
  height: 14px;
  width: auto;
  transform: translate(-50%, -50%) scale(.35);
  opacity: 0;
  filter: blur(3px);
  transition: transform var(--dur-slow) var(--ease-spring), opacity var(--dur-base) var(--ease-out), filter var(--dur-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .btn-morph:hover .dm-search { transform: scale(.35); opacity: 0; filter: blur(3px); }
  .btn-morph:hover .dm-nut { transform: translate(-50%, -50%) scale(1); opacity: 1; filter: blur(0); }
}
.btn-morph:focus-visible .dm-search { transform: scale(.35); opacity: 0; filter: blur(3px); }
.btn-morph:focus-visible .dm-nut { transform: translate(-50%, -50%) scale(1); opacity: 1; filter: blur(0); }
@media (prefers-reduced-motion: reduce) {
  .dm-search, .dm-nut { transition: none; }
  .btn-morph:hover .dm-search, .btn-morph:focus-visible .dm-search { transform: none; opacity: 1; filter: none; }
  .btn-morph:hover .dm-nut, .btn-morph:focus-visible .dm-nut { opacity: 0; }
}
.picker-error { margin-top: .75rem; font-size: .8125rem; color: var(--accent-text); }

.up-title { margin-top: 3rem; }
.up-list { margin-top: 1rem; }
/* wrap between pills, never inside one: without nowrap the labels broke
   mid-phrase at 320px and each pill rendered as a tall oval */
.slate-tabs { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-4); }
/* the tab row is now followed by a panel wrapper, not the list itself */
.slate-tabs + .up-list,
.slate-tabs + [role="tabpanel"] > .up-list,
.slate-tabs + [role="tabpanel"] > .slate-note { margin-top: var(--sp-4); }
.slate-tab {
  white-space: nowrap;
  font-size: .8125rem;
  font-weight: var(--w-medium);
  color: var(--ink-60);
  border: 1px solid var(--hairline-strong);
  border-radius: 999px;
  padding: .25rem .75rem;
  transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.slate-tab:hover { color: var(--ink); border-color: var(--line-mid); }
.slate-tab[aria-selected="true"], .slate-tab[aria-pressed="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--surface);
}
/* h2h time-window pills sit at the end of the toolbar row */
.h2h-filter { display: inline-flex; gap: .375rem; margin-left: auto; }
/* The count is the least load bearing token in the label and it was taking
   both the accent hue and weight 650. On the selected tab, whose background
   is var(--ink), the orange landed at 1.71:1 in dark where --ink is near
   white. The override is load bearing rather than defensive: an element's own
   color always beats an inherited one whatever the specificity, so without it
   a plain --ink-40 count renders near-black on that pill. */
.tab-count { color: var(--ink-40); margin-left: .125rem; }
.slate-tab[aria-selected="true"] .tab-count,
.slate-tab[aria-pressed="true"] .tab-count { color: inherit; opacity: .72; }
.chip-gap { color: var(--accent-text); border-color: var(--accent-border); }
/* ---------- slate row: one shape at every width ----------
   A scoreboard, not a sentence. The two sides stack and their numbers share
   grid tracks, so maps and win probability line up vertically and can be
   compared at a glance. `display: contents` on the side lets both sides'
   cells sit in the parent's tracks, which is what makes them align; sizing
   each side independently would not. */
.up-row {
  position: relative;   /* anchor for the stretched matchup link */
  display: grid;
  /* the head line, the two sides and the chip strip are three groups within
     one match, so they sit a rung above the gap between the two sides */
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-3);
  margin: 0 calc(var(--sp-3) * -1);
  border-radius: .625rem;
  transition: background var(--dur-micro) var(--ease-out), transform var(--dur-micro) var(--ease-out);
}
/* Rows arrive in sequence rather than all at once, which reads as the slate
   filling in. The stagger is capped at eight because past that the last row
   waits longer than anyone will look at an empty space, and the whole
   cascade is done inside half a second. Switching tabs replays it, since
   the panel unhiding re-matches the selector. */
.up-list > .up-row { animation: row-in var(--dur-enter) var(--ease-out) both; }
.up-list > .up-row:nth-child(2) { animation-delay: .06s; }
.up-list > .up-row:nth-child(3) { animation-delay: .12s; }
.up-list > .up-row:nth-child(4) { animation-delay: .18s; }
.up-list > .up-row:nth-child(5) { animation-delay: .24s; }
.up-list > .up-row:nth-child(6) { animation-delay: .3s; }
.up-list > .up-row:nth-child(7) { animation-delay: .36s; }
.up-list > .up-row:nth-child(n+8) { animation-delay: .42s; }
/* 10px rather than 6: at the old distance the travel was finished before
   the eye found it, and the cascade read as a flicker instead of a fill. */
@keyframes row-in { from { opacity: 0; transform: translateY(10px); } }

/* 3. the row lifts a hair under the pointer, so the whole card reads as the
   target rather than just the link inside it */
.up-row:hover { background: var(--wash); transform: translateY(-1px); }

/* A finished row says who won by weight and ink, the same encoding the
   ledger letters use: 700 at full ink for the winner, 450 stepped back for
   the loser. No color, so it survives a colorblind reader and a print. */
.up-side.is-win .up-name { font-weight: 700; }
.up-side.is-loss .up-name { font-weight: 450; color: var(--ink-60); }
.up-side.is-loss .up-maps { font-weight: 450; color: var(--ink-60); }
/* At 136px a row is too tall for 32px of whitespace to read as a boundary:
   the eye cannot hold the gap in view against the block it is separating.
   Proximity was already correct here, 32px between rows against 8px within
   them, and it still was not enough at this scale, which is where Uniform
   Connectedness earns its place over proximity alone.

   The line replaces the margin rather than adding to it. Vertical padding
   goes up by the 8px the margin gave back, so the total gap is the same 32px
   it was, now with a boundary at its midpoint instead of nothing. */
.up-row + .up-row { margin-top: 0; border-top: 1px solid var(--hairline); }
.news-row + .news-row { border-top: 1px solid var(--hairline); }

.up-head { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.up-time { font-family: var(--font-mono); font-size: .8125rem; color: var(--ink-60); white-space: nowrap; }
.up-live { color: var(--accent-text); font-family: var(--font-primary); font-weight: 600; font-size: .8125rem; display: inline-flex; align-items: center; gap: .3125rem; }
.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-bright);
  animation: live-pulse 1.6s ease-in-out infinite;
}
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
/* the event is the least important thing in the row, so it is the one thing
   allowed to truncate, and it does so on a line of its own */
.up-event {
  flex: 1 1 auto;
  min-width: 0;
  font-size: .6875rem;
  color: var(--ink-60);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.up-sides {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
}
.up-side { display: contents; }
.up-sides .logo-badge { flex: none; }
.up-name {
  min-width: 0;
  font-size: .9375rem;
  font-weight: var(--w-medium);
  line-height: var(--lh-snug);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* fixed minimum tracks, so the two rows' numbers align even when one is empty */
.up-maps, .up-pct {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.up-maps { min-width: .75rem; font-size: .9375rem; font-weight: var(--w-strong); color: var(--ink); }
.up-pct { min-width: 2.5rem; font-size: .8125rem; color: var(--ink-60); }

.up-chips { display: flex; align-items: center; flex-wrap: wrap; gap: .375rem; }
.chip-muted { color: var(--ink-40); border-style: dashed; }
/* live odds chip: accent border + the site's live-pulse dot; flashes on price moves */
.chip-live { border-color: var(--accent-border); color: var(--ink-80); }
.chip-live .live-dot { width: 6px; height: 6px; margin-right: .375rem; }
@keyframes pm-flash {
  from { background: color-mix(in srgb, var(--accent-bright) 24%, transparent); }
  to { background: transparent; }
}
.pm-flash { animation: pm-flash .9s var(--ease-out); }
/* record digits in team colors, mixed toward ink so light logo colors stay readable in both themes */
/* the readable value per theme is computed in recNum, because a single mix
   ratio cannot clear 4.5:1 for every team color without draining them all */
.rec-num { color: var(--rec-l, var(--ink)); font-weight: var(--w-strong); }
[data-theme="dark"] .rec-num { color: var(--rec-d, var(--ink)); }

.news-heading { margin-top: 3rem; }
/* news TLDR panel: expands in place beneath the clicked headline, pushing
   the rows below down; the 0fr -> 1fr grid transition animates the height */
.news-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-slow) var(--ease-out);
  /* bleed lives out here: inside .news-panel-inner it would be clipped
     by its overflow:hidden and the card would stop short of the rows */
  margin: 0 -.5rem;
}
.news-panel.open { grid-template-rows: 1fr; }
.news-panel-inner { overflow: hidden; min-height: 0; }
/* hangs off its own headline: 4px up binds it, 16px down separates it from
   the next, unrelated headline. It was 6px/10px, near enough to equidistant
   that the panel read as floating between the two. */
.news-panel-card {
  margin: var(--sp-1) 0 var(--sp-4);
  padding: var(--pad-row);
  background: var(--wash);
  border-radius: .375rem; /* same radius as the news rows */
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out) var(--dur-micro);
}
.news-panel.open .news-panel-card { opacity: 1; }
.news-panel-sum { font-size: .8125rem; color: var(--ink-60); line-height: var(--lh-body); margin-bottom: .5rem; }
.news-panel-link { font-size: .8125rem; font-weight: var(--w-medium); color: var(--accent-text); transition: color var(--dur-base) var(--ease-out); }
.news-panel-link:hover { color: var(--accent-bright); }
.news-src { font-size: .6875rem; color: var(--ink-40); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .news-panel, .news-panel-card { transition: none; }
}
.hltv-logo { height: 19px; width: auto; vertical-align: -5px; margin-left: .3125rem; }
.news-list { margin-top: 1rem; }
.news-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: var(--pad-row);
  margin: 0 -.5rem;
  border-radius: .375rem;
  transition: background var(--dur-micro) var(--ease-out);
}
.news-row:hover { background: var(--wash); }
.news-badge { color: var(--ink-40); }
.news-badge svg { width: 13px; height: 13px; }
.news-team { position: relative; display: inline-flex; flex-shrink: 0; }
.news-mini {
  position: absolute;
  right: -5px;
  bottom: -4px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  color: var(--body-bg);
  box-shadow: 0 0 0 2px var(--body-bg);
}
.news-mini svg { width: 7px; height: 7px; }
/* A headline wraps, it does not clip. At 375px the box is 111px against 522px
   of text, so an ellipsis threw away 79% of the sentence and every row read
   the same. Two lines cost 17px and keep the story. */
.news-title {
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  font-size: .8125rem;
  font-weight: 500;
}
.news-time { margin-left: auto; font-size: .6875rem; color: var(--ink-40); white-space: nowrap; }
.news-fresh { color: var(--accent-text); font-weight: var(--w-medium); }
.news-ext {
  font-size: .8125rem;
  color: var(--ink-40);
  transition: transform var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
/* The outward nudge belongs to the arrow only. A row with a summary expands
   in place rather than leaving the site, so it gets a chevron that rotates
   instead. Scoped to the variant rather than cancelled on the chevron, which
   would be an equal specificity source order trap. */
.news-row:hover .news-ext:not(.news-ext-open) { transform: translate(2px, -2px); color: var(--ink-60); }
.news-row:hover .news-ext-open { color: var(--ink-60); }
.news-ext-open { transition: transform var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out); }
.news-row[aria-expanded="true"] .news-ext-open { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .news-ext-open { transition: none; } }

/* Two nowrap links ride this heading, and at 320px they cannot share its
   line: the second ran 23px past the panel. The heading wraps them instead,
   and the trailing hairline stays on the last line where it lands. */
.team-links-title { margin-top: 3rem; flex-wrap: wrap; row-gap: var(--sp-1); }
.team-links { display: flex; flex-wrap: wrap; gap: .375rem; margin-top: 1rem; }
.team-link {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-badge);
  font-size: .8125rem;
  font-weight: 500;
  padding: .375rem .625rem;
  border-radius: .375rem;
  box-shadow: inset 0 0 0 1px var(--hairline);
  background: var(--surface);
  transition: box-shadow var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.team-link:hover { background: var(--wash); box-shadow: inset 0 0 0 1px var(--line-mid); }

/* Uniform square logo badge: fixed size, logo fitted inside. Light wash
   tile normally, dark ink tile for predominantly-white logos (flagged
   offline in teams.js). The logo gets a hair-thin dark halo so white
   shapes read on light tiles. */
.logo-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: var(--wash);
  border-radius: 6px;
  flex-shrink: 0;
  line-height: 0;
}
.logo-badge img {
  max-width: 72%;
  max-height: 72%;
  width: auto;
  height: auto;
  object-fit: contain;
  filter: drop-shadow(0 0 1px rgba(19, 26, 34, .45));
}
.logo-badge.logo-inverse { background: var(--ink); }
.logo-badge.logo-inverse img { filter: none; }
.team-logo .logo-badge { width: 64px; height: 64px; border-radius: 14px; }

/* ---------- history page ---------- */

.versus-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.25rem;
  margin-bottom: 1.5rem;
}
.vs-big {
  align-self: center;
  font-family: var(--font-title);
  font-style: italic;
  font-size: 1.125rem;
  color: var(--ink-40);
}

.team-header { display: flex; gap: 1rem; align-items: center; min-width: 0; }
.team-header.right { flex-direction: row-reverse; text-align: right; }
.team-header.right .chips { justify-content: flex-end; }
.team-logo img { max-height: 48px; max-width: 48px; width: auto; object-fit: contain; }
/* min-width: 0 on the parent alone is not enough. A flex child defaults to
   min-width: auto and refuses to shrink under its content, so .team-meta held
   its natural width and pushed 5px off a 320px screen. */
.team-meta { min-width: 0; }
.team-meta h1 {
  font-family: var(--font-title);
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -.01rem;
  margin-bottom: .5rem;
  white-space: nowrap;
}
.flag { font-size: .9375rem; vertical-align: .125rem; }
/* the flame gif has a baked-in white background; badge it in white so it
   reads as a deliberate tile in both themes */
.fire-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--hairline);
  margin-left: .375rem;
  vertical-align: -3px;
}
.fire { height: 18px; width: 18px; display: block; }
.chips { display: flex; gap: .375rem; flex-wrap: wrap; }

.toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; margin-bottom: 2rem; }
.btn-secondary {
  display: inline-flex;
  align-items: center;
  padding: .375rem .75rem;
  font-size: .8125rem;
  font-weight: 500;
  border-radius: .375rem;
  background: var(--surface);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
  transition: background var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.btn-secondary:hover {
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--line-mid);
}

/* the share action is the one colored button on the page */
.btn-share {
  display: inline-flex;
  align-items: center;
  gap: .4375rem;
  padding: .375rem .75rem;
  font-size: .8125rem;
  font-weight: var(--w-medium);
  border-radius: .375rem;
  color: var(--accent-text);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--accent-border);
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.btn-share:hover {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 4px 12px rgba(208, 124, 10, .3);
  transform: translateY(-1px);
}
.btn-share:active { transform: translateY(0); box-shadow: none; }
.btn-share-nut { height: 13px; width: auto; }

.history-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: 3rem;
  align-items: start;
}

/* match cards */

.match-card {
  border: 1px solid var(--hairline);
  border-radius: .5rem;
  padding: var(--pad-card);
  margin-bottom: .75rem;
  background: var(--surface);
  transition: border-color var(--dur-base) var(--ease-out);
}
.match-card:hover { border-color: var(--hairline-strong); }
.match-card-upset { border-color: var(--accent-border); }
.match-card-upset:hover { border-color: var(--accent-bright); }

/* The header wraps when the event name is long, and it read as crammed for
   three reasons at once. The row gap equalled the column gap, so a chip that
   dropped to its own line sat the same 8px from the title as it sat from the
   chip beside it, and nothing said a new row had started. The chips then
   right-aligned on that new line by their own auto margin, stranded under
   nothing. And the whole block closed on 10px, which is not a rung.

   The row gap is now a rung above the column gap, so wrapping reads as
   wrapping. The push to the right edge moved from the chips onto the title,
   which gets the same result while the two share a line and lets the chips
   fall back to the title's own left edge the moment they do not. */
/* Three columns rather than one wrapping row. As a flex row, a long event
   name pushed the whole chip group onto a line of its own, which left the
   title's line half empty and the chips stranded under it. Each part now
   wraps INSIDE its own column instead: the name takes a second line within
   its own width and the chips stay beside it, so neither creates dead space
   for the other.

   fit-content caps the chip column so a long odds chip cannot squeeze the
   name to nothing, and lets the chips wrap among themselves once they reach
   that cap. align-items: start keeps the date and the chips on the name's
   first line when it runs to two. */
.match-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) fit-content(19rem);
  align-items: start;
  column-gap: var(--sp-3);
  row-gap: var(--sp-2);
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
}
.match-event { min-width: 0; }
.match-date {
  font-size: .6875rem;
  font-weight: 500;
  letter-spacing: .01rem;
  color: var(--ink-40);
}
.match-event { font-size: .8125rem; font-weight: 500; color: var(--ink-80); }
/* chips travel as one right-aligned group; margin-left:auto keeps them
   flush right even when a long event name wraps them to their own line */
.match-chips {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* Keyed to the CARD's width, not the viewport's, because they are not the
   same thing here: at a 900px viewport the sidebar layout leaves the card
   about 430px, and a viewport query still saw 900 and kept three columns.
   The title column was squeezed to 73px and the header ran to 91px while the
   chips sat on a comfortable 280. A container query measures the box the
   columns actually have to fit in.

   The threshold is against the CONTENT box, not the border box: a 576px card
   with 24px side padding queries as 526. That matters more than it sounds,
   because 526 is not a passing width, it is THE width. #app caps at 60rem
   and these pages split into a match column and a sidebar, so the card sits
   at 526 from a 1000px viewport all the way to 1600. A 34rem threshold sat
   just above it and stacked every card on every desktop, which is the one
   case the columns existed for.

   30rem instead, measured rather than guessed. Over eight real cards at 526:
   three columns averages 59px against 60 stacked, and the gain is not in the
   average but in the spread. A short event name closes to 36px where
   stacking always pays 60, and a long one costs 67. Narrower caps are worse
   on both counts, 13rem averaging 83, and folding the date into the title
   column is worst at 87.

   30rem rather than 28 because of the one card width between them: the h2h
   page at a 900px viewport leaves 462, where three columns squeeze the title
   enough to reach a 91px header while stacking holds 60. Below this, which
   is a phone, stacking wins outright. */
.match-card { container-type: inline-size; }
@container (max-width: 30rem) {
  .match-head { grid-template-columns: auto minmax(0, 1fr); }
  .match-chips { grid-column: 1 / -1; justify-content: flex-start; }
}

.map-row {
  display: grid;
  /* Four tracks, not five. The fifth was phantom, nothing filled it, and the
     two name columns paid its 5.5rem out of a 576px main column: the name box
     measured 84px against 156px for "CYBERSHOKE Prospects", so the row read
     "CYBERSHO…" and became indistinguishable from plain CYBERSHOKE. The
     narrow branch below already ships these exact widths and proves them
     safe: widest map chip 87px under 96, widest score 45px under 56. */
  grid-template-columns: 6rem minmax(0, 1fr) 3.5rem minmax(0, 1fr);
  align-items: center;
  gap: .625rem;
  padding: .3125rem 0;
}
.map-row .chip-map { justify-self: start; }
.map-team { display: inline-flex; align-items: center; gap: var(--gap-badge); min-width: 0; max-width: 100%; overflow: hidden; }
.map-row .map-team:nth-of-type(1) { min-width: 0; justify-self: end; flex-direction: row-reverse; }
.map-team-name {
  min-width: 0;
  font-size: .8125rem;
  font-weight: 450;
  color: var(--ink-40);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.map-team.won .map-team-name { font-weight: 600; color: var(--ink); }
.map-team:hover .map-team-name { color: var(--ink); }
/* losers fade as a unit, badge tile included */
.map-team:not(.won) .logo-badge { opacity: .55; }

.score {
  justify-self: center;
  font-family: var(--font-mono);
  font-size: .8125rem;
  white-space: nowrap;
}
.score i { font-style: normal; color: var(--ink-40); padding: 0 .1875rem; }
.s-win { font-weight: 600; color: var(--ink); }
.s-loss { font-weight: 450; color: var(--ink-40); }

/* sidebar */

.sidebar { position: sticky; top: 1.5rem; }
.sidebar h2 { margin-bottom: .875rem; }
.sidebar h2:not(:first-child) { margin-top: 1.75rem; }
/* 4px between bullets lost to 9px of leading inside a wrapped bullet, so a
   second line read as separate from its own first line as the next bullet did */
.stat-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding-left: var(--sp-4);
}
.stat-list li {
  font-size: .9375rem;
  font-weight: 450;
  line-height: 1.45rem;
  color: var(--ink-60);
}
.stat-list li::marker { color: var(--ink-25); }
.stat-list strong { font-weight: 600; color: var(--ink); }

/* A map sat 8px from the next map and 18px or more from its own numbers, so
   the column read as the unit instead of the row. The .ctx-row table in the
   same rail already solves this with a hairline, so match it rather than
   inventing more gap. */
.map-stat {
  display: grid;
  grid-template-columns: 6rem 1fr 2.75rem 1.75rem;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--hairline);
}
.map-stat:last-child { border-bottom: 0; }
.map-stat-name { font-family: var(--font-mono); font-size: .8125rem; color: var(--ink-80); white-space: nowrap; }
.bar { height: 4px; background: var(--wash); border-radius: 2px; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--accent-bright); border-radius: 2px; }

/* recency edge: two-tone split bar in the h2h sidebar */
.rec-edge .h2h-score { margin-bottom: .4375rem; }
.rec-edge-pct { font-family: var(--font-mono); font-size: .8125rem; font-weight: 600; color: var(--ink-80); white-space: nowrap; }
.rec-bar { display: flex; height: 6px; border-radius: 3px; }
.rec-bar .bar-fill { border-radius: 0; }
/* team-colored fills, mixed toward ink like .rec-num so both themes read */
/* The mix pulls less than it used to, because softenColor already caps the
   incoming saturation in JS. At 72 and 52 against a raw brand colour the bar
   was being calmed twice over, which is what put it out of step with the
   digit beside it. Enough pull remains to keep a near-black or near-white
   team visible against its own ground. */
/* The pull is lighter than the old 72 and 52 because softenColor already
   capped the saturation in JS, so mixing that hard calmed the bar twice and
   put it out of step with the digit beside it. It cannot go to zero: the
   mix is what keeps a near-black team visible on a dark ground, and what
   keeps two segments of a split bar apart. */
.team-fill { background: color-mix(in srgb, var(--team-color, var(--accent-bright)) 88%, var(--ink)); }
[data-theme="dark"] .team-fill { background: color-mix(in srgb, var(--team-color, var(--accent-bright)) 74%, var(--ink)); }

/* h2h scoreline: the record leads, counts ride as a caption */
.h2h-score {
  display: flex;
  align-items: center;
  gap: var(--gap-badge);
  margin-bottom: .625rem;
  cursor: help;
}
/* --t-body, not its own 16px tier. 16 against 15 is the same imperceptible
   step the scale exists to remove, and 24px was considered and rejected:
   mono at weight 650 that large reads heavier than the two team names it
   sits between. */
.h2h-score .rec-num { font-family: var(--font-mono); font-size: .9375rem; font-weight: var(--w-strong); }
.h2h-score-sep { color: var(--ink-40); font-size: .9375rem; }
/* quiet caption line under a scoreline */
.h2h-sub { font-size: .8125rem; color: var(--ink-40); margin: -.125rem 0 .875rem; }
/* ---------- duo table: two teams as columns ----------
   The rail is 222px and the widest team name is 154px, so a table shaped
   [name | value | value] can never fit; three patches proved it. Transposed,
   the two teams are COLUMNS headed by their badges and every cell is a
   bounded number, so the widest possible layout is knowable in advance:
   label 70px, two 56px columns, two 16px gaps, 214 worst case.

   Badges are enough to head the columns because the whole sidebar already
   speaks badge-digit-badge in versus order; the tooltip on each badge still
   carries the name. Every number right-aligns, and the badge right-aligns
   with them, so each column presents one shared edge down its full height.
   The label column is the only elastic track and it holds prose, which is
   what 1fr should be spent on. */
.duo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  column-gap: var(--sp-4);
  row-gap: var(--sp-2);
  align-items: center;
  margin-top: var(--sp-3);
  font-size: var(--t-small);
}
.duo .logo-badge { justify-self: end; }
.duo-label { color: var(--ink-60); }
.duo-cell, .duo-idx {
  justify-self: end;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.duo-none { color: var(--ink-40); }
.duo-idx { font-size: var(--t-micro); }

/* single stat row: badge + team-colored number + unit */
.h2h-mini {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-mono);
  font-size: .8125rem;
  margin-bottom: .4375rem;
  cursor: help;
}
.h2h-mini .rec-num { font-weight: var(--w-strong); }
.h2h-mini-unit { font-family: var(--font-primary); font-size: .8125rem; color: var(--ink-40); }

/* story of the week: home-page teaser card */
.story-teaser {
  display: flex;
  align-items: center;
  gap: 1rem;
  border: 1px solid var(--hairline-strong);
  border-radius: .625rem;
  padding: var(--pad-card);
  margin-top: 3rem;
  transition: border-color var(--dur-base) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
.story-teaser:hover { border-color: var(--accent-border); background: var(--wash); }
.story-text { min-width: 0; }
.story-kicker {
  display: block;
  font-size: .6875rem;
  font-weight: 600;
  color: var(--accent-text);
  margin-bottom: .25rem;
}
.story-title {
  display: block;
  font-family: var(--font-title);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--ink);
}
.story-meta { display: block; font-size: .8125rem; color: var(--ink-40); margin-top: .375rem; }
.story-badges { display: inline-flex; gap: .375rem; margin-left: auto; flex-shrink: 0; }
.story-archive-title { margin-top: 2.5rem; }
.story-archive { max-width: 56rem; }
.story-archive .story-teaser { margin-top: 1rem; }

/* insights: canvas charts sized for social sharing */
.insight { max-width: 56rem; margin-top: 2.5rem; }
.insight canvas {
  width: 100%;
  height: auto;
  border: 1px solid var(--hairline);
  border-radius: .625rem;
  display: block;
  margin-bottom: .75rem;
}
/* 60ch matches .ev-field-none and sits beside .slate-note's 58ch and
   .dim-lede's 56ch, a measure the file already uses */
.insight-lead { max-width: 60ch; margin-bottom: .5rem; line-height: var(--lh-prose); }
/* the toolbar is a control group, not the next line of the paragraph; at
   8px it read as part of the lead. Collapses with the lead's own 8px. */
.insight-lead + .toolbar { margin-top: var(--sp-5); }

/* common opponents: shown only when a pair has no direct history */
/* It now follows the match list rather than replacing it, so it needs a
   section boundary above it, not the 40px it had as the only thing on the
   page. 48px matches every other section break on the site. */
.common-ops { max-width: 30rem; margin-top: var(--sp-7); }
.common-ops .form-grid { margin-bottom: .625rem; }
.common-total { color: var(--ink-60); }

/* circled "i": section explanations without subtitle text */
.info-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--hairline-strong);
  font-family: var(--font-primary);
  font-size: .6875rem;
  font-weight: 600;
  font-style: normal;
  color: var(--ink-40);
  cursor: help;
  margin-left: .25rem;
  vertical-align: 1px;
}
/* the only focusable element the a/button focus rule does not reach; the
   tooltip appearing is feedback, but the focused element needs its own ring */
.info-dot:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.info-dot:hover { color: var(--ink); border-color: var(--line-mid); }

/* recent form as columns: teams compare vertically at a glance */
.form-grid {
  display: grid;
  /* the name column needs a floor: minmax(0, 1fr) lets the value columns
     take everything and starve it, which clipped names in the h2h sidebar */
  grid-template-columns: minmax(7rem, 1.4fr) auto auto;
  gap: .4375rem .875rem;
  align-items: center;
  font-size: .8125rem;
}
.form-col { font-size: .6875rem; color: var(--ink-40); text-align: right; }
.form-team {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-badge);
  font-weight: var(--w-medium);
  min-width: 0;
}
/* Names wrap instead of clipping. "CYBERSHOKE Prospects" needs 185px and the
   h2h sidebar column is ~120px, so nowrap could only ever chop it. The name
   needs its own element: overflow-wrap on the flex container does not reach
   a bare text node, so the long word kept overflowing into the next column. */
.form-team-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: var(--lh-snug); }
.form-team .logo-badge, .form-team img { flex: none; }
/* four columns in a narrow sidebar: the name column must keep a floor or
   grid starves it to 0 and the badge disappears */
.form-grid-4 { grid-template-columns: minmax(4.5rem, 1fr) auto auto auto; gap: .4375rem .625rem; }

.form-grid-4 .form-cell { font-size: .6875rem; }
.form-cell { font-family: var(--font-mono); font-size: .8125rem; text-align: right; white-space: nowrap; }
.setting-idx { font-weight: var(--w-strong); cursor: help; }
.idx-lan { color: var(--accent-text); }
.idx-online { color: var(--ink-60); }
.form-cell em { font-style: normal; color: var(--ink-40); margin-left: .25rem; }
.form-none { color: var(--ink-40); font-family: var(--font-primary); }

/* both-sided per-map record on h2h pages */
.map-duel {
  display: grid;
  grid-template-columns: 6.25rem 1.125rem minmax(0, 1fr) 1.125rem;
  align-items: center;
  gap: .5rem;
  padding: .3125rem 0;
  cursor: help;
}
.map-duel-n { font-family: var(--font-mono); font-size: .8125rem; text-align: center; }
.map-stat-pct { font-size: .8125rem; font-weight: var(--w-medium); text-align: right; }
.map-stat-n { font-family: var(--font-mono); font-size: .6875rem; color: var(--ink-40); text-align: right; }

.chip-link { color: var(--ink-60); transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out); }
.chip-link:hover { color: var(--ink); border-color: var(--line-mid); }
.list-title { margin-bottom: 1rem; }

/* Team match list preview. The caption states the real total because the list
   below it may be a slice, and it sits before the first card so a screen
   reader hears it on the way in. */
.list-caption {
  font-size: .8125rem;
  color: var(--ink-40);
  margin: -.5rem 0 1rem;
}
/* Dashed, like the empty state: a seam in the list rather than a call to act.
   The border is decorative and neither --line-mid (1.53:1 light) nor
   --line-strong (2.21:1) reaches 3:1 against --surface. WCAG 1.4.11 is met by
   the text label at 9.96:1, which is what identifies the control. */
.list-more {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 2.75rem;
  margin: .25rem 0 .5rem;
  padding: var(--pad-row);
  font-size: .8125rem;
  font-weight: var(--w-medium);
  color: var(--ink-80);
  background: var(--surface);
  border: 1px dashed var(--line-strong);
  border-radius: .5rem;
  transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.list-more:hover {
  color: var(--ink);
  border-color: var(--ink-40);
  background: var(--wash);
}
/* Ledger: matches 11 and up, one 44px row each. A scannable date column beats
   a card stack for "did they play FaZe in June", and the map detail already
   lives on the h2h page each row links to. */
/* Tracks live on the list, not the row, so every date lines up down the column.
   With the grid on each row, auto sizes per row and the result letters stagger,
   which is the one thing a scannable date column cannot do. Auto rather than a
   fixed width because SF Mono is absent on Android and Linux and the fallback
   metrics differ. */
.led {
  display: grid;
  grid-template-columns: auto 1.25rem 2.75rem minmax(7rem, 1fr) minmax(0, 1.4fr) auto;
}
.led-scope { font-size: .8125rem; color: var(--ink-40); margin: -.5rem 0 .75rem; }
.led-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  gap: .625rem;
  min-height: 2.75rem;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--hairline);
  color: var(--ink-80);
  font-size: .8125rem;
  transition: background var(--dur-base) var(--ease-out);
}
.led-row:hover { background: var(--wash); color: var(--ink); }
.led-date { font-family: var(--font-mono); color: var(--ink-40); white-space: nowrap; }
/* weight and ink level, not red and green, matching .s-win and .s-loss. The
   letter carries the meaning on its own, so this reads the same to a
   colorblind reader and in a black and white print. */
.led-res { text-align: center; }
.led-W { font-weight: 700; color: var(--ink); }
.led-L { font-weight: 450; color: var(--ink-40); }
.led-D { font-weight: 450; color: var(--ink-40); }
.led-score { font-family: var(--font-mono); color: var(--ink-60); white-space: nowrap; }
.led-opp { display: flex; align-items: center; gap: var(--gap-badge); min-width: 0; }
.led-opp > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.led-logo { min-width: 0; width: 1.125rem; height: 1.125rem; flex: none; }
.led-ev { min-width: 0; color: var(--ink-40); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.led-tags { display: flex; align-items: center; gap: .375rem; color: var(--ink-40); font-size: .8125rem; white-space: nowrap; }
.led-upset { color: var(--accent-text); font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .led-row { transition: none; } }
/* Phones: the event column is the first thing worth losing, then the score.
   The date, result and opponent are what the column is scanned for. */
@media (max-width: 40em) {
  /* the track count drops on the parent, not the row: the hidden cells stop
     participating, so a 6 track subgrid would leave the tags stranded in the
     fourth column with two empty tracks after it */
  .led { grid-template-columns: auto 1.25rem minmax(5rem, 1fr) auto; }
  .led-row { gap: .5rem; }
  .led-score, .led-ev { display: none; }
}

.list-more-head { margin: 1.75rem 0 1rem; scroll-margin-top: 1.5rem; }
/* focus-visible still paints the ring; this only kills it for the programmatic
   focus move after the reveal, where the heading is the announcement */
.list-more-head:focus { outline: none; }
@media (prefers-reduced-motion: reduce) { .list-more { transition: none; } }

/* ---------- where these teams differ ---------- */
/* Two cards on a head-to-head page. The pair grid never collapses to one
   column, not even at 320px: the whole point is the two teams sitting against
   each other, and stacking them turns a comparison into two lists. Everything
   inside is sized to survive that instead. */

/* --sp-7 in, matching the --sp-7 the grid's margin-bottom leaves on the way
   out to Common opponents. It was 0 against the empty state's dashed box and
   12px after the last match card, which is the gap BETWEEN two match cards,
   so the panel read as one more card rather than a new section. */
.dim-title { margin-top: var(--sp-7); margin-bottom: var(--sp-2); }
.dim-lede {
  max-width: 56ch;
  margin: 0 0 var(--sp-4);
  font-size: .8125rem;
  line-height: var(--lh-body);
  color: var(--ink-40);
}
/* 24rem, not 15rem. A card holds two sides, and a side holds a map row whose
   fixed tracks total 136px, so a card narrower than about 380px starves the
   bar's 1fr track to zero and the bars silently stop rendering. At the 592px
   head-to-head column that means one card per row, which is the correct
   answer: two cards fit there only by breaking both. */
/* Two cards per dimension, one per team, sitting side by side. Splitting the
   content halves what each card must hold, which is exactly why they now fit
   two-up in the 592px column where the combined table did not. min() so the
   floor cannot exceed a phone's container. */
.dim-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.dim-sub {
  margin: 0 0 var(--sp-2);
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-weight: var(--w-medium);
  color: var(--ink-40);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.dim-grid + .dim-sub { margin-top: var(--sp-5); }
.dim-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: .5rem;
}
.dim-head { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.dim-team { display: flex; align-items: center; gap: var(--gap-badge); min-width: 0; }
.dim-logo { width: 1.125rem; height: 1.125rem; }
.dim-name {
  font-size: .8125rem;
  font-weight: var(--w-medium);
  color: var(--ink-80);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dim-figure { font-family: var(--font-mono); font-size: 1.5rem; font-weight: 600; line-height: var(--lh-tight); }
/* the short state is a phrase, not a numeral, so it drops out of the mono
   face and off the figure size rather than pretending to be a measurement */
.dim-figure.dim-thin { font-family: var(--font-primary); font-size: .9375rem; font-weight: 500; color: var(--ink-40); }
.dim-unit { font-size: .6875rem; line-height: var(--lh-snug); color: var(--ink-40); }

/* One team per row now, so the bar sits directly between the map name and its
   own rate with nothing else in the run. Both cards render the same maps in
   the same order, so row N lines up across them. */
.dim-table { display: flex; flex-direction: column; gap: 2px; }
.dim-row {
  display: grid;
  grid-template-columns: minmax(0, 4.25rem) 1fr 2.5rem 1.25rem;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: .6875rem;
  color: var(--ink-60);
}
.dim-map-n { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dim-bar { min-width: 0; height: 3px; border-radius: 2px; background: var(--wash); overflow: hidden; }
.dim-bar i { display: block; height: 100%; background: var(--accent-bright); opacity: .75; }
.dim-map-p { text-align: right; }
.dim-rate-narrow { display: none; }
.dim-map-c { text-align: right; color: var(--ink-40); }
/* below even drops to neutral ink: the bar is a rate, not a verdict, and an
   orange fill on a losing map reads as an endorsement of it */
.dim-row.dim-under { color: var(--ink-40); }
.dim-row.dim-under .dim-bar i { background: var(--ink-25); opacity: 1; }
/* too few plays to rank is blank, never a 0% bar, which would read as a record
   of losing every time. The row still renders so the two cards stay aligned. */
.dim-row.dim-none { color: var(--ink-40); }
.dim-row.dim-none .dim-bar { background: none; }
.dim-row.dim-none .dim-map-p { font-family: var(--font-primary); font-size: .6875rem; }

.dim-opps { display: flex; flex-wrap: wrap; gap: 3px; margin-top: var(--sp-1); }
.dim-opp {
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--wash);
  font-family: var(--font-mono);
  font-size: .6875rem;
  color: var(--ink-60);
  white-space: nowrap;
}

@media (max-width: 26em) {
  .dim-figure { font-size: 1.125rem; }
  /* the play count goes first at this width, the bar needs the room more */
  .dim-row { grid-template-columns: minmax(0, 3.5rem) 1fr 2.75rem; gap: var(--sp-1); }
  /* The count column goes, but the denominator must not: 3 of 4 at 75% and
     30 of 40 at 75% are different claims and rendered identically here. The
     rate cell swaps to the fraction, which is on average narrower than the
     percentage it replaces because pct() carries a decimal. */
  .dim-map-c { display: none; }
  .dim-rate-wide { display: none; }
  .dim-rate-narrow { display: inline; }
}

/* ---------- last 10 maps ---------- */
/* After .score (style.css:1056) on purpose: .mf-sc and .score have equal
   specificity, so .mf-sc has to come later to win. */

.mf-title { margin-bottom: .5rem; }
/* the payload sentence sits at body ink because it is what a two second
   reader is meant to leave with; .mf-meta is the caption register below it,
   one size step down and one ink step down. It does not go below --ink-60,
   which is 4.9:1 on the light body, because a caveat that qualifies the claim
   above it has to stay readable at 12px in daylight. */
/* Capped at roughly 75 characters. Full width this runs to 142 on a wide
   column, past comfortable measure, and it is the only full width prose high
   enough in the main column to pass under the fixed theme toggle at 678px
   while scrolling. */
.mf-meta { max-width: 32rem; margin: 0 0 .875rem; font-size: .8125rem; line-height: var(--lh-body); color: var(--ink-60); }

.mf { margin: 0 0 2rem; padding: 0; list-style: none; }
/* The whole series is one click target, not just its header. The anchor is
   still only the header in the DOM, so a screen reader keeps a short link
   name and reads the map rows as their own list items, but its ::after is
   stretched over the group so a click or a hover anywhere in the block hits
   the link. The tradeoff is that text inside a group cannot be selected,
   which is worth it here: these are eight character scores, and a group that
   highlights but does not respond to a click reads as broken. */
.mf-series {
  position: relative;
  list-style: none;
  margin: 0 -.5rem;
  padding: var(--sp-2) .5rem 0;
  border-top: 1px solid var(--hairline-strong);
  border-radius: .375rem;
  transition: background var(--dur-base) var(--ease-out);
}
.mf-series:first-child { border-top: 0; }
.mf-series:hover { background: var(--wash); }
.mf-series:hover .mf-name { color: var(--ink); }
/* the stretched hit area, sized to the group rather than the header */
.mf-head::after { content: ""; position: absolute; inset: 0; }
/* keyboard parity: the ring still draws on the header, the group lights up
   the same way it does under a pointer */
.mf-series:has(.mf-head:focus-visible) { background: var(--wash); }

/* The series header is the adjacency fix made physical: 44px of full width
   text with a hairline above it sits between every group of map rows, so the
   W and L column below can never run unbroken across a series boundary. */
.mf-head {
  display: grid;
  /* auto, not 1.125rem. The track was sized to .mf-logo's 18px while the
     .logo-badge wrapping it renders 24, so the badge overflowed its own
     track by 6 and ate 6 of the 8px gap: the name sat 2px off the badge and
     read as touching it. Sizing the track to what is actually in it puts the
     gap back to the one value every other badge and label pair uses. */
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--gap-badge);
  min-height: 2.75rem;
  padding: .375rem 0;
  font-size: .8125rem;
  color: var(--ink-80);
}

/* The name is the only elastic thing in the header. It ellipsizes rather
   than wrapping, matching .mf-m in the rows below, so a header can never grow
   past its 44px and shove the map rows around. */
.mf-head .logo-badge { flex: none; }
.mf-logo { width: 1.125rem; height: 1.125rem; }
.mf-name { font-weight: var(--w-medium); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Right group lands in the third column, whose right edge is the same edge
   the map scores align to, so dates and scores form one column. */
.mf-right { display: flex; align-items: baseline; gap: .5rem; }
.mf-when {
  font-family: var(--font-mono);
  font-size: .6875rem;
  color: var(--ink-40);
  white-space: nowrap;
}
/* prose face, so "1 of 3 maps" cannot read as a score. This is the only
   qualifier left in the header, so it takes --ink-60 rather than the
   --ink-40 the date uses. */
.mf-cut { font-size: .6875rem; color: var(--ink-60); white-space: nowrap; }

/* Map rows sit flush with the block's left edge, not indented under their
   header. The W and L column is the scan target and an indent would push it
   off the margin. Grouping is carried by the header row and its hairline,
   which costs no horizontal space. */
.mf-maps { margin: 0; padding: 0 0 var(--sp-3); list-style: none; }
.mf-map {
  display: grid;
  grid-template-columns: 1.125rem auto 1fr;
  align-items: center;
  gap: .5rem;
  padding: var(--pad-dense);
  list-style: none;
}
/* .led-W, .led-L and .led-D are bare selectors, so the shipped weight and ink
   treatment applies as is: 700 at full ink for a win, 450 at --ink-40 for a
   loss. No new color, and it survives a black and white print. */
.mf-res { text-align: center; font-size: .8125rem; }
.mf-m {
  font-family: var(--font-mono);
  font-size: .8125rem;
  color: var(--ink-80);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* .score is sized for .map-row, where it sits beside .8125rem team names. Here
   the neighbors are .75rem, so state the size instead of inheriting it.
   Neutral ink like .led-score: the letter carries win and loss on its own, and
   s-win on a loss row put full ink on the opponent's number. The mono face and
   the dimmed colon still come from .score. */
.mf-sc { justify-self: end; font-size: .8125rem; color: var(--ink-60); }

@media (prefers-reduced-motion: reduce) { .mf-series { transition: none; } }

/* Screen reader only. Absolutely positioned so it never claims a grid track,
   clipped so it can never widen the page. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.team-meta h1 a { transition: color var(--dur-base) var(--ease-out); }
.team-meta h1 a:hover { color: var(--ink-60); }

/* team page sidebar */

.roster {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: .375rem;
}
.roster li { font-size: .9375rem; font-weight: 450; color: var(--ink-60); }
.roster strong { font-weight: 600; color: var(--ink); }
.roster-name { color: var(--ink-40); font-size: .8125rem; }

.wld { display: flex; gap: 2rem; margin-bottom: 1rem; }
.wld-item { display: flex; flex-direction: column; }
.wld-num { font-family: var(--font-title); font-size: 1.5rem; font-weight: 600; line-height: var(--lh-tight); }
.wld-muted { color: var(--ink-40); }
.wld-label {
  font-size: .6875rem;
  font-weight: var(--w-medium);
  letter-spacing: .01em;
  color: var(--ink-40);
}
.sidebar .stat-list { margin-bottom: .5rem; }

/* share modal */

.share-overlay {
  position: fixed;
  inset: 0;
  background: rgba(19, 26, 34, .35);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 1.5rem;
}
.share-panel {
  background: var(--body-bg);
  border-radius: .75rem;
  box-shadow: 0 20px 60px rgba(19, 26, 34, .3);
  padding: 1.5rem;
  max-width: 30rem;
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
}
.share-panel h2 { margin-bottom: 1rem; }
.share-preview canvas {
  width: 100%;
  height: auto;
  border: 1px solid var(--hairline);
  border-radius: .5rem;
  display: block;
}
.share-hint { font-size: .8125rem; color: var(--ink-40); line-height: var(--lh-body); margin: .75rem 0 1rem; }
.share-actions { display: flex; flex-wrap: wrap; gap: .5rem; }

/* copy button: stacked icons cross-fade with a blur + slide swap on success */
.btn-copy { display: inline-flex; align-items: center; gap: .4375rem; }
.btn-copy:disabled { cursor: default; }
.btn-copy-icon { position: relative; width: 14px; height: 14px; flex-shrink: 0; }
.btn-copy-icon svg {
  position: absolute;
  inset: 0;
  width: 14px;
  height: 14px;
  transition: opacity var(--dur-base) var(--ease-out), filter var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.btn-copy .ic-check { opacity: 0; filter: blur(4px); transform: translateY(6px); }
.btn-copy[data-state="copied"] .ic-copy { opacity: 0; filter: blur(4px); transform: translateY(-6px); }
.btn-copy[data-state="copied"] .ic-check { opacity: 1; filter: blur(0); transform: none; }
.btn-copy[data-state="copying"] .ic-copy { opacity: .5; }
@media (prefers-reduced-motion: reduce) {
  .btn-copy-icon svg { transition: none; }
}

/* rankings page */

.rankings-grid { max-width: none; }
.rank-cols {
  columns: 3 15rem;
  column-gap: 3rem;
  margin-top: 1rem;
}
.rank-row { break-inside: avoid; }
.rankings-grid h2 { margin-bottom: .75rem; }
.rank-row {
  display: grid;
  grid-template-columns: 1.75rem auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: .5rem;
  padding: var(--sp-2) .5rem;
  margin: 0 -.5rem;
  border-radius: .375rem;
  transition: background var(--dur-micro) var(--ease-out);
}
.rank-row:hover { background: var(--wash); }
.rank-num {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: .8125rem;
  color: var(--ink-40);
  text-align: right;
}
.rank-name {
  min-width: 0;
  font-size: .8125rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-pts { font-family: var(--font-mono); font-size: .6875rem; color: var(--ink-40); }

.rankings-link {
  font-size: .8125rem;
  font-weight: 500;
  color: var(--accent-text);
  white-space: nowrap;
  margin-left: .5rem;
}
.rankings-link:hover { color: var(--accent-bright); }

@media (max-width: 640px) {
  .banner { padding-left: 1rem; padding-right: 1rem; }
  /* slate rows: chips get their own full-width line instead of overflowing
     onto the team names */
  /* stacked full-width inputs; the floating "vs" reads as clutter here */
  .vs-label { display: none; }
  .combo, .combo input { width: 100%; }
  /* map rows: logos + scores carry the line; truncated names ("V…") don't */
  .map-team-name { display: none; }
  .map-row { grid-template-columns: minmax(0, 6rem) 1fr 3.5rem 1fr; }
  /* long team names must break rather than force horizontal scroll */
  .team-meta h1 { white-space: normal; }
  /* .chips is the team header's set and was never covered here, so a long
     best-map chip stayed nowrap and pushed 5px off a 320px screen. */
  .match-chips .chip, .chips .chip { white-space: normal; }
  /* clear of the (taller, wrapping) banner */

}

.empty {
  border: 1px dashed var(--hairline-strong);
  border-radius: .5rem;
  padding: 3rem 1.5rem;
  text-align: center;
  font-size: .9375rem;
  color: var(--ink-40);
  line-height: var(--lh-body);
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  .history-grid { grid-template-columns: 1fr; gap: 2rem; }
  .sidebar { position: static; }
  .versus-row { flex-direction: column; align-items: stretch; gap: 1rem; }
  .team-header.right { flex-direction: row; text-align: left; }
  .team-header.right .chips { justify-content: flex-start; }
  .map-row .map-team:nth-of-type(1) { justify-self: start; flex-direction: row; }
  .vs-big { align-self: flex-start; }
  .map-row { grid-template-columns: 6rem minmax(0, 1fr) 3.5rem minmax(0, 1fr); }
}

/* ---------- maps page ---------- */

.map-table { max-width: 56rem; margin-top: 1.5rem; }
.map-row-full {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr) 4.5rem 7rem 5.5rem;
  align-items: center;
  gap: .875rem;
  padding: var(--pad-row);
  margin: 0 -.5rem;
  border-radius: .375rem;
  font-size: .8125rem;
}
.map-row-full:hover { background: var(--wash); }
.map-head {
  font-size: .6875rem;
  color: var(--ink-40);
  border-bottom: 1px solid var(--hairline);
  border-radius: 0;
}
.map-head:hover { background: none; }
.map-name-cell {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: .8125rem;
  font-weight: 600;
}
.map-side-cell { display: flex; align-items: center; gap: .75rem; min-width: 0; }
/* CS convention: T side tan, CT side blue */
.side-bar { display: flex; height: 8px; border-radius: 4px; flex: 1 1 auto; min-width: 5rem; cursor: help; }
.side-bar .bar-fill { border-radius: 0; }
.side-t { background: #c8a45c; }
.side-ct { background: #5b8bc4; }
.map-side-read { font-size: .8125rem; color: var(--ink-60); white-space: nowrap; }
.map-side-read em { font-style: normal; font-family: var(--font-mono); color: var(--ink-40); margin-left: .25rem; }
.map-side-none { font-size: .8125rem; color: var(--ink-40); }
.map-num { font-family: var(--font-mono); font-size: .8125rem; text-align: right; cursor: help; }
.map-head .map-num { font-family: var(--font-primary); }
.map-foot { max-width: 60ch; margin-top: 1.5rem; font-size: .8125rem; line-height: var(--lh-body); color: var(--ink-40); }

@media (max-width: 640px) {
  .map-row-full { grid-template-columns: 6.5rem minmax(0, 1fr) 3.5rem; }
  .map-row-full > .map-num:nth-child(4),
  .map-row-full > .map-num:nth-child(5),
  .map-head > span:nth-child(4),
  .map-head > span:nth-child(5) { display: none; }
  /* The side split was hidden here and survived only in a hover tooltip,
     which is nothing at all on a phone. Keep the number and drop the word
     instead, so the column still says which side the map favours. */
  .map-side-read { font-size: .6875rem; }
  .map-side-read .side-word { display: none; }
  .map-side-read em { margin-left: 0; }
  .map-side-cell { gap: .5rem; }
  .side-bar { min-width: 3rem; }
}

/* sortable column headers on the Maps page */
.map-sort {
  display: inline-flex;
  align-items: center;
  gap: .3125rem;
  font-family: var(--font-primary);
  font-size: .6875rem;
  color: var(--ink-40);
  padding: 0;
  transition: color var(--dur-base) var(--ease-out);
}
.map-head .map-sort.map-num { justify-content: flex-end; width: 100%; }
.map-sort:hover { color: var(--ink); }
.map-sort[aria-pressed="true"] { color: var(--ink); font-weight: var(--w-strong); }
/* the caret is always visible so the column reads as sortable, and fills
   in on the active column */
/* exempt from the type scale: a single caret glyph, not text */
.sort-caret {
  font-size: .5rem;
  line-height: 1;
  color: var(--ink-40);
  transition: color var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.map-sort:hover .sort-caret { color: var(--ink-60); }
.map-sort[aria-pressed="true"] .sort-caret { color: var(--accent-text); transform: translateY(1px); }
/* the whole header cell is the hit target, and it reads as clickable */
.map-sort {
  cursor: pointer;
  padding: .25rem .375rem;
  margin: -.25rem -.375rem;
  border-radius: .25rem;
  transition: color var(--dur-base) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
.map-sort:hover { background: var(--wash); }
.map-sort:hover .sort-caret { transform: translateY(1px); }
/* underdog signals on the value radar, distinct from the market-gap chip */
.chip-dog { color: var(--ink-80); border-color: var(--line-mid); }

/* insights chart picker: trigger + listbox */
/* wrap: at 320px the label, the trigger and the count do not fit on one
   line, and without this the count was pushed past the right edge where
   html{overflow-x:clip} hid it rather than letting it scroll into view */
.insight-picker { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }
.picker-label { font-size: .8125rem; color: var(--ink-40); }
.insight-count { font-size: .8125rem; color: var(--ink-40); }

.dropdown { position: relative; }
.dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-primary);
  font-size: .9375rem;
  font-weight: var(--w-medium);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: .375rem;
  padding: .4375rem .625rem;
  min-width: 15rem;
  text-align: left;
  transition: border-color var(--dur-base) var(--ease-out), background var(--dur-micro) var(--ease-out);
}
.dropdown-trigger:hover { border-color: var(--line-mid); }
.dropdown-value { flex: 1 1 auto; }
.dropdown-chevron {
  width: 14px;
  height: 14px;
  color: var(--ink-40);
  transition: transform var(--dur-base) var(--ease-spring);
}
.dropdown-trigger[aria-expanded="true"] .dropdown-chevron { transform: rotate(180deg); }

.dropdown-menu {
  position: absolute;
  top: calc(100% + .375rem);
  left: 0;
  z-index: 70;
  min-width: 100%;
  max-height: 20rem;
  overflow-y: auto;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: .5rem;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .16);
  padding: .25rem;
  outline: none;
  animation: dropdown-in var(--dur-base) var(--ease-out) both;
}
@keyframes dropdown-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}
.dropdown-menu [role="option"] {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .4375rem .5rem;
  border-radius: .375rem;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--ink-80);
  cursor: pointer;
  white-space: nowrap;
  animation: dropdown-item var(--dur-base) var(--ease-out) both;
  animation-delay: calc(min(var(--i, 0), 8) * 14ms);
}
@keyframes dropdown-item {
  from { opacity: 0; transform: translateX(-4px); }
  to { opacity: 1; transform: none; }
}
.dropdown-menu [role="option"].is-focused { background: var(--wash); color: var(--ink); }
.dropdown-menu [role="option"][aria-selected="true"] { color: var(--ink); font-weight: var(--w-strong); }
.dropdown-check { width: 13px; height: 13px; flex-shrink: 0; color: var(--accent-text); }
.dropdown-check svg { width: 13px; height: 13px; display: block; }

@media (prefers-reduced-motion: reduce) {
  .dropdown-menu, .dropdown-menu [role="option"] { animation: none; }
  .dropdown-chevron { transition: none; }
}

/* missing-logo fallback: logos/_unknown.svg brings its own tile, so the
   badge drops its background and the image fills the whole square */
.logo-badge.logo-none { background: none; }
.logo-badge.logo-none img {
  /* the base rule caps logos at 72% to inset them in the tile; this
     asset IS the tile, so it fills the badge edge to edge */
  max-width: 100%;
  max-height: 100%;
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: none;
}

/* Value radar preamble. The tab is a claim about relevance, not a tip
   sheet, so the panel says what the signals are before showing any. */
.slate-note {
  margin-top: .75rem;
  max-width: 58ch;
  font-size: .8125rem;
  line-height: var(--lh-body);
  color: var(--ink-60);
}
.slate-note + .up-list { margin-top: .75rem; }

/* ---------- betting overview page ---------- */
.bet-section { max-width: 46rem; margin-top: 2.5rem; }
.bet-section h2 { margin-bottom: .75rem; }
/* the section cap is 46rem but the prose inside it was uncapped, running
   about 98 characters, well past the readable band */
.bet-section p { max-width: 68ch; margin-bottom: .875rem; line-height: var(--lh-prose); color: var(--ink-80); }
.bet-section p:last-child { margin-bottom: 0; }

.bet-table {
  margin: 1.25rem 0;
  border: 1px solid var(--hairline);
  border-radius: .625rem;
  /* overflow:hidden here was only ever meant to clip the rounded corners,
     but the row's grid minimums total more than a 320px screen leaves, so it
     was also swallowing the whole right-hand column: the counts were drawn
     31px past the edge and could not be scrolled to. Vertical stays hidden
     for the corners, horizontal scrolls instead of clipping. */
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
}
.bet-row {
  display: grid;
  grid-template-columns: minmax(6rem, 1fr) repeat(3, minmax(3.5rem, auto));
  gap: .5rem 1rem;
  align-items: center;
  padding: var(--pad-row);
  font-size: .8125rem;
}
.bet-row + .bet-row { border-top: 1px solid var(--hairline); }
.bet-head {
  font-size: .6875rem;
  color: var(--ink-60);
  background: var(--wash);
}
.bet-band { overflow-wrap: break-word; }
.bet-num { font-family: var(--font-mono); text-align: right; }
.bet-head .bet-num { font-family: var(--font-primary); }
.bet-n { color: var(--ink-60); }
.bet-foot { font-size: .8125rem; color: var(--ink-60); }

.bet-list { margin: 0 0 .875rem 1.125rem; }
.bet-list li { margin-bottom: .5rem; line-height: var(--lh-prose); color: var(--ink-80); }

.bet-disclosure {
  margin-top: 1.25rem;
  padding: var(--pad-card);
  border: 1px solid var(--hairline-strong);
  border-radius: .625rem;
  font-size: .8125rem;
  color: var(--ink-60);
}

/* ---------- changelog ---------- */
.cl-list { margin-top: 2.5rem; max-width: 46rem; }
.cl-entry {
  display: grid;
  grid-template-columns: minmax(7rem, 9rem) minmax(0, 1fr);
  gap: 1.5rem;
  padding-bottom: 2.25rem;
  margin-bottom: 2.25rem;
  border-bottom: 1px solid var(--hairline);
}
.cl-entry:last-child { border-bottom: 0; margin-bottom: 0; }
.cl-meta { display: flex; flex-direction: column; align-items: flex-start; gap: .5rem; }
/* the date rail collapses above the entry on narrow screens rather than
   squeezing the body copy into a column too thin to read */
@media (max-width: 640px) {
  .cl-entry { grid-template-columns: 1fr; gap: .625rem; }
  .cl-meta { flex-direction: row; align-items: center; gap: .625rem; }
}
.cl-date { font-size: .8125rem; font-weight: var(--w-medium); color: var(--ink-60); white-space: nowrap; }
.cl-tag {
  display: inline-block;
  padding: .1875rem .5rem;
  border-radius: 999px;
  font-size: .6875rem;
  font-weight: 600;
  border: 1px solid var(--hairline-strong);
  color: var(--ink-60);
  white-space: nowrap;
}
.tag-feature { color: var(--accent-text); border-color: var(--accent-border); }
.cl-title { font-size: .9375rem; font-weight: 600; margin-bottom: .625rem; }
/* h2 elsewhere draws a trailing rule; the changelog title is a heading in
   a body column, not a section divider */
.cl-title::after { content: none; }
.cl-body p { margin-bottom: .75rem; line-height: var(--lh-prose); color: var(--ink-80); font-size: .9375rem; }
.cl-body p:last-child { margin-bottom: 0; }
.cl-empty {
  max-width: 34rem;
  color: var(--ink-60);
  font-size: .9375rem;
  line-height: var(--lh-prose);
}
.credits-link {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.credits-link:hover { color: var(--ink-60); }

/* sub-filter rail under the insight picker; only the side-splits chart
   uses one today, and the container collapses when it is empty */
.chart-filter { display: flex; flex-wrap: wrap; gap: .375rem; margin-top: .875rem; }
.chart-filter:empty { display: none; }

/* a played fixture still sitting in the upcoming window: the score replaces
   the price, and the label replaces the clock */
.chip-final { border-color: var(--hairline-strong); }

/* The matchup link covers the whole row via a pseudo element, so the row
   stays one click target without an <a> wrapping other links. Anything
   interactive inside the row is raised above it. */
.up-stretch { display: block; min-width: 0; }
.up-stretch::after { content: ""; position: absolute; inset: 0; border-radius: .5rem; }
.up-watch {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: .3125rem;
  padding: .1875rem .5rem;
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  font-size: .6875rem;
  font-weight: 600;
  color: var(--accent-text);
  white-space: nowrap;
  transition: background var(--dur-micro) var(--ease-out);
}
/* No dot here on purpose. A live row already carries one, pulsing, in the
   time column, and the pending banner carries one beside "Live now". A
   second static dot on the watch pill put two orange dots in the same
   strip and diluted the only signal that is supposed to mean "in progress".
   The pill reads as a link from its border and color alone. */
.up-watch:hover { background: color-mix(in srgb, var(--accent-bright) 14%, transparent); }

/* Live or scheduled meeting, above the finished history on a h2h page */
.pending {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .625rem .875rem;
  margin: 1.5rem 0;
  padding: var(--pad-card);
  border: 1px solid var(--accent-border);
  border-radius: .625rem;
  background: color-mix(in srgb, var(--accent-bright) 7%, transparent);
  font-size: .8125rem;
}
.pending-live {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  font-weight: var(--w-strong);
  color: var(--accent-text);
}
.pending-when { font-weight: 600; color: var(--ink-80); }
.pending-meta { color: var(--ink-60); min-width: 0; overflow-wrap: break-word; }
.pending-chips { margin-left: auto; display: inline-flex; align-items: center; gap: .375rem; }

/* ---------- tournament pages ---------- */
.ev-list { display: flex; flex-direction: column; margin-top: .75rem; }
.ev-row {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr) minmax(0, auto);
  gap: .75rem;
  align-items: center;
  padding: var(--pad-row);
  margin: 0 -.5rem;
  border-radius: .5rem;
  border-bottom: 1px solid var(--hairline);
  transition: background var(--dur-micro) var(--ease-out);
}
.ev-row:hover { background: var(--wash); }
.ev-row:last-child { border-bottom: 0; }
@media (max-width: 620px) {
  .ev-row { grid-template-columns: 1fr; gap: .375rem; }
}
.ev-when { font-family: var(--font-mono); font-size: .8125rem; color: var(--ink-60); white-space: nowrap; }
.ev-teams { display: flex; align-items: center; flex-wrap: wrap; gap: .375rem .5rem; min-width: 0; font-size: .8125rem; font-weight: var(--w-medium); }
/* one side is a single wrappable unit, so a long name never leaves its badge */
.ev-side { display: inline-flex; align-items: center; gap: var(--gap-badge); min-width: 0; color: var(--ink-60); }
.ev-side.is-win { color: var(--ink); }
.ev-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: var(--lh-snug); }
.ev-side .logo-badge { flex: none; }
.ev-vs { font-style: normal; color: var(--ink-40); font-weight: 450; }
.ev-meta { display: inline-flex; align-items: center; gap: .375rem; justify-content: flex-end; flex-wrap: wrap; }
.ev-score { font-family: var(--font-mono); font-size: .8125rem; font-weight: var(--w-strong); white-space: nowrap; }
.ev-dash { color: var(--ink-40); margin: 0 .25rem; font-weight: 450; }
.ev-odds { font-family: var(--font-mono); }
.ev-back { margin-top: var(--sp-6); font-size: .8125rem; }
.ev-back a { color: var(--accent-text); text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 2px; }

.ev-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: .5rem;
  margin-top: .75rem;
  margin-bottom: 2rem;
}
.ev-card {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: var(--pad-card);
  border: 1px solid var(--hairline);
  border-radius: .5rem;
  background: var(--surface);
  transition: border-color var(--dur-micro) var(--ease-out);
}
.ev-card:hover { border-color: var(--hairline-strong); }

/* Announced events are not links, so they must not offer a link's
   affordances. No hover shift, no pointer, and a dashed edge that reads as
   provisional next to the solid cards around it. */
/* dashed reads as provisional next to the solid played cards */
.ev-card-soon { border-style: dashed; }
.ev-card-soon .ev-card-name { font-weight: var(--w-medium); }
/* The signed up teams sit under the card's own row, so the card grows from a
   line into a block. .ev-card-soon is flex row, hence the full width basis. */
/* the field lives on the event's own page now, so the card is a plain link
   row again and needs none of the column overrides it briefly carried */
.ev-field { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.ev-team {
  display: inline-flex;
  align-items: center;
  gap: .3125rem;
  padding: .1875rem .4375rem;
  border-radius: .3125rem;
  background: var(--wash);
  font-size: .6875rem;
  color: var(--ink-80);
  white-space: nowrap;
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
a.ev-team:hover { background: var(--surface); color: var(--ink); box-shadow: inset 0 0 0 1px var(--hairline-strong); }
/* a team we do not track gets no link, so it must not look like one */
.ev-team-off { color: var(--ink-40); }
.ev-team-logo { width: .875rem; height: .875rem; }
/* heading -> its content is --sp-3, the same rung .ev-list uses two rules
   up. These carried no top margin at all and sat flush against their own
   heading, which is rule 1 in the scale comment. */
.ev-field, .ev-field-src, .ev-field-none { margin-top: var(--sp-3); }
.ev-field-src { font-size: .8125rem; color: var(--ink-40); }
.ev-field-none { font-size: .8125rem; line-height: var(--lh-body); color: var(--ink-40); max-width: 60ch; }
@media (prefers-reduced-motion: reduce) { .ev-team { transition: none; } }

.ev-soon-tag {
  margin-left: auto;
  flex: none;
  font-family: var(--font-mono);
  font-size: .6875rem;
  color: var(--ink-40);
  white-space: nowrap;
}
/* --sp-2 under the heading, the rung for a heading and its own subtitle, the
   same gap .dim-title leaves its lede. It was a negative margin, which pulled
   the note 4px INTO the heading. The gap below comes from .ev-cards' own
   12px, so this section matches the plain h2 sections around it. */
.ev-soon-note {
  max-width: 62ch;
  margin: var(--sp-2) 0 0;
  font-size: .8125rem;
  line-height: var(--lh-body);
  color: var(--ink-40);
}
.ev-card-name { font-size: .9375rem; font-weight: 600; line-height: var(--lh-snug); overflow-wrap: break-word; }
.ev-card-meta { font-size: .8125rem; color: var(--ink-60); }
.match-event a { text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 2px; }
.match-event a:hover { color: var(--accent-text); }

/* event page header */
/* flex-end, not center: the logo tile is 40px against a 31px text block, so
   centering left its bottom edge 5px below the title and the tier pill.
   baseline is worse here, it lifts the logo 6px above the pill, because a
   replaced element baselines at its own bottom edge. The h1 carries only
   3px of descender space, so aligning the boxes reads as aligning the
   letterforms. */
.ev-head { display: flex; align-items: flex-end; gap: var(--sp-4); margin-bottom: var(--sp-4); }
/* Must follow the rule above, not join the other max-width:640px blocks: both
   selectors are one class, so source order decides and a query placed earlier
   silently loses. That is the fourth time in this file.

   Bottom alignment only reads right while the title is one line. Once it
   wraps, pinning the logo to the bottom strands it beside the tier pill with
   a hole above it, so on narrow screens it sits beside the first line. */
@media (max-width: 640px) { .ev-head { align-items: flex-start; } }
.ev-logo {
  width: 40px; height: 40px; flex: none;
  object-fit: contain; border-radius: .375rem;
  background: var(--wash); padding: 3px;
}
.ev-logo-none { display: inline-block; box-shadow: inset 0 0 0 1px var(--hairline); }
/* A white-on-transparent mark disappears against the light theme's white
   card, and 104 of the 268 event logos measure as light. Same fix team logos
   have carried from the start, an inverted tile behind the mark, with the
   mirror case for near-black marks in dark mode. The tone is classified once
   in fetch_tournaments rather than read from a canvas at runtime, because a
   canvas pixel read is blocked on some viewing surfaces. */
.ev-logo-light { background: var(--ink); }
[data-theme="dark"] .ev-logo-light { background: var(--wash); }
[data-theme="dark"] .ev-logo-dark { background: rgba(232, 230, 224, .88); }
/* center, not baseline. Baseline puts the pill's TEXT on the title's
   baseline, then its padding and border hang 7px below it, which reads as
   the pill sagging. Centring costs 1px against the baseline and puts the
   pill's optical centre 3px from the title's cap-height centre instead of
   9px, so it sits with the letterforms rather than under them. */
.ev-head-text { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); min-width: 0; }
/* same treatment as .team-meta h1; without it the browser default took over
   and rendered the name as 30px bold sans instead of the site's serif */
.ev-head h1 {
  font-family: var(--font-title);
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -.01rem;
  min-width: 0;
  overflow-wrap: break-word;
}
.ev-tier {
  font-family: var(--font-mono); font-size: .6875rem; font-weight: 700;
  padding: .1875rem .4375rem; border-radius: 999px;
  border: 1px solid var(--hairline-strong); color: var(--ink-60); white-space: nowrap;
}
/* only the top tier gets the accent; if every tier were coloured the badge
   would stop carrying information */
.ev-tier-s { color: var(--accent-text); border-color: var(--accent-border); background: color-mix(in srgb, var(--accent-bright) 12%, transparent); }

/* The 1px gap is the divider, but it is drawn by each cell rather than by a
   hairline background on the container. With five facts in three columns,
   which is what auto-fit resolves to on a phone, the container version left
   the sixth cell empty and painted it as a solid grey block. */
.ev-facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: 1px; background: var(--body-bg);
  border: 1px solid var(--hairline); border-radius: .5rem;
  overflow: hidden; margin-bottom: 2rem;
}
.ev-fact { background: var(--body-bg); padding: var(--pad-row); box-shadow: 0 0 0 1px var(--hairline); }
.ev-fact dt {
  font-size: .6875rem; font-weight: var(--w-medium);
  color: var(--ink-40); margin-bottom: .125rem;
}
.ev-fact dd { font-size: .9375rem; font-weight: 600; font-variant-numeric: tabular-nums; }

/* index cards carry the mark */
.ev-card { flex-direction: row; align-items: center; gap: .625rem; }
.ev-card .ev-logo { width: 28px; height: 28px; }
.ev-card-text { display: flex; flex-direction: column; gap: .1875rem; min-width: 0; }

/* tournaments index: spacing and the name filter */
/* the first heading sat 8px under the lead while every other page gives a
   section 3rem; scoped to this page so no other h2 rhythm shifts */
.ev-page h2 { margin-top: var(--sp-7); }

.ev-page .ev-cards { margin-top: .75rem; margin-bottom: 0; }

/* Binds up to the lead it belongs with. At 32px it sat equidistant between
   that lead and the first section heading, which it does not filter. */
.ev-search {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-4);
}
.ev-search-label { font-size: .8125rem; font-weight: 600; color: var(--ink-80); }
.ev-search input {
  font-family: var(--font-primary);
  font-size: .9375rem;
  font-weight: 500;
  color: var(--ink);
  width: 15rem;
  max-width: 100%;
  padding: .5rem .75rem;
  border: 1px solid var(--hairline-strong);
  border-radius: .375rem;
  background: var(--surface);
  outline: none;
  transition: border-color var(--dur-base) var(--ease-out);
}
.ev-search input:hover { border-color: var(--line-mid); }
.ev-search input:focus { border-color: var(--line-strong); }
.ev-count { font-size: .8125rem; color: var(--ink-40); font-variant-numeric: tabular-nums; }
.ev-empty { margin-top: 1.5rem; font-size: .9375rem; color: var(--ink-60); }

/* ---------- context splits on team pages ---------- */
.ctx-table { display: flex; flex-direction: column; margin-top: .75rem; container-type: inline-size; }
.ctx-row {
  display: grid;
  grid-template-columns: minmax(4.5rem, 1fr) auto 3.25rem 2.75rem;
  gap: .5rem;
  align-items: center;
  padding: .3125rem 0;
  border-bottom: 1px solid var(--hairline);
  font-size: .8125rem;
}
.ctx-row:last-child { border-bottom: 0; }
/* The 222px rail cannot hold a prose label and three numeric columns on one
   line: the 4.5rem label floor plus the record, the two number columns and
   three gaps comes to 247 against 222. Free space goes negative, so the 1fr
   never leaves its floor and "Ranked 84 and worse" clipped to "Ranked 8…" at
   EVERY width, phone and desktop alike, while the row spilled its own panel.
   The threshold is the label's own container, so the numbers keep their line
   wherever there is room for it and drop under the label where there is not. */
@container (max-width: 22rem) {
  .ctx-row { grid-template-columns: auto 3.25rem 2.75rem; }
  .ctx-label { grid-column: 1 / -1; white-space: normal; overflow: visible; }
}
.ctx-head {
  font-size: .6875rem;
  font-weight: var(--w-medium);
  color: var(--ink-40);
  border-bottom-color: var(--hairline-strong);
}
.ctx-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctx-rec, .ctx-rate, .ctx-edge {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.ctx-rate { color: var(--ink-60); }
/* only the market-adjusted column takes colour; the raw record is neutral
   because beating a soft schedule is not the same as beating a price */
.ctx-up { color: var(--good, #2e7d52); font-weight: var(--w-strong); }
.ctx-down { color: var(--ink-40); }
.ctx-none { font-family: var(--font-primary); font-size: .6875rem; color: var(--ink-40); }
[data-theme="dark"] .ctx-up { color: #6ec894; }

/* The tracks belong to the list, not the row. Sized per row the map name
   column was auto, so the bar for Overpass started further right and ran
   shorter than the one for Nuke at the same win rate, which put the bar
   lengths in open disagreement with the best-first sort they sit in. */
.pool-list {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) 2.75rem;
  row-gap: var(--sp-2);
  column-gap: .5rem;
  margin-top: .75rem;
}
.pool-row { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
.pool-bar { height: 6px; border-radius: 3px; background: var(--wash); overflow: hidden; }
.pool-bar > span { display: block; height: 100%; background: var(--accent-bright); border-radius: 3px; }
.pool-rate {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: .8125rem;
  text-align: right;
  color: var(--ink-60);
}
.pool-note { margin-top: .5rem; font-size: .6875rem; color: var(--ink-40); line-height: var(--lh-body); }

/* rolling form against the market, on h2h pages */
.form-chart { margin-top: .75rem; display: flex; flex-direction: column; gap: .5rem; }
.form-chart svg { width: 100%; height: auto; display: block; color: var(--ink); }
.form-legend { display: flex; flex-wrap: wrap; gap: .375rem .875rem; font-size: .8125rem; color: var(--ink-60); }
.form-key { display: inline-flex; align-items: center; gap: .375rem; min-width: 0; }
.form-key i { width: .5rem; height: .5rem; border-radius: 50%; flex: none; }
.form-key b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: var(--w-strong); color: var(--ink); }
.form-note { font-size: .6875rem; color: var(--ink-40); line-height: var(--lh-body); }

/* roster change impact, shown only once a tracked move has matches on both sides */
.ctx-note { font-size: .8125rem; color: var(--ink-60); margin: .25rem 0 .5rem; }

/* Chart strokes take the same mix toward ink as .team-fill, so a near-black
   team color stays visible in dark mode and follows a theme toggle. */
.form-line, .form-dot { transition: none; }
.form-line { stroke: color-mix(in srgb, var(--team-color, var(--accent-bright)) 88%, var(--ink)); }
.form-dot { fill: color-mix(in srgb, var(--team-color, var(--accent-bright)) 88%, var(--ink)); }
.form-key i { background: color-mix(in srgb, var(--team-color, var(--accent-bright)) 88%, var(--ink)); }
[data-theme="dark"] .form-line { stroke: color-mix(in srgb, var(--team-color, var(--accent-bright)) 74%, var(--ink)); }
[data-theme="dark"] .form-dot { fill: color-mix(in srgb, var(--team-color, var(--accent-bright)) 74%, var(--ink)); }
[data-theme="dark"] .form-key i { background: color-mix(in srgb, var(--team-color, var(--accent-bright)) 74%, var(--ink)); }
/* exempt from the type scale: SVG chart furniture, and every value it
   labels is restated as text in .form-key b */
.form-axis { font-size: 9px; fill: currentColor; opacity: .35; font-family: var(--font-mono); }

/* Map pool: overall and S-tier only, swapped by the checkbox with no
   re-render. :has() keeps the toggle declarative and the input native. */
.pool-filter { margin: 0 0 .625rem; }
.pool-wrap .pool-top { display: none; }
.pool-wrap:has(.pool-check:checked) .pool-all { display: none; }
.pool-wrap:has(.pool-check:checked) .pool-top { display: block; }

/* SmoothUI checkbox (smoothui.dev/docs/components/checkbox).
   Motion's SPRING_DEFAULT is duration .25s at bounce .1, a spring CSS has no
   direct equivalent for, so the curve below is a bezier with the same small
   overshoot. The tick scales and fades in while its path draws 50ms behind,
   which is the ordering that gives the original its handwritten feel. */
:root { --sui-spring: cubic-bezier(.34, 1.32, .58, 1); }

.sui-check { display: inline-flex; align-items: center; gap: .4375rem; }
.sui-box { position: relative; display: inline-flex; flex: none; }

.sui-input {
  appearance: none; -webkit-appearance: none;
  width: 1rem; height: 1rem; margin: 0; flex: none;
  border: 1px solid var(--line-mid); border-radius: 4px;
  background: var(--body-bg);
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.sui-input:hover { border-color: var(--line-strong); }
.sui-input:checked { background: var(--ink); border-color: var(--ink); }
.sui-input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.sui-input:disabled { opacity: .5; cursor: not-allowed; }

/* inset + auto margins center the tick over the box whatever size it is */
.sui-mark {
  position: absolute; inset: 0; margin: auto;
  width: .875rem; height: .875rem;
  pointer-events: none;
  fill: none; stroke: var(--surface);
  stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;
  opacity: 0; transform: scale(.8); transform-origin: center;
  transition: opacity var(--dur-slow) var(--sui-spring), transform var(--dur-slow) var(--sui-spring);
}
.sui-mark path {
  stroke-dasharray: 1; stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--dur-slow) var(--sui-spring);
}
.sui-input:checked ~ .sui-mark { opacity: 1; transform: scale(1); }
.sui-input:checked ~ .sui-mark path { stroke-dashoffset: 0; transition-delay: .05s; }

.sui-check label { font-size: .8125rem; color: var(--ink-60); cursor: pointer; }
.sui-check:hover label { color: var(--ink-80); }

@media (prefers-reduced-motion: reduce) {
  .sui-input, .sui-mark, .sui-mark path {
    transition-duration: .01ms; transition-delay: 0s;
  }
}

/* The hidden attribute is a UA rule, so any class that sets display beats
   it. That silently broke the tournament filter, where .ev-card's own
   display: flex outranked it. This makes hidden mean hidden everywhere. */
[hidden] { display: none !important; }

/* Tournament combobox. Event names run long, so unlike the team pickers
   these wrap instead of truncating, and the list is wider to suit. */
.ev-combo { width: 15rem; max-width: 100%; }
.ev-combo .combo-list { width: min(24rem, calc(100vw - 2.5rem)); }
.ev-combo-item { align-items: flex-start; }
.ev-combo-name { white-space: normal; overflow: visible; text-overflow: clip; line-height: var(--lh-snug); }
.ev-combo-item .combo-rank { white-space: nowrap; padding-top: .0625rem; }
.ev-combo-item .combo-rank .live-dot { margin-right: .25rem; }
.ev-combo-item .ev-logo { width: 20px; height: 20px; flex: none; }

/* Team search above the browse grid, same shape as the tournament search */
.team-search { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem .75rem; margin: 1rem 0; }
.team-search-label { font-size: .8125rem; font-weight: 600; color: var(--ink-80); }

/* Live score chip. Warmer border than .chip-final so an in-progress score
   reads as current rather than settled, matching the live dot elsewhere. */
.chip-score { border-color: var(--accent-border); }

/* ---------- site nav: row on wide screens, sheet on narrow ---------- */

/* The nav element carries `popover` for the small-screen sheet. A closed
   popover is display:none by UA rule, so above the breakpoint that rule has
   to be undone and the element returned to normal flow. */
.site-nav[popover] {
  display: flex;
  position: static;
  inset: auto;
  width: auto;
  max-width: none;
  height: auto;
  /* cancels the UA popover's centring margin without also cancelling the
     margin-left:auto that pushes the row to the right of the masthead */
  margin: 0 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  overflow: visible;
}
.nav-toggle { display: none; }
.nav-ic { width: 22px; height: 22px; }

@media (max-width: 40em) {
  /* 44px is the smallest comfortable touch target, per WCAG 2.5.5 and both
     platform HIGs. The negative margin keeps the icon optically aligned with
     the lockup while the hit area stays full size. */
  .nav-toggle {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    margin: -11px 0 -11px auto;
    color: var(--ink-60);
    border-radius: .5rem;
    cursor: pointer;
    transition: color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
  }
  .nav-toggle:hover { color: var(--ink); background: var(--wash); }
  .nav-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .nav-ic-close { display: none; }
  .masthead:has(.site-nav:popover-open) .nav-ic-open { display: none; }
  .masthead:has(.site-nav:popover-open) .nav-ic-close { display: block; }

  /* closed: hand the UA rule back */
  .site-nav[popover] { display: none; }
  .site-nav[popover]:popover-open {
    display: flex;
    flex-direction: column;
    gap: 0;
    position: fixed;
    top: 3.75rem;
    /* Both edges pinned, not just the right one. A single offset is only
       correct if the containing block is exactly the visible viewport, and
       when that assumption is off the panel slides out past the edge with
       nothing to stop it. With left and right both set the box can never be
       wider than the band between them; width:max-content keeps it hugging
       its content and margin-left:auto keeps it against the right. 100vw is
       avoided here for the same reason: it counts the scrollbar. */
    /* Starting position only. placeNavMenu() measures where this actually
       lands and corrects it, because a fixed box's offsets are only as
       trustworthy as its containing block. These values keep the panel sane
       if that script never runs. */
    left: auto;
    right: 1rem;
    width: max-content;
    max-width: calc(100% - 2rem);
    margin: 0;
    min-width: min(11rem, 100%);
    padding: .375rem;
    border: 1px solid var(--hairline-strong);
    border-radius: .75rem;
    background: var(--surface);
    box-shadow: 0 12px 32px rgba(0, 0, 0, .22);
  }
  .site-nav[popover]:popover-open a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 .75rem;
    border-radius: .5rem;
    font-size: .9375rem;
  }
  .site-nav[popover]:popover-open a[aria-current="page"] {
    border-bottom: 0;
    padding-bottom: 0;
    background: var(--wash);
    color: var(--ink);
  }
  .site-nav::backdrop { background: rgba(0, 0, 0, .35); }
}

/* Tied to the menu button, so it shares the button's breakpoint rather than
   the layout one: centred on the masthead row beside it, not dropped below
   where it would sit on top of it. */
@media (max-width: 40em) {
  /* absolute, not fixed: on a narrow screen there is no margin for it to sit
     in, so a fixed switch hovers over whatever scrolls beneath and lands on
     the slate rows. Absolute keeps it parked beside the menu button and lets
     it leave with the masthead. */

}

@media (max-width: 40em) and (prefers-reduced-motion: no-preference) {
  .site-nav[popover]:popover-open { animation: nav-sheet var(--dur-base) var(--ease-out); }
  @keyframes nav-sheet { from { opacity: 0; transform: translateY(-6px); } }
}

/* iOS Safari zooms the viewport when a focused input is under 16px, which
   throws the whole page off-centre and does not zoom back out. Only touch
   devices need the larger text, so the pointer query keeps the desktop
   sizing intact. */
@media (pointer: coarse) {
  .combo input, .ev-search input { font-size: 16px; }
}

/* ---------- scope notice ----------
   Sits in the hero column under the lead, not as a bar across the top, so it
   reads as a note about the numbers below rather than as a site alert. Warning
   weight, not error: this is a scope statement, not a failure. */
.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--accent-bright) 30%, transparent);
  border-left-width: 3px;
  border-left-color: var(--accent-bright);
  border-radius: .5rem;
  background: color-mix(in srgb, var(--accent-bright) 7%, transparent);
  font-size: .8125rem;
  line-height: var(--lh-body);
  color: var(--ink-80);
}
/* Dismissal is a collapse, not a vanishing. Removing the node outright made
   the picker below jump up by the notice's whole height with nothing to
   explain the movement. The box now empties and closes, so the shift reads
   as caused by the click. The ink leaves faster than the box does, which is
   what makes it feel dismissed rather than merely resized. Overflow is set
   only while leaving, since permanent clipping would cut the focus ring off
   the X inside. */
.notice {
  transition: height var(--dur-exit) var(--ease-move),
              opacity var(--dur-slow) var(--ease-out),
              margin-top var(--dur-exit) var(--ease-move),
              padding-top var(--dur-exit) var(--ease-move),
              padding-bottom var(--dur-exit) var(--ease-move),
              border-top-width var(--dur-exit) var(--ease-move),
              border-bottom-width var(--dur-exit) var(--ease-move);
}
.notice.is-leaving {
  overflow: hidden;
  opacity: 0;
  margin-top: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-top-width: 0;
  border-bottom-width: 0;
}
.notice p { margin: 0; }
.notice-ic { width: 15px; height: 15px; flex: none; margin-top: .1875rem; color: var(--accent-text); }
.notice-x {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: -.25rem -.375rem -.25rem 0;
  border-radius: .375rem;
  color: var(--ink-40);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.notice-x svg { width: 14px; height: 14px; }
.notice-x:hover { color: var(--ink); background: var(--wash); }
.notice-x:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (max-width: 640px) {
  /* 44px hit area on touch without growing the visual button */
  .notice-x { width: 44px; height: 44px; margin: -.5rem -.625rem -.5rem 0; }
}

/* .info-dot is 15px, focusable, and carries a data-tip, so it is a genuine
   tap target under the 44px floor. Same trick as .notice-x above: grow the
   hit area on touch without growing the mark. Scoped to coarse pointers so
   the desktop dot keeps its size beside a heading. */
@media (pointer: coarse) {
  .info-dot { position: relative; }
  .info-dot::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* Qualifier links. A qualifier is a separate tournament in the feed, so each
   page has to point at the other or the main event reads as missing matches. */
.ev-rel { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-4); }
.ev-rel-label { font-size: .6875rem; font-weight: var(--w-strong); color: var(--ink-40); }
.ev-rel-link {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: .1875rem .5rem;
  border: 1px solid var(--hairline-strong);
  border-radius: 999px;
  font-size: .8125rem;
  color: var(--ink-80);
  transition: border-color var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
.ev-rel-link:hover { border-color: var(--line-mid); color: var(--ink); }
.ev-rel-n { font-family: var(--font-mono); font-size: .6875rem; color: var(--ink-40); }

/* Phones: only the HORIZONTAL rung steps down. A 24px side pad costs 48px of a
   320px screen, which the longest team names cannot spare, but vertical space
   scrolls and costs nothing, and stepping it down here made cards tighter than
   they were before the sweep, which is the opposite of the point. */
@media (max-width: 30em) {
  /* Rows do not step down at all. At 12/16 they are already modest, and
     stepping them cut .bet-row and .list-more below where they started. */
  :root { --pad-card: var(--sp-4); }
}

/* ---------- re-render and reveal signals ----------
   Both are user initiated. Nothing here animates on first arrival, because a
   team or matchup page is a destination someone navigated to in order to
   read a number, and gating that behind motion would cost the thing they
   came for. The home page cascade is different: arriving at a slate is a
   browse. */

/* The window pills rewrite the whole page. Content rises from faint rather
   than fading from nothing, so a figure is legible the entire time. The
   masthead and toolbar are excluded: they did not change, and the pill just
   pressed should read as the cause. */
#app.is-refreshed > *:not(.masthead):not(.toolbar) {
  animation: refreshed var(--dur-slow) var(--ease-out) both;
}
@keyframes refreshed { from { opacity: .35; } }

/* Rows the reader asked for, cascading like the slate but travelling less,
   since a ledger row is a line of data rather than a card. */
.led.is-revealing > .led-row { animation: led-in var(--dur-enter) var(--ease-out) both; }
.led.is-revealing > .led-row:nth-child(2) { animation-delay: .04s; }
.led.is-revealing > .led-row:nth-child(3) { animation-delay: .08s; }
.led.is-revealing > .led-row:nth-child(4) { animation-delay: .12s; }
.led.is-revealing > .led-row:nth-child(5) { animation-delay: .16s; }
.led.is-revealing > .led-row:nth-child(6) { animation-delay: .2s; }
.led.is-revealing > .led-row:nth-child(n+7) { animation-delay: .24s; }
@keyframes led-in { from { opacity: 0; transform: translateY(6px); } }

/* ---------- name containment ----------
   Team and event names are DATA, not copy. They are as long as they are
   ("CYBERSHOKE Prospects", "Ninjas in Pyjamas"), they are sometimes one
   unbreakable word ("QUINTESSENCIA", "SportsBetExpert"), and their casing is
   whatever the org chose. None of that may push a column open or split a
   word down the middle.

   Every name follows ONE of two patterns and nothing invents a third:

     clip  one line, ellipsis when it will not fit. For cells and rows, where
           a second line would change the height of the row around it.
     wrap  several lines, breaking at spaces, and mid-word ONLY when a single
           word cannot fit on a line by itself.

   overflow-wrap: anywhere is banned, and scripts/validate.py fails if it
   returns. It breaks at ANY character even when a space was available two
   letters earlier, which is what split "Nuclear TigeRES" into "Nuclear
   TigeRE" and a lone "S", and "Ninjas in Pyjamas" into "Pyjama" and "s".
   break-word only breaks a word that has no other option.

   Both patterns carry min-width: 0, because a flex or grid child defaults to
   min-width: auto and refuses to shrink below its own content. That is the
   other half of how text escapes a container in this file. */

/* ---------- panel ----------
   The home page was one 2109px column with sixteen blocks in it and exactly
   one enclosure among them, so everything read as loose text on a single
   ground. A panel is the surface for a section that is an OBJECT: a tool, a
   feed, an index. Prose is not an object and stays on the page ground, which
   is what keeps this from becoming a wall of boxes.

   Same fill, hairline and radius as .match-card, so a section and the cards
   inside it belong to one family. In light the border does most of the work,
   since --surface is barely off the page; in dark the fill does, since
   #1a212a lifts clearly off #11161d. */
.panel {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: .75rem;
  padding: var(--pad-card);
  margin-top: var(--sp-6);
}
/* A panel's own heading is the first thing inside it, so it carries no top
   margin of its own. Rule 1 of the spacing standard: a new component sets no
   margin on anything that already has one. */
.panel > :first-child { margin-top: 0; }
.panel > :last-child { margin-bottom: 0; }
/* Rows already bleed to their container's padding edge by negative margin,
   which inside a panel is exactly what full bleed should mean. */
@media (max-width: 30em) {
  .panel { padding: var(--pad-card); border-radius: .625rem; }
}

/* ---------- panel, applied to sections that already exist ----------
   Every page measured the same as the home page did: one contained block,
   the footer, and everything else loose on the page ground, including a
   2808px match column and a 1392px rankings grid. These selectors are the
   objects on those pages, so they take the same surface rather than each
   page inventing its own. Prose is still excluded: leads, notes and the
   betting explainer paragraphs stay on the ground, which is what stops this
   reading as a page of boxes. */
.sidebar,
.map-table,
.insight,
.cl-list,
.rankings-grid > section {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: .75rem;
  padding: var(--pad-card);
}
/* These carried their own top margin before they had a surface, and the
   panel rung replaces it so the gap does not double. */
.map-table, .insight { margin-top: var(--sp-6); }
.sidebar > :first-child,
.map-table > :first-child,
.insight > :first-child,
.cl-list > :first-child,
.rankings-grid > section > :first-child { margin-top: 0; }
/* A log is a feed, so it takes the container and its entries stay records
   inside it. One panel per entry would be a stack of prose boxes, and the
   entries already carry rules between them. The last entry drops its rule
   and its margin, so nothing dangles against the panel's own edge. */
.cl-list { margin-top: var(--sp-6); }
/* The sidebar is one panel holding several stat sections rather than a stack
   of small boxes. It gets NO divider between sections: every h2 on the site
   already draws a trailing hairline through h2::after, so a rule above the
   heading put two lines within 24px of each other doing one job. The
   heading's own line stays, since it is the established pattern, and the gap
   below does the separating instead. */
.sidebar h2:not(:first-child) { margin-top: var(--sp-6); }
