@font-face { font-family: Barlow; src: url(../fonts/barlow-400.woff2) format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: Barlow; src: url(../fonts/barlow-500.woff2) format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: Barlow; src: url(../fonts/barlow-600.woff2) format('woff2'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'Barlow Condensed'; src: url(../fonts/barlow-condensed-500.woff2) format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Barlow Condensed'; src: url(../fonts/barlow-condensed-600.woff2) format('woff2'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'Barlow Condensed'; src: url(../fonts/barlow-condensed-700.woff2) format('woff2'); font-weight: 700; font-display: swap; }

:root {
  color-scheme: light;
  --paper: #f6f1e6;
  --card: #fffcf6;
  --map-max: 52%; /* the map of the spots' darkest shade: the ink on it still reads at 4.5 to 1 */
  --ink: #1b2230;
  --ink-2: #444c5c;
  --muted: #646b7b;
  --line: #e6dccb;
  --line-strong: #d3c7b2;
  --accent: #c8202f;
  --accent-ink: #ffffff;
  --chip: #efe7d8;
  --shadow: 0 1px 2px rgba(60, 40, 20, .06), 0 6px 24px rgba(60, 40, 20, .07);
  /* Pitch families: validated together (light surface) */
  --fast: #eda100;
  --break: #2a78d6;
  --off: #008300;
  --odd: #e87ba4;
  /* What happens on a pitch: the pitcher's outcomes in blues (darkest is best for him), the hitter's in oranges */
  --o-whiff: #184f95; --o-called: #2a78d6; --o-foul: #6da7ec; --o-out: #b7d3f6; --o-ball: #f5b48e; --o-hit: #d95926;
  /* The numbers printed on those, white or dark, whichever reads at 4.5 to 1 on each (black where the ink falls short) */
  --o-whiff-on: #fff; --o-called-on: #000; --o-foul-on: #1b2230; --o-out-on: #1b2230; --o-ball-on: #1b2230; --o-hit-on: #000;
  /* The palest get a darker edge of their own colour, so they stand off the card at 3 to 1 */
  --o-foul-edge: color-mix(in srgb, var(--o-foul) 50%, #1b2230); --o-out-edge: color-mix(in srgb, var(--o-out) 50%, #1b2230); --o-ball-edge: color-mix(in srgb, var(--o-ball) 50%, #1b2230);
  /* An at-bat's results in words, like its marks: strikes red, balls green, balls in play blue */
  --res-strike: #c23b33; --res-ball: #23804b; --res-play: #2f66b8;
  --display: 'Barlow Condensed', 'Arial Narrow', system-ui, sans-serif;
  --body: Barlow, system-ui, -apple-system, 'Segoe UI', sans-serif;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper: #12161f;
    --card: #1a202c;
    --map-max: 37%;
    --ink: #ece7dc;
    --ink-2: #cfcabe;
    --muted: #9aa0ad;
    --line: #2a3242;
    --line-strong: #3a4458;
    --accent: #ef5a5f;
    --accent-ink: #1a0b0c;
    --chip: #232b3a;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 28px rgba(0, 0, 0, .25);
    --fast: #c98500;
    --o-whiff: #86b6ef; --o-called: #3987e5; --o-foul: #256abf; --o-out: #1c4f8a; --o-ball: #a85a2c; --o-hit: #f08a52;
    --o-whiff-on: #1b2230; --o-called-on: #000; --o-foul-on: #fff; --o-out-on: #fff; --o-ball-on: #fff; --o-hit-on: #1b2230;
    /* In the dark the out's blue is the one too close to the card, so it gets a paler edge */
    --o-foul-edge: transparent; --o-out-edge: color-mix(in srgb, var(--o-out) 50%, #ece7dc); --o-ball-edge: transparent;
    --res-strike: #f0736b; --res-ball: #4cbf7d; --res-play: #78a8f0;
    --break: #3987e5;
    --off: #008300;
    --odd: #d55181;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #12161f;
  --card: #1a202c;
  --map-max: 37%;
  --ink: #ece7dc;
  --ink-2: #cfcabe;
  --muted: #9aa0ad;
  --line: #2a3242;
  --line-strong: #3a4458;
  --accent: #ef5a5f;
  --accent-ink: #1a0b0c;
  --chip: #232b3a;
  --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 28px rgba(0, 0, 0, .25);
  --fast: #c98500;
  --o-whiff: #86b6ef; --o-called: #3987e5; --o-foul: #256abf; --o-out: #1c4f8a; --o-ball: #a85a2c; --o-hit: #f08a52;
  --o-whiff-on: #1b2230; --o-called-on: #000; --o-foul-on: #fff; --o-out-on: #fff; --o-ball-on: #fff; --o-hit-on: #1b2230;
  --o-foul-edge: transparent; --o-out-edge: color-mix(in srgb, var(--o-out) 50%, #ece7dc); --o-ball-edge: transparent;
  --res-strike: #f0736b; --res-ball: #4cbf7d; --res-play: #78a8f0;
  --break: #3987e5;
  --off: #008300;
  --odd: #d55181;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 16px/1.5 var(--body);
  padding: 0 max(16px, env(safe-area-inset-right)) env(safe-area-inset-bottom) max(16px, env(safe-area-inset-left));
}
button, select, input { font: inherit; color: inherit; }
h1, h2, h3 { font-family: var(--display); font-weight: 700; line-height: 1.05; margin: 0; letter-spacing: .01em; }
p { margin: 0; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.noscript { padding: 16px; text-align: center; }

.fam-fast { --fam: var(--fast); }
.fam-break { --fam: var(--break); }
.fam-off { --fam: var(--off); }
.fam-odd { --fam: var(--odd); }
.key { display: inline-block; width: .62em; height: .62em; border-radius: 50%; background: var(--fam); margin-right: .4em; vertical-align: .05em; box-shadow: 0 0 0 2px var(--card); }

/* ---------------------------------------------------------------- header */
.top { max-width: 1200px; margin: 0 auto; padding: 22px 0 10px; display: grid; gap: 6px; }
.mark { display: flex; align-items: center; gap: 10px; font-size: 40px; text-transform: uppercase; letter-spacing: .04em; }
.mark svg { width: 34px; height: 34px; flex: none; }
.mark-ball { fill: #fbf7ee; stroke: var(--line-strong); stroke-width: 1.2; }
.mark-seam { fill: none; stroke: #c8202f; stroke-width: 2; stroke-dasharray: 2.2 1.6; }
.lede { color: var(--ink-2); max-width: 46em; font-size: 17px; }
/* The way into Pitch to a hitter: in the page's ink, underlined in its red, the same once visited. */
.lede a { color: var(--ink); text-decoration: underline 2px var(--accent); text-underline-offset: 3px; }
.lede a:visited { color: var(--ink); }
/* Hover only where something hovers: on a touch screen the last thing tapped would keep its hover look. */
@media (hover: hover) { .lede a:hover { color: var(--accent); } }
.lede a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* ---------------------------------------------------------------- picker */
.picker {
  max-width: 1200px; margin: 8px auto 14px;
  display: flex; gap: 6px 18px; flex-wrap: wrap;
}
.group { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.group-name { font: 600 13px/1 var(--display); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); margin-right: 2px; white-space: nowrap; }
.chip, .ab-chip {
  border: 1px solid var(--line-strong); background: var(--card); border-radius: 999px;
  padding: 6px 13px; font-weight: 500; font-size: 15px; cursor: pointer; white-space: nowrap;
  transition: background .15s, border-color .15s;
}
@media (hover: hover) { .chip:hover, .ab-chip:hover { border-color: var(--fam); } }
.chip[aria-pressed="true"], .ab-chip[aria-checked="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.chip:focus-visible, .ab-chip:focus-visible, button:focus-visible, select:focus-visible, .dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------------------------------------------------------------- main */
.pitch {
  max-width: 1200px; margin: 0 auto;
  display: grid; gap: 18px; scroll-margin-top: 12px;
}
@media (min-width: 980px) {
  .pitch { grid-template-columns: minmax(0, 1.22fr) minmax(0, 1fr); align-items: start; }
  .stage { position: sticky; top: 12px; }
}

/* The grass green shows on each picture until it's drawn. It's the canvas's own, not the frame's: a frame that starts
   part-way through a pixel showed its green as a line along the bottom of the picture. */
.frame { position: relative; border-radius: 14px; overflow: hidden; box-shadow: var(--shadow); }
#flight, #ab-flight, #hit-flight { background: #3f8540; }
#flight { display: block; width: 100%; aspect-ratio: 16 / 11; touch-action: manipulation; }
@media (max-width: 640px) { #flight { aspect-ratio: 4 / 3.6; } }
/* A phone held sideways is a short screen: there the picture is no taller than it, with a little room above and below,
   in the middle with its controls under it, and in the shape it has on a computer. (The at-bat's picture goes beside the
   call box, further down.) */
@media (orientation: landscape) and (max-height: 540px) and (min-width: 600px) {
  .stage { width: 100%; margin-inline: auto; max-width: calc((100vh - 24px) * 16 / 11); max-width: calc((100svh - 24px) * 16 / 11); }
  #flight { aspect-ratio: 16 / 11; }
}
/* The words over the picture are white with a dark halo round every letter, so they read on the pale sky and the stands
   as well as on the grass. */
.readout {
  position: absolute; left: 12px; top: 10px; color: #fff; text-shadow: 0 0 2px rgba(0, 0, 0, .85), 0 0 4px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .55);
  font-family: var(--display); pointer-events: none;
}
.readout .speed b { font-size: 34px; font-weight: 700; letter-spacing: .01em; }
.readout .speed span { font-size: 16px; font-weight: 600; opacity: .9; }
/* Figures of one width, so the running time doesn't shift about as it counts. */
.readout .time { font-size: 16px; font-weight: 600; min-height: 1.3em; font-variant-numeric: tabular-nums; }
.legend {
  position: absolute; right: 10px; top: 10px; display: grid; gap: 2px; justify-items: end;
  font: 600 13px/1.3 var(--display); color: #fff; text-shadow: 0 0 2px rgba(0, 0, 0, .85), 0 0 4px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .55); pointer-events: none;
}
.legend > span { display: flex; align-items: center; gap: 6px; }
.legend .narrow { display: none; }
/* Each swatch has a dark edge, so the pale blue of a second pitch shows on the sky too. The ghost's is dashed like its
   line on the field. */
.legend .ln { width: 20px; height: 3px; background: var(--c, #fff); border-radius: 2px; filter: drop-shadow(0 0 1px rgba(10, 15, 25, .9)); }
.legend .ln.dash { height: 2px; border-radius: 0; background: repeating-linear-gradient(90deg, rgba(255, 255, 255, .9) 0 4px, transparent 4px 8px); }
.legend .ring { width: 11px; height: 11px; border-radius: 50%; border: 2px dashed #fff; filter: drop-shadow(0 0 1px rgba(10, 15, 25, .9)); }
/* Two pitches' times, kept in the legend from the throw and shown there on a phone once they're in (js/app.js). */
.legend .t { visibility: hidden; font-variant-numeric: tabular-nums; }
.legend.timed .t { visibility: visible; }
/* On a phone the dashed ring's label is one word, and two pitches' times go beside their names in the legend rather
   than in a line under their speeds, which on the narrowest ran under the legend. */
@media (max-width: 520px) {
  .legend { font-size: 12px; } .legend .wide { display: none; } .legend .narrow { display: inline; } .readout .speed b { font-size: 28px; }
  .readout .time .both { display: none; }
}
.cue {
  position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
  background: rgba(15, 20, 30, .78); color: #fff; padding: 6px 12px; border-radius: 999px;
  font: 600 15px/1.2 var(--display); letter-spacing: .03em; opacity: 0; transition: opacity .12s; pointer-events: none; white-space: nowrap;
}
.deciding .cue { opacity: 1; }

.controls { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin-top: 12px; }
.throw {
  display: inline-flex; align-items: center; gap: 6px; border: 0; border-radius: 999px; cursor: pointer;
  background: var(--accent); color: var(--accent-ink); font: 700 17px/1 var(--display); letter-spacing: .04em; text-transform: uppercase;
  padding: 10px 16px 10px 13px;
}
.throw svg { width: 16px; height: 16px; fill: currentColor; }
/* Throw, the plain buttons and the segmented buttons (and on a phone the pitches up top) reach 44px tall, as Apple's
   guidelines and WCAG 2.2's enhanced target size (SC 2.5.5) ask, with a see-through layer above and below rather than
   padding, so nothing looks or sits any different. Each row of them has 8px or more between it and the next, so no
   reach takes another's taps. */
.throw, .plain, .seg button { position: relative; }
.throw::after, .plain::after, .seg button::after { content: ''; position: absolute; left: 0; right: 0; top: min(0px, calc(50% - 22px)); bottom: min(0px, calc(50% - 22px)); }
.throw:active { transform: translateY(1px); }
/* Positioned, so a tap on one of these is always its own, even where the No-spin ghost checkbox's reach overlaps it.
   The pill clips its buttons only sideways (where a label too long for a narrow phone is cut off), so their reach
   above and below shows through; the rounded ends are the end buttons' own. */
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 999px; overflow: hidden; background: var(--card); position: relative; }
@supports (overflow: clip) { .seg { overflow: visible; overflow-x: clip; } }
.seg button { border: 0; background: transparent; padding: 7px 11px; font-size: 14px; font-weight: 500; cursor: pointer; white-space: nowrap; }
.seg button + button { border-left: 1px solid var(--line); }
.seg button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
/* The pill clips its buttons, so a ring outside one would be cut down to slivers: it's drawn just inside it instead,
   standing off a pressed button's dark fill by a ring of the page's colour. The end buttons have the pill's rounded
   ends of their own, so the ring follows them round rather than being cut square. */
.seg button:first-child { border-radius: 999px 0 0 999px; }
.seg button:last-child { border-radius: 0 999px 999px 0; }
.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; box-shadow: inset 0 0 0 4px var(--card); }
.seg button[aria-pressed="true"]:focus-visible { box-shadow: inset 0 0 0 4px var(--paper), inset 0 0 0 6px var(--ink); }
/* The small things to tap reach further than they look: padding, given back by an equal negative margin, so nothing moves. */
.check { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; cursor: pointer; padding: 11.5px 0; margin: -11.5px 0; }
.check input { width: 17px; height: 17px; accent-color: var(--accent); margin: 0; }
.pairpick, .handpick { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; }
.pairpick select { border: 1px solid var(--line-strong); background: var(--card); border-radius: 8px; padding: 6px 8px; font-size: 14px; }
/* The speed it's thrown at, with your own mound: a slider from 40 to 100 mph, and the way back to the pitch's own. */
.pacepick { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; flex-wrap: wrap; }
.pacepick input { width: 180px; accent-color: var(--accent); margin: 0; height: 28px; }
.pacepick output { font-variant-numeric: tabular-nums; min-width: 4.6em; font-weight: 600; }
/* Under the time over the picture: from your own mound, the big-league speed that gets there as soon. */
.readout .like { font-size: 14px; font-weight: 600; max-width: 15em; line-height: 1.2; }
.readout .like:empty { display: none; }
.caption { color: var(--muted); font-size: 14px; margin-top: 10px; max-width: 60em; }

/* ---------------------------------------------------------------- card */
.card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 18px; box-shadow: var(--shadow); display: grid; gap: 16px; }
.card-head { display: grid; gap: 4px; }
.fam { font: 600 14px/1 var(--display); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.fam .code { margin-left: 6px; padding: 2px 5px; border: 1px solid var(--line-strong); border-radius: 4px; font-size: 12px; letter-spacing: .04em; }
.card h2 { font-size: 40px; text-transform: uppercase; letter-spacing: .02em; }
.card h2 small { font-size: 20px; font-weight: 600; color: var(--muted); text-transform: none; letter-spacing: 0; }
/* Focus comes to the name when a pitch is picked further down the page. */
.card h2:focus { outline: none; }
.card h2:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.line { font-size: 18px; color: var(--ink-2); }

.stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: 0; background: var(--line); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.stats > div { background: var(--card); padding: 9px 10px 10px; min-width: 0; }
.stats dt { font: 600 12px/1.1 var(--display); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.stats dd { margin: 3px 0 0; display: grid; }
.stats b { font: 700 24px/1.1 var(--display); letter-spacing: .01em; font-variant-numeric: tabular-nums; }
/* The small line under each number: the dd's own span, not one inside the number. */
.stats dd > span { font-size: 13px; color: var(--muted); line-height: 1.25; }

.duo { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.panel { margin: 0; display: grid; gap: 6px; align-content: start; }
.panel canvas { width: 100%; aspect-ratio: 1; display: block; background: var(--paper); border-radius: 10px; }
/* The spinning ball stops and starts with a tap. */
#spin { cursor: pointer; touch-action: manipulation; }
#spin:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.panel h3 { font-size: 20px; text-transform: uppercase; letter-spacing: .03em; margin-bottom: 2px; }
.panel p { font-size: 14.5px; color: var(--ink-2); }
.panel p.small { font-size: 12.5px; color: var(--muted); margin-top: 4px; }
.panel p.young a { color: var(--ink-2); text-decoration: underline 1px var(--accent); text-underline-offset: 2px; }
.panel p.young a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
@media (hover: hover) { .panel p.young a:hover { color: var(--accent); } }
.seg.mini { justify-self: center; margin-top: -2px; max-width: 100%; min-width: 0; }
.seg.mini button { font-size: 12.5px; padding: 5px 9px; position: relative; }
/* These are smaller still, so they reach 8px above and below (44px in all), with the same see-through layer. */
.seg.mini button::after { inset: -8px 0; }
@media (max-width: 480px) { .seg.mini { justify-self: stretch; } .seg.mini button { flex: 1; padding: 5px 2px; font-size: 12px; } }
/* Up to 370px wide "From behind" and "From the side" don't fit side by side, so there they're "Behind" and "Side". */
.seg.mini .short { display: none; }
@media (max-width: 370px) { .seg.mini .long { display: none; } .seg.mini .short { display: inline; } }
@media (max-width: 420px) { .duo { gap: 10px; } .panel p { font-size: 14px; } }

.how h3 { font-size: 20px; text-transform: uppercase; letter-spacing: .03em; margin-bottom: 4px; }
.how p { color: var(--ink-2); }
.facts { margin: 10px 0 0; padding: 0; list-style: none; display: grid; gap: 4px; font-size: 14.5px; color: var(--ink-2); }
.facts li { padding-left: 16px; position: relative; }
.facts li::before { content: ''; position: absolute; left: 2px; top: .62em; width: 6px; height: 6px; border-radius: 50%; background: var(--fam); }
/* Previous and next, and at the end of the row, under them, the way on down to Pitch to a hitter, which comes next on
   the page, in the lede link's ink and red underline. (Beside next, on a computer, it read as another pitch.) The rows
   are far enough apart that each keeps the whole of its 44px reach. */
.next { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 22px 10px; border-top: 1px solid var(--line); padding-top: 12px; }
.next nav { display: flex; justify-content: space-between; gap: 10px; flex: 1 1 100%; }
.next button, .onward { border: 0; background: none; padding: 14px 0; margin: -10px 0; font: 600 16px/1 var(--display); letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2); cursor: pointer; }
.onward { margin-left: auto; color: var(--ink); text-decoration: underline 2px var(--accent); text-underline-offset: 4px; white-space: nowrap; }
.onward:visited { color: var(--ink); }
.onward:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
@media (hover: hover) { .next button:hover, .onward:hover { color: var(--accent); } }

/* ---------------------------------------------------------------- all pitches */
.all { max-width: 1200px; margin: 40px auto 0; display: grid; gap: 10px; }
.all > h2 { font-size: 34px; text-transform: uppercase; letter-spacing: .03em; }
.intro { color: var(--ink-2); }
.charts { display: grid; gap: 18px; margin-top: 6px; }
@media (min-width: 980px) { .charts { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); align-items: start; } }
.chart { margin: 0; background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px; display: grid; gap: 10px; min-width: 0; }
.chart h3 { font-size: 22px; text-transform: uppercase; letter-spacing: .03em; }
.chart figcaption p { font-size: 14.5px; color: var(--muted); margin-top: 3px; }
.source { font-size: 13px; color: var(--muted); max-width: 70em; }

.move-chart { width: 100%; height: auto; display: block; overflow: visible; }
.move-chart .grid line { stroke: var(--line); stroke-width: 1; }
.move-chart .grid line.zero { stroke: var(--line-strong); }
.move-chart .tick { fill: var(--muted); font: 500 12px var(--body); }
.move-chart .axis { fill: var(--muted); font: 600 12.5px var(--display); letter-spacing: .03em; text-transform: uppercase; }
.dot { cursor: pointer; outline: none; }
/* The middle 80% of pitchers round a dot: the chosen pitch's, and the one under the pointer or with focus. */
.move-chart .spread { fill: var(--fam); fill-opacity: .1; stroke: var(--fam); stroke-opacity: .55; stroke-width: 1.5; stroke-dasharray: 4 3; opacity: 0; transition: opacity .15s; pointer-events: none; }
.move-chart .spread.on, .move-chart .spread.near { opacity: 1; }
/* A real pitcher's pitches: rings joined to the league's dots, over them, with a ring of the card's colour round each so
   it stands off a dot it sits on. */
.move-chart .his-rings { pointer-events: none; }
.move-chart .his-rings circle { fill: none; stroke: var(--fam); stroke-width: 2.5; paint-order: stroke; filter: drop-shadow(0 0 1.5px var(--card)) drop-shadow(0 0 1px var(--card)); }
.move-chart .his-rings line { stroke: var(--fam); stroke-width: 1.6; opacity: .7; }
.dot .hit { fill: transparent; }
.dot .mark { fill: var(--fam); stroke: var(--card); stroke-width: 2; fill-opacity: .9; }
.dot .sel { fill: none; stroke: var(--ink); stroke-width: 2; opacity: 0; }
.dot.on .sel { opacity: 1; }
.dot .name { fill: var(--ink-2); font: 600 14px var(--display); letter-spacing: .02em; paint-order: stroke; stroke: var(--card); stroke-width: 4px; stroke-linejoin: round; }
.dot.on .name { fill: var(--ink); }
.dot:focus-visible .mark { fill-opacity: 1; stroke: var(--ink); }
@media (hover: hover) { .dot:hover .mark { fill-opacity: 1; stroke: var(--ink); } }

.lineup-wrap { overflow-x: auto; }
.lineup { width: 100%; border-collapse: collapse; font-size: 14px; }
.lineup thead th { text-align: left; font: 600 12px/1.2 var(--display); text-transform: uppercase; letter-spacing: .07em; color: var(--muted); padding: 0 6px 6px; vertical-align: bottom; }
.lineup .hint { display: block; text-transform: none; letter-spacing: 0; font: 400 11.5px/1.2 var(--body); }
.lineup .num { text-align: right; }
/* "Misses per" and "100 swings" keep a line each, however much the speed's hint wants the room. */
.lineup thead th.num { white-space: nowrap; }
.lineup tbody tr { border-top: 1px solid var(--line); }
.lineup tbody tr.on { background: color-mix(in srgb, var(--fam) 12%, transparent); }
.lineup th[scope="row"] { padding: 0; font-weight: 500; white-space: nowrap; }
.lineup th button { border: 0; background: none; padding: 7px 6px; cursor: pointer; font-weight: 600; text-align: left; width: 100%; }
@media (hover: hover) { .lineup th button:hover { color: var(--accent); } }
.lineup td { padding: 5px 6px; vertical-align: middle; }
.lineup .cell { display: flex; align-items: center; gap: 6px; min-width: 110px; }
.lineup .bar { flex: 1; height: 10px; position: relative; }
.lineup .bar span { position: absolute; left: 0; top: 0; bottom: 0; background: var(--fam); border-radius: 0 4px 4px 0; min-width: 2px; }
.lineup .val { font-variant-numeric: tabular-nums; color: var(--ink-2); min-width: 3.4em; text-align: right; font-size: 13px; }
.lineup .range { flex: 1; height: 14px; position: relative; }
.lineup .range::before { content: ''; position: absolute; left: 0; right: 0; top: 50%; border-top: 1px solid var(--line); }
.lineup .range .span { position: absolute; top: 4px; height: 6px; background: color-mix(in srgb, var(--fam) 45%, transparent); border-radius: 3px; }
.lineup .range .avg { position: absolute; top: 3px; width: 8px; height: 8px; margin-left: -4px; border-radius: 50%; background: var(--fam); box-shadow: 0 0 0 2px var(--card); }
/* Off the scale (the eephus): the dot at the left end, then a break in the line before the scale starts. */
.lineup .range.off .avg { margin-left: 0; }
.lineup .range.off::before { left: 20px; }
.lineup .range .break { position: absolute; left: 11px; top: 2px; width: 6px; height: 10px; }
.lineup .range .break::before, .lineup .range .break::after { content: ''; position: absolute; top: 0; height: 10px; border-left: 1.5px solid var(--muted); transform: skewX(-25deg); }
.lineup .range .break::before { left: 0; }
.lineup .range .break::after { left: 4px; }
.lineup tfoot td { padding-top: 2px; }
.lineup .scale { position: relative; display: block; height: 16px; flex: 1; font-size: 11px; color: var(--muted); }
.lineup .scale span { position: absolute; transform: translateX(-50%); }
/* The end numbers sit inside the column, over the ends of the line. */
.lineup .scale span:first-child { transform: none; }
.lineup .scale span:last-child { transform: translateX(-100%); }
@media (max-width: 600px) {
  /* Each pitch becomes two lines: name and misses, then share and speed side by side. */
  .lineup, .lineup thead, .lineup tbody, .lineup tfoot { display: block; }
  .lineup tr { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr) auto; column-gap: 12px; }
  /* The misses' column is as wide on every row and under the scale, where it's empty, so the scale lines up with the bars. */
  .lineup tbody tr, .lineup tfoot tr { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr) 2em; }
  .lineup thead tr { row-gap: 2px; padding-bottom: 6px; }
  .lineup thead th { padding: 0; }
  .lineup thead th:nth-child(1) { grid-area: 1 / 1 / 2 / 3; }
  .lineup thead th:nth-child(4) { grid-area: 1 / 3 / 2 / 4; }
  .lineup thead th:nth-child(2) { grid-area: 2 / 1 / 3 / 2; }
  .lineup thead th:nth-child(3) { grid-area: 2 / 2 / 3 / 4; }
  .lineup thead th:nth-child(1) { visibility: hidden; height: 0; }
  .lineup thead th:nth-child(4) { text-align: right; align-self: end; }
  .lineup .hint { display: none; }
  .lineup tbody tr { padding: 4px 0 7px; }
  .lineup tbody th { grid-area: 1 / 1 / 2 / 3; }
  .lineup tbody td:nth-child(4) { grid-area: 1 / 3 / 2 / 4; align-self: center; }
  .lineup tbody td:nth-child(2) { grid-area: 2 / 1 / 3 / 2; }
  .lineup tbody td:nth-child(3) { grid-area: 2 / 2 / 3 / 4; }
  .lineup tfoot td:nth-child(3) { grid-area: 1 / 2 / 2 / 4; }
  .lineup tfoot td:not(:nth-child(3)) { display: none; }
  .lineup td { padding: 0; }
  .lineup th button { padding: 11.5px 0; margin: -7.5px 0; position: relative; z-index: 1; } /* its reach sits over the bars below it */
  .lineup .cell { min-width: 0; }
  .lineup .val { min-width: 3em; }
  .lineup thead th:nth-child(4) br { display: none; }
}

/* The pointer can move onto it (see the tooltip in js/app.js). */
.tip {
  position: absolute; z-index: 10; max-width: 240px;
  background: var(--ink); color: var(--paper); border-radius: 8px; padding: 7px 10px; font-size: 13px; line-height: 1.35;
  box-shadow: var(--shadow);
}

/* ---------------------------------------------------------------- footer */
.jd-foot {
  max-width: 1200px; margin: 36px auto 0; padding: 18px 0 calc(24px + env(safe-area-inset-bottom));
  text-align: center; font-size: 13px; color: var(--muted); border-top: 1px solid var(--line);
}
.jd-foot a { color: inherit; text-decoration: none; }
.jd-foot a:focus-visible { text-decoration: underline; }
@media (hover: hover) { .jd-foot a:hover { text-decoration: underline; } }

@media (max-width: 640px) {
  .mark { font-size: 34px; }
  .lede { font-size: 16px; }
  .card { padding: 14px; }
  .card h2 { font-size: 34px; }
  .stats b { font-size: 21px; }
  /* A row that scrolls sideways clips what's above and below it too, so it has room for the pitches' reach (44px tall,
     like Throw's), 4px above them, 2px of it from the margin above. */
  .picker { flex-wrap: nowrap; overflow-x: auto; margin-top: 6px; margin-left: -16px; margin-right: -16px; padding: 4px 16px 6px; scrollbar-width: none; }
  .picker .chip { position: relative; }
  .picker .chip::after { content: ''; position: absolute; left: 0; right: 0; top: min(0px, calc(50% - 22px)); bottom: min(0px, calc(50% - 22px)); }
  .picker::-webkit-scrollbar { display: none; }
  .group { flex-wrap: nowrap; }
  /* 10px between the rows, so the buttons' reach (see Throw's) stays clear of the row above and below. */
  .controls { gap: 10px 8px; }
  .seg button { padding: 7px 9px; font-size: 13.5px; }
  /* "Throw with" gets a row of its own, and the list the rest of it, so the controls stay the same size whichever pitch
     is up and a long name like "Four-seam fastball (usual partner)" has room. On the narrowest phones the list gives
     way, not the label. */
  .pairpick { flex-basis: 100%; white-space: nowrap; }
  /* 16px, as the link field is: an iPhone zooms the page into a form field set smaller than that when it's tapped,
     and doesn't zoom back out. The padding gives back what the bigger words take, so it stays as tall. */
  .pairpick select { width: 100%; min-width: 0; font-size: 16px; padding-block: 5px; }
  /* A row of its own, the slider taking what's left; the way back to the pitch's own speed goes on a line under it, so
     the slider keeps its width when the way back appears, as it's first moved, rather than shrinking under the thumb. */
  .pacepick { flex-basis: 100%; row-gap: 0; }
  .pacepick input { flex: 1 1 100px; width: auto; min-width: 0; }
  .pacepick::after { content: ''; flex-basis: 100%; order: 1; }
  .pacepick .plain { order: 2; margin-top: 6px; }
}
/* On the narrowest phones the card's big numbers are a size smaller, and smaller again where even that doesn't fit, so
   "2,605 rpm", "↓ 10.3 in" and "10 ft 11 in" stay on one line. */
@media (max-width: 360px) { .stats b { font-size: 18px; } }
@media (max-width: 350px) { .stats b { font-size: 16px; } }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* ---------------------------------------------------------------- pitch to a hitter, and be the hitter */
.bat { max-width: 1200px; margin: 40px auto 0; display: grid; gap: 12px; scroll-margin-top: 12px; }
/* Focus comes here with the lede's link, as the place the next Tab starts from, not something to press. */
.bat:focus { outline: none; }
.bat-head { display: grid; gap: 6px; }
.bat-head h2 { font-size: 34px; text-transform: uppercase; letter-spacing: .03em; }
.bat-head .intro { color: var(--ink-2); max-width: 62em; }
.bat-grid { display: grid; gap: 18px; }
/* The picture beside the call box, on a computer and on a phone held sideways. Sideways the screen is short: with the
   call box under the picture, Throw would send the picture off the top of the screen. Beside it, the picture stays put
   while you call the pitch, throw it and watch it, and it's no taller than the screen. What's brought into view stops
   level with the picture's top. */
@media (min-width: 980px), (orientation: landscape) and (max-height: 540px) and (min-width: 600px) {
  .bat-grid { grid-template-columns: minmax(0, 1.22fr) minmax(0, 1fr); align-items: start; }
  .bat-stage { position: sticky; top: 12px; }
}
@media (orientation: landscape) and (max-height: 540px) and (min-width: 600px) {
  .bat-stage > .frame { margin-inline: auto; max-width: calc((100vh - 24px) * 16 / 11); max-width: calc((100svh - 24px) * 16 / 11); }
  #ab-callbox, #ab-end, #ab-chart, .hit .callbox, #hit-end, .bat-grid :is(button, select, input, summary, .seg, .spots) { scroll-margin-top: 12px; }
}
#ab-flight, #hit-flight { display: block; width: 100%; aspect-ratio: 16 / 11; touch-action: manipulation; }
@media (max-width: 640px) { #ab-flight, #hit-flight { aspect-ratio: 16 / 10.5; } }
/* Narrower than that the call box is under the picture, so on a screen taller than it's wide the picture stays at the
   top while you call the pitch, throw it and watch it (and on a phone it's shorter, so the call fits under it). The
   pitcher, batter and speed row and the caption scroll away under it. A ring of the page's colour round the picture
   covers whatever scrolls past its rounded corners and the margins beside it. Things scrolled into view stop below
   it: js/atbat-ui.js keeps --ab-frame at the picture's height. */
@media (max-width: 979px) and (orientation: portrait) {
  .bat-grid { gap: 0; }
  .bat-stage { display: contents; }
  .bat-stage > .frame { position: sticky; top: 0; z-index: 3; box-shadow: 0 0 0 12px var(--paper); }
  .bat-call { margin-top: 18px; }
  #ab-callbox, #ab-end, #ab-chart, .hit .callbox, #hit-end, .bat-grid :is(button, select, input, summary, .seg, .spots) { scroll-margin-top: calc(var(--ab-frame, 0px) + 12px); }
}
.count { display: flex; align-items: center; gap: 12px; }
.count .ct { font-size: 34px; font-weight: 700; letter-spacing: .02em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.count .dots { display: inline-flex; align-items: center; gap: 4px; font-size: 15px; }
.count .dots b { margin-right: 2px; }
.count .dots i { width: 11px; height: 11px; border-radius: 50%; border: 2px solid rgba(255, 255, 255, .85); }
.count .dots i.b { background: #23804b; border-color: #fff; }
.count .dots i.s { background: #c23b33; border-color: #fff; }
.count .dots i.o { background: #e0a800; border-color: #fff; }
.count .ct.kl { display: inline-block; transform: scaleX(-1); }
.count .on { font-size: 16px; font-weight: 600; }
/* What happened, in a tag filled with its mark's colour, a shade darker so the white words on it read at 4.5 to 1 or
   more. The same in either palette, since it sits on the picture. */
:is(#ab-ro, #hit-ro) .tag { padding: 1px 3px; border-radius: 4px; font-weight: 700; text-shadow: none; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
:is(#ab-ro, #hit-ro) .k-strike { background: #c23b33; }
:is(#ab-ro, #hit-ro) .k-ball { background: #23804b; }
:is(#ab-ro, #hit-ro) .k-play { background: #2f66b8; }
/* The words over the at-bat's picture: the count, then what happened over the pitch, on two lines. They stop short of
   where a pitch high and off the plate on that side crosses (--ro-max, from js/atbat-ui.js), wrapping to fit, so they
   never cover its mark. One that misses wilder, or hits the batter, can still cross under them, so they sit on a dark
   veil that both read through. */
.bat-stage > .frame { container: ab-frame / inline-size; }
.bat-stage .readout { left: 6px; top: 6px; box-sizing: border-box; max-width: calc(var(--ro-max, 100%) - 6px); padding: 4px 8px 6px 6px; border-radius: 10px; background: rgba(15, 20, 30, .5); }
.bat-stage :is(#ab-ro, #hit-ro) { margin-top: 2px; line-height: 1.3; }
.bat-stage :is(#ab-ro, #hit-ro):empty { display: none; }
:is(#ab-ro, #hit-ro) .sep { display: none; }
:is(#ab-ro, #hit-ro) > span:last-child { display: block; }
/* The pitch's speed, big in the picture's top-right corner as on a stadium's radar board, the explorer's size, with its
   name under it, and under that the key to the picture. They stop short of where a pitch high and off the plate on that
   side crosses (--rb-max, from js/atbat-ui.js), as the count does on the left, wrapping the name to fit; the board is on
   the same dark veil. */
.bat-stage .corner {
  position: absolute; right: 6px; top: 6px; max-width: calc(var(--rb-max, 100%) - 6px); display: grid; justify-items: end; gap: 6px;
  pointer-events: none;
}
.bat-stage .board {
  box-sizing: border-box; max-width: 100%; padding: 2px 8px 6px;
  border-radius: 10px; background: rgba(15, 20, 30, .5); color: #fff; text-align: right; line-height: 1.15; pointer-events: none;
  font-family: var(--display); text-shadow: 0 0 2px rgba(0, 0, 0, .85), 0 0 4px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .55);
}
.bat-stage .board:empty { display: none; }
.board .speed { white-space: nowrap; }
.board .speed b { font-size: 34px; font-weight: 700; letter-spacing: .01em; font-variant-numeric: tabular-nums; }
.board .speed span { font-size: 16px; font-weight: 600; opacity: .9; }
.board .nm { font-size: 16px; font-weight: 600; }
.board .nw, #ab-map-key .nw { white-space: nowrap; }
/* The key: the last pitch's path, faint as it's drawn, and the decision ring. */
.bat-stage .legend { position: static; margin-right: 2px; }
.bat-stage .legend:empty { display: none; }
.legend .ln.prev { background: #ffd23f; opacity: .55; }
/* The hitter's read at the decision point, at Slow, over the bottom of the picture. */
.bat-stage .cue { bottom: 8px; }
/* Where the picture is narrower, on a phone, a phone held sideways, a tablet or a small window, the readout keeps to its
   top-left corner, clear of the zone and the pitcher's hand: the balls and strikes stacked beside the count. */
@container ab-frame (max-width: 599px) {
  .bat-stage .readout { left: 5px; top: 5px; max-width: calc(var(--ro-max, 100%) - 5px); padding: 3px 4px 5px 5px; border-radius: 9px; }
  .count { display: grid; grid-template-columns: auto auto; gap: 2px 8px; justify-content: start; }
  .count .ct { grid-row: span 2; font-size: 28px; line-height: 1; }
  .count.outs .ct { grid-row: span 3; }
  .count.outs .dots b { min-width: 1.75em; }
  .count .on { grid-row: span 2; font-size: 14px; }
  .count .dots { font-size: 13px; gap: 3px; }
  .count .dots i { width: 9px; height: 9px; border-width: 1.5px; }
  .bat-stage :is(#ab-ro, #hit-ro) { font-size: 14px; line-height: 1.25; margin-top: 4px; }
  .bat-stage .corner { right: 5px; top: 5px; max-width: calc(var(--rb-max, 100%) - 5px); gap: 4px; }
  .bat-stage .board { padding: 1px 5px 5px; border-radius: 9px; }
  .bat-stage .cue { font-size: 14px; padding: 5px 10px; }
  .board .speed b { font-size: 28px; }
  .board .speed span, .board .nm { font-size: 14px; }
}
/* On the narrowest phones the readout has 80 to 90px across before the high spot off the plate: the count is smaller,
   and so are the words under it. */
@container ab-frame (max-width: 330px) {
  .count .ct { font-size: 24px; } .bat-stage :is(#ab-ro, #hit-ro) { font-size: 13px; }
  .board .speed b { font-size: 24px; } .board .speed span, .board .nm { font-size: 13px; }
}
@container ab-frame (max-width: 310px) {
  .count { column-gap: 6px; }
  .count .ct { font-size: 22px; }
  .count .on { font-size: 13px; }
  .count .dots { font-size: 12px; }
  .count .dots i { width: 8px; height: 8px; }
  .bat-stage :is(#ab-ro, #hit-ro) { font-size: 12px; }
}

.bat-call { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 18px; box-shadow: var(--shadow); display: grid; gap: 14px; min-width: 0; }
.bat-call h3 { font-size: 18px; text-transform: uppercase; letter-spacing: .04em; }
.callbox { display: grid; gap: 10px; }
.ab-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.ab-chips .ab-chip small { color: var(--muted); font-size: 12.5px; margin-left: 2px; }
.ab-chips .ab-chip[aria-checked="true"] small { color: inherit; opacity: .8; }
.spots-wrap { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.spots { display: grid; grid-template-columns: repeat(5, 40px); grid-template-rows: repeat(5, 40px); gap: 3px; padding: 4px; border-radius: 10px; background: var(--paper); position: relative; }
/* The strike zone is one framed shape, the nine spots inside it, as plain to see as the zone on the picture. The frame
   sits in the gap round them. The ring of spots outside it is dashed, in grey that's plain against the page too. */
.spots::before { content: ''; position: absolute; grid-area: 2 / 2 / 5 / 5; inset: -2.5px; border: 2px solid var(--ink-2); border-radius: 8px; pointer-events: none; }
.spot { border: 1px dashed var(--muted); background: transparent; border-radius: 6px; cursor: pointer; padding: 0; position: relative; }
.spot.in { border: 1px solid var(--line-strong); background: color-mix(in srgb, var(--ink) 6%, transparent); }
@media (hover: hover) { .spot:hover { border-color: var(--ink-2); } }
/* Focus is ringed inside the spot, clear of the zone's frame in the gap round it. */
.spot:focus-visible { outline-offset: -2px; }
.spot[aria-checked="true"]::after { content: ''; position: absolute; inset: 9px; border-radius: 50%; background: #fbf7ee; box-shadow: 0 0 0 2px #c8202f, 0 1px 3px rgba(0, 0, 0, .35); }
/* His pitches, under the pitcher's line: folded away on a phone. The chart beside the rows where there's room. */
.his { margin-top: 10px; }
.his summary { font: 600 15px/1.2 var(--display); text-transform: uppercase; letter-spacing: .05em; color: var(--ink-2); cursor: pointer; padding: 13px 0; margin: -13px 0; width: max-content; }
.his summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.his-body { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 8px 18px; align-items: start; margin-top: 18px; }
@media (max-width: 640px) { .his-body { grid-template-columns: minmax(0, 1fr); } .his-chart-wrap { max-width: 340px; } }
.his-rows { list-style: none; margin: 0 0 6px; padding: 0; display: grid; gap: 4px; font-size: 14px; color: var(--ink-2); }
.his-rows li { padding-left: 1.05em; text-indent: -1.05em; }
.his-rows b { color: var(--ink); font-weight: 600; }
.his-chart .his-own circle { fill: var(--fam); stroke: var(--card); stroke-width: 1.5; }
.his-chart .his-league circle { fill: none; stroke: var(--fam); stroke-width: 1.6; }
.his-chart .his-league line { stroke: var(--fam); stroke-width: 1.4; opacity: .55; }
.his-chart .name { fill: var(--ink-2); font: 600 11px var(--display); letter-spacing: .02em; paint-order: stroke; stroke: var(--card); stroke-width: 3px; stroke-linejoin: round; }
.his-chart .tick { font-size: 9.5px; }
.his-chart .axis { font-size: 10px; }
/* The map of the spots: each shaded in the hundred throws' swinging-strike blue, by what the pitch does there (--f, from
   0 for the map's fewest to 1 for its most), with the number in it and the hits under it, in the ink, which reads on the
   darkest shade at 4.5 to 1 or more in either palette (--map-max). The spot picked is ringed round its edge instead, so
   its number shows. */
.spots.mapped .spot { background: color-mix(in srgb, var(--o-whiff) calc(8% + var(--f, 0) * (var(--map-max) - 8%)), var(--card)); color: var(--ink); display: grid; align-content: center; justify-items: center; line-height: 1; gap: 2px; }
.spots.mapped .spot b { font: 700 15px/1 var(--display); }
.spots.mapped .spot small { font-size: 10px; font-weight: 500; opacity: .9; }
.spots.mapped .spot[aria-checked="true"]::after { inset: -4px; border-radius: 9px; background: none; box-shadow: 0 0 0 2px #c8202f; }
.mapper { display: grid; gap: 4px; }
.mapper .check { justify-self: start; }
.spots-wrap .side { font: 600 14px/1.2 var(--display); text-transform: uppercase; letter-spacing: .05em; color: var(--muted); max-width: 9em; }
.spots-wrap .side.left { text-align: right; }
/* The batter's side comes before the spots, so a screen reader reads it first; a left-handed batter's is drawn after. */
.spots-wrap .side.right { order: 1; }
.choice { font-weight: 600; }
.call-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.plain { border: 1px solid var(--line-strong); background: var(--card); border-radius: 999px; padding: 9px 14px; font-size: 14px; font-weight: 500; cursor: pointer; }
@media (hover: hover) { .plain:hover { border-color: var(--ink-2); } }
.throw:disabled, .throw[aria-disabled="true"] { opacity: .45; cursor: default; }
.throw[aria-disabled="true"]:active { transform: none; }
.plain[aria-disabled="true"] { opacity: .45; cursor: default; border-color: var(--line-strong); }
.small { font-size: 13px; color: var(--muted); }
/* What a link asked for that couldn't be done: a pitcher not here, or pitches left out. */
.notice { font-size: 14px; color: var(--ink-2); background: var(--paper); border-radius: 10px; padding: 10px 12px; }
.notice:empty { display: none; }
/* Watching a link's at-bat from the first pitch: right under the notice that says it's from a link. */
.watch-link { justify-self: start; margin-top: -6px; }

.last:empty { display: none; }
#ab-version:empty { display: none; }
.last .res { font: 700 26px/1.1 var(--display); text-transform: uppercase; letter-spacing: .03em; margin-bottom: 4px; }
.last .res-strike, .end-result.res-strike { color: var(--res-strike); }
.last .res-ball, .end-result.res-ball { color: var(--res-ball); }
.last .res-play, .end-result.res-play { color: var(--res-play); }
.last .lbl { font: 600 13px/1 var(--display); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); margin-bottom: 6px; }
.last p { color: var(--ink-2); }
.log { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; font-size: 14px; color: var(--ink-2); }
/* Each pitch is a button that opens why it went that way under it: the plain line it was, a little taller to tap, with a
   chevron at its end that turns up while it's open, and the paper's colour behind the open one. */
.log .row {
  display: flex; gap: 8px; align-items: baseline; box-sizing: border-box; width: calc(100% + 12px); min-height: 28px; margin: 0 -6px;
  padding: 4px 6px; border: 0; border-radius: 8px; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
}
@media (hover: hover) { .log .row:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); } }
.log .row[aria-expanded="true"] { background: var(--paper); }
/* The pitch being watched again: lit in the accent's colour, its number's mark ringed in it. */
.log .row[aria-current] { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.log .row[aria-current] .n { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--accent); }
.log .row:focus-visible { outline-offset: -2px; }
.log .more { flex: none; align-self: center; width: 7px; height: 7px; margin: 0 3px 4px auto; border: solid var(--muted); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.log .row[aria-expanded="true"] .more { transform: rotate(-135deg); margin-bottom: 0; margin-top: 4px; }
.log .why { margin: 2px 0 8px 28px; color: var(--ink-2); }
/* Each pitch's number in the shape and colour of its mark on the picture: a strike's in a red disc, a ball's in a green
   ring, a ball in play's in a blue square, the white numbers at 5 to 1 or more in either palette. The ring is the end
   card's own green, which is paler in the dark palette, so its number reads on the dark card too. */
.log .n { flex: none; box-sizing: border-box; width: 20px; height: 20px; border-radius: 50%; color: #fff; font: 700 12px/20px var(--display); text-align: center; }
.log .k-strike .n { background: #c23b33; }
.log .k-ball .n { border: 2px solid var(--res-ball); color: var(--res-ball); line-height: 16px; }
.log .k-play .n { background: #2f66b8; border-radius: 4px; }
.log b { color: var(--ink); font-weight: 600; }
.log .at { font-variant-numeric: tabular-nums; white-space: nowrap; }
.end { display: grid; gap: 10px; padding: 14px; border-radius: 12px; background: var(--paper); }
/* Nothing in it, not even a link that won't fit, makes it wider than the call box. */
.end > * { min-width: 0; }
#ab-copied { overflow-wrap: anywhere; }
#ab-tally:empty { display: none; }
/* In an inning: where it stands after each batter, and at the end how each batter's at-bat went, numbered. */
.inning-line { font-weight: 600; }
.inning-line:empty, .batters:empty { display: none; }
.batters { margin: 0; padding-left: 1.6em; display: grid; gap: 2px; font-size: 15px; color: var(--ink-2); }
.batters li::marker { color: var(--muted); font-variant-numeric: tabular-nums; }
.batters b { color: var(--ink); font-weight: 600; }
.link-field {
  width: 100%; min-width: 0; font: 400 16px/1.3 var(--body); color: var(--ink); background: var(--card);
  border: 1px solid var(--line-strong); border-radius: 8px; padding: 8px 10px;
}
.link-field:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A heading of its own (so a screen reader can jump to how it ended), in the look it had as words. */
.bat-call .end-result { font: 700 30px/1 var(--display); text-transform: uppercase; letter-spacing: .03em; }
/* Focus comes here when the at-bat ends from the pitch controls, which go. */
.end-result:focus { outline: none; }
.end-result:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }
/* Be the hitter: no count over the picture while you name pitches, what it was and the streak under Throw. */
.bat-stage .readout[hidden] { display: none; }
.hit .ask { display: grid; gap: 8px; }
.hit .said { font-weight: 600; }
.hit .said:empty { display: none; }
/* Step in's pitches: the at-bat's log lines, with nothing to open. */
#hit-log li { display: flex; gap: 8px; align-items: baseline; }
#hit-log:empty { display: none; }
#hit-flight { cursor: crosshair; }
.hundred { display: grid; gap: 10px; border-top: 1px solid var(--line); padding-top: 14px; }
.hundred .plain { justify-self: start; }
.legend-row { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 13px; color: var(--ink-2); }
.legend-row li { display: inline-flex; align-items: center; gap: 5px; }
.legend-row i { width: 12px; height: 12px; border-radius: 3px; }
.hundred-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.hundred-bars li { display: grid; grid-template-columns: 6.5em minmax(0, 1fr); gap: 2px 10px; align-items: center; font-size: 14px; }
.hundred-bars .nm { font-weight: 600; }
.hundred-bars .bar { display: flex; gap: 2px; height: 18px; }
.hundred-bars .sum { grid-column: 2; font-size: 12.5px; color: var(--muted); }
.hundred-bars .rest { display: block; min-width: 0; }
/* Each piece's number of the 100 sits in it, where it has 8 or more. */
.sg {
  display: flex; align-items: center; justify-content: center; min-width: 0; height: 100%; overflow: hidden;
  font: 600 12.5px/1 var(--display); letter-spacing: .02em; font-variant-numeric: tabular-nums;
}
.sg:first-child { border-radius: 4px 0 0 4px; }
.sg:last-of-type { border-radius: 0 4px 4px 0; }
.sg:first-child:last-of-type { border-radius: 4px; }
.sg-whiff { background: var(--o-whiff); color: var(--o-whiff-on); } .sg-called { background: var(--o-called); color: var(--o-called-on); }
.sg-foul { background: var(--o-foul); color: var(--o-foul-on); box-shadow: inset 0 0 0 1px var(--o-foul-edge, transparent); }
.sg-out { background: var(--o-out); color: var(--o-out-on); box-shadow: inset 0 0 0 1px var(--o-out-edge, transparent); }
.sg-ball { background: var(--o-ball); color: var(--o-ball-on); box-shadow: inset 0 0 0 1px var(--o-ball-edge, transparent); }
.sg-hit { background: var(--o-hit); color: var(--o-hit-on); }
/* The chart's caption is its heading, in the look of the small print round it. */
.bat-call .hundred .cap { font: 400 13px/1.5 var(--body); text-transform: none; letter-spacing: 0; color: var(--muted); }
@media (max-width: 640px) {
  .bat-head h2 { font-size: 30px; }
  .bat-call { padding: 14px; }
  .spots { grid-template-columns: repeat(5, 44px); grid-template-rows: repeat(5, 44px); }
}
/* The batter's side goes on one line just above the grid, at his end of it so the arrow points at him, and the two
   sit centred together. The pitches go two to a row, each with its speed and share under its name. So too on a phone
   held sideways, where the call box beside the picture is as narrow as on a phone. */
@media (max-width: 640px), (orientation: landscape) and (max-height: 540px) and (min-width: 600px) and (max-width: 979px) {
  .spots-wrap { display: grid; justify-content: center; gap: 6px; }
  .spots-wrap .side { max-width: none; white-space: nowrap; }
  .spots-wrap .side.left { text-align: left; }
  .spots-wrap .side.right { order: 0; text-align: right; }
  .ab-chips { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The colour dot hangs in the left padding, so a name that wraps ("Knuckle curve" on the narrowest phones) lines up
     under its first word, and the numbers under both. */
  .ab-chips .ab-chip { white-space: normal; text-align: left; border-radius: 16px; padding: 6px 12px 6px 27px; position: relative; }
  .ab-chips .ab-chip .key { position: absolute; left: 12px; top: calc(6px + (1.5em - .62em) / 2); margin: 0; }
  .ab-chips .ab-chip small { display: block; margin: 1px 0 0; }
}
/* On the narrowest phones the sides are closer in, and the names a size smaller, so "Knuckle curve" keeps to a line. */
@media (max-width: 360px) {
  .ab-chips .ab-chip { padding-left: 25px; padding-right: 10px; }
  .ab-chips .ab-chip .key { left: 10px; }
}
@media (max-width: 340px) {
  .ab-chips .ab-chip { font-size: 14px; padding-left: 24px; }
  /* The pitcher list's words are 16px (see "Throw with"), so here it goes under "Pitching", to have room for the
     longest of them, "Jacob Misiorowski – power arm". */
  .bat-stage .pairpick { flex-wrap: wrap; row-gap: 4px; }
  .bat-stage .pairpick select { flex: 1 0 100%; }
}
/* On a phone the picture stays at the top of the screen while you call the pitch, and the call fits under it: from the
   first pitch to Throw on an iPhone 14 once Safari has tucked its bars away (750px tall). The pitches are two lines
   set close, the grid is headed by the batter's side alone (its heading is still its name to a screen reader), the gaps
   are a little tighter, and what the frame on the grid means goes under Throw (still read with the grid). */
@media (max-width: 640px) {
  .callbox { gap: 8px; }
  .ab-chips .ab-chip { line-height: 1.2; min-height: 48px; }
  .ab-chips .ab-chip .key { top: calc(6px + (1.2em - .62em) / 2); }
  #ab-where-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .spots-wrap { gap: 4px; }
  .choice { line-height: 1.25; }
  .callbox > #ab-key, .callbox > .mapper { order: 1; }
  /* The hundred throws' button wraps to two lines on the narrower phones, and after the at-bat on most: its words keep
     to the left, and its corners are the pitches' rounded ones, so it doesn't turn into a lozenge. */
  .hundred .plain { text-align: left; border-radius: 16px; }
}
/* Held sideways, the grid's spots are as big as on a computer, and a little smaller where the call box is narrowest, so
   the grid fits in it. */
@media (orientation: landscape) and (max-height: 540px) and (min-width: 600px) and (max-width: 666px) {
  .spots { grid-template-columns: repeat(5, 36px); grid-template-rows: repeat(5, 36px); }
}
