/* Self-hosted so the site makes no third-party font request. Both faces are
   SIL Open Font License 1.1, see fonts/README.md. unicode-range means a
   latin-only visitor never downloads the latin-ext files. */
@font-face {
  font-family: "Chakra Petch";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("./fonts/chakra-petch-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Chakra Petch";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("./fonts/chakra-petch-600-latin.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: "Chakra Petch";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("./fonts/chakra-petch-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Chakra Petch";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("./fonts/chakra-petch-700-latin.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: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("./fonts/inter-400-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("./fonts/inter-400-600-latin.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 {
  /* Four surface steps, so a card reads as sitting above the page rather than
     being painted on it. The old theme had one panel colour for everything. */
  --bg: #070b14;
  --s1: #0d1420;
  --s2: #131c2b;
  --s3: #1b2536;
  --line: #22304a;
  --line-soft: #182238;

  --text: #e8eef7;
  /* **M21, and the numbers are measured rather than eyeballed.** --dim was
     #5d6b81, which fails 4.5:1 on every surface it is used on: 3.64 on --bg,
     3.41 on --s1, 3.16 on --s2, 2.85 on --s3. All 23 uses are 10 to 12.5px
     text, so 4.5 is the bar, not the 3.0 that large text gets.

     --muted moved too, and that is the part worth explaining. Raising --dim
     alone to clear the floor puts it at 1.13 contrast against the old --muted,
     which is not a step at all: the three-level hierarchy collapses to two and
     every "primary, secondary, tertiary" reading on the site stops working.
     Nudging both keeps three real steps.

                    bg     s1     s2     s3    dim->muted  muted->text
       before     3.64*  3.41*  3.16*  2.85*      1.89         2.45
       naive fix  6.08   5.70   5.28   4.75       1.13         2.45
       this       6.08   5.70   5.28   4.75       1.41         1.97

     The steps are tighter than they were, which is unavoidable: three levels
     now have to fit above a 4.5 floor instead of running down past it. */
  --muted: #9cacc6;
  --dim: #7e90ae;

  --gold: #c9a345;
  --win: #3fb9a0;
  --loss: #e05666;
  --live: #ff7a45;

  --pad: clamp(16px, 4vw, 32px);
  --gap: clamp(10px, 1.6vw, 16px);
  --r: 14px;
  --ease: cubic-bezier(.2, .7, .2, 1);

  --display: "Chakra Petch", "Inter", ui-sans-serif, system-ui, sans-serif;
  --body: "Inter", ui-sans-serif, system-ui, sans-serif;
}

* { box-sizing: border-box; }
html { color-scheme: dark; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(1100px 520px at 78% -12%, #16233a 0%, transparent 62%),
    var(--bg);
  color: var(--text);
  font: 400 15px/1.55 var(--body);
  /* Digits line up between rows instead of jittering. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

h1, h2, .brand, .kda, .tier, .rank-t { font-family: var(--display); letter-spacing: .01em; margin: 0; }

a { color: inherit; }
img { max-width: 100%; }

/* ---------- shell and sidebar ---------- */

/* main is capped at 1080px and centred, so on a wide screen there is dead
   margin either side. The sidebar lives in it: at 1364px and up it costs the
   content nothing. Below the breakpoint it becomes a drawer instead of
   squeezing the column. See web/nav.js. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  padding: 10px 16px; background: var(--s3); color: var(--text);
  border-radius: 0 0 10px 0; text-decoration: none;
}
.skip:focus { left: 0; }

.shell { display: flex; flex: 1; min-height: 100vh; }

.side {
  flex: none;
  width: 220px;
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 18px 12px 16px;
  border-right: 1px solid var(--line-soft);
  /* **LF11.** This was `--s1` at 60% over a `blur(12px)` backdrop filter, on
     a sticky element the full height of the viewport. A backdrop filter has to
     re-sample and re-blur everything behind its box, and a full-height sticky
     box means it does that on every scroll frame, on the compositor's critical
     path.
     And on this layout it was blurring nothing. The rail is a flex sibling of
     the content column, not an overlay, so the only thing behind it is the page
     background: a flat colour, and a blurred flat colour is the same flat
     colour. The mobile drawer already set `backdrop-filter: none` for the
     same reason, with the comment to say so.
     The value below is the exact composite of what the translucency produced,
     written as a color-mix against --bg rather than a hex so it stays correct
     if either token moves. */
  background: color-mix(in srgb, var(--s1) 60%, var(--bg));
  overflow-y: auto;
  overscroll-behavior: contain;
}
.side .brand { padding: 4px 10px 18px; }

.brand { font-size: 21px; font-weight: 700; text-decoration: none; white-space: nowrap; }
.brand b { color: var(--gold); font-weight: 700; }

.side nav { display: flex; flex-direction: column; gap: 2px; }
.side nav a {
  position: relative;
  display: block;
  padding: 9px 12px;
  border-radius: 9px;
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  transition: color .18s var(--ease), background .18s var(--ease);
}
.side nav a:hover { color: var(--text); background: var(--s2); }
/* Search is the front door, so it sits above the rest rather than
   alphabetised into the middle of it. The rule is inset to the width of a
   link's text, not run edge to edge, so it reads as a grouping inside the
   list and not as the end of the sidebar. nav.js only ever looks for
   `a[href]` and `button`, so this cannot land in the focus trap. */
.side nav .nav-sep {
  height: 1px;
  margin: 7px 12px;
  border: 0;
  background: var(--line-soft);
}
.side nav a[aria-current="page"] {
  color: var(--gold);
  background: color-mix(in srgb, var(--gold) 10%, var(--s2));
}
/* Vertical list: the current item is marked down its own edge, not underlined. */
.side nav a[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 0; top: 8px; bottom: 8px;
  width: 2px; border-radius: 2px; background: var(--gold);
}
.side .status { margin-top: auto; padding: 14px 12px 0; }

.colwrap { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* The mobile bar. Hidden entirely while the sidebar is furniture. */
.top {
  display: none;
  position: sticky;
  top: 0;
  z-index: 20;
  align-items: center;
  gap: 14px;
  padding: 10px var(--pad);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line-soft);
}

.burger {
  display: grid;
  grid-template-columns: 18px auto;
  align-items: center;
  gap: 3px 9px;
  padding: 7px 11px 7px 9px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--s1);
  color: var(--muted);
  cursor: pointer;
  font: inherit;
}
.burger span {
  grid-column: 1;
  width: 18px; height: 2px; border-radius: 2px;
  background: currentColor;
  transition: transform .2s var(--ease), opacity .15s var(--ease);
}
.burger em {
  grid-column: 2; grid-row: 1 / -1;
  font-style: normal; font-size: 13px; font-weight: 500;
}
.burger:hover { color: var(--text); border-color: var(--line); background: var(--s2); }
.burger[aria-expanded="true"] { color: var(--gold); border-color: var(--gold); }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

.scrim {
  position: fixed; inset: 0; z-index: 35;
  /* Dark enough that the drawer reads as lifted off the page rather than
     painted on it. */
  background: rgba(2, 5, 11, .68);
  backdrop-filter: blur(3px);
}

@media (max-width: 1179px) {
  .top { display: flex; }
  .side {
    position: fixed;
    inset: 0 auto 0 0;
    height: 100dvh;
    width: min(280px, 82vw);
    /* Opaque, and a step lighter than the page it covers. At --s1 the drawer
       was the same value as the scrimmed page behind it and read as empty. */
    background: var(--s2);
    border-right: 1px solid var(--line);
    /* The desktop sidebar is translucent and blurs the page behind it. As a
       drawer it is opaque and sits on a scrim, so the blur samples a backdrop
       nobody can see - two stacked backdrop-filters for nothing. */
    backdrop-filter: none;
    /* translate, not left: it is relative to the drawer own width, so the
       closed position tracks the width above instead of needing a magic number
       kept in sync with it. */
    translate: -100% 0;
    transition: translate .24s var(--ease);
    box-shadow: 0 0 40px rgba(0, 0, 0, .5);
  }
  .side[data-open="true"] { translate: 0 0; }
  body.drawer-open { overflow: hidden; }
}

@media (min-width: 1180px) {
  /* Furniture, not an overlay: the scrim must never intercept clicks. */
  .scrim { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .side { transition: none; }
  .burger span { transition: none; }
}

/* Key health, surfaced instead of buried in /health. */
.status { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex: none; }
.dot.ok { background: var(--win); box-shadow: 0 0 0 3px color-mix(in srgb, var(--win) 22%, transparent); }
.dot.bad { background: var(--loss); box-shadow: 0 0 0 3px color-mix(in srgb, var(--loss) 22%, transparent); }
/* **LU21.** `display: none` removed the label from the accessibility tree as
   well as from the screen, so on a narrow viewport the only indication of API
   health was a coloured dot with no text anywhere -- unreadable to a screen
   reader and meaningless to anyone who cannot distinguish the colours. Hidden
   visually, still announced. */
@media (max-width: 560px) {
  .status .label {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

main { width: min(1080px, 100%); margin: 0 auto; padding: var(--pad) var(--pad) 56px; flex: 1; }

footer {
  border-top: 1px solid var(--line-soft);
  padding: 16px var(--pad);
  color: var(--dim);
  font-size: 12px;
}

/* ---------- headings ---------- */

h1 { font-size: clamp(24px, 4.2vw, 38px); font-weight: 700; line-height: 1.06; }
h2 { font-size: 14px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }

.sec { display: flex; align-items: baseline; gap: 10px; margin: 0 0 12px; }
.sec:not(:first-child) { margin-top: 30px; }
.sec-note { font-size: 12.5px; color: var(--dim); margin-left: auto; }

.muted { color: var(--muted); }
.gold { color: var(--gold); }
.stack { display: flex; flex-direction: column; gap: 8px; }

/* ---------- controls ---------- */

input, select, button { font: 400 15px var(--body); }

input, select {
  background: var(--s2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 11px 13px;
  transition: border-color .18s var(--ease);
}
input:focus-visible, select:focus-visible { outline: none; border-color: var(--gold); }
input { flex: 1; min-width: 0; }

button {
  border: 1px solid var(--line);
  background: var(--s2);
  color: var(--text);
  font-weight: 500;
  padding: 10px 16px;
  border-radius: 10px;
  cursor: pointer;
  transition: transform .16s var(--ease), background .16s var(--ease), border-color .16s var(--ease);
}
button:hover:not(:disabled) { background: var(--s3); border-color: var(--gold); transform: translateY(-1px); }
button:disabled { opacity: .55; cursor: default; }
button.primary { background: var(--gold); color: #16110a; border-color: var(--gold); font-weight: 600; }
button.primary:hover { background: #d9b256; }
button.on { border-color: color-mix(in srgb, var(--loss) 50%, var(--line)); }

.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

.pill {
  background: var(--s2);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  padding: 6px 13px;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}
.pill:hover { color: var(--text); transform: none; }
.pill.active { border-color: var(--gold); color: var(--gold); background: color-mix(in srgb, var(--gold) 10%, var(--s2)); }

/* ---------- hero ---------- */

.hero {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--s1);
  overflow: hidden;
  margin-bottom: clamp(20px, 3vw, 30px);
}

/* Champion art as identity. Match-v5 carries no skin field, so match and
   profile art is always the base skin. See docs/API.md. */
.hero-art {
  position: absolute;
  inset: 0 0 auto auto;
  width: 62%;
  height: 100%;
  background-size: cover;
  background-position: 50% 20%;
  opacity: .5;
  mask-image: linear-gradient(to left, #000 0%, #000 34%, transparent 92%);
  -webkit-mask-image: linear-gradient(to left, #000 0%, #000 34%, transparent 92%);
}

.hero-body {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: var(--gap);
  flex-wrap: wrap;
  padding: clamp(20px, 3vw, 30px) clamp(18px, 2.4vw, 26px) 18px;
}
.hero-id { display: flex; align-items: center; gap: 16px; margin-right: auto; min-width: 0; }
.hero-avatar {
  width: clamp(56px, 8vw, 76px);
  height: clamp(56px, 8vw, 76px);
  border-radius: 16px;
  border: 2px solid var(--line);
  background: var(--s3);
  flex: none;
}
.hero-sub { margin: 5px 0 0; color: var(--muted); font-size: 13.5px; }

.ranks {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px;
  background: var(--line-soft);
  border-top: 1px solid var(--line-soft);
}
.rank { background: var(--s1); padding: 14px clamp(18px, 2.4vw, 26px); }
.rank-q { font-size: 11.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--dim); }
.rank-t { font-size: 17px; font-weight: 600; margin-top: 3px; }
.rank-t .lp { color: var(--muted); font-weight: 500; }
.rank-m { font-size: 12.5px; color: var(--muted); margin-top: 2px; }

.track { height: 4px; border-radius: 4px; background: var(--s3); margin-top: 9px; overflow: hidden; }
.track i { display: block; height: 100%; background: var(--win); border-radius: 4px; width: 0; transition: width .6s var(--ease); }

/* ---------- two columns ---------- */

.cols { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: clamp(18px, 2.6vw, 30px); }
@media (max-width: 880px) { .cols { grid-template-columns: minmax(0, 1fr); } }

/* ---------- match row ---------- */

/* The spine carries the result before any text is read. Square on that edge:
   a rounded corner against a single-sided border looks broken. */
.match {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  background: var(--s1);
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--dim);
  border-radius: 0 var(--r) var(--r) 0;
  text-decoration: none;
  transition: transform .18s var(--ease), background .18s var(--ease), border-color .18s var(--ease);
  animation: rise .42s var(--ease) both;
}
.match:hover { background: var(--s2); transform: translateX(3px); border-color: var(--line); }
.match.w { border-left-color: var(--win); }
.match.l { border-left-color: var(--loss); }

.champ { position: relative; flex: none; }
.champ img { width: 46px; height: 46px; border-radius: 11px; background: var(--s3); display: block; }
.champ .tag {
  position: absolute; bottom: -4px; right: -4px;
  background: var(--s3); border: 1px solid var(--line);
  border-radius: 6px; font-size: 10.5px; padding: 0 4px; color: var(--muted);
}

.kda { font-size: 17px; font-weight: 600; white-space: nowrap; }
.kda .d { color: var(--loss); }
.kda-r { font-size: 11.5px; color: var(--muted); margin-top: 1px; }

.m-q { font-size: 13.5px; font-weight: 500; }
.m-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }

.bar-row { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
.bar { height: 3px; flex: 1; max-width: 190px; background: var(--s3); border-radius: 3px; overflow: hidden; }
.bar i { display: block; height: 100%; background: var(--gold); border-radius: 3px; width: 0; transition: width .6s var(--ease); }
.bar-n { font-size: 11px; color: var(--dim); white-space: nowrap; }

.res { font-size: 11.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
.res.w { color: var(--win); }
.res.l { color: var(--loss); }
.res.n { color: var(--dim); }

/* ---------- compact rows: mastery, meta, watchlist, hits ---------- */

.card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--s1);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  text-decoration: none;
  animation: rise .42s var(--ease) both;
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.card:hover { background: var(--s2); border-color: var(--line); }
.card.plain { grid-template-columns: minmax(0, 1fr) auto; }
.card img.ico { width: 34px; height: 34px; border-radius: 9px; background: var(--s3); flex: none; }
.card-n { font-size: 13.5px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-s { font-size: 11.5px; color: var(--muted); }
.card-v { font-size: 12.5px; color: var(--gold); font-weight: 500; white-space: nowrap; }

.tier {
  min-width: 32px; text-align: center;
  font-size: 13px; font-weight: 700;
  padding: 3px 7px; border-radius: 8px;
  background: var(--s3); color: var(--muted);
}
.tier[data-t="S+"], .tier[data-t="S"] { background: color-mix(in srgb, var(--gold) 20%, var(--s3)); color: var(--gold); }
.tier[data-t="A"] { background: color-mix(in srgb, var(--win) 18%, var(--s3)); color: var(--win); }
.tier[data-t="B"] { background: var(--s3); color: var(--text); }
/* **M21.** --loss on its own 16% wash measures 3.49:1. The other three
   badges land at 4.51 (S), 4.57 (A) and 13.19 (B) by using tokens that happen
   to be lighter, so this one needs a lighter red of its own rather than a
   different recipe. Thinning the wash does not reach the bar: it tops out at
   3.93 even at 6%, because --loss itself is too dark against --s3. Measured at
   4.58:1. Scoped to the badge, so --loss keeps its value everywhere else. */
.tier[data-t="C"] { background: color-mix(in srgb, var(--loss) 16%, var(--s3)); color: #ff677a; }

/* ---------- match detail + live ---------- */

.team { margin-top: 22px; }
.team-h { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.team-h h2 { text-transform: none; letter-spacing: 0; font-size: 16px; color: var(--text); }

.player {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--s1);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.player:hover { border-color: var(--line); }
.player.me { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 6%, var(--s1)); }

/* A player's name opens their profile. Underline on hover only, so ten rows of
   links do not read as a wall of blue. */
.pname { text-decoration: none; color: var(--text); }
.pname:hover { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }
.pname.off { color: var(--muted); cursor: help; }

.dmg { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; white-space: nowrap; }

.skills-btn { padding: 6px 11px; font-size: 12.5px; border-radius: 8px; }
.skills-btn[aria-pressed="true"] { border-color: var(--gold); color: var(--gold); background: color-mix(in srgb, var(--gold) 12%, var(--s2)); }

/* Below this the damage column and the button crowd the name off the row. */
@media (max-width: 560px) {
  .player { grid-template-columns: auto minmax(0, 1fr) auto; }
  .player .dmg { grid-column: 2; align-items: flex-start; }
  .player .skills-btn { grid-row: 1; grid-column: 3; }
}
.items { display: flex; gap: 3px; margin-top: 5px; flex-wrap: wrap; }

/* Arena augments. Named rather than icon-only: an augment icon means nothing
   unless you already know the augment, and the name is the whole point. */
.augs { display: flex; gap: 4px; margin-top: 5px; flex-wrap: wrap; }
.aug {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px 2px 3px; border-radius: 999px;
  border: 1px solid var(--line-soft); background: var(--s2);
  font-size: 11.5px; color: var(--muted); line-height: 1.5;
}
.aug img { width: 18px; height: 18px; border-radius: 50%; background: var(--s3); flex: none; }
/* An id we could not name. Dimmed so it reads as missing data rather than as
   an augment called "Augment 374". */
.aug.unknown { color: var(--dim); border-style: dashed; padding-left: 8px; }
.items img { width: 22px; height: 22px; border-radius: 5px; background: var(--s3); }

.skills { display: flex; gap: 4px; flex-wrap: wrap; margin: 8px 0; }
.skill {
  width: 22px; height: 22px; border-radius: 5px;
  background: var(--s3); border: 1px solid var(--line);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 600;
}
.skill[data-k="R"] { border-color: var(--gold); color: var(--gold); }

.panel { background: var(--s1); border: 1px solid var(--line-soft); border-radius: 12px; padding: 14px; }

/* ---------- states ---------- */

.warn {
  color: var(--gold);
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  background: color-mix(in srgb, var(--gold) 8%, transparent);
  border-radius: 10px;
  padding: 9px 13px;
  font-size: 13.5px;
}
.error { color: var(--loss); font-size: 14px; }

.empty { text-align: center; padding: 40px 20px; color: var(--muted); }
.empty h2 { text-transform: none; letter-spacing: 0; font-size: 17px; color: var(--text); margin-bottom: 6px; }

.skel {
  border-radius: 12px;
  background: linear-gradient(100deg, var(--s1) 30%, var(--s2) 50%, var(--s1) 70%) 0 0 / 220% 100%;
  animation: shimmer 1.25s infinite linear;
}
@keyframes shimmer { to { background-position: -220% 0; } }
@keyframes rise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }

/* **LU23.** This shortened every animation to a millisecond and left
   `animation-iteration-count` alone. The skeleton shimmer is the one infinite
   animation on the site, so for a reader who had asked the operating system to
   reduce motion it became a 1ms loop repeating forever: a flicker, which is
   worse than the slow sweep it replaced rather than better.
//
   Capping the count is what actually stops it. The stagger goes too: LU16 caps
   the per-row delay in app.js, but somebody who has asked for less motion
   should not be waiting on a stagger at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001s !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: .001s !important;
  }
  /* The shimmer was the only thing that made a skeleton read as "loading", so
     with it gone the block needs a tone of its own to still say so. */
  .skel { animation: none !important; background: var(--s2); }
}

/* ---------- post-game stats ---------- */

/* Dealt above taken, both scaled to the lobby's maximum, so the pair reads as
   a shape rather than two five-digit numbers. */
.dmg-line { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }
.dmg-line b { min-width: 52px; text-align: right; font-weight: 600; }
.dmg-line .dim { min-width: 52px; text-align: right; }
.dmg-line .bar { width: 70px; flex: none; max-width: none; }
.bar i.dealt { background: var(--gold); }
.bar i.taken { background: var(--loss); }
.dim { color: var(--dim); }
.dmg .dim[title] { font-size: 11.5px; }

/* The expander spans the whole row, below the columns. */
.stat-wrap { grid-column: 1 / -1; margin-top: 10px; border-top: 1px solid var(--line-soft); padding-top: 12px; }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); gap: 14px 22px; }
.stat-group { min-width: 0; }
.stat-h {
  font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--gold); margin-bottom: 6px;
}
.stat {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 12.5px; padding: 3px 0;
  border-bottom: 1px solid var(--line-soft);
}
.stat:last-child { border-bottom: 0; }
.stat span { color: var(--muted); min-width: 0; }
.stat b { font-weight: 500; white-space: nowrap; }

/* Build is a selection across the whole lobby, so gold means "this player's
   build is in the panel above". Stats is a per-row disclosure and several can
   be open at once, so it gets a neutral active state and a chevron instead, and
   the two controls don't compete for the same meaning. */
.stats-btn { display: inline-flex; align-items: center; gap: 6px; }
.chev {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform .18s var(--ease);
}
.stats-btn[aria-expanded="true"] { background: var(--s3); border-color: var(--line); color: var(--text); }
.stats-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }

/* The whole row selects a player, so it has to look clickable. The expanded
   stat panel inside it does not, because it is a reference table, not a target. */
.player { cursor: pointer; }
.player:not(.me):hover { background: var(--s2); }
.player .stat-wrap { cursor: default; }
.player .pname.off { cursor: help; }

/* ---------- team header, objectives, awards ---------- */

.team-h { flex-wrap: wrap; }
.team-stats { margin-left: auto; font-size: 12.5px; color: var(--muted); white-space: nowrap; }

.objs { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 10px; }
.obj {
  display: inline-flex; align-items: baseline; gap: 5px;
  background: var(--s1); border: 1px solid var(--line-soft);
  border-radius: 999px; padding: 4px 11px;
  font-size: 12px; color: var(--muted);
}
.obj b { color: var(--text); font-weight: 600; }
.obj i {
  font-style: normal; font-size: 10px; font-weight: 600;
  color: var(--gold); border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  border-radius: 4px; padding: 0 3px;
}

.bans { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; margin: 0 0 10px; }
.bans img { width: 22px; height: 22px; border-radius: 5px; background: var(--s3); filter: grayscale(1); opacity: .65; }
.bans img:hover { filter: none; opacity: 1; }

/* MVP is our own weighting, not a Riot statistic, and the title on the badge says
   so. There is no ACE badge: that word means a five-man wipe in League. */
.award {
  font-family: var(--display);
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
  border-radius: 5px; padding: 1px 5px; vertical-align: 1px; cursor: help;
}
.award.mvp { background: var(--gold); color: #16110a; }

/* A Riot ID is the identifier for the row, so it is the last thing that should be
   sacrificed for space, so it wraps rather than truncating. A name caps at 16
   characters plus a short tag (~161px), against 653px of column at 1280px wide,
   so this effectively never wraps on desktop and still shows in full when it
   does. overflow-wrap covers names with no break opportunity at all. */
/* .card-n sets nowrap + hidden for the compact side rows; both have to be
   undone here or the name still refuses to wrap. */
.pline { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; white-space: normal; overflow: visible; }
.pline .pname { overflow-wrap: anywhere; min-width: 0; }
.pline .award { flex: none; }

/* ---------- legal pages ---------- */

/* Standalone pages rather than hash routes: legal documents need real,
   linkable, crawlable URLs and must render without JavaScript. */
main.legal { width: min(760px, 100%); }
main.legal h1 { font-size: clamp(26px, 4vw, 34px); margin-bottom: 6px; }
main.legal h2 {
  font-size: 17px; text-transform: none; letter-spacing: 0;
  color: var(--text); font-family: var(--display);
  margin: 30px 0 8px;
}
main.legal p, main.legal li { color: var(--muted); line-height: 1.65; }
main.legal strong { color: var(--text); font-weight: 600; }
main.legal ul { padding-left: 20px; }
main.legal li { margin: 6px 0; }
main.legal a { color: var(--gold); }
main.legal code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; background: var(--s2); border: 1px solid var(--line-soft);
  border-radius: 5px; padding: 1px 5px; color: var(--text);
}
main.legal .warn { margin: 18px 0 6px; }
footer a { color: var(--muted); }
footer a:hover { color: var(--gold); }

/* Live rows link through to a profile, and a chosen skin replaces the flat
   champion icon with that skin's own art. */
a.player { text-decoration: none; color: inherit; }
.player .ico.skin { object-fit: cover; object-position: center 15%; }

/* ---------- landing ---------- */
/* Champion splash art is permitted under Riot's Legal Jibber Jabber policy.
   Their logos are not, so none appear anywhere on this site. */
.land {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--s1);
  overflow: hidden;
  margin-bottom: clamp(22px, 3vw, 34px);
}
.land-art { position: absolute; inset: 0; background-size: cover; background-position: 50% 18%; opacity: .62; }
.land::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--bg) 92%, transparent) 0%,
      color-mix(in srgb, var(--bg) 78%, transparent) 34%,
      color-mix(in srgb, var(--bg) 30%, transparent) 68%,
      transparent 100%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--bg) 25%, transparent) 0%,
      color-mix(in srgb, var(--bg) 45%, transparent) 55%,
      color-mix(in srgb, var(--s1) 92%, transparent) 100%);
}
.land-body {
  position: relative;
  z-index: 1;
  padding: clamp(30px, 5vw, 56px) clamp(18px, 2.4vw, 28px) clamp(22px, 3vw, 30px);
  max-width: 640px;
}
.land-h { font: 700 clamp(26px, 4.6vw, 40px)/1.08 var(--display); margin: 0; letter-spacing: .2px; }
.land-sub { margin: 12px 0 22px; color: var(--muted); font-size: clamp(14px, 1.6vw, 16px); max-width: 70ch; }
.land-form { margin: 0; }
.land-credit {
  position: absolute; right: 12px; bottom: 10px; z-index: 1;
  font-size: 11px; color: var(--dim); letter-spacing: .3px;
}

/* ---------- focus ---------- */

/* **LU22.** Inputs and selects replaced the focus ring with a 1px border
   colour change, and nothing else on the site defined a focus style at all, so
   keyboard focus was either invisible or a single pixel of hue. :focus-visible
   rather than :focus, so a mouse click does not draw a ring the clicker does
   not need, and one rule covers every control rather than each component
   inventing its own.

   The outline is drawn outside the box with an offset, so it never changes
   layout and never has to fit inside a border radius. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 6px;
}
/* #main takes focus after every client-side navigation (M17) and is not itself
   a control, so a ring on it would be noise on every single route change. The
   announcement is what tells a screen reader user where they are. */
#main:focus-visible, #main:focus { outline: none; }

/* ---------- focus ---------- */

/* **LU22.** Inputs and selects replaced the focus ring with a 1px border
   colour change, and nothing else on the site defined a focus style at all, so
   keyboard focus was either invisible or a single pixel of hue. :focus-visible
   rather than :focus, so a mouse click does not draw a ring the clicker does
   not need, and one rule covers every control rather than each component
   inventing its own.

   The outline is drawn outside the box with an offset, so it never changes
   layout and never has to fit inside a border radius. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 6px;
}
/* #main takes focus after every client-side navigation (M17) and is not itself
   a control, so a ring on it would be noise on every single route change. The
   announcement is what tells a screen reader user where they are. */
#main:focus-visible, #main:focus { outline: none; }

/* ---------- screen-reader-only text ---------- */

/* **H3.** Nothing in web/ defined one of these, which is why several controls
   had their meaning only in a title attribute: there was no way to add a label
   for assistive technology without also drawing it. Added here so there is.

   The clip-rect-plus-1px form rather than display:none or visibility:hidden,
   both of which remove the text from the accessibility tree as well as from
   the page. white-space: nowrap stops a long label collapsing into a
   1px-wide column of single letters, which some screen readers then read a
   character at a time. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- champion browser ---------- */
/* **M18.** The search field had no visible label and no format help. The
   label sits above the row rather than inside it, so the input and the region
   select keep their side-by-side layout. */
.land-form { flex-wrap: wrap; }
.land-label {
  flex: 1 0 100%;
  font: 600 12px var(--body); letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin-bottom: -2px;
}
.land-help { margin: 8px 0 0; font-size: 12.5px; color: var(--muted); }
#q-error[hidden] { display: none; }
#q-error { margin: 8px 0 0; font-size: 13px; }
#q[aria-invalid="true"] { border-color: var(--loss); }

#cfilter { display: block; width: 100%; max-width: 320px; margin: 0 0 14px; }
.cgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 8px; }
.ctile {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 12px 6px 10px;
  border: 1px solid transparent; border-radius: 12px;
  text-decoration: none; color: var(--text);
  transition: background .16s var(--ease), border-color .16s var(--ease), transform .16s var(--ease);
}
.ctile:hover { background: var(--s2); border-color: var(--line); transform: translateY(-2px); }
.ctile img { width: 52px; height: 52px; border-radius: 11px; background: var(--s3); }
.ctile span { font-size: 11.5px; line-height: 1.25; text-align: center; color: var(--muted); }
.ctile:hover span { color: var(--text); }
/* .ctile sets display:flex, which would otherwise beat the hidden attribute. */
.ctile[hidden] { display: none; }

/* The skin a player picked. Spectator is the only Riot endpoint that names it. */
.skin-n { color: var(--gold); }
.skin-n::before { content: "· "; color: var(--dim); }


/* ---------- mode tabs ---------- */

/* Shared by the patch page and the two reference pages. Lived in patch.html
   until the reference pages needed them too; one definition beats two that
   drift. The reference pages use links rather than buttons, because there
   each mode is a real page, so both element types are styled here. */
.modes { display: flex; gap: 6px; flex-wrap: wrap; }
/* Only the reference pages need the gap below the bar; the patch page sets
   its own spacing on the wrapper above it. */
.modes.ref { margin: 0 0 18px; }
.mode {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 16px; border-radius: 999px;
  border: 1px solid var(--line-soft); background: var(--s1); color: var(--muted);
  font: 500 13.5px var(--body); cursor: pointer; text-decoration: none;
  transition: color .2s var(--ease), border-color .2s var(--ease), background .2s var(--ease), transform .2s var(--ease), box-shadow .2s var(--ease);
}
.mode:hover { color: var(--text); border-color: var(--line); transform: translateY(-2px); }
.mode b { font: 600 11.5px var(--body); color: var(--dim); transition: color .2s var(--ease); }
/* Both selectors, because the two components express "this one" differently
   and both are correct for what they are: the reference pages are links, so
   they use aria-current="page" (M20), and the patch page has real buttons that
   swap a panel in place, so it keeps aria-selected. */
.mode[aria-selected="true"], .mode[aria-current="page"] {
  color: #16110a; background: linear-gradient(180deg, #dcb75c, var(--gold)); border-color: var(--gold);
  box-shadow: 0 4px 18px -6px color-mix(in srgb, var(--gold) 60%, transparent);
}
/* **M21.** 60% opacity over the gold gradient measures 3.52 at the dark end
   and 3.88 at the light end. Dropping the transparency uses the same colour
   the pill's own label uses and measures 7.87 to 9.81. The count reads as
   secondary from its size and weight, which is enough. */
.mode[aria-selected="true"] b, .mode[aria-current="page"] b { color: #16110a; }

/* The player hub's pager, which is the same pill row with two differences.
   It carries nineteen-odd numbers rather than five words, so the pills are
   narrower and the row is centred rather than left-aligned; and the current
   page is a <span> rather than an <a>, because a link to the page you are
   already on is a link that does nothing. That span still needs .mode's
   padding and shape or the row would jump where the current page sits. */
.hub-pager { justify-content: center; margin: 18px 0; }
.hub-pager .mode { padding: 7px 13px; cursor: default; }
.hub-pager a.mode { cursor: pointer; }
span.mode { user-select: none; }

/* ---------- reference tiles ---------- */

/* Items and augments, on the champion grid.

   **H3.** These were divs carrying the whole description in a title
   attribute, on a page that told the reader to hover. They are <details> now:
   <summary> is focusable and operable by keyboard with no script, which
   matters because these pages are deliberately not hydrated and the CSP
   forbids inline script.

   **The open tile becomes its own full-width grid row.** Two other approaches
   were tried and measured first, which is why this one is here.

   Expanding the panel inside an 88px column makes the whole row as tall as the
   panel and shoves the rest of the page down. Floating it absolutely fixes
   that and introduces a worse bug: the panel is centred on its tile, so for
   the rightmost column it runs off the side. Measured at a 1280px viewport,
   the worst tile overflowed by **68px and made the document scroll
   sideways** -- and CSS cannot clamp it, because centring on the tile has no
   way to ask how far the viewport edge is without script.

   `grid-column: 1 / -1` sidesteps both. The open tile spans the row, so the
   panel has the full grid width to sit in and can never overflow; the layout
   stays entirely in flow with no positioning arithmetic; and the reflow is one
   tile moving to its own line rather than every tile below it dropping by the
   height of a panel. */
.ctile.ref { cursor: pointer; }

/* Deliberately no `display` on this summary: it is a UA-implemented control,
   several engines have shipped bugs when it is restyled, and .ref-face below
   can carry the flex column instead for the price of one span. A precaution,
   not a fix for something observed here.

   The marker is hidden without touching display. list-style plus the
   -webkit- pseudo-element, because Safari honours only the second. */
.ctile.ref > summary { list-style: none; cursor: pointer; }
.ctile.ref > summary::-webkit-details-marker { display: none; }
.ctile.ref > summary::marker { content: ""; }
.ctile.ref > summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 10px; }
.ref-face { display: flex; flex-direction: column; align-items: center; gap: 7px; }
.ctile.ref em {
  font: 500 10.5px var(--body); font-style: normal; color: var(--dim);
  text-align: center; letter-spacing: .01em;
}
.ctile.ref:hover em, .ctile.ref[open] em { color: var(--muted); }
.ctile.ref[open] {
  grid-column: 1 / -1;
  flex-direction: row; align-items: flex-start; gap: 14px;
  background: var(--s2); border-color: var(--line);
  padding: 12px 14px;
}
/* The face keeps its own column layout but stops stretching once the tile is
   a full-width row. */
.ctile.ref[open] > summary { flex: none; }
.ctile.ref[open] .ref-face { width: 76px; }

.ref-detail {
  flex: 1; min-width: 0;
  text-align: left;
}
.ref-detail b { display: block; color: var(--text); font: 600 13px var(--body); margin-bottom: 6px; }
.ref-detail p {
  margin: 0 0 5px; font-size: 12.5px; line-height: 1.45; color: var(--muted);
}
.ref-detail p:last-child { margin-bottom: 0; }
/* The prose description, separated from the stat lines above it the way the
   blank line in the old title attribute did. */
.ref-detail .ref-blurb {
  margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--line-soft);
  color: var(--text);
}
@media (prefers-reduced-motion: reduce) { .mode { transition: none; } }

/* ---------- service banner ---------- */

/* Riot's own incidents and maintenance, above the router's output so it shows
   on every view. Severity carries the colour; the tag carries the words, so it
   still reads without colour. */
#banner { display: grid; gap: 8px; margin: 0 0 18px; }
.banner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: 12px;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--muted);
  border-radius: var(--r);
  background: var(--s1);
  font-size: 13px;
}
.banner.info { border-left-color: var(--muted); }
.banner.warning { border-left-color: var(--gold); }
.banner.critical { border-left-color: var(--loss); }
.banner-tag {
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--s3);
  color: var(--muted);
  white-space: nowrap;
}
.banner.warning .banner-tag { background: color-mix(in srgb, var(--gold) 18%, var(--s3)); color: var(--gold); }
.banner.critical .banner-tag { background: color-mix(in srgb, var(--loss) 18%, var(--s3)); color: var(--loss); }
.banner-body { display: grid; gap: 3px; min-width: 0; }
.banner-body b { color: var(--text); font-weight: 600; }
.banner-body span { color: var(--muted); line-height: 1.5; }
/* **M22.** This was 18px of glyph in 2px of padding, so about 14 by 18
   device-independent pixels: under the 24 by 24 minimum outright, and the
   hardest control on the site to hit on a phone. Sized by the box rather than
   the font so the × keeps its optical size. */
.banner-x {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; min-height: 24px;
  background: none; border: 0; color: var(--dim);
  font-size: 18px; line-height: 1; padding: 0; cursor: pointer;
  border-radius: 8px;
}
.banner-x:hover { color: var(--text); background: var(--s3); }

/* ---------- free rotation ---------- */

/* Its own box above the full champion list. The pool is the point, and it is
   invisible highlighted inside 170 tiles. */
.rot {
  margin: 14px 0 22px;
  padding: 14px 16px 16px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r);
  background: var(--s1);
}
.rot-h { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.rot-h h3 {
  margin: 0; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); font-weight: 600;
}
.rot .cgrid { grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); }
.rot .ctile { background: var(--s2); }
.rot-note { margin: 12px 0 0; font-size: 12px; color: var(--dim); }
/* Filtering to a name nobody in the rotation matches should take the whole box
   away, not leave its heading over an empty grid. */
.rot[data-filtered="empty"] { display: none; }

/* ---------- ladder ---------- */

.lb-controls { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 14px; }
.lb-controls select { min-width: 110px; }
.lb-row { grid-template-columns: auto minmax(0, 1fr) auto; }
.lb-rank {
  font-variant-numeric: tabular-nums;
  font-size: 12.5px; color: var(--dim);
  min-width: 58px;
}
.lb-who { display: grid; gap: 2px; min-width: 0; }
.lb-name { color: var(--text); font-weight: 600; font-size: 14px; text-decoration: none; }
a.lb-name:hover { color: var(--gold); }
.lb-lp { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.lb-pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 18px; }
.lb-pager button:disabled { opacity: .4; cursor: default; }

/* ---------- lore ---------- */

.lore-hero .hero-body { gap: 12px; }
.lore-tags { gap: 6px; margin-top: 12px; }
.lore-tags .pill { cursor: default; }
/* A reading measure, which is why it needs a neighbour: on its own in a
   1080px column it looked like a layout that had failed rather than a line
   length that was chosen. */
.lore-text { max-width: 70ch; line-height: 1.75; color: var(--muted); font-size: 14px; margin: 0; }

.glance-facts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.glance-facts .pill { cursor: default; }
.glance {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 10px 14px;
  margin: 0;
}
.glance dt { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.glance dd { margin: 0; }
.pips { display: flex; gap: 4px; }
/* Filled and empty read apart without colour alone: the empty ones are
   hollow, the filled ones solid. */
.pips i {
  width: 26px; height: 6px; border-radius: 3px;
  background: var(--s3);
  border: 1px solid var(--line-soft);
}
.pips i.on { background: var(--gold); border-color: var(--gold); }
.lore-ability { align-items: start; }
.lore-slot {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--s3); color: var(--gold);
  font-size: 12px; font-weight: 700;
}
.lore-ability .card-s { line-height: 1.6; white-space: normal; }
.lore-nums { color: var(--dim); margin-top: 4px; font-variant-numeric: tabular-nums; }
.lore-tips { margin: 0; padding-left: 18px; color: var(--muted); font-size: 13px; line-height: 1.7; }
.lore-tips li { margin-bottom: 7px; }
/* Said once, quietly: 26 of 173 champions have no tips and the page should
   not look like it lost them. */
.tipless { margin-top: 32px; max-width: 70ch; font-size: 13px; }

/* ---------- clash ---------- */

.clash { display: block; padding: 14px 16px; }
.clash-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.clash-days { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-top: 12px; }
.clash-day {
  display: grid; gap: 3px;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: 11px;
  background: var(--s2);
  font-size: 12.5px;
}
.clash-day b { color: var(--text); font-size: 12px; letter-spacing: .04em; }
.clash-day span { color: var(--muted); }
.clash-day.off { opacity: .55; }
.clash-day.off b { color: var(--loss); }

/* A skin is its art. Portrait loading art in a grid is how the collection
   itself presents them, and it is the one thing a list of names cannot show. */
/* Back to a dense grid. The chroma preview is a floating panel now, so the
   card no longer has to be wide enough to contain it. */
.skins { display: grid; grid-template-columns: repeat(auto-fill, minmax(152px, 1fr)); gap: 14px; }
.skin {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line-soft);
  border-radius: 13px;
  background: var(--s1);
  overflow: hidden;
  transition: transform .18s var(--ease), border-color .18s var(--ease);
}
.skin:hover { transform: translateY(-3px); border-color: var(--line); }
.skin-art {
  position: relative;
  aspect-ratio: 308 / 560;
  background: var(--s3);
}
/* Direct child only: the chroma preview is an img inside .skin-art too, and
   this rule height: 100% was stretching it -- max-height never overrides it. */
.skin-art > img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Art Data Dragon does not have yet: an empty plate, not a broken image. */
.skin-art.no-art > img { display: none; }
.skin-art.no-art {
  background:
    repeating-linear-gradient(135deg, var(--s3) 0 8px, var(--s2) 8px 16px);
}
/* The art runs under the name, so the badge needs its own ground to sit on. */
.skin-badges { position: absolute; top: 7px; right: 7px; display: grid; gap: 4px; justify-items: end; }
.skin-badge {
  font-size: 9.5px; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(6px);
  color: var(--muted);
  white-space: nowrap;
}
/* Riot's own ladder: Epic, then Legendary, Mythic, Transcendent. */
.skin-badge.epic { color: #c3a6ff; }
.skin-badge.legendary { color: var(--gold); }
.skin-badge.mythic { color: #ff9aa5; }
.skin-badge.transcendent { color: var(--win); }
.skin-badge.legacy { color: var(--dim); }

.skin-body { padding: 9px 11px 11px; display: grid; gap: 7px; align-content: start; }
.skin-name { font-size: 12.5px; line-height: 1.35; color: var(--text); }
.chroma-dots { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }

/* **M22.** 15 by 15, and a <span>, so it failed the 24px minimum and was
   unreachable by keyboard at the same time. It is a <button> in app.js now,
   and the target is 24px while the coloured dot stays 15px: growing the dot
   itself would change the design, and it is the *target* the guideline is
   about. The swatch is drawn with a pseudo-element so the button can be
   transparent and still show the chroma colour. */
.chroma-dot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  background: none; border: 0; border-radius: 50%;
  flex: none; cursor: pointer;
}
.chroma-dot::after {
  content: ""; display: block;
  width: 15px; height: 15px; border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .26);
  background: var(--chroma, var(--s3));
}
.chroma-dot:hover::after, .chroma-dot:focus-visible::after {
  border-color: rgba(255, 255, 255, .6);
}
.chroma-count { font-size: 10.5px; color: var(--dim); margin-left: 2px; }

/* The chroma preview, floating free of the grid so it can be its own size.
   Positioned by web/app.js: beside the card, flipped when there is no room,
   and clamped inside the viewport. */
.chroma-panel {
  position: fixed;
  z-index: 30;
  /* Roughly the render's own 270px, and near full width on a phone, where the
     clamp then centres it. */
  width: min(292px, calc(100vw - 20px));
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--s2);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
  display: grid;
  gap: 8px;
  justify-items: center;
  /* It follows the pointer's card; it must never intercept the pointer. */
  pointer-events: none;
}
/* An author display beats the UA rule behind [hidden], so it needs saying. */
.chroma-panel[hidden] { display: none; }
.chroma-panel img { width: 100%; height: auto; display: block; border-radius: 8px; }
.chroma-panel span { font-size: 12.5px; line-height: 1.35; color: var(--text); text-align: center; }

@media (prefers-reduced-motion: reduce) { .skin { transition: none; } }

/* ---------- charts ---------- */

/* Inline SVG rather than a charting library: script-src is 'self', and none of
   these earn the one CDN exception that would need. */
.chart { margin: 14px 0 0; }
.chart figcaption { font-size: 12px; color: var(--dim); margin-bottom: 8px; }
.chart svg { width: 100%; height: auto; display: block; overflow: visible; }
.chart .ax { font-size: 10px; fill: var(--dim); font-family: var(--body); }
.chart .ax-line { stroke: var(--line); stroke-width: 1; }

.gold-line { fill: none; stroke: var(--gold); stroke-width: 2; stroke-linejoin: round; }
/* The fill says who is ahead; the line alone would need reading against the
   axis to tell. */
.gold-area { stroke: none; opacity: .22; }
.gold-area.blue { fill: #4a83d0; }
.gold-area.red { fill: var(--loss); }

.chart.map { max-width: 320px; }
.map-bg { fill: var(--s2); stroke: var(--line-soft); stroke-width: .5; }
/* Only drawn when the map image is unavailable, as a hint at mid lane. */
.map-diag { stroke: var(--line-soft); stroke-width: .5; }

/* Knocked well back. The Rift minimap is bright and busy and the dots are the
   point of the figure; at full strength the blue ones disappear into the blue
   half of the map. Dark enough to read as a background, legible enough to tell
   the lanes apart. */
.map-img { opacity: .38; filter: saturate(.6) brightness(.85); }

/* A dark outline on every dot, so a kill reads against jungle, river or base
   alike rather than only against the flat panel it used to sit on. */
.kill { stroke: #05080f; stroke-width: .5; paint-order: stroke; }
.kill.blue { fill: #7db8ff; }
.kill.red { fill: #ff6b7a; }

.objs-line { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.obj {
  font-size: 11.5px; padding: 3px 9px; border-radius: 999px;
  background: var(--s2); border: 1px solid var(--line-soft); color: var(--muted);
}
.obj b { color: var(--text); font-variant-numeric: tabular-nums; }
.obj.blue { border-color: color-mix(in srgb, #4a83d0 40%, var(--line-soft)); }
.obj.red { border-color: color-mix(in srgb, var(--loss) 35%, var(--line-soft)); }

.rank-line { fill: none; stroke: var(--win); stroke-width: 2; stroke-linejoin: round; }
.rank-area { fill: var(--win); opacity: .14; stroke: none; }
.rank-dot { fill: var(--win); }
#rankgraph:not(:empty) {
  position: relative;
  background: var(--s1);
  border-top: 1px solid var(--line-soft);
  padding: 14px clamp(18px, 2.4vw, 26px);
}
.rank-note { margin: 0; font-size: 12.5px; max-width: 70ch; color: var(--muted); }

/* ---------- live scouting ---------- */

/* The reason to open a live game page: not who is in it, but who they are. */
.scout { display: grid; gap: 1px; justify-items: end; text-align: right; white-space: nowrap; }
.scout span { font-size: 11px; color: var(--dim); }
.scout-rank { font-size: 12px; font-weight: 600; color: var(--muted); }
.scout-rank.t-iron, .scout-rank.t-bronze { color: #9c7a5a; }
.scout-rank.t-silver { color: #9fb0c4; }
.scout-rank.t-gold { color: var(--gold); }
.scout-rank.t-platinum, .scout-rank.t-emerald { color: var(--win); }
.scout-rank.t-diamond { color: #7fb4ff; }
.scout-rank.t-master, .scout-rank.t-grandmaster, .scout-rank.t-challenger { color: #d08bff; }
.scout-rank.t-none { color: var(--dim); font-weight: 500; }
@media (max-width: 700px) { .scout span { display: none; } }

/* ---------- touch targets ---------- */

/* **M22.** Keyed to the pointer, not to the viewport width. A layout
   breakpoint asks "is the window narrow", which is the wrong question: a
   1024px tablet is touch and a 380px desktop window is not. `pointer: coarse`
   asks whether the primary input is imprecise, which is the actual reason
   44px exists.

   Scoped to real controls. An earlier reading of this would have grown every
   .pill, including the ones that are not buttons, which is how a touch fix
   turns into a layout change. */
@media (pointer: coarse) {
  .banner-x, .chroma-dot { min-width: 44px; min-height: 44px; }
  button.pill, .mode, .skills-btn { min-height: 44px; }
}
