/* Black and white only */
/* The three faces, served from this site rather than from Google: the same
   files Google serves for the Latin range, so no visitor's address goes to a
   third party for a font. Downloaded 2026-10-28. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../../fonts/Archivo-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("../../fonts/Archivo-800.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../../fonts/IBMPlexMono-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../../fonts/IBMPlexMono-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../../fonts/IBMPlexSans-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../../fonts/IBMPlexSans-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../../fonts/IBMPlexSans-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light;
  --bg: #fafaf9;
  --bg-2: #efeeec;
  --text: #111110;
  --text-2: #4a4946;
  --muted: #6d6c68;
  --line: #dcdbd7;
  --strong: #111110;
  --ink: #111110;
  --on-ink: #fafaf9;

  --display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --nav-h: 4rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0e0e0d;
    --bg-2: #191918;
    --text: #f3f2ef;
    --text-2: #b6b5b1;
    --muted: #8a8985;
    --line: #2a2a28;
    --strong: #f3f2ef;
    --ink: #f3f2ef;
    --on-ink: #0e0e0d;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0e0e0d;
  --bg-2: #191918;
  --text: #f3f2ef;
  --text-2: #b6b5b1;
  --muted: #8a8985;
  --line: #2a2a28;
  --strong: #f3f2ef;
  --ink: #f3f2ef;
  --on-ink: #0e0e0d;
}

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

/* Smooth, but not until the page has settled.

   Chrome restores your scroll position on a refresh by scrolling to it - and
   with scroll-behavior on the root it animates that, so a reload part-way down
   the page slid several hundred pixels into place, through whatever was on the
   way. Refreshing on Skyway could glide past Turret and look like it had taken
   you there. The restore is instant now; the class is added once the page has
   loaded, so pressing a link in the nav still glides. */
html { scroll-behavior: auto; }
html.is-settled { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-2);
  font: 400 1rem/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
a { color: inherit; }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
::selection { background: var(--ink); color: var(--on-ink); }

.wrap { max-width: 76rem; margin-inline: auto; padding-inline: 2rem; }

.skip {
  position: absolute;
  top: -4rem;
  left: 1rem;
  z-index: 100;
  padding: 0.5rem 0.75rem;
  background: var(--ink);
  color: var(--on-ink);
  font-weight: 600;
  text-decoration: none;
}
.skip:focus { top: 1rem; }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  padding: 0.8rem 1.15rem;
  border: 1px solid var(--strong);
  border-radius: 2px;
  color: var(--text);
  font: 600 0.9375rem/1 var(--sans);
  text-decoration: none;
  transition: background-color 0.15s, color 0.15s;
}
.btn:hover { background: var(--ink); color: var(--on-ink); }

.btn-solid { background: var(--ink); color: var(--on-ink); }
.btn-solid:hover { background: transparent; color: var(--text); }

/* Nav */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.nav-inner { display: flex; align-items: center; gap: 1.75rem; height: var(--nav-h); }
.nav-name {
  margin-right: auto;
  font: 700 1.0625rem/1 var(--display);
  letter-spacing: -0.01em;
  color: var(--text);
  text-decoration: none;
}
/* Stretched to the bar's height - the <nav> between them too - so the Work menu
   can hang from the bottom of the bar rather than from the bottom of the
   tallest word in it. Items stay centred, so nothing in the row moves. */
.nav-inner > nav { display: flex; align-self: stretch; }
.nav-links { display: flex; align-self: stretch; align-items: center; gap: 1.75rem; font-size: 0.9375rem; }
.nav-links a { color: var(--text-2); text-decoration: none; transition: color 0.15s, background-color 0.15s; }
.nav-links a:hover { color: var(--text); }
.nav-links .nav-cta {
  display: inline-block;
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--strong);
  border-radius: 2px;
  background: var(--ink);
  color: var(--on-ink);
  font-weight: 600;
}
.nav-links .nav-cta:hover { background: transparent; color: var(--text); }

.theme-btn {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: none;
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.15s;
}
.theme-btn:hover { border-color: var(--text); }
.theme-btn svg { width: 1rem; height: 1rem; }

/* Hero */
.hero { padding-top: 3rem; }

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.25rem 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--muted);
}

.hero-grid {
  display: grid;
  grid-template-columns: auto minmax(0, 28rem);
  justify-content: space-between;
  align-items: end;
  gap: 2rem 3rem;
  padding-block: 2.75rem 3.5rem;
}

.hero-name {
  margin-left: -0.04em;
  font-family: var(--display);
  /* Smaller than it was: the name is who, not what, and twelve rems of it was
     the first thing on the page and most of the screen. */
  font-size: clamp(2.5rem, 7vw, 6rem);
  font-weight: 800;
  line-height: 0.86;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  color: var(--text);
}
.hero-name .line { display: block; animation: rise 0.9s cubic-bezier(0.2, 0.75, 0.2, 1) both; }
.hero-name .line + .line { animation-delay: 0.08s; }

.cursor {
  display: inline-block;
  width: 0.5em;
  height: 0.7em;
  margin-left: 0.07em;
  background: var(--ink);
  animation: blink 1s steps(1, end) 1s 4;
}

.hero-side { padding-bottom: 0.4rem; animation: fade 0.8s 0.25s cubic-bezier(0.2, 0.75, 0.2, 1) both; }
.hero-lede { font-size: clamp(1.125rem, 1.4vw, 1.375rem); line-height: 1.45; color: var(--text); }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.625rem; margin-top: 1.75rem; }

.facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 2px solid var(--strong);
  border-bottom: 1px solid var(--line);
}
.facts li { padding: 1.5rem 1.5rem 1.75rem; border-left: 1px solid var(--line); }
.facts li:first-child { padding-left: 0; border-left: 0; }
.fact-big {
  display: block;
  font: 700 clamp(2.25rem, 4.5vw, 3.75rem)/1 var(--display);
  letter-spacing: -0.03em;
  color: var(--text);
}
.fact-label { display: block; max-width: 20rem; margin-top: 0.75rem; font-size: 0.9375rem; }

@keyframes rise { from { opacity: 0; transform: translateY(0.18em); } to { opacity: 1; transform: none; } }
@keyframes fade { from { opacity: 0; transform: translateY(0.75rem); } to { opacity: 1; transform: none; } }
@keyframes blink { 50% { opacity: 0; } }

/* Sections */
.section { padding-top: 6.5rem; }
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-bottom: 2.5rem;
  padding-top: 1.25rem;
  border-top: 2px solid var(--strong);
}
.section-head h2 {
  font: 700 clamp(2.25rem, 5vw, 4rem)/1 var(--display);
  letter-spacing: -0.035em;
  color: var(--text);
}
.section-note { font: 400 0.8125rem/1.5 var(--mono); color: var(--muted); }

/* Interests */
.interests { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2rem 1.5rem; }
.interest { padding-top: 1rem; border-top: 1px solid var(--line); }
.interest-num { font: 500 0.8125rem/1 var(--mono); color: var(--muted); }
.interest h3 {
  margin-top: 1.25rem;
  font: 700 1.375rem/1.15 var(--display);
  letter-spacing: -0.02em;
  color: var(--text);
}
.interest p { margin-top: 0.625rem; font-size: 0.9375rem; }

/* Projects */
.cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; }
.card {
  display: flex;
  min-width: 0;   /* a scrolling child must not be able to widen its grid track */
  flex-direction: column;
  min-height: 24rem;
  padding: 1.75rem 2rem;
  border-radius: 2px;
  background: var(--bg-2);
}
.card-top {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--muted);
}
.card-num { font-weight: 500; color: var(--text); }
.card h3 {
  max-width: 18ch;
  margin-top: 4rem;
  font: 700 clamp(1.625rem, 2.6vw, 2.25rem)/1.05 var(--display);
  letter-spacing: -0.025em;
  color: var(--text);
}
.card p { max-width: 36rem; margin-top: 1rem; margin-bottom: 2rem; }
.card .card-topics {
  margin-top: auto;
  margin-bottom: 0;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font: 400 0.8125rem/1.5 var(--mono);
  color: var(--muted);
}

/* Experience */
.job { display: grid; grid-template-columns: 16rem minmax(0, 1fr); gap: 0.5rem 2rem; }
.job-date { padding-top: 0.55rem; font: 400 0.875rem/1.5 var(--mono); color: var(--muted); }
.job h3 {
  font: 700 clamp(1.625rem, 2.6vw, 2.25rem)/1.1 var(--display);
  letter-spacing: -0.025em;
  color: var(--text);
}
.job-org { margin-top: 0.5rem; font-size: 1.0625rem; }
.ticks { display: grid; gap: 0.75rem; max-width: 44rem; margin-top: 1.5rem; }
.ticks li { position: relative; padding-left: 1.5rem; color: var(--text); }
.ticks li::before {
  content: "";
  position: absolute;
  top: 0.6em;
  left: 0;
  width: 0.45rem;
  height: 0.45rem;
  background: var(--ink);
}

/* Skills */
.skills { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2.5rem 1.25rem; }
.skill-group { padding-top: 1rem; border-top: 1px solid var(--line); }
.skill-group h3 {
  font: 500 0.75rem/1.5 var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.skill-group ul { display: grid; gap: 0.3rem; margin-top: 0.875rem; font-size: 1.125rem; color: var(--text); }

.now {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1.5rem;
  margin-top: 3rem;
  padding: 1.25rem 1.5rem;
  background: var(--bg-2);
  color: var(--text);
}
.now-label {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font: 500 0.75rem/1.5 var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.now-label::before { content: ""; width: 0.5rem; height: 0.5rem; background: var(--ink); }

/* Resume */
.doc-links { border-top: 1px solid var(--line); }
.doc-links li { border-bottom: 1px solid var(--line); }
.doc-links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.75rem 0.25rem;
  color: var(--text);
  text-decoration: none;
  transition: background-color 0.2s, color 0.2s, padding 0.2s;
}
.doc-title {
  font: 700 clamp(1.5rem, 3.4vw, 2.75rem)/1.05 var(--display);
  letter-spacing: -0.03em;
}
.doc-meta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: 400 0.8125rem/1 var(--mono);
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.2s;
}
.doc-arrow { display: inline-block; font-size: 1.25rem; transition: transform 0.2s; }
.doc-links a:hover { padding-inline: 1.25rem; background: var(--ink); color: var(--on-ink); }
.doc-links a:hover .doc-meta { color: var(--on-ink); }
.doc-links a:hover .doc-arrow { transform: translate(0.2rem, -0.2rem); }

/* Contact */
.contact {
  margin-top: 7rem;
  padding-block: 5rem 5.5rem;
  background: var(--ink);
  color: var(--on-ink);
}
.contact ::selection { background: var(--on-ink); color: var(--ink); }
.contact :focus-visible { outline-color: var(--on-ink); }
.contact-label { font: 500 0.75rem/1.5 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }
.contact-text {
  max-width: 40rem;
  margin-top: 1.25rem;
  font-size: clamp(1.125rem, 1.6vw, 1.375rem);
  line-height: 1.45;
}
.contact-email {
  display: inline-block;
  margin-top: 2.5rem;
  font: 800 clamp(1.5rem, 6.4vw, 5.5rem)/1 var(--display);
  letter-spacing: -0.04em;
  overflow-wrap: anywhere;
  text-decoration: underline;
  text-decoration-thickness: 0.06em;
  text-underline-offset: 0.12em;
}
.contact-email:hover { text-decoration-thickness: 0.11em; }
.contact-links { display: flex; gap: 1.5rem; margin-top: 2.5rem; font-weight: 600; }
.contact-links a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
.contact-links a:hover { text-decoration-thickness: 2px; }

/* Footer */
.footer { padding-block: 1.5rem; font: 400 0.8125rem/1.5 var(--mono); color: var(--muted); }
.footer-inner { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; }

@media (max-width: 60rem) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .hero-name { font-size: clamp(2.5rem, 13vw, 5rem); }
  .interests { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cards { grid-template-columns: minmax(0, 1fr); }
  .card { min-height: 0; }
  .card h3 { margin-top: 2.5rem; }
  .job { grid-template-columns: minmax(0, 1fr); }
  .job-date { padding-top: 0; }
  .skills { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 44rem) {
  .wrap { padding-inline: 1.25rem; }
  .nav-inner, .nav-links { gap: 1rem; }
  .nav-links .hide-sm { display: none; }
  .hero { padding-top: 1.75rem; }
  .hero-grid { padding-block: 2rem 2.5rem; }
  .facts { grid-template-columns: minmax(0, 1fr); }
  .facts li { padding: 1.25rem 0; border-top: 1px solid var(--line); border-left: 0; }
  .facts li:first-child { border-top: 0; }
  .section { padding-top: 4.5rem; }
  .section-head { margin-bottom: 1.75rem; }
  .interests { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
  .card { padding: 1.5rem 1.25rem; }
  .skills { gap: 2rem 1rem; }
  .doc-links a { padding-block: 1.25rem; }
  .contact { margin-top: 5rem; padding-block: 3.5rem 4rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* Featured project card */
.card-wide { grid-column: 1 / -1; min-height: 0; }
/* The reader's card runs the full width in the wide layout, where its two
   paragraphs sit side by side as a wide card's do; in the column it is a
   plain card and they follow each other. */
:root[data-layout="wide"] #reader .card-body, #reader.card-wide .card-body { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem 2.5rem; margin-top: 1rem; }
:root[data-layout="wide"] #reader .card-body p, #reader.card-wide .card-body p { max-width: none; margin: 0; }
.card-wide h3 { margin-top: 2rem; max-width: 24ch; }
.card-wide .card-body { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem 2.5rem; margin-top: 1rem; }
.card-wide .card-body p { max-width: none; margin: 0; }
@media (max-width: 60rem) {
  .card-wide .card-body { grid-template-columns: minmax(0, 1fr); }
}

/* Inline project chart. A figure carries margin: 1em 40px from the browser;
   here it sits on the text's own edges, so a chart or a map is as wide as
   the card lets it be. */
.embed { margin: 1.5rem 0 0; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.embed-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  margin-bottom: 1rem;
}
.embed-title, .embed-read .k {
  font: 500 0.75rem/1.4 var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.embed-read { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; }
.embed-read .ro { display: flex; align-items: baseline; gap: 0.4rem; }
.embed-read .v { font: 500 0.9375rem/1.3 var(--mono); font-variant-numeric: tabular-nums; color: var(--text); }
.embed svg { display: block; width: 100%; height: auto; }
.embed-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 1rem;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}
.embed-stats b {
  display: block;
  font: 700 1.125rem/1 var(--display);
  letter-spacing: -0.02em;
  color: var(--text);
}
.embed-stats span { display: block; margin-top: 0.3rem; font-size: 0.8125rem; line-height: 1.35; color: var(--muted); }
.embed-note { margin-top: 0.9rem; font-size: 0.875rem; color: var(--muted); }
/* The reading of the chart, as against the instruction above it. Tighter to the
   note it follows, and in the text colour, because it is the answer rather than
   the caption. */
.embed-says { margin-top: 0.35rem; color: var(--text-2); }
/* The caption under the R-squared, for a number that reads as a typo without it.
   Same muted style as the sublabel above it, tucked close so it reads as the
   rest of that line rather than a label of its own. */
.embed-stats .note { margin-top: 0.15rem; }
@media (max-width: 60rem) { .embed-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.embed + .card-topics { margin-top: 1.25rem; padding-top: 0; border-top: 0; }


.embed-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1.5rem; margin-bottom: 0.75rem; }
.embed-ctl label { display: flex; flex: 1 1 15rem; align-items: center; gap: 0.6rem; min-width: 0; }
.embed-ctl input[type=range] { flex: 1; min-width: 6rem; height: 1.4rem; accent-color: var(--ink); }
.embed-ctl .num { font: 500 1rem/1 var(--mono); font-variant-numeric: tabular-nums; min-width: 2.6em; }

.embed-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem 2.5rem; }
.embed-col svg { display: block; width: 100%; height: auto; margin-top: 0.6rem; }
.embed-title { margin-bottom: 0.6rem; }
.embed-foot { margin-top: 1.1rem; padding-top: 0.9rem; }
@media (max-width: 52rem) { .embed-cols { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 60rem) { .embed-cols { grid-template-columns: minmax(0, 1fr); } }
.embed-foot { border-top: 1px solid var(--line); }

.embed-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.75rem 0.9rem; }
.embed-read { gap: 0.25rem 1rem; }
.embed-col .embed-read { margin-top: 0.75rem; }
@media (max-width: 60rem) { .embed-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.embed-read { font-size: 0.9375rem; }
.embed-read .v { font-size: 0.875rem; }
.embed-stats span { font-size: 0.75rem; }

/* Skyway seat map */
:root { --seat-first: #8c2a1c; --seat-biz: #c26a4a; --seat-econ: #e0b7a2; }
@media (prefers-color-scheme: dark) {
  :root { --seat-first: #f0a084; --seat-biz: #c06848; --seat-econ: #7a4232; }
}
.sw-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.6rem; }
.sw-tab {
  padding: 0.3rem 0.55rem;
  border: 1px solid var(--rule, var(--line));
  background: none;
  color: var(--ink-2, var(--text-2));
  font: 400 0.75rem/1 var(--mono);
  cursor: pointer;
}
.sw-tab[aria-pressed="true"] { background: var(--ink, var(--text)); border-color: var(--ink, var(--text)); color: var(--paper, var(--bg)); }
.sw-legend { display: grid; gap: 0.35rem; margin-top: 0.9rem; font-size: 0.875rem; color: var(--ink-2, var(--text-2)); }
.sw-legend li { display: flex; align-items: center; gap: 0.5rem; }
.sw-legend i { width: 0.7rem; height: 0.7rem; border-radius: 2px; flex: none; }
.sw-legend i.sold { border: 1px solid var(--muted); }
.sw-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 1rem; }
.embed-foot code { font: 400 0.8125rem/1.5 var(--mono); word-break: break-word; }

/* Skyway mini */
:root { --seat-first: #111110; --seat-biz: #6d6c68; --seat-econ: #c7c6c2; --land: #e6e5e1; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --seat-first: #f3f2ef; --seat-biz: #9a9995; --seat-econ: #4a4945; --land: #201f1e; }
}
:root[data-theme="dark"] { --seat-first: #f3f2ef; --seat-biz: #9a9995; --seat-econ: #4a4945; --land: #201f1e; }

.mini-cols { margin-top: 1.25rem; }
/* The ceiling for the width, set down the ladder below as --cap. */
.fl-list { --cap: 13.5rem; max-height: var(--cap); overflow-y: auto; border: 1px solid var(--line); }
.fl-row {
  display: grid;
  grid-template-columns: 4rem minmax(0, 1fr) 3.6rem 4.4rem;
  gap: 0.4rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--text);
  font: 400 0.875rem/1.25 var(--sans);
  text-align: left;
  cursor: pointer;
}
/* Lit from the map, and it has to read exactly as a hover does: the point is
   that the arc and the row are the same flight, so they cannot look like two
   different states.

   Not --bg-2, which is what this was: the card is already --bg-2, so the lit
   row was being painted its own background and the highlight has been invisible
   on this design the whole time. A lift towards the text instead, which goes
   lighter on the dark theme and darker on the light one, and reads as a lift in
   both rather than as a colour. */
.fl-row:hover, .fl-row.is-lit { background: color-mix(in srgb, var(--text) 9%, transparent); }
.fl-row[aria-pressed="true"] { background: var(--ink); color: var(--on-ink); }
.fl-row[aria-pressed="true"] em, .fl-row[aria-pressed="true"] .fl-no { color: inherit; opacity: 0.7; }
.fl-row em { display: block; font-style: normal; font-size: 0.6875rem; color: var(--muted); }
.fl-no { font: 500 0.75rem/1.5 var(--mono); color: var(--muted); }
.fl-fare { text-align: right; font-variant-numeric: tabular-nums; }
.fl-when { font-variant-numeric: tabular-nums; }
.embed-foot code { font: 400 0.75rem var(--mono); word-break: break-word; }

.fl-row > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.fl-route, .fl-when, .fl-fare, .fl-no { white-space: nowrap; }
.fl-route em { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-wide .mini-cols { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }

/* Skyway mini, tightened */
#routeMap { max-width: 42rem; margin-inline: auto; }
.mini-cols { margin-top: 1rem; gap: 1.25rem 2rem; }
.fl-list { --cap: 11.25rem; }
.fl-row { padding: 0.35rem 0.55rem; font-size: 0.8125rem; }
.fl-row em { font-size: 0.6563rem; }
.fl-clear {
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
#cabinMap { max-width: 34rem; }

/* Skyway mini inside a half-width card */
.wrap { max-width: 82rem; }
/* The network map runs past the text's edges in the two-column layout, to
   half the card's padding: wider than the words, with a margin left to frame it. */
@media (min-width: 60.01rem) {
  :root:not([data-layout="wide"]) #skyway #routeMap { max-width: none; width: calc(100% + 2rem); margin-left: -1rem; }
}
.mini-stack { display: grid; gap: 1rem; margin-top: 1rem; }
.mini-stack .embed-col { min-width: 0; }
#routeMap { max-width: none; margin-inline: 0; }
#cabinMap { max-width: none; }
.fl-list { --cap: 11.25rem; }
.fl-row { grid-template-columns: 3.6rem minmax(0, 1fr) 3.2rem 4rem; font-size: 0.78rem; }
.embed-read { gap: 0.2rem 0.9rem; }

/* Skyway mini: list and cabin side by side, nothing below the fold */
.mini-grid { display: grid; grid-template-columns: minmax(0, 1fr) 11rem; gap: 1rem; margin-top: 0.9rem; align-items: start; }
.mini-left { min-width: 0; }
.mini-right { min-width: 0; }
.mini-head { margin-top: 0.9rem; }
/* The flight's card, as a booking app lays one out: the time and the code
   at each end, the duration on the line between them, and the fare as the
   one big number. The seat under the pointer takes the foot: its number and
   cabin on the left, its price on the right. */
.fl-card { margin-top: 0.9rem; padding: 0.85rem 1rem 0.9rem; border: 1px solid var(--line); }
.fl-card-top { display: flex; justify-content: space-between; gap: 1rem; font: 500 0.6875rem/1.4 var(--mono);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.fl-card-top b { font-weight: 600; color: var(--text); }
.fl-leg { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 1rem; align-items: center; margin-top: 0.75rem; }
.fl-end { display: grid; gap: 0.15rem; }
.fl-end b { font: 500 1.5rem/1.1 var(--mono); font-variant-numeric: tabular-nums; color: var(--text); }
.fl-end b sup { margin-left: 0.15rem; font-size: 0.6875rem; line-height: 1; vertical-align: top; color: var(--muted); }
.fl-end strong { font: 600 0.9375rem/1.2 var(--sans, inherit); color: var(--text); }
.fl-end i { font: 400 0.75rem/1.3 var(--sans, inherit); font-style: normal; color: var(--muted); }
.fl-end.is-to { text-align: right; }
.fl-mid { display: grid; justify-items: center; gap: 0.35rem; min-width: 5rem; font: 400 0.6875rem/1 var(--mono); color: var(--muted); }
.fl-mid i { font-style: normal; }
.fl-line { position: relative; display: block; width: 100%; height: 1px; background: var(--muted); }
.fl-line::before, .fl-line::after { content: ""; position: absolute; top: -2.5px; width: 6px; height: 6px; border-radius: 50%; }
.fl-line::before { left: -1px; background: var(--bg-2); box-shadow: inset 0 0 0 1px var(--muted); }
.fl-line::after { right: -1px; background: var(--text); }
.fl-card-foot { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem;
  margin-top: 0.9rem; padding-top: 0.8rem; border-top: 1px solid var(--line); }
/* The foot is one height in every state: each line keeps its place whether
   or not it has anything to say, and nothing wraps, so the card does not
   grow and shrink as the pointer crosses the seats. */
.fl-pick { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.15rem 0.6rem; align-items: baseline; min-width: 0; }
.fl-pick .k { grid-column: 1 / -1; font: 500 0.625rem/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.fl-pick b { font: 500 1.25rem/1.1 var(--mono); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fl-pick #seatCabin { font: 500 0.8125rem/1.2 var(--sans, inherit); font-style: normal; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fl-pick #seatState { grid-column: 1 / -1; min-height: 1.4em; font: 400 0.75rem/1.4 var(--mono); font-style: normal; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The state in the seat map's own colours: red for a seat someone else
   holds, green for yours. Pressed on a taken seat, the "Booked" gives a
   small shake and settles. Only the words move. */
#seatState.is-taken { color: var(--es-red); }
#seatState.is-mine { color: var(--es-on); }
#seatState.is-nudged { animation: seat-refused 0.5s ease; }
@keyframes seat-refused {
  0% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(2px); }
  100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  @keyframes seat-refused { 0% { opacity: 0.3; } 100% { opacity: 1; } }
}
.fl-price { flex: none; text-align: right; }
.fl-price b { display: block; font: 600 1.5rem/1.1 var(--mono); font-variant-numeric: tabular-nums; color: var(--text); }
.fl-price i { display: block; min-height: 1.3em; margin-top: 0.25rem; font: 400 0.6875rem/1.3 var(--mono); font-style: normal;
  color: var(--muted); white-space: nowrap; }
.fl-price #seatTotal { color: var(--text); }
@media (max-width: 44rem) {
  .fl-end b, .fl-price b { font-size: 1.25rem; }
  .fl-card { padding: 0.75rem 0.8rem; }
  /* Narrow, the two halves of the foot stack; the lines may wrap there. */
  .fl-card-foot { flex-direction: column; align-items: stretch; }
  .fl-price { text-align: left; }
  .fl-price i, .fl-pick #seatState { white-space: normal; }
}

.fl-list { --cap: 11.25rem; }
.fl-row { grid-template-columns: 3.4rem minmax(0, 1fr) 3rem 3.6rem; padding: 0.3rem 0.5rem; font-size: 0.75rem; }
.fl-row em { font-size: 0.625rem; }
#cabinMap { width: 100%; max-width: 11rem; }
.mini-right .sw-legend { gap: 0.25rem; margin-top: 0.6rem; font-size: 0.75rem; }
.mini-right .sw-legend i { width: 0.6rem; height: 0.6rem; }
.mini-right .embed-note { margin-top: 0.5rem; font-size: 0.6875rem; line-height: 1.35; }
.embed-read .k { font-size: 0.625rem; }
.embed-read .v { font-size: 0.8125rem; }
@media (max-width: 60rem) { .mini-grid { grid-template-columns: minmax(0, 1fr); } #cabinMap { max-width: 14rem; } }

.fl-list { --cap: 11.25rem; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 12rem; }
#cabinMap { max-width: 12rem; }

/* Skyway mini: clock and sizing */
.mini-topline { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.4rem 1rem; }
.sw-clock { display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; }
.sw-clock-col { display: flex; align-items: center; gap: 0.35rem; }
.sw-clock-code { font: 500 0.625rem/1 var(--mono); letter-spacing: 0.08em; color: var(--muted); }
.flaps { display: inline-flex; gap: 1px; }
.flap {
  position: relative;
  min-width: 0.72rem;
  padding: 0.12rem 0.04rem;
  background: var(--bg-2);
  color: var(--text);
  font: 500 0.6875rem/1 var(--mono);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.flap::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  left: 0;
  border-top: 1px solid var(--bg);
  opacity: 0.8;
}
.flap--sep { min-width: 0.34rem; background: none; color: var(--muted); }
.flap--sep::after { display: none; }

.mini-grid { grid-template-columns: minmax(0, 1fr) 14rem; }
#cabinMap { max-width: 14rem; }
.fl-list { --cap: 11.25rem; }

/* give the interactive card more of the row than the text-only one */
.cards { grid-template-columns: 1.45fr 1fr; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 13rem; gap: 1.1rem; }
#cabinMap { max-width: 13rem; }
.fl-row { grid-template-columns: 3.4rem minmax(0, 1fr) 3rem 3.4rem; }

/* quieter clock, bigger map */
.sw-clock { gap: 0.25rem 1.1rem; }
.sw-clock-col { gap: 0.3rem; }
.sw-clock-code { font-size: 0.5625rem; }
.flaps { font: 400 0.6875rem/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--text-2); letter-spacing: 0.02em; }
.flap, .flap--sep { all: unset; }
.cards { grid-template-columns: 1.75fr 1fr; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 14rem; }
#cabinMap { max-width: 14rem; }

/* bigger cabin */
.mini-grid { grid-template-columns: minmax(0, 1fr) 17rem; }
#cabinMap { max-width: 17rem; }

/* clock sits on the title line, clear of the map; legend beside the cabin */
.mini-topline { margin-bottom: 1.1rem; }
.mini-topline .embed-title { margin-bottom: 0; }
#routeMap { margin-top: 0.35rem; }
.cabin-pair { display: grid; grid-template-columns: auto minmax(5.5rem, max-content); gap: 0.75rem; align-items: start; }
.cabin-pair #cabinMap { max-width: 11.5rem; }
.cabin-pair .sw-legend { margin-top: 0.2rem; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 19rem; }

.mini-grid { grid-template-columns: minmax(0, 1fr) 21rem; }
.cabin-pair { grid-template-columns: auto minmax(5.5rem, max-content); }
.cabin-pair #cabinMap { max-width: 13.5rem; }

/* more room for the interactive: wider page, wider card, cabin fills the gap */
.wrap { max-width: 88rem; }
.cards { grid-template-columns: 2fr 1fr; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 24rem; gap: 1.25rem; }

.cabin-pair { grid-template-columns: minmax(0, 1fr) max-content; gap: 1rem; align-items: start; }
.cabin-pair #cabinMap { width: 100%; max-width: none; }
.cabin-pair .sw-legend { white-space: nowrap; }

/* the scrollbar gets its own lane instead of sitting on the last column */
.fl-list { scrollbar-gutter: stable; --cap: 11.25rem; }   /* four flights at a glance: 4 x 44px rows and the border */
.fl-row { grid-template-columns: 3.9rem minmax(0, 1fr) 3.6rem 4.4rem; gap: 0.6rem; padding: 0.4rem 0.6rem; font-size: 0.8125rem; }
.fl-row em { font-size: 0.6875rem; }

/* room for both: wider page and card, tighter right column */
.wrap { max-width: 92rem; }
.cards { grid-template-columns: 2.2fr 1fr; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 22rem; }
.cabin-pair { grid-template-columns: minmax(0, 1fr) max-content; gap: 0.9rem; }
.sw-legend { font-size: 0.75rem; }

.fl-head {
  display: grid;
  grid-template-columns: 3.9rem minmax(0, 1fr) 3.6rem 4.4rem;
  gap: 0.6rem;
  padding: 0 0.6rem 0.35rem;
  font: 500 0.625rem/1.4 var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  scrollbar-gutter: stable;
}
.fl-head span:last-child { text-align: right; }

/* back to a normal page width, with real room for the esports card */
.wrap { max-width: 82rem; }
.cards { grid-template-columns: 1.5fr 1fr; }
.mini-grid { grid-template-columns: minmax(0, 1fr) 20rem; }

/* legend under the cabin, two across, so the list keeps its width */
.mini-grid { grid-template-columns: minmax(0, 1fr) 14rem; gap: 1.5rem; }
.cabin-pair { display: block; }
.cabin-pair #cabinMap { width: 100%; max-width: none; }
.mini-right .sw-legend {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.3rem 0.6rem;
  margin-top: 0.7rem;
  font-size: 0.6875rem;
}
.fl-row, .fl-head { grid-template-columns: 3.7rem minmax(0, 1fr) 3.4rem 4rem; gap: 0.5rem; }

.hero-name .cursor { transform-origin: bottom; will-change: transform; }

/* a little more width for the list, and padding clear of the scrollbar lane */
.mini-grid { grid-template-columns: minmax(0, 1fr) 12.5rem; gap: 1.25rem; }
.fl-row { padding-right: 1rem; }
.fl-head { padding-right: 1rem; }

.fl-head em {
  display: block;
  font-style: normal;
  font-size: 0.5625rem;
  letter-spacing: 0.06em;
  text-transform: none;
  opacity: 0.75;
}
.fl-head { align-items: end; padding-bottom: 0.45rem; }

/* every header cell is two lines, so the row is even */
.fl-head {
  align-items: start;
  padding-bottom: 0.55rem;
  line-height: 1.5;
}
.fl-head em {
  margin-top: 0.05rem;
  font-size: 0.5625rem;
  letter-spacing: 0.05em;
  opacity: 0.6;
}

.fl-head em { white-space: nowrap; }

.mini-right .sw-legend { margin-top: 0.4rem; }
.mini-right .embed-note { margin-top: 0.4rem; }

.sw-legend i.mine { background: var(--ink); border: 0; }

/* seats you hold, listed under the fare maths */
.mybk { margin-top: 1rem; }
.mybk .embed-title em { font-style: normal; text-transform: none; letter-spacing: 0; opacity: 0.7; }
.mybk ul { display: grid; gap: 0.3rem; margin-top: 0.5rem; }
.mybk li { display: flex; align-items: center; gap: 0.4rem; }
.bk-go {
  flex: 1;
  display: grid;
  grid-template-columns: 3.6rem 2.4rem minmax(0, 1fr) auto;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--line);
  background: none;
  color: var(--text);
  font: 400 0.75rem/1.3 var(--mono);
  text-align: left;
  cursor: pointer;
}
.bk-go:hover { border-color: var(--text); }
.bk-go span:last-child { text-align: right; }
.bk-drop {
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--line);
  background: none;
  color: var(--muted);
  font: 400 0.8125rem/1 var(--mono);
  cursor: pointer;
}
.bk-drop:hover { color: var(--text); border-color: var(--text); }

/* the bookings list is capped and scrolls instead of growing forever */
.mybk ul { max-height: 6.75rem; overflow-y: auto; scrollbar-gutter: stable; padding-right: 0.1rem; }
.bk-head { display: flex; align-items: baseline; gap: 0.4rem; }
.bk-count { font-family: var(--mono); color: var(--text); }
.bk-head em { flex: none; }
.bk-clear {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: 500 0.625rem/1.4 var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: underline;
  cursor: pointer;
}
.bk-clear:hover { color: var(--text); }

/* bookings grouped per flight */
.mybk ul { max-height: 8.5rem; }
.bk-group { display: block; padding-left: 0.5rem; border-left: 2px solid var(--line); }
.bk-group.is-current { border-left-color: var(--ink); }
.bk-flight {
  display: grid;
  grid-template-columns: 3.6rem minmax(0, 1fr) auto;
  gap: 0.4rem;
  width: 100%;
  padding: 0.2rem 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: 500 0.6875rem/1.4 var(--mono);
  letter-spacing: 0.04em;
  text-align: left;
  cursor: pointer;
}
.bk-group.is-current .bk-flight { color: var(--text); }
.bk-flight:hover { color: var(--text); }
.bk-group ul { display: grid; gap: 0.25rem; margin: 0.15rem 0 0.45rem; max-height: none; overflow: visible; }
.bk-go { grid-template-columns: 2.4rem minmax(0, 1fr) auto; }

/* seats held show as a count on the flight row */
.fl-held { display: block; font-style: normal; font-size: 0.625rem; color: var(--text); opacity: 0.85; }
.fl-row[aria-pressed="true"] .fl-held { opacity: 1; }
.bk-where { font-family: var(--mono); color: var(--text); }
.mybk ul { max-height: 5.5rem; }
.bk-group, .bk-flight { display: none; }

.fl-mine, .fl-clear {
  margin-left: 0.5rem;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--line);
  background: none;
  color: var(--text-2);
  font: 500 0.625rem/1.5 var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.fl-mine:hover, .fl-clear:hover { border-color: var(--text); color: var(--text); }

/* booking is just: click a free seat, click it again to cancel */
.mybk, .fl-mine, .fl-clear, .bk-group, .bk-flight, .bk-head, .bk-go, .bk-drop { display: none !important; }
.fl-held { display: block; font-style: normal; font-size: 0.625rem; color: var(--text); opacity: 0.85; }

.sw-legend i.mine {
  background: var(--seat-econ);
  box-shadow: 0 0 0 1.5px var(--bg), 0 0 0 3px var(--ink);
  margin-right: 0.15rem;
}

/* legend in the same language as the map: outline = free, solid grey = taken, solid = yours */
.sw-legend i { box-shadow: none; }
.sw-legend i.free-first { background: transparent; border: 1.5px solid var(--seat-first); }
.sw-legend i.free-biz { background: transparent; border: 1.5px solid var(--seat-biz); }
.sw-legend i.free-econ { background: transparent; border: 1.5px solid var(--seat-econ); }
.sw-legend i.sold { background: var(--seat-econ); border: 0; opacity: 0.6; }
.sw-legend i.mine { background: var(--ink); border: 0; box-shadow: none; }

/* cabin tones: economy has to stay visible as an outline against the panel */
:root { --seat-first: #111110; --seat-biz: #56544f; --seat-econ: #8b8a85; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --seat-first: #f3f2ef; --seat-biz: #bcbbb6; --seat-econ: #86857f; }
}
:root[data-theme="dark"] { --seat-first: #f3f2ef; --seat-biz: #bcbbb6; --seat-econ: #86857f; }

/* taken seats are filled dark, so an occupied seat reads as blocked */
:root { --seat-taken: #6e6d68; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --seat-taken: #0a0a09; } }
:root[data-theme="dark"] { --seat-taken: #0a0a09; }
.sw-legend i.sold { background: var(--seat-taken); border: 1px solid var(--line); opacity: 1; }

/* taken: filled and clearly blocked, without punching a hole in the cabin */
:root { --seat-taken: #c2c0ba; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --seat-taken: #34332f; } }
:root[data-theme="dark"] { --seat-taken: #34332f; }

/* more width for the list and clear space before the scrollbar lane */
.mini-grid { grid-template-columns: minmax(0, 1fr) 11.25rem; gap: 1.1rem; }
.fl-list { padding-right: 0.15rem; }
.fl-row, .fl-head { padding-right: 1.5rem; }
.fl-row { grid-template-columns: 4rem minmax(0, 1fr) 3.7rem 4.6rem; gap: 0.65rem; }
.fl-head { grid-template-columns: 4rem minmax(0, 1fr) 3.7rem 4.6rem; gap: 0.65rem; }

/* a cabin arriving should settle, not snap */
@keyframes seat-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
#cabinMap rect { transform-box: fill-box; transform-origin: center; }
#flHead, #flMeta, #cabinTotals { transition: opacity .18s ease; }
.fl-row { transition: background-color .15s ease, color .15s ease; }
@media (prefers-reduced-motion: reduce) {
  #cabinMap rect { animation: none !important; }
  #routeMap path, #routeMap g, #cabinMap rect { transition: none !important; }
}

/* resumes preview in place */
.doc-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  padding: 1.75rem 0.25rem;
  border: 0;
  background: none;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s, padding 0.2s;
}
.doc-toggle:hover { padding-inline: 1.25rem; background: var(--ink); color: var(--on-ink); }
.doc-toggle:hover .doc-meta { color: var(--on-ink); }
.doc-toggle[aria-expanded="true"] .doc-arrow { transform: rotate(180deg); }
.doc-preview { padding-bottom: 1.5rem; }
.doc-preview iframe { display: block; width: 100%; height: 34rem; border: 1px solid var(--line); background: var(--bg-2); }
.doc-preview-foot { margin-top: 0.6rem; font: 400 0.8125rem/1.5 var(--mono); color: var(--muted); }
.doc-preview-foot a { color: var(--text); }
@media (max-width: 44rem) { .doc-preview iframe { height: 24rem; } }

/* a short glow on load, so a refresh is visible even when the scroll is kept */
@keyframes header-wake {
  0%   { text-shadow: 0 0 0 rgba(243, 242, 239, 0); }
  25%  { text-shadow: 0 0 18px rgba(243, 242, 239, 0.55); }
  100% { text-shadow: 0 0 0 rgba(243, 242, 239, 0); }
}
@media (prefers-color-scheme: light) {
  @keyframes header-wake {
    0%   { text-shadow: 0 0 0 rgba(17, 17, 16, 0); }
    25%  { text-shadow: 0 0 16px rgba(17, 17, 16, 0.35); }
    100% { text-shadow: 0 0 0 rgba(17, 17, 16, 0); }
  }
}
@media (prefers-reduced-motion: reduce) {
  .section-head h2, .hero-name { animation: none !important; }
}

/* The whole page at once. Sized to the width, a letter sheet is taller than any
   screen, so opening the resume meant scrolling an image inside a page that also
   scrolls. It is bound by height instead, and sits on a surface with room around
   it rather than being a borderless slab of white. */
.doc-preview {
  padding: 1.1rem; border: 1px solid var(--line); background: var(--bg-2);
  text-align: center;
}
.doc-preview .doc-page {
  display: inline-block;
  width: auto; max-width: 100%;
  max-height: min(78vh, 58rem);
  border: 1px solid var(--line);
  background: #fff;
}
.doc-preview iframe { height: min(78vh, 58rem); }
.doc-preview-foot { margin-top: 0.75rem; text-align: center; }
@media (max-width: 44rem) {
  /* On a phone the sheet is unreadable at any height, so it goes back to the
     width and the two links below it do the work. */
  .doc-preview .doc-page { max-height: none; width: 100%; }
}

/* --- live esports panel (projects/esports-mini.js) ---------------------- */
/* Black and white everywhere except the two sides of a game, which need to be
   told apart at a glance; those two are checked for colour-vision separation. */
:root { --es-blue: #1f5c99; --es-red: #a8331f; --es-on: #1f7a46; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --es-blue: #4a90e2; --es-red: #e2603c; --es-on: #5fbd8a; }
}
:root[data-theme="dark"] { --es-blue: #4a90e2; --es-red: #e2603c; --es-on: #5fbd8a; }

.esm-status {
  display: flex; align-items: baseline; gap: 0.5rem;
  font: 500 0.75rem/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
.esm-status strong { font-weight: 500; }
.esm-status.is-live { color: var(--es-on); }
.esm-status.is-off { color: var(--text-2); }
.esm-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: currentColor; }
.esm-status.is-live .esm-dot { animation: esm-pulse 2s ease-in-out infinite; }
@keyframes esm-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
@media (prefers-reduced-motion: reduce) { .esm-status.is-live .esm-dot { animation: none; } }

.esm-body { margin-top: 0.9rem; }
.esm-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 0.75rem; }
.esm-head strong { font: 700 1.375rem/1.1 var(--display); letter-spacing: -0.02em; color: var(--text); }
.esm-head em { font: 400 0.8125rem/1.4 var(--mono); font-style: normal; color: var(--muted); }

.esm-bar {
  position: relative; height: 0.7rem; margin-top: 0.8rem;
  background: var(--es-red); overflow: hidden;
}
.esm-bar i { display: block; height: 100%; background: var(--es-blue); transition: width 0.6s ease; }
@media (prefers-reduced-motion: reduce) { .esm-bar i { transition: none; } }
.esm-ends {
  display: flex; justify-content: space-between; margin-top: 0.35rem;
  font: 500 0.8125rem/1.3 var(--mono); font-variant-numeric: tabular-nums;
}
.esm-blue { color: var(--es-blue); }
.esm-red { color: var(--es-red); }

.esm-stats {
  display: flex; flex-wrap: wrap; gap: 0.3rem 1.25rem; margin-top: 0.9rem;
  font: 500 0.875rem/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--text);
}
.esm-stats b {
  margin-right: 0.4rem; font: 500 0.6875rem/1.4 var(--mono);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.esm-spark { width: 100%; max-width: 22rem; height: auto; margin-top: 0.9rem; }

.esm-switch { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.9rem; }
.esm-tab {
  padding: 0.2rem 0.5rem; border: 1px solid var(--line); background: none; color: var(--text-2);
  font: 500 0.75rem/1.5 var(--mono); cursor: pointer;
}
.esm-tab[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }

.esm-next { font-size: 0.9375rem; color: var(--text-2); }
.esm-recent-head {
  margin-top: 0.9rem; font: 500 0.6875rem/1.4 var(--mono);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.esm-recent { margin-top: 0.4rem; display: grid; gap: 0.3rem; }
.esm-recent li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; font-size: 0.9375rem; color: var(--text-2); }
.esm-recent b { font-weight: 600; color: var(--text); }
.esm-recent em { font: 400 0.75rem/1.4 var(--mono); font-style: normal; color: var(--muted); }
.esm-hint, .esm-foot { margin-top: 0.9rem; font-size: 0.875rem; color: var(--muted); }

/* upcoming-match calendar, shown when nothing is being played */
.esm-days { display: flex; gap: 0.4rem; margin-top: 0.5rem; overflow-x: auto; padding-bottom: 0.2rem; scrollbar-width: thin; }
.esm-day {
  flex: 0 0 auto; display: grid; gap: 0.15rem; padding: 0.35rem 0.6rem;
  border: 1px solid var(--line); background: none; color: var(--text-2);
  text-align: left; cursor: pointer;
}
.esm-day b { font: 500 0.8125rem/1.2 var(--mono); color: var(--text); }
.esm-day span { font: 400 0.6875rem/1.2 var(--mono); color: var(--muted); }
.esm-day:hover { border-color: var(--text-2); }
.esm-day[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); }
.esm-day[aria-pressed="true"] b, .esm-day[aria-pressed="true"] span { color: var(--on-ink); }
/* The day's fixtures, in a panel of their own. Loose on the card a single match
   read as an unfinished page rather than as a quiet day, and a fixed fifteen
   rems of height meant most days showed one line and a lot of nothing: it takes
   the height it needs now, up to the point where it starts to scroll. */
.esm-matches { margin-top: 0.5rem; display: grid; gap: 0; align-content: start;
  padding: 0.15rem 0.75rem; list-style: none;
  /* No floor on the height: a quiet day is one fixture, and five and a half rems
     of box around a single line is the hole this panel was meant to close. */
  max-height: 15rem; overflow-y: auto; scrollbar-gutter: stable;
  border: 1px solid var(--line); background: var(--bg-2);
  container-type: inline-size; }
/* The matchup is never cut: it takes the width it needs, with its logos, and
   the league beside it takes what is left - a round's name can run long, and
   ends in an ellipsis rather than under its neighbour. */
.esm-matches li { display: grid; grid-template-columns: 4.6rem max-content minmax(0, 1fr);
  align-items: baseline; gap: 0.6rem; padding: 0.5rem 0;
  font-size: 0.9375rem; color: var(--text-2); }
.esm-matches li > span { white-space: nowrap; }
.esm-matches li > em { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.esm-matches li > em { font-style: normal; font-size: 0.6875rem; letter-spacing: 0.04em;
  text-align: right; color: var(--muted); }
/* A list too narrow for all of that on one line - the compact layout's panel
   beside a calendar, or a phone - puts the league under the matchup. Decided
   by the list's own width, since that is what the row has to fit. */
@container (max-width: 24rem) {
  .esm-matches li { grid-template-columns: 4.6rem minmax(0, 1fr); row-gap: 0.15rem; }
  .esm-matches li > em { grid-column: 2; text-align: left; }
}
.esm-matches li + li { border-top: 1px solid var(--line); }
.esm-time { font: 500 0.8125rem/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--text); }
.esm-vs { font-weight: 600; color: var(--text); }

/* A team's logo, in its own colours, sized to the type so it sits in the line
   like a word rather than beside it like a picture. team-logos.js draws each
   one twice, for each theme, with whatever would vanish into that theme's
   background redrawn in its ink; the stylesheet picks the one for the theme in
   force. The space is kept, empty, until the drawing is ready. */
.team-logo {
  display: inline-block;
  width: 1.15em; height: 1.15em;
  vertical-align: -0.22em;
}
.team-logo.is-inked { background: var(--logo-l) center / contain no-repeat; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .team-logo.is-inked { background-image: var(--logo-d); }
}
:root[data-theme="dark"] .team-logo.is-inked { background-image: var(--logo-d); }
/* Each mark sits on its team's side of the name, facing out: before the first
   team, after the second. The same in the live and archived headers, the
   calendar's list and the sample games' scores. */
.esm-logo:first-child { margin-right: 0.4em; }
.esm-logo:last-child  { margin-left: 0.4em; }
/* The hour a match began, replaced by the fact that it is being played. Same
   weight as a time so the column does not jump; the colour is what carries it. */
.esm-time.is-live { color: var(--text); font-weight: 600; }
.esm-matches em { font: 400 0.6875rem/1.4 var(--mono); font-style: normal; text-align: right; color: var(--muted); }
.esm-filter { margin-top: 0.9rem; align-items: center; flex-wrap: wrap; }
.esm-filter-note { flex-basis: 100%; min-height: 1.05rem; font: 400 0.75rem/1.4 var(--mono); color: var(--muted); }

#esportsMini, .esm-days, .esm-matches { min-width: 0; }
.esm-days { max-width: 100%; }
.esm-views { margin-bottom: 0.9rem; }
.esm-archive { display: grid; gap: 0; padding: 0; margin-top: 0.2rem; list-style: none;
  height: 26rem; overflow-y: auto; scrollbar-gutter: stable; border-top: 1px solid var(--line); }
.esm-archive li { border-bottom: 1px solid var(--line); }
.esm-game { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline;
  gap: 0.2rem 0.75rem; width: 100%; padding: 0.45rem 0.1rem; border: 0; background: none;
  color: var(--text-2); font: inherit; text-align: left; cursor: pointer; }
.esm-game:hover { background: var(--bg); }
.esm-game .esm-vs { grid-column: 1; font-size: 0.9375rem; }
.esm-game .esm-vs i { font-style: normal; font-weight: 400; color: var(--muted); }
.esm-game em { grid-column: 1; font: 400 0.6875rem/1.4 var(--mono); font-style: normal; color: var(--muted); }
.esm-game .esm-time { grid-column: 2; grid-row: 1 / span 2; }
/* Kept to its own width and the left: stretched by the wide layout's flex
   column, a button centres its text. */
.esm-back { align-self: flex-start; text-align: left; margin-bottom: 0.6rem; padding: 0; border: 0; background: none; color: var(--muted);
  font: 500 0.75rem/1.4 var(--mono); cursor: pointer; }
.esm-back:hover { color: var(--text); }
.esm-trackbox { margin-top: 0.8rem; }
.esm-sparkkey { font: 500 10px var(--mono); }
.esm-fill { min-height: 10.5rem; }

/* miniature scoreboard for an archived game */
.esm-board { margin-top: 0.9rem; border-top: 1px solid var(--line); }
.esm-team {
  display: flex; align-items: baseline; gap: 0.5rem; padding: 0.5rem 0 0.35rem;
  font: 500 0.75rem/1.3 var(--mono); color: var(--muted);
}
.esm-team b { font-size: 0.875rem; color: var(--text); }
.esm-team em { margin-left: auto; font-style: normal; letter-spacing: 0.08em; text-transform: uppercase; }
.esm-team.is-win b { font-weight: 600; }
.esm-team.is-win em { color: var(--text-2); }
.esm-p {
  /* The portrait, then three lines beside it, each the width of the row:
     the champion and the K/D/A; the role and the player with the figures;
     the items. The figures had a column of their own once, which had no
     room for them in words, so they wrapped. */
  display: grid; grid-template-columns: 26px minmax(0, 1fr);
  /* Room between the portrait, the name and the items: at a tenth of a rem the
     three ran together into one block of ink per player. */
  gap: 0.25rem 0.8rem; align-items: center; padding: 0.6rem 0;
  border-top: 1px solid var(--line);
}
.esm-p img { display: block; border-radius: 2px; }
.esm-p > .esm-port { grid-row: 1 / span 2; align-self: center; position: relative; display: block; line-height: 0; }
/* A drunk elixir on the portrait's corner, like a buff: Riot's feed lists it
   while its effect lasts, but it left the inventory the moment it was drunk. */
.esm-port .esm-elixir { position: absolute; right: -4px; bottom: -4px; width: 11px; height: 11px; border-radius: 2px;
  box-shadow: 0 0 0 1px var(--bg); }
.esm-ptop, .esm-pmid { grid-column: 2; display: flex; justify-content: space-between; align-items: baseline;
  gap: 0.75rem; min-width: 0; }
.esm-ptop b { font: 500 0.8125rem/1.25 var(--sans, inherit); color: var(--text); min-width: 0; }

.esm-ptop .esm-pkda { flex: none; font: 500 0.8125rem/1.25 var(--mono); font-variant-numeric: tabular-nums; }
/* The role and the player's name: wrapped when the row is short of room,
   never cut - a name with its end missing is wrong, not tidy. The figures
   never wrap: they have the right of way, and the name gives. */
.esm-pmid i { font: 400 0.625rem/1.3 var(--mono); font-style: normal; letter-spacing: 0.02em; color: var(--muted);
  min-width: 0; overflow-wrap: anywhere; }
/* On a phone the figures, with the level in front of them since today, no
   longer fit beside the role and the player, and the label was squeezed to
   one letter a line. The label keeps its line; the figures drop under it. */
.esm-pmid { flex-wrap: wrap; row-gap: 0.1rem; }
.esm-pmid i:first-child { white-space: nowrap; }
.esm-pmid .esm-pfig { margin-left: auto; }
.esm-pmid .esm-pfig { flex: none; white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
.esm-items { grid-column: 2; display: flex; flex-wrap: wrap; gap: 3px; margin-top: 0.2rem; }
.esm-items img { background: var(--bg); }
/* An empty slot, so a player who never filled their inventory still takes the
   same width as one who did. */
.esm-items .esm-slot {
  display: block; width: 16px; height: 16px; border-radius: 2px;
  background: var(--bg); opacity: 0.45;
  box-shadow: inset 0 0 0 1px var(--line);
}
/* One cell: the item, and a count on its corner where the item stacks. An
   item read off the stream that Riot's feed leaves out is marked with a dotted
   edge; one Riot lists that the stream does not show is dimmed. */
.esm-items .esm-cell { position: relative; display: inline-block; line-height: 0; }
.esm-items .esm-cell img { display: block; }
.esm-items .esm-cell.is-stream { outline: 1px dotted var(--muted); outline-offset: -1px; }
.esm-items .esm-cell.is-missing img { opacity: 0.4; }
.esm-stack { position: absolute; right: 1px; bottom: 0; font: 700 8px/1.2 var(--mono); color: #fff;
  text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 3px #000; pointer-events: none; }
/* The trinket set off from the six, then the quest cell: dashed while the
   lane quest is on and its reward not yet in, solid once it is. */
.esm-items > :nth-child(7) { margin-left: 5px; }
.esm-items .esm-quest { display: inline-block; box-sizing: border-box; width: 18px; height: 18px; margin-left: 5px; border-radius: 3px;
  border: 1px dashed var(--muted); line-height: 0; overflow: hidden; }
.esm-items .esm-quest.is-done { border-style: solid; border-color: var(--text); }
.esm-items .esm-quest img { display: block; width: 16px; height: 16px; border-radius: 0; }

/* Room for the scrollbar to be drawn over rather than beside.

   scrollbar-gutter: stable reserves a strip, but only where the scrollbar takes
   width. macOS draws an overlay bar on top of the content instead, reserves
   nothing, and these lists put a right-aligned league name hard against the
   edge - so the bar came down through the text. The padding is what keeps them
   apart, the way the flight list has done since it had the same problem. */
.esm-matches, .esm-archive { padding-right: 1.15rem; }

.esm-beathead { margin-top: 1rem; padding-top: 0.6rem; border-top: 1px solid var(--line);
  font: 500 0.6875rem/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.esm-timeline { width: 100%; height: auto; margin-top: 0.3rem; }
.esm-beat { font: 500 8px var(--mono); }

/* recent games, grouped into the series they belong to */
.esm-day-head {
  position: sticky; top: 0; z-index: 1;
  padding: 0.5rem 0 0.25rem; background: var(--bg);
  font: 500 0.6875rem/1.4 var(--mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.esm-series { display: flex; align-items: baseline; gap: 0.5rem; padding-top: 0.35rem; }
/* Wide enough for the longest scoreline, so the league names that follow line
   up in a column instead of stepping in and out with each one's width. */
.esm-score { display: flex; align-items: baseline; gap: 0.4rem; min-width: 8rem; font: 500 0.9375rem/1.3 var(--sans, inherit); color: var(--muted); }
.esm-score b { font-weight: 500; color: var(--text-2); }
.esm-score b.is-win { font-weight: 700; color: var(--text); }
.esm-score i { font: 500 0.8125rem var(--mono); font-style: normal; font-variant-numeric: tabular-nums; color: var(--muted); }
/* Beside the score, not pinned to the far edge. margin-left: auto put the
   league a clear nine hundred pixels from its own scoreline in the wide layout,
   which read as a stray label rather than as part of the row it belongs to. */
.esm-series em { font: 400 0.625rem/1.4 var(--mono); font-style: normal; letter-spacing: 0.06em; color: var(--muted); }
.esm-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; padding: 0.35rem 0 0.5rem; border-bottom: 1px solid var(--line); }
.esm-chip {
  display: flex; align-items: baseline; gap: 0.35rem;
  padding: 0.2rem 0.45rem; border: 1px solid var(--line); background: none;
  color: var(--text-2); cursor: pointer;
}
.esm-chip:hover { background: var(--bg); border-color: var(--text-2); }
.esm-chip b { font: 500 0.6875rem var(--mono); color: var(--text); }
.esm-chip i { font: 400 0.6875rem var(--mono); font-style: normal; font-variant-numeric: tabular-nums; }
.esm-chip u { font: 400 0.625rem var(--mono); text-decoration: none; color: var(--muted); }
.esm-chip.is-comeback b::after { content: "*"; color: var(--es-red); }

/* What that star means, in the one place it appears. The asterisk here is the
   same colour as the ones it explains, or the reader has to guess that the two
   are the same mark. */
/* The key to the stars, one to a line. */
.esm-legend { display: grid; gap: 0.15rem; margin-top: 0.3rem; margin-bottom: 0.1rem; font-size: 0.8125rem; line-height: 1.4; color: var(--muted); }
.esm-legend b { color: var(--es-red); font-weight: 400; }
.esm-strip { margin-top: 0.2rem; }

/* Scrolling inside a panel stays inside it: at the top of the list the page no
   longer jumps away underneath the pointer. */
.fl-list { overscroll-behavior: contain; }

/* The panel sits inside a card whose paragraphs carry a 2rem bottom margin,
   which opened a hole under every date heading. Spacing here is set locally. */
#esportsMini p { margin: 0; max-width: none; }
#esportsMini .esm-next, #esportsMini .esm-hint, #esportsMini .esm-foot { margin-top: 0.85rem; }
#esportsMini .esm-recent-head { margin-top: 0.9rem; }
#esportsMini .esm-beathead { margin-top: 1rem; }
#esportsMini .esm-day-head { margin: 0; }
#esportsMini .esm-body { margin-top: 0.8rem; }

/* titled chart with a live readout, and the labelled team totals */
.esm-figure + .esm-board { margin-top: 1rem; }
.esm-figbar { display: flex; align-items: flex-end; justify-content: space-between;
  gap: 0.75rem; flex-wrap: nowrap; }
/* The rule above a chart's title runs the width of the chart, not of the
   word; and the first chart under a header sits close to it, the rest a
   step apart. */
#esportsMini .esm-figbar { margin-top: 1.1rem; padding-top: 0.6rem; border-top: 1px solid var(--line); }
#esportsMini .esm-figbar .esm-beathead { margin-top: 0; padding-top: 0; border-top: 0; }
#esportsMini .esm-colcharts > .esm-figure:first-child .esm-figbar,
#esportsMini .esm-strip > .esm-figure:first-child .esm-figbar { margin-top: 0.35rem; }
/* One line, always. A long readout used to wrap, which made the row taller,
   which pushed the chart down under the pointer, which changed the readout:
   dragging across a multikill set the whole thing jumping. */
.esm-read { flex: 1 1 auto; min-width: 0; height: 1.05rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font: 400 0.6875rem/1.05rem var(--mono); color: var(--text-2); text-align: right; }
.esm-prob { width: 100%; height: auto; margin-top: 0.3rem; }
.esm-axis { font: 400 8px var(--mono); }
.esm-team { display: block; padding: 0.6rem 0 0.45rem; }
.esm-teamname { display: flex; align-items: baseline; gap: 0.5rem; }
.esm-teamname b { font: 600 0.9375rem/1.2 var(--sans, inherit); color: var(--text); }
.esm-teamname em { font: 500 0.625rem/1.3 var(--mono); font-style: normal; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); }
.esm-team.is-win .esm-teamname em { color: var(--text-2); }
.esm-totals { display: flex; flex-wrap: wrap; gap: 0.15rem 0.85rem; margin-top: 0.3rem; }
.esm-totals span { display: flex; align-items: baseline; gap: 0.3rem; }
.esm-totals i { font: 400 0.625rem/1.4 var(--mono); font-style: normal; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); }
.esm-totals b { font: 500 0.8125rem/1.3 var(--mono); font-variant-numeric: tabular-nums; color: var(--text); }

/* both teams' totals, side by side above the scrolling detail */
.esm-h2h { margin-top: 0.9rem; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.esm-h2h-row {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 0.75rem;
  padding: 0.22rem 0;
}
.esm-h2h-row + .esm-h2h-row { border-top: 1px solid var(--line); }
.esm-h2h-row b { font: 500 0.875rem/1.3 var(--mono); font-variant-numeric: tabular-nums; color: var(--text-2); }
.esm-h2h-row b:first-child { text-align: left; }
.esm-h2h-row b:last-child { text-align: right; }
.esm-h2h-row b.is-ahead { color: var(--text); font-weight: 600; }
.esm-h2h-row i { font: 400 0.625rem/1.4 var(--mono); font-style: normal; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted); }
.esm-h2h-row.is-head { padding: 0.4rem 0 0.35rem; }
.esm-h2h-row.is-head b { font: 600 1rem/1.2 var(--sans, inherit); color: var(--text-2); }
.esm-h2h-row.is-head b.is-win { color: var(--text); }
.esm-h2h-row.is-head i { letter-spacing: 0.1em; }

/* timeline lanes */
.esm-key { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin-top: 0.35rem;
  font: 400 0.625rem/1.4 var(--mono); color: var(--muted); }
.esm-key span { display: inline-flex; align-items: center; gap: 0.35rem; }
.esm-key i { width: 0.45rem; height: 0.45rem; border-radius: 50%; }

.esm-read.is-pinned { color: var(--text); }
.esm-timeline { cursor: crosshair; }
.esm-stamp { font: 500 9px var(--mono); font-variant-numeric: tabular-nums; }

.esm-figbar .esm-beathead { flex: 0 0 auto; white-space: nowrap; }

/* A full-width card keeps its prose readable rather than running the line
   across the page, and gives the whole width to the thing it is showing. */
.card-wide > p { max-width: 68ch; }

/* Wide layout, off by default. Every project card takes the page, and the ones
   holding an interactive lay their own contents out in columns. Compact keeps
   two cards to a row, which is the denser read. */
:root[data-layout="wide"] .cards { grid-template-columns: minmax(0, 1fr); }
:root[data-layout="wide"] .card { min-height: 0; }
:root[data-layout="wide"] .card > p { max-width: 68ch; }

.esm-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.25rem 2rem; }
.esm-cols > * { min-width: 0; }
@media (min-width: 64rem) {
  /* Twice the width is for room, not for twice the content at the same
     density: a wider gutter between the columns, more air between the
     figures, and the scoreboard's type a step up. */
  :root[data-layout="wide"] .esm-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.75rem 3.5rem; }
  :root[data-layout="wide"] .esm-colcharts .esm-figure + .esm-figure { margin-top: 1.6rem; }
  :root[data-layout="wide"] .esm-stats { gap: 0.5rem 1.6rem; }
  :root[data-layout="wide"] #esportsMini .esm-beathead { margin-top: 1.4rem; }
}

.layout-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; margin-right: 0.25rem;
  border: 1px solid var(--line); border-radius: 2px; background: none;
  color: var(--text-2); cursor: pointer;
  /* Hidden until the project cards are on screen. Visibility rather than
     display, so the header keeps the space and nothing shifts. */
  opacity: 0; visibility: hidden; transform: scale(0.88);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
}
.layout-btn.is-on { opacity: 1; visibility: visible; transform: none; }
@media (prefers-reduced-motion: reduce) { .layout-btn { transition: none; } }
.layout-btn svg { width: 1.05rem; height: 1.05rem; }
/* The icon shows the layout you are in, not the one the press would give you. */
.layout-btn .ico-wide { display: none; }
.layout-btn[aria-pressed="true"] .ico-compact { display: none; }
.layout-btn[aria-pressed="true"] .ico-wide { display: block; }
.layout-btn:hover { color: var(--text); border-color: var(--text-2); }
.layout-btn[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.esm-colboard .esm-h2h { margin-top: 0; }
.esm-colcharts .esm-figure + .esm-figure { margin-top: 1.1rem; }

/* Skyway in the wide layout. Left to itself the flight list swallowed every
   extra pixel (906px of it) while the cabin column stayed at the 11rem it has
   when the card is half the page, so the legend had nowhere to go but under the
   seats. The list gives the width back and the two sit side by side. */
@media (min-width: 64rem) {
  :root[data-layout="wide"] .mini-grid { grid-template-columns: minmax(0, 1fr) 26rem; gap: 2rem; }
  :root[data-layout="wide"] .cabin-pair {
    display: grid; grid-template-columns: minmax(0, 1fr) max-content;
    gap: 1.25rem; align-items: start;
  }
  :root[data-layout="wide"] .cabin-pair #cabinMap { width: 100%; max-width: 13rem; }
  :root[data-layout="wide"] .mini-right .sw-legend {
    margin-top: 0.2rem; white-space: nowrap; font-size: 0.8125rem;
  }
  :root[data-layout="wide"] .mini-right .sw-legend i { width: 0.7rem; height: 0.7rem; }
}

/* In the wide layout there is room, so the panels hold more and are scrolled
   through less: a list you have to scroll inside is a list that competes with
   the page. */
@media (min-width: 64rem) {
  :root[data-layout="wide"] .esm-archive { height: 30rem; }
  :root[data-layout="wide"] .esm-matches { max-height: 20rem; }
  :root[data-layout="wide"] .esm-trackbox { height: auto; overflow: visible; padding-right: 0; }
  :root[data-layout="wide"] .fl-list { --cap: 16.75rem; }   /* six flights: enough to see it is a list, few enough to scroll */
}

/* Skyway and the facts card share the left column, so the space the taller
   esports card leaves beside it is used rather than left as a hole. */
.card-col { display: grid; gap: 1.25rem; align-content: start; min-width: 0; }
/* Alone in its column, with no game card under it, Skyway stretches to the
   row so its bottom edge meets the panel's; the page script takes the class
   off while a game card is sized under it. */
.card-col.is-alone { align-content: stretch; grid-template-rows: minmax(0, 1fr); }
:root[data-layout="wide"] .card-col { grid-column: 1 / -1; }

.card-facts { min-height: 0; }
.card-facts .card-top { margin-bottom: 0.25rem; }

/* The card beside the esports panel: what the archive and the feed can say
   about the open game that the panel does not. Sized to the gap beside the
   panel by main.js, which leaves out whole sections that do not fit. */
.card-game { display: flex; flex-direction: column; }
.card-game .card-top { margin-bottom: 0.9rem; }
/* A column: the feed in the middle takes whatever height the sections around
   it leave, and scrolls - it is the one thing here that should. */
.gs { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 1.35rem; min-width: 0; }
.gs > * { flex: 0 0 auto; }
.gs > .gs-feedsec { flex: 1 1 auto; min-height: 12rem; }
/* Named twice over: the section rule below says grid, and in a grid taller
   than its rows the title, the note and the feed each took a third of it. */
.gs-sec.gs-feedsec { display: flex; flex-direction: column; }
.gs-feedsec > * { flex: 0 0 auto; }
/* Framed, as the flight list is, so where it begins and ends is plain and
   the wheel knows which it is scrolling: the box keeps its scrolling to
   itself at either end rather than handing it to the page. The same
   clearance from the scrollbar the other lists have: against the text it
   read as a bar laid over the words. The box is as tall as what it holds,
   up to the room it has: a feed with one line in it is one line tall, not
   a frame around an empty column. */
.gs-feed { flex: 0 1 auto; min-height: 0; overflow-y: auto; scrollbar-gutter: stable; overscroll-behavior: contain;
  padding: 0.5rem 1.15rem 0.5rem 0.5rem; border: 1px solid var(--line); }
.card-game .gs-next { margin: 0 0 0.2rem; }
/* The chart's heading bar rules across the card, as the section titles do. */
.card-game .esm-figbar { padding-top: 0.6rem; border-top: 1px solid var(--line); }
.card-game .esm-figbar .esm-beathead { margin-top: 0; padding-top: 0; border-top: 0; }
.card-game .gs-min { max-width: none; margin: 0.7rem 0 0.2rem; padding-left: 0.6rem;
  font: 600 0.625rem/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.gs-min:first-child { margin-top: 0; }
/* One thing that happened: the minute, then what, with the champions as
   their art, and on the moments that moved the estimate, how far. The rule
   down the left is the side it happened to. */
.gs-ev { display: grid; grid-template-columns: 2.9rem minmax(0, 1fr) auto; gap: 0.6rem; align-items: center;
  padding: 0.3rem 0.4rem 0.3rem 0.6rem; border-left: 2px solid var(--line);
  font: 400 0.8125rem/1.4 var(--sans, inherit); color: var(--text-2); }
.gs-ev.is-blue { border-left-color: var(--es-blue); }
.gs-ev.is-red { border-left-color: var(--es-red); }
.gs-ev.is-big { background: color-mix(in srgb, var(--text) 5%, transparent); }
/* Hovered, faintly, whether or not another row is selected; selected, more so. */
.gs-ev:hover { background: color-mix(in srgb, var(--text) 6%, transparent); }
.gs-ev.is-lit, .gs-ev.is-lit:hover { background: color-mix(in srgb, var(--text) 12%, transparent); }
/* The feed is what its rows' offsets are measured from, for the scroll that
   brings a lit row into view. */
.gs-feed { position: relative; }
/* The card's charts run the width of the card; the panel's figures are capped
   at a reading measure, which this is not. */
.card-game .esm-figure { max-width: none; }
.gs-ev time { font: 500 0.75rem/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--muted); }
.gs-evbody { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 0.3rem 0.35rem; min-width: 0; }
.gs-evbody img { width: 18px; height: 18px; border-radius: 2px; vertical-align: middle; }
.gs-evbody b { font-weight: 600; color: var(--text); }
.gs-evbody .gs-arrow { color: var(--muted); }
.gs-swing { font: 500 0.6875rem/1.4 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* In the wide layout the feed sits under the charts and runs down to the
   foot of the scoreboard beside it, however tall that is: the column is
   stretched to the row, and the feed is the part of it that gives. Its own
   height is written as nothing so that it never sets the row's height, which
   two hundred rows of it otherwise would. */
@media (min-width: 64rem) {
  :root[data-layout="wide"] .esm-colcharts { display: flex; flex-direction: column; min-height: 0; }
  :root[data-layout="wide"] .esm-colcharts > * { flex: 0 0 auto; }
  :root[data-layout="wide"] .esm-colcharts .gs-feedsec { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 14rem; }
  :root[data-layout="wide"] .esm-colcharts .gs-feed { flex: 0 1 auto; min-height: 0; }
}
.esm-colcharts .gs-feed { max-height: 34rem; }
.esm-colcharts .gs-sec { margin-top: 1.25rem; }
.esm-colcharts .gs-title { max-width: none; margin: 0 0 0.15rem; padding-top: 0.6rem; border-top: 1px solid var(--line);
  font: 500 0.6875rem/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.esm-colcharts .gs-title i { font-style: normal; letter-spacing: 0.04em; text-transform: none; opacity: 0.8; }
.esm-colcharts .gs-note { max-width: none; margin: 0.35rem 0 0; font: 400 0.8125rem/1.5 var(--sans, inherit); color: var(--text-2); }
.esm-colcharts .gs-min { max-width: none; margin: 0.7rem 0 0.2rem; padding-left: 0.6rem;
  font: 600 0.625rem/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.esm-colcharts .gs-sec > * { flex: none; }
.gs-head { margin: 0; }
.gs-head strong { font-size: 1.25rem; }
.gs-sec { display: grid; gap: 0.45rem; min-width: 0; }
.gs-sec[hidden] { display: none; }
/* Scoped to the card: the cards' paragraph rule (a reading measure, and a
   paragraph's margins) is more specific than a class on its own, and stopped
   these rules short of the card's edge with a gap under each title. */
.card-game .gs-title { max-width: none; margin: 0 0 0.15rem; padding-top: 0.6rem; border-top: 1px solid var(--line);
  font: 500 0.6875rem/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.card-game .gs-title i { font-style: normal; letter-spacing: 0.04em; text-transform: none; color: var(--muted); opacity: 0.8; }
.card-game .gs-note { max-width: none; margin: 0.35rem 0 0; font: 400 0.8125rem/1.5 var(--sans, inherit); color: var(--text-2); }
.gs-note b { font-weight: 600; color: var(--text); }
.gs-link { margin: 0; padding: 0; border: 0; background: none; font: inherit; color: var(--text); text-decoration: underline; cursor: pointer; }

/* The esports card's opening: its prose, and in the wide layout the map of
   the open game beside it. The prose keeps its reading measure; the map takes
   the width that was going spare. In the compact layout there is no aside. */
.card-aside[hidden] { display: none; }
.card-aside { display: none; }
@media (min-width: 64rem) {
  /* The prose keeps its measure; the picture takes the rest of the width,
     centred in it and sitting at the foot of the prose, so that what follows
     on its side - the game's name, the scoreboard - comes straight after. */
  :root[data-layout="wide"] #esports .card-lead { display: grid; grid-template-columns: minmax(0, 68ch) minmax(0, 1fr);
    gap: 1rem 3rem; align-items: end; }
  :root[data-layout="wide"] #esports .card-intro > p:last-child { margin-bottom: 0; }
  :root[data-layout="wide"] #esports .embed { margin-top: 1rem; }
  :root[data-layout="wide"] #esports .embed-head { margin-bottom: 0.6rem; }
  :root[data-layout="wide"] #esports .embed-head .esm-status { margin: 0; }
  :root[data-layout="wide"] #esportsMini .esm-body { margin-top: 0.4rem; }
  /* A little up from the foot: room to breathe under it, without the gap
     coming back. */
  :root[data-layout="wide"] #esports .card-aside { display: block; min-width: 0; width: 100%; justify-self: center; margin-bottom: 2.5rem; }
  :root[data-layout="wide"] #esports .card-aside .esm-mapbox { max-width: 26rem; margin: 0 auto; }
  :root[data-layout="wide"] #esports .card-aside[hidden] { display: none; }
}
.card-aside .esm-map { margin: 0; }
.card-aside .esm-mapbox, .card-aside .esm-mapnote { max-width: 22rem; }
.card-aside .esm-figbar { margin-top: 0; }
.card-aside .esm-beathead { margin-top: 0; padding-top: 0; border-top: 0; }
.card-aside .esm-mapctl { margin: 0.7rem 0 0.7rem; }
/* The blank title and controls that hold the idle picture and the live map
   at the archived map's height: there for the space, not to be seen. */
.card-aside .is-blank { visibility: hidden; pointer-events: none; }
/* The picture: the map alone, with a game clock in its corner. */
.esm-art .esm-mapbox { position: relative; }
.esm-artclock { position: absolute; left: 0.6rem; bottom: 0.45rem; font: 500 0.75rem/1 var(--mono);
  font-variant-numeric: tabular-nums; letter-spacing: 0.04em; color: var(--text); opacity: 0.85; pointer-events: none; }
.card-aside .esm-more { margin-top: 0.6rem; }

/* Where it happened: Summoner's Rift with the reader's positions or the
   placed kills over it. Square, the width of its column up to a reading
   measure; the controls above it in the panel's own tab style. */
.esm-map { margin-bottom: 1.1rem; }
.esm-mapctl { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; margin: 0.5rem 0 0.6rem; }
.esm-mapctl .esm-switch { margin: 0; }
.esm-mapbox { max-width: 34rem; border: 1px solid var(--line); background: var(--bg-2); }
.esm-mapkey { margin-top: 0.5rem; }
.esm-mapbox canvas { display: block; width: 100%; height: auto; cursor: crosshair; }
/* The readout, as a caption along the foot of the map: there when something
   is under the pointer, gone when nothing is. */
.esm-map .esm-mapbox { position: relative; }
.card-aside .esm-mapread, .esm-mapread { position: absolute; left: 0; right: 0; bottom: 0; margin: 0; max-width: none;
  padding: 0.35rem 0.6rem; background: color-mix(in srgb, var(--bg) 80%, transparent);
  font: 400 0.6875rem/1.4 var(--mono); color: var(--text); pointer-events: none; }
.esm-mapread:empty { display: none; }
.esm-mapnote { max-width: 34rem; }
/* The team buttons double as the map's key. */
.esm-mapctl .esm-tab.is-blue:not([aria-pressed="true"]) { color: var(--es-blue); }
.esm-mapctl .esm-tab.is-red:not([aria-pressed="true"]) { color: var(--es-red); }
/* A blue star on a game with a map, the way the red one marks a comeback. */
.esm-chip .esm-mapstar { text-decoration: none; color: var(--es-blue); font-weight: 700; }
.esm-legend .is-map { color: var(--es-blue); }

/* The two summoner spells at the end of a scoreboard row, from the broadcast
   reader; a spell that is down is dimmed until it is back. */
.esm-spells { display: inline-flex; gap: 3px; margin-left: 0.7rem; padding-left: 0.55rem; border-left: 1px solid var(--line);
  vertical-align: middle; min-width: 2.2rem; }
.esm-spells img { width: 16px; height: 16px; border-radius: 2px; }
.esm-spells img.is-down { opacity: 0.3; filter: grayscale(1); }

/* The stream standing in for Riot's feed: a section of its own, headed for
   what it is, in the panel's own type - never dressed as the feed. */
.esm-backup { margin: 0.9rem 0 0.2rem; }
.esm-backup .esm-beathead { display: block; }
.esm-backup .esm-stats { margin: 0.45rem 0 0.3rem; }
.esm-backup > p { margin: 0; font: 400 0.8125rem/1.5 var(--sans, inherit); color: var(--text-2); }
.esm-backup > p b { font-weight: 600; color: var(--text); }
.esm-backup > i { display: block; font: 400 0.75rem/1.5 var(--mono); font-style: normal; color: var(--muted); }
.esm-check { margin-top: 0.3rem; }

/* The way out and the series on one line, a hairline between them. */
.esm-gamenav { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; margin-bottom: 0.9rem; }
.esm-gamenav .esm-back { margin: 0; }
.esm-gamenav .esm-series-tabs { margin: 0; padding-left: 1rem; border-left: 1px solid var(--line); }

/* The wide layout's header row: the view tabs and the game's name side by
   side, on the same two columns as the charts and the scoreboard below. */
.esm-topbar { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0.5rem 3.5rem;
  align-items: end; margin-bottom: 1rem; }
.esm-topbar > * { min-width: 0; }
.esm-topbar .esm-views { margin-bottom: 0; }
.esm-topbar .esm-head { margin: 0; }

/* The panel when its data file did not arrive. */
.esm-failed { padding: 1rem 0; font: 400 0.875rem/1.5 var(--sans, inherit); color: var(--muted); }

/* A number and what it counts, one per line. Named apart from .facts, which is
   the education strip and lays out in three columns. */
.stat-list { margin-top: 1.25rem; display: grid; gap: 0.6rem; }
.stat-list > div {
  display: grid; grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: 0.9rem; align-items: baseline;
  padding-bottom: 0.6rem; border-bottom: 1px solid var(--line);
}
.stat-list > div:last-child { padding-bottom: 0; border-bottom: 0; }
.stat-list dt {
  font: 600 1.25rem/1.2 var(--mono); font-variant-numeric: tabular-nums;
  text-align: right; color: var(--text);
}
.stat-list dd { margin: 0; font-size: 0.9375rem; line-height: 1.4; color: var(--text-2); }

/* The facts card fills the space the compact grid leaves beside a tall card.
   The wide layout has no such space, so it is not shown there. */
:root[data-layout="wide"] .card-facts { display: none; }

/* The scoreboard is no longer a box of its own: it runs to its full height and
   the page carries it, which is what scrolling should move. */
.esm-trackbox { padding-right: 0; }
@media (min-width: 64rem) {
  /* Deep enough to read into the day before, which is what browsing needs. */
  :root[data-layout="wide"] .esm-archive { height: 34rem; }
  :root[data-layout="wide"] .fl-list { --cap: 16.75rem; }   /* six flights: enough to see it is a list, few enough to scroll */
}

/* Room for the sticky header when a card is scrolled to, so the layout toggle
   does not tuck the top of it under the bar. */
.cards .card { scroll-margin-top: calc(var(--nav-h) + 1rem); }

/* The live clock and the feed's own paused flag, in the game's header line. */
.esm-head .esm-clock { font-variant-numeric: tabular-nums; color: var(--text); }
.esm-head .esm-paused { color: var(--es-red, #a8331f); }

/* Asking for the part of a live game that happened before the page opened. */
.esm-more {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  width: 100%; margin-top: 0.6rem; padding: 0.5rem 0.6rem;
  border: 1px solid var(--line); background: none; color: var(--text-2);
  font: 500 0.8125rem/1.3 var(--mono); text-align: left; cursor: pointer;
}
.esm-more:hover:not(:disabled) { background: var(--bg); color: var(--text); }
.esm-more:disabled { color: var(--muted); cursor: default; }
.esm-more i { font-style: normal; color: var(--muted); }

/* The card beside the esports panel, as a terminal. The structure is what makes
   it read as one - a title strip, a prompt line, and output in a pane of its own
   - not colour. The site has one ink and three greys and this card uses those,
   so it sits in the page rather than on top of it. */
.card-term { padding-bottom: 1.1rem; }
.card-term .term-bar {
  display: flex; align-items: center; gap: 0.5rem;
  margin: -0.2rem 0 0.9rem; padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
  font: 500 0.6875rem/1.4 var(--mono); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
}
.term-dot {
  width: 0.5rem; height: 0.5rem; border-radius: 50%;
  border: 1px solid var(--muted); flex: none;
}
.term-line {
  margin: 0 0 0.5rem; font: 400 0.8125rem/1.6 var(--mono); color: var(--text);
}
.term-p, .term-sign { color: var(--muted); }
.term-cmd { color: var(--text); }
.term-out {
  margin: 0 0 1.1rem; padding: 0.85rem 0.95rem;
  border: 1px solid var(--line); background: var(--bg-2);
  color: var(--text-2);
  font: 400 0.75rem/1.75 var(--mono);
  white-space: pre; overflow-x: auto; scrollbar-width: thin;
}
.term-out-slim { line-height: 1.6; white-space: pre-wrap; }
/* The dump is read by weight, the way the rest of the page is: the keys and the
   figures carry the ink, the punctuation stays out of the way. */
.term-out .tk { color: var(--text); }
.term-out .ts { color: var(--text-2); }
.term-out .tn, .term-out .tb { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.term-out .tp { color: var(--muted); }
.term-out a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.term-out a:hover { color: var(--text); }
/* A block where the cursor would be. It does not blink. */
.term-caret {
  display: inline-block; width: 0.5rem; height: 0.95em; margin-left: 0.4rem;
  background: var(--muted); vertical-align: -0.15em; opacity: 0.65;
}
@media (max-width: 34rem) {
  .term-out {
    white-space: pre-wrap; overflow-wrap: anywhere;
    text-indent: -1.2rem; padding-left: 2.15rem;
  }
}

/* Given a height to fill, the dump takes what is left of it and scrolls inside
   rather than pushing the card past the gap it was measured for. */
.card-term { overflow: hidden; }
.card-term .term-out:first-of-type { flex: 1 1 auto; min-height: 4rem; overflow-y: auto; }

/* A card hidden by script has to actually go: [hidden] is a browser rule and a
   class setting display beats it, so this card went on being laid out while the
   measurement believed it was gone - which is how it came to overshoot the panel
   it was supposed to be filling under. */
.card[hidden] { display: none; }

/* Three sizes. The blocks are, in order: the dump of what exists, where the
   work lives, and how the estimator did. The middle one is the advertisement,
   so it is the one that never goes; the dump is the first to be dropped, since
   the page says the same things in prose elsewhere. */
.card-term.is-slim { min-height: 0; padding-bottom: 1.25rem; }
.card-term.is-slim .term-line:nth-of-type(1),
.card-term.is-slim .term-line:nth-of-type(3),
.card-term.is-slim .term-line:nth-of-type(4),
.card-term.is-slim .term-out:nth-of-type(1),
.card-term.is-slim .term-out:nth-of-type(3) { display: none; }
.card-term.is-slim .term-out-slim { margin-bottom: 0; }

.card-term.is-mid .term-line:nth-of-type(1),
.card-term.is-mid .term-out:nth-of-type(1) { display: none; }
/* With the dump gone, the table is what takes up the slack. */
.card-term.is-mid .term-out:nth-of-type(3) { flex: 1 1 auto; min-height: 4rem; overflow-y: auto; }

/* The wide layout gives the panel about twice the width, and the items were
   still drawn at the size they need in the compact card, where six of them have
   to fit beside the numbers. Here there is room to read them. The file is the
   same one: Data Dragon serves these at 64px, so they have the detail to spare. */
@media (min-width: 64rem) {
  :root[data-layout="wide"] .esm-items { gap: 4px; }
  :root[data-layout="wide"] .esm-items img { width: 24px; height: 24px; }
  :root[data-layout="wide"] .esm-p > .esm-port > img { width: 34px; height: 34px; }
  :root[data-layout="wide"] .esm-port .esm-elixir { width: 14px; height: 14px; }
  /* The same three lines the compact card has, with the room the wide layout
     gives them spent on size. One line per player was tried, and the items
     spilled into the name once the spells joined them. */
  :root[data-layout="wide"] .esm-p {
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 0.3rem 1.1rem; padding: 0.75rem 0;
  }
  :root[data-layout="wide"] .esm-ptop b { font-size: 0.9375rem; }
  :root[data-layout="wide"] .esm-ptop .esm-pkda { font-size: 0.875rem; }
  :root[data-layout="wide"] .esm-pmid i { font-size: 0.6875rem; }
  :root[data-layout="wide"] .esm-teamname { padding-top: 0.4rem; }
  :root[data-layout="wide"] .esm-items { gap: 4px; align-items: center; }
  :root[data-layout="wide"] .esm-items img,
  :root[data-layout="wide"] .esm-items .esm-slot { width: 24px; height: 24px; }
  :root[data-layout="wide"] .esm-items .esm-quest { width: 26px; height: 26px; }
  :root[data-layout="wide"] .esm-stack { font-size: 9px; right: 2px; }
  :root[data-layout="wide"] .esm-spells img { width: 18px; height: 18px; }
}

/* The other games of the match being read, beside the one that is open. The
   winner's code rides under the game number so the series can be read at a
   glance without opening each one. */
/* Off the header above by a step, and the charts a step below. */
.esm-series-tabs { margin: 0.75rem 0 0.9rem; flex-wrap: wrap; }
.esm-series-tabs .esm-tab { display: inline-flex; align-items: baseline; gap: 0.4rem; }
.esm-series-tabs .esm-tab i {
  font-style: normal; font-size: 0.6875rem; color: var(--muted);
}
.esm-series-tabs .esm-tab[aria-pressed="true"] i { color: var(--on-ink); opacity: 0.75; }

/* The upcoming fixtures as a month rather than a row of chips: which days have
   games, how far ahead the feed goes, and where today falls, in one look. */
.esm-cal { margin: 0.7rem 0 0.9rem; }
.esm-cal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  margin-bottom: 0.5rem;
}
.esm-cal-head b {
  font: 500 0.8125rem/1.3 var(--mono); letter-spacing: 0.04em; color: var(--text);
}
.esm-cal-step {
  min-width: 1.6rem; padding: 0.1rem 0.4rem; border: 1px solid var(--line);
  background: none; color: var(--text-2); font: 400 0.875rem/1.2 var(--mono); cursor: pointer;
}
.esm-cal-step:hover:not(:disabled) { background: var(--bg-2); color: var(--text); }
.esm-cal-step:disabled { opacity: 0.35; cursor: default; }
.esm-cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.esm-cal-dow {
  padding: 0.2rem 0; text-align: center;
  font: 500 0.625rem/1.2 var(--mono); letter-spacing: 0.06em; color: var(--muted);
}
.esm-cal-pad { min-height: 1.9rem; }
.esm-cal-day {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.2rem; min-height: 1.9rem; padding: 0.1rem 0;
  border: 1px solid transparent; background: var(--bg-2); color: var(--text-2);
  font: inherit; cursor: pointer;
}
.esm-cal-day b { font: 500 0.75rem/1 var(--mono); font-variant-numeric: tabular-nums; }
.esm-cal-day i {
  display: block; width: 3px; height: 3px; border-radius: 50%;
  background: var(--text-2); font-style: normal;
}
.esm-cal-day:hover:not(.is-empty) { border-color: var(--text-2); color: var(--text); }
/* Nothing scheduled: a date, not a button. */
.esm-cal-day.is-empty { background: none; color: var(--muted); opacity: 0.5; cursor: default; }
.esm-cal-day.is-today { border-color: var(--text-2); }
.esm-cal-day.is-on { background: var(--ink); border-color: var(--ink); }
.esm-cal-day.is-on b { color: var(--on-ink); }
.esm-cal-day.is-on i { background: var(--on-ink); opacity: 0.8; }
/* Which day the list below is showing. */
.esm-cal-on {
  margin: 0 0 0.4rem; font: 500 0.6875rem/1.4 var(--mono);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}

/* A fixture whose teams are not known yet: on the page because the date is real,
   quieter than one that can be watched. */
.esm-matches li.is-tbd .esm-vs { color: var(--text-2); font-weight: 400; }
.esm-cal-day i.is-tbd { background: none; box-shadow: inset 0 0 0 1px var(--text-2); }

/* The month and the day it is showing. Stacked in the compact card, side by side
   where there is width for it: a seven column grid stretched across a thousand
   pixels gives cells five times wider than they are tall, which reads as a table
   of nothing rather than as a month. */
.esm-up { display: grid; gap: 0.2rem 1.5rem; }
/* The month at its own width, centred in the column. Not with auto margins:
   on a grid item those shrink it to its content, and the month collapsed to
   half its width. */
.esm-cal { margin: 0.7rem 0 0.4rem; width: 100%; max-width: 22rem; justify-self: center; }
@media (min-width: 64rem) {
  :root[data-layout="wide"] .esm-up {
    grid-template-columns: 19rem minmax(0, 1fr);
    align-items: start;
  }
  :root[data-layout="wide"] .esm-cal { margin-top: 0; max-width: none; }
  /* Squarer cells, now that they are not being stretched across the card. */
  :root[data-layout="wide"] .esm-cal-day,
  :root[data-layout="wide"] .esm-cal-pad { min-height: 2.3rem; }
  :root[data-layout="wide"] .esm-up-day .esm-cal-on { margin-top: 0.1rem; }
  /* The fixtures need a reading width, not all the width there is: stretched to
     the card the time and the league end up a hand apart. */
  :root[data-layout="wide"] .esm-up-day { max-width: 34rem; }
}

/* A way back to the top of a page that runs to several thousand pixels.

   It fills as you read, the way the block beside the name empties: that one is a
   cursor collapsing once you have started, this one is the same block coming
   back as you reach the end. The fill is ink, and the label and arrow inside it
   are the paper colour, so the whole thing inverts as it rises rather than
   printing grey text over a dark half. */
.to-top {
  position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 40;
  display: inline-block; padding: 0; overflow: hidden;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(6px);
  color: var(--text-2);
  cursor: pointer;
  opacity: 0; transform: translateY(0.5rem);
  transition: opacity 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}
.to-top.is-on { opacity: 1; transform: none; }
.to-top:hover { border-color: var(--text-2); }
.to-top:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

.to-top-face {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.6rem 0.85rem;
  font: 500 0.6875rem/1 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
}

/* The ink half, clipped to how much of the page is behind you. */
.to-top-fill {
  position: absolute; inset: 0;
  background: var(--ink); color: var(--on-ink);
  clip-path: inset(calc(100% - var(--read, 0) * 100%) 0 0 0);
  transition: clip-path 0.12s linear;
}

.to-top-mark {
  width: 0.8rem; height: 0.8rem; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: square;
  transition: transform 0.2s ease;
}
.to-top:hover .to-top-mark { transform: translateY(-1px); }

@media (max-width: 40rem) {
  .to-top { right: 1rem; bottom: 1rem; }
  .to-top-face { padding: 0.6rem; }
  .to-top-label { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .to-top, .to-top-mark, .to-top-fill { transition: none; }
}

/* "Nothing today" carries the weight in that line; the day being shown instead
   is the quieter half of it. */
.esm-cal-on b { color: var(--text); font-weight: 600; }

/* These charts are drawn in a 320 unit box and take whatever width they are
   given, so in the wide layout the live panel stretched one across the whole
   card: lane labels five times their size and a timeline the height of the
   screen. They are given a reading width instead, the same one the archive's
   column gives them, and the space beside them is left to the scoreboard. */
.esm-figure { max-width: 40rem; }
.esm-prob, .esm-timeline { width: 100%; height: auto; }


/* ---------------------------------------------------------------------------
   Phones, restated last.

   The block of rules from roughly line 640 onwards is a ladder of wide-screen
   tuning - a wider page, a wider interactive card, a cabin that fills the gap -
   that was written without the media queries it was meant to sit inside. Being
   last in the file, it beat the max-width: 60rem collapse near the top, so the
   two-column card grid survived all the way down to a phone. On a 390px screen
   that gave tracks of 198px and 132px: a route map 78px across, a flight list
   4px across, "Pick a flight or a route" cut off, and a calendar wide enough to
   push the document to 423px, which is why the phone zoomed the page out.

   Re-wrapping that ladder would mean inventing breakpoints nothing records and
   re-tuning the desktop layout against them. The collapse is restated here
   instead, after it, for the properties it actually clobbered. */
@media (max-width: 60rem) {
  /* The terminal card is filler for the two-column row: it exists to absorb
     whatever the esports panel leaves beside it. Stacked, there is no row to
     balance, and a dashboard wedged between two projects on a phone is just
     something else to scroll past. */
  .card-term, .card-game { display: none; }

  .cards { grid-template-columns: minmax(0, 1fr); }
  .mini-grid { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .cabin-pair { grid-template-columns: minmax(0, 1fr); }
  .cabin-pair #cabinMap, #cabinMap { width: 100%; max-width: 22rem; }
}

@media (max-width: 44rem) {
  /* A figure carries margin: 1em 40px from the browser itself, which nothing
     here ever overrode. Inside a card that is 310px wide on a phone, those two
     margins take a quarter of the row, and everything measured against the
     card - the map, the flight list, the cabin - was drawn into what was left. */
  .embed { margin-left: 0; margin-right: 0; }

  /* The flight row's four tracks were tuned for a card half the page wide:
     4rem + 3.7rem + 4.6rem of fixed columns plus the gaps come to more than the
     row has on a phone, so the route - the one column that should flex - was
     squeezed to nothing and the fare was clipped. */
  .fl-row, .fl-head {
    grid-template-columns: 3.4rem minmax(0, 1fr) 3rem 3.6rem;
    gap: 0.5rem;
    padding-right: 0.55rem;
  }
}


/* Touch targets.

   Everything here was drawn for a cursor, which can land on a 22px arrow. A
   fingertip is nearer 9mm, so the calendar's month arrows, its day cells, the
   panel's tabs and the slider on the distribution chart were all smaller than
   the thing pressing them. Only the room around them changes; the type, the
   borders and the spacing of the grids stay as they are.

   Asked of the pointer rather than the width, because a phone held sideways is
   844px across - wider than any sensible phone breakpoint - and still a phone.
   The width clause stays for narrow windows on machines with a mouse. */
@media (max-width: 44rem), (pointer: coarse) {
  .esm-tab { padding: 0.6rem 0.8rem; }
  .esm-cal-step { min-width: 2.75rem; padding: 0.55rem 0.5rem; }
  .esm-cal-day, .esm-cal-pad { min-height: 2.75rem; }
  .embed-ctl input[type="range"] { height: 2.75rem; }
  .to-top-face { padding: 0.85rem; }
  .contact-email { padding: 0.3rem 0; }
  .contact-links a, .nav-links a, .nav-name { display: inline-block; padding: 0.55rem 0; }
}

@media (max-width: 44rem) {
  /* The fixture row gave its middle column whatever the league and format left
     over, which on a phone was not enough for the teams - the one thing the row
     is for - so "FUR vs LOUD" came out as "FUR vs ...". The league drops onto a
     line of its own underneath instead, where it has all the width it needs. */
  .esm-matches li { grid-template-columns: 4.6rem minmax(0, 1fr); row-gap: 0.15rem; }
  .esm-matches li > em { grid-column: 2; text-align: left; }
}


/* The Work menu.

   Three projects sit behind one word in the nav, and reaching the third meant
   scrolling past the first two. The menu names them and goes straight there.

   It hangs from the item rather than the bar so it lines up with the word, and
   it is drawn in the same furniture as everything else: a hairline, the panel
   background, no shadow. The gap between the word and the panel is padding on
   the panel, not margin, so the pointer never crosses dead space on its way
   down and the menu cannot flicker shut under it. */
/* Every item is stretched to the full height of the bar, not just the one with
   a menu, so that "below the item" and "below the bar" are the same line. Left
   to the height of its own text the menu opened twenty pixels up, overlapping
   the bar it hangs from - and stretching that item alone sat its word two
   pixels higher than the rest of the row. */
.nav-links > li {
  display: flex;
  align-self: stretch;
  align-items: center;
}
.nav-has-menu { position: relative; }

.nav-menu {
  position: absolute;
  top: calc(100% + 1px);      /* clear of the bar's own bottom rule */
  left: -0.9rem;
  z-index: 60;
  display: grid;
  gap: 0;              /* the rule between items does the dividing, not a gap */
  min-width: 15rem;
  margin: 0;
  padding: 0.45rem;
  border: 1px solid var(--line);
  background: var(--bg-2);
  list-style: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.25rem);
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s;
}
/* Hover and keyboard both open it without asking a script to. */
.nav-has-menu:hover .nav-menu,
.nav-has-menu:focus-within .nav-menu,
.nav-has-menu.is-open .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.nav-links .nav-menu a {
  display: block;
  padding: 0.5rem 0.65rem;
  color: var(--text-2);
  font-size: 0.875rem;
  line-height: 1.3;
}
.nav-links .nav-menu a:hover,
.nav-links .nav-menu a:focus-visible { background: var(--bg); color: var(--text); }

/* A hairline between them, so the three read as three before you hover any of
   them. Between only - the panel already has its own border, and a rule under
   the last one would sit a pixel inside it and read as a mistake. The same
   --line as every other rule on the page. */
.nav-menu li + li { border-top: 1px solid var(--line); }

/* The word keeps a mark while its menu is down, so it is clear what opened. */
.nav-has-menu:hover > a,
.nav-has-menu:focus-within > a,
.nav-has-menu.is-open > a { color: var(--text); }

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


/* The cabin in the wide layout.

   Wide view is for making things bigger and the cabin was the one thing that
   ignored it: the column it sits in grew from 180px to 416, and the map stayed
   at the thirteen rems it is capped to, seats 13.6px across in both layouts.

   The legend drops below it rather than sitting beside it, so the width of the
   column is the cabin's to use. The cap is what keeps this honest: the map is
   two and a half times as tall as it is wide, so every pixel of width costs two
   and a half of height, and the column is already the taller of the two.
   Seventeen rems was too much of that - a tall slab of cabin against a short
   column of flights. Fourteen and a half still reads a third larger than the
   compact card and sits better in the row.

   Sized by width, never by height: with no flight chosen the cabin collapses
   its own viewBox to a strip, and a height here would stretch that strip to
   fill it. */
:root[data-layout="wide"] .cabin-pair { grid-template-columns: minmax(0, 1fr); }
:root[data-layout="wide"] .cabin-pair #cabinMap {
  width: 100%;
  max-width: 14.5rem;
  justify-self: center;
}

/* The draft, while the game itself has not started on the feed: each side's
   five, portrait, champion, role and player, in the side's colour. */
.esm-draft { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; margin-top: 0.6rem; }
:root[data-layout="wide"] .esm-draft { column-gap: 3.5rem; }
.esm-draftteam { display: flex; align-items: center; gap: 0.5rem; padding-bottom: 0.45rem;
  border-bottom: 2px solid var(--es-blue); font: 700 0.875rem/1.3 var(--mono); color: var(--text); }
.esm-draftside.is-red .esm-draftteam { border-bottom-color: var(--es-red); }
.esm-draftteam i { margin-left: auto; font: 400 0.625rem/1 var(--mono); font-style: normal; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted); }
.esm-draftp { display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; align-items: center; gap: 0.6rem;
  padding: 0.5rem 0; border-bottom: 1px solid var(--line); }
.esm-draftp img { display: block; width: 30px; height: 30px; border-radius: 3px; }
:root[data-layout="wide"] .esm-draftp { grid-template-columns: 36px minmax(0, 1fr) auto; gap: 0.75rem; }
:root[data-layout="wide"] .esm-draftp img { width: 36px; height: 36px; }
.esm-draftp span { min-width: 0; }
.esm-draftp b { display: block; font: 500 0.875rem/1.25 var(--sans, inherit); color: var(--text); overflow-wrap: anywhere; }
:root[data-layout="wide"] .esm-draftp b { font-size: 0.9375rem; }
.esm-draftp i { display: block; margin-top: 0.1rem; font: 400 0.625rem/1.4 var(--mono); font-style: normal;
  letter-spacing: 0.02em; color: var(--muted); overflow-wrap: anywhere; }
:root[data-layout="wide"] .esm-draftp i { font-size: 0.6875rem; letter-spacing: 0.04em; }
.esm-draftp .esm-spells { margin-left: 0; padding-left: 0; border-left: 0; }
.esm-draft .esm-banlist { display: flex; margin: 0.4rem 0 0.15rem; }

/* The draft as a strip: each team's bans, slashed and greyed, and its picks
   in the order they were locked, the order on the cell's corner. */
.esm-bans { margin: 0.9rem 0 0.4rem; }
.esm-bans .esm-beathead { display: block; }
.esm-bans .esm-beathead i, .esm-banlist > i, .esm-picklist > i { font: 400 0.625rem/1 var(--mono); font-style: normal;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.esm-bans .esm-beathead i { margin-left: 0.5rem; }
.esm-banrow { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.9rem; padding: 0.4rem 0; border-top: 1px solid var(--line); }
.esm-banrow > b { display: inline-flex; align-items: center; gap: 0.35rem; min-width: 3.4rem; font: 700 0.8125rem/1.3 var(--mono); color: var(--es-blue); }
.esm-banrow.is-red > b { color: var(--es-red); }
.esm-banlist, .esm-picklist { display: inline-flex; align-items: center; gap: 3px; }
.esm-banlist > i, .esm-picklist > i { margin-right: 0.3rem; }
/* Each pick in the band carries the number it was locked at, beside its icon. */
.esm-banlist > i, .esm-picklist > i { white-space: nowrap; }
.esm-cell.esm-ban, .esm-cell.esm-pick { position: relative; display: inline-block; width: 20px; height: 20px; border-radius: 2px;
  background: var(--bg-2); box-shadow: inset 0 0 0 1px var(--line); line-height: 0; overflow: hidden; }
.esm-cell.esm-ban img, .esm-cell.esm-pick img { display: block; width: 20px; height: 20px; }
.esm-cell.esm-ban img { filter: grayscale(0.7); opacity: 0.65; }
.esm-cell.esm-ban:not(:empty)::after { content: ""; position: absolute; left: -4px; top: 9px; width: 28px; height: 2px;
  background: var(--es-red); transform: rotate(-45deg); }
:root[data-layout="wide"] .esm-cell.esm-ban, :root[data-layout="wide"] .esm-cell.esm-pick,
:root[data-layout="wide"] .esm-cell.esm-ban img, :root[data-layout="wide"] .esm-cell.esm-pick img { width: 24px; height: 24px; }
:root[data-layout="wide"] .esm-cell.esm-ban:not(:empty)::after { top: 11px; width: 32px; }

/* Wide layout, Skyway: the footnote sits under the flight card, beside the
   cabin, instead of under both columns with the cabin's height of nothing
   above it. The figure becomes the grid and the list-and-cabin grid
   dissolves into it; the cabin comes down a size so the columns end near
   each other. */
:root[data-layout="wide"] #skyway .embed { display: grid; grid-template-columns: minmax(0, 1fr) 24rem; column-gap: 2rem; align-items: start; }
:root[data-layout="wide"] #skyway .mini-topline, :root[data-layout="wide"] #skyway #routeMap { grid-column: 1 / -1; }
:root[data-layout="wide"] #skyway .mini-grid { display: contents; }
:root[data-layout="wide"] #skyway .mini-left { grid-column: 1; grid-row: 3; margin-top: 0.9rem; }
:root[data-layout="wide"] #skyway .mini-right { grid-column: 2; grid-row: 3 / span 2; margin-top: 0.9rem; }
:root[data-layout="wide"] #skyway .embed-foot { grid-column: 1; grid-row: 4; }
:root[data-layout="wide"] #skyway .cabin-pair #cabinMap, :root[data-layout="wide"] #skyway #cabinMap { max-width: 12.5rem; }
