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

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

/* The palette has no light half, so the UA is told once which one it draws
   its own chrome for. On Windows a scrollbar is drawn rather than overlaid,
   and a page that stays silent gets the light track over the dark ground. */
html {
    color-scheme: dark;
    min-height: 100%;
    -webkit-text-size-adjust: 100%;
}

body,
h1,
h2,
h3,
h4,
p,
ul,
ol,
li,
figure,
dl,
dd {
    margin: 0;
    padding: 0;
}

ul,
ol {
    list-style: none;
}

img,
svg {
    display: block;
    max-width: 100%;
}

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
    background: none;
    border: 0;
}

button {
    cursor: pointer;
}

a {
    color: inherit;
    cursor: pointer;
    text-decoration: none;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

th {
    text-align: left;
    font-weight: inherit;
}

summary {
    cursor: pointer;
    list-style: none;
}

summary::-webkit-details-marker {
    display: none;
}

summary::marker {
    content: "";
}

hr {
    border: 0;
}

:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

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

:root {
    --color-bg: #0b0e13;
    --color-surface: #14181f;
    --color-surface-raised: #1b2029;
    /* Under the surface rather than over it: a card that holds a list of its
       own, whose header strip is the surface above this ground. */
    --color-surface-sunken: #10141a;
    --color-border: #2a3039;
    /* Hairlines and tracks the page draws over its own background rather than
       over a surface: a tint of the text, so they hold at any depth. */
    --color-border-faint: rgb(255 255 255 / 9%);
    --color-track: rgb(255 255 255 / 6%);
    --color-border-strong: #3d4451;
    --color-text: #e3e8ef;
    --color-text-soft: #b3bbc7;
    --color-muted: #868f9d;
    --color-muted-dim: #6a707a;
    --color-accent: #5b8def;
    --color-danger: #e5646a;

    /* Spell text, the one place the site borrows the game's own colour. The
       tooltip prints four kinds of line and this is what separates them, and
       the ring around a talent the ladder agrees on is the same gold: what the
       tooltip says in words, the tile says in colour. */
    --color-spell: #e0b551;
    /* The game's other tooltip colour: what an item grants beyond its primary
       stats — the secondaries, the `Equip:` line and the set bonuses. */
    --color-bonus: #46d97a;

    --font-sans: -apple-system, blinkmacsystemfont, "Segoe UI", system-ui,
        "Helvetica Neue", arial, sans-serif;

    /*
     * Everything that should grow when the reader raises their browser font
     * size is in `rem`: type, spacing, and any box sized to hold text. Only
     * borders, outlines and hairlines stay in `px` — a 1px rule is a rule, and
     * scaling it just makes it blurry.
     */
    --size-2xs: 0.625rem;
    --size-xs: 0.6875rem;
    --size-sm: 0.75rem;
    --size-base: 0.8125rem;
    --size-md: 0.875rem;
    --size-lg: 1rem;
    --size-xl: 1.25rem;
    --size-2xl: 1.75rem;

    --space-1: 0.25rem;
    --space-2: 0.375rem;
    --space-3: 0.5rem;
    --space-4: 0.75rem;
    --space-5: 1rem;
    --space-6: 1.5rem;
    --space-7: 2rem;
    --space-8: 3rem;

    --radius: 0.25rem;
    --sidebar-width: 12.5rem;
    --content-max: 45rem;
    --gutter-min: 9.375rem;
    --num-column: 3.875rem;
    --tap-target: 2.75rem;

    /* A talent's tooltip, and the narrower one a hero tree's four columns leave
       room for. Both shrink again at the narrow breakpoint. */
    --tip-width: 20rem;
    /* The count column of a tally row, sized to the widest thing in it: a
       `CRAFT` chip at 50px. Every pixel it does not need is one the item name
       has to truncate away. */
    --tally-meta: 52px;
    --gear-tip-width: 22rem;
    /* Fixed, so the gear panel's bands read as one column of labels rather than
       as four blocks each indented to its own longest word. Each band is its
       own grid, so `max-content` would align none of them — this is the width
       of the longest label there is, `Item level`, and nothing more — so it is
       set per language, below. */
    --gear-tip-label: 3.75rem;
    --tip-width-hero: 16rem;

    /* Canonical in game, and how players read a leaderboard. */
    --color-class-deathknight: #c41e3a;
    --color-class-demonhunter: #a330c9;
    --color-class-druid: #ff7c0a;
    --color-class-evoker: #33937f;
    --color-class-hunter: #aad372;
    --color-class-mage: #3fc7eb;
    --color-class-monk: #00ff98;
    --color-class-paladin: #f48cba;
    --color-class-priest: #ffffff;
    --color-class-rogue: #fff468;
    --color-class-shaman: #0070dd;
    --color-class-warlock: #8788ee;
    --color-class-warrior: #c69b6d;

    /*
     * Invented, unlike the class colours: the game gives a race no colour, and
     * the portrait art is nearly all skin, so sampling it yields 26 browns.
     * These are spread by hue and lightness against the pairs that actually
     * meet on a bar. 26 categories cannot all be told apart, so the colour
     * separates neighbours and the portrait names the race — never the colour
     * alone. `scripts/race-colors.py` regenerates them.
     */
    --color-race-blood-elf: #9d3b60;
    --color-race-dark-iron-dwarf: #a74d82;
    --color-race-dracthyr: #006f9f;
    --color-race-draenei: #009f78;
    --color-race-dwarf: #b1512e;
    --color-race-earthen: #a06200;
    --color-race-gnome: #c3608c;
    --color-race-goblin: #7fa039;
    --color-race-haranir: #844592;
    --color-race-highmountain-tauren: #b24b5a;
    --color-race-human: #5a93e4;
    --color-race-kul-tiran: #00abac;
    --color-race-lightforged-draenei: #7f5e00;
    --color-race-maghar-orc: #c28413;
    --color-race-mechagnome: #0093c7;
    --color-race-night-elf: #a56bbd;
    --color-race-nightborne: #465caf;
    --color-race-orc: #418432;
    --color-race-pandaren: #9a8700;
    --color-race-tauren: #d37640;
    --color-race-troll: #007a53;
    --color-race-undead: #777700;
    --color-race-void-elf: #7f78d3;
    --color-race-vulpera: #d86d72;
    --color-race-worgen: #a23d37;
    --color-race-zandalari-troll: #00897f;

    /*
     * Item quality, the one colour gear carries in game — the game's own eight
     * values, unaltered. Rare (3.70:1) and epic (3.64:1) sit under 4.5:1 on the
     * card, and that is accepted: the colour repeats what the row already says
     * in words, and a recoloured purple stops reading as epic to a player.
     */
    --color-quality-poor: #9d9d9d;
    --color-quality-common: #ffffff;
    --color-quality-uncommon: #1eff00;
    --color-quality-rare: #0070dd;
    --color-quality-epic: #a335ee;
    --color-quality-legendary: #ff8000;
    --color-quality-artifact: #e6cc80;
    --color-quality-heirloom: #00ccff;

    /* Wide enough for `Обновление`, which is the longest of the four. */
    --about-label: 7rem;

    /* The only motion on the site. `--ease` starts fast and settles. */
    --motion-fast: 140ms;
    --ease: cubic-bezier(0.2, 0, 0.2, 1);
}

/* The Russian labels are longer, and `Ограничение` is one word with nowhere
   to wrap — a track sized for `Item level` is overrun rather than reflowed. */
html[lang="ru"] {
    --gear-tip-label: 5.75rem;
}


/*
 * Breakpoints. These cannot be custom properties: a media query condition is
 * evaluated before the cascade runs, so `@media (max-width: var(--x))` never
 * matches. The names below are the contract instead — every `@media` in this
 * stylesheet is labelled with one, and there are only three.
 *
 *   desktop   80em      (1280px)  the sidebar's outer gutter collapses
 *   tablet    53.75em    (860px)  the sidebar becomes a row of chips
 *   narrow    44em       (704px)  padding tightens, thin columns drop, the
 *                                 standings row stacks — the width below which
 *                                 its seven columns leave the name unreadable
 *
 * `em` rather than `px` so the layout responds to a raised default font size
 * too; inside a media query it resolves against the browser's initial 16px.
 */

/*
 * `--class-color` is what every class-tinted rule reads. Setting it on a block
 * modifier rather than inline keeps the tint out of the generated HTML.
 * `.spec--{class}` wraps a whole specialization page, so the header, the hero
 * talent chips and every icon ring below them read the same tint.
 */
.class-card--deathknight,
.sidebar-nav__link--deathknight,
.spec--deathknight {
    --class-color: var(--color-class-deathknight);
}

.class-card--demonhunter,
.sidebar-nav__link--demonhunter,
.spec--demonhunter {
    --class-color: var(--color-class-demonhunter);
}

.class-card--druid,
.sidebar-nav__link--druid,
.spec--druid {
    --class-color: var(--color-class-druid);
}

.class-card--evoker,
.sidebar-nav__link--evoker,
.spec--evoker {
    --class-color: var(--color-class-evoker);
}

.class-card--hunter,
.sidebar-nav__link--hunter,
.spec--hunter {
    --class-color: var(--color-class-hunter);
}

.class-card--mage,
.sidebar-nav__link--mage,
.spec--mage {
    --class-color: var(--color-class-mage);
}

.class-card--monk,
.sidebar-nav__link--monk,
.spec--monk {
    --class-color: var(--color-class-monk);
}

.class-card--paladin,
.sidebar-nav__link--paladin,
.spec--paladin {
    --class-color: var(--color-class-paladin);
}

.class-card--priest,
.sidebar-nav__link--priest,
.spec--priest {
    --class-color: var(--color-class-priest);
}

.class-card--rogue,
.sidebar-nav__link--rogue,
.spec--rogue {
    --class-color: var(--color-class-rogue);
}

.class-card--shaman,
.sidebar-nav__link--shaman,
.spec--shaman {
    --class-color: var(--color-class-shaman);
}

.class-card--warlock,
.sidebar-nav__link--warlock,
.spec--warlock {
    --class-color: var(--color-class-warlock);
}

.class-card--warrior,
.sidebar-nav__link--warrior,
.spec--warrior {
    --class-color: var(--color-class-warrior);
}

/*
 * `--race-color` is the same idea one block down: the race is data, so the
 * segment carries a modifier and the value stays out of the HTML. Only the
 * count is inline, since a share has no fixed set of values to name.
 */
.race-bar__seg--blood-elf,
.race-bar__legend-item--blood-elf {
    --race-color: var(--color-race-blood-elf);
}

.race-bar__seg--dark-iron-dwarf,
.race-bar__legend-item--dark-iron-dwarf {
    --race-color: var(--color-race-dark-iron-dwarf);
}

.race-bar__seg--dracthyr,
.race-bar__legend-item--dracthyr {
    --race-color: var(--color-race-dracthyr);
}

.race-bar__seg--draenei,
.race-bar__legend-item--draenei {
    --race-color: var(--color-race-draenei);
}

.race-bar__seg--dwarf,
.race-bar__legend-item--dwarf {
    --race-color: var(--color-race-dwarf);
}

.race-bar__seg--earthen,
.race-bar__legend-item--earthen {
    --race-color: var(--color-race-earthen);
}

.race-bar__seg--gnome,
.race-bar__legend-item--gnome {
    --race-color: var(--color-race-gnome);
}

.race-bar__seg--goblin,
.race-bar__legend-item--goblin {
    --race-color: var(--color-race-goblin);
}

.race-bar__seg--haranir,
.race-bar__legend-item--haranir {
    --race-color: var(--color-race-haranir);
}

.race-bar__seg--highmountain-tauren,
.race-bar__legend-item--highmountain-tauren {
    --race-color: var(--color-race-highmountain-tauren);
}

.race-bar__seg--human,
.race-bar__legend-item--human {
    --race-color: var(--color-race-human);
}

.race-bar__seg--kul-tiran,
.race-bar__legend-item--kul-tiran {
    --race-color: var(--color-race-kul-tiran);
}

.race-bar__seg--lightforged-draenei,
.race-bar__legend-item--lightforged-draenei {
    --race-color: var(--color-race-lightforged-draenei);
}

.race-bar__seg--maghar-orc,
.race-bar__legend-item--maghar-orc {
    --race-color: var(--color-race-maghar-orc);
}

.race-bar__seg--mechagnome,
.race-bar__legend-item--mechagnome {
    --race-color: var(--color-race-mechagnome);
}

.race-bar__seg--night-elf,
.race-bar__legend-item--night-elf {
    --race-color: var(--color-race-night-elf);
}

.race-bar__seg--nightborne,
.race-bar__legend-item--nightborne {
    --race-color: var(--color-race-nightborne);
}

.race-bar__seg--orc,
.race-bar__legend-item--orc {
    --race-color: var(--color-race-orc);
}

.race-bar__seg--pandaren,
.race-bar__legend-item--pandaren {
    --race-color: var(--color-race-pandaren);
}

.race-bar__seg--tauren,
.race-bar__legend-item--tauren {
    --race-color: var(--color-race-tauren);
}

.race-bar__seg--troll,
.race-bar__legend-item--troll {
    --race-color: var(--color-race-troll);
}

.race-bar__seg--undead,
.race-bar__legend-item--undead {
    --race-color: var(--color-race-undead);
}

.race-bar__seg--void-elf,
.race-bar__legend-item--void-elf {
    --race-color: var(--color-race-void-elf);
}

.race-bar__seg--vulpera,
.race-bar__legend-item--vulpera {
    --race-color: var(--color-race-vulpera);
}

.race-bar__seg--worgen,
.race-bar__legend-item--worgen {
    --race-color: var(--color-race-worgen);
}

.race-bar__seg--zandalari-troll,
.race-bar__legend-item--zandalari-troll {
    --race-color: var(--color-race-zandalari-troll);
}

/*
 * `--quality-color` is the third of these: an item's quality is data, so the
 * element carries a modifier and the value stays out of the HTML. One class
 * rather than one per section — a gear row, a gem and the pieces inside an
 * embellishment's panel all tint the same way.
 */
.quality--poor {
    --quality-color: var(--color-quality-poor);
}

.quality--common {
    --quality-color: var(--color-quality-common);
}

.quality--uncommon {
    --quality-color: var(--color-quality-uncommon);
}

.quality--rare {
    --quality-color: var(--color-quality-rare);
}

.quality--epic {
    --quality-color: var(--color-quality-epic);
}

.quality--legendary {
    --quality-color: var(--color-quality-legendary);
}

.quality--artifact {
    --quality-color: var(--color-quality-artifact);
}

.quality--heirloom {
    --quality-color: var(--color-quality-heirloom);
}

/* ------------------------------------------------------------------- base */

/*
 * Nothing on the page is a text field, so the I-beam only ever says "you could
 * select this" — which is noise on a page made of numbers. Anything clickable
 * sets its own cursor back in the reset.
 */
body {
    cursor: default;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-sans);
    font-size: var(--size-base);
    line-height: 1.45;
}

a:hover {
    color: var(--color-accent);
}

/* The note every page ends on, so a page does not stop dead on its last
   section. Its own ground, set back from the sections above it. */
.about {
    margin-top: var(--space-7);
    padding: var(--space-6);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
}

.about__title {
    color: var(--color-text);
    font-size: var(--size-lg);
    font-weight: 600;
}

.about__lede {
    margin-top: var(--space-3);
    max-width: 60ch;
    color: var(--color-muted);
}

/* The same label column the gear tooltip uses, at the page's own size. */
.about__facts {
    display: grid;
    grid-template-columns: var(--about-label) minmax(0, 1fr);
    gap: var(--space-3) var(--space-5);
    margin-top: var(--space-5);
    padding-top: var(--space-5);
    border-top: 1px solid var(--color-border);
}

.about__label {
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.about__value {
    max-width: 72ch;
    color: var(--color-text);
    font-size: var(--size-sm);
}

/* mobile */
@media (max-width: 44em) {
    /* A 7rem label column leaves the value a third of the screen. */
    .about__facts {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-1) 0;
    }

    .about__label:not(:first-of-type) {
        margin-top: var(--space-3);
    }
}

/* ----------------------------------------------------------------- layout */

.site-header {
    border-bottom: 1px solid var(--color-border);
    background: var(--color-surface);
}

.site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2) var(--space-5);
    padding: var(--space-4) var(--space-5) var(--space-4) var(--space-7);
}

.site-header__tagline {
    color: var(--color-muted);
    font-size: var(--size-sm);
}

.site-header__tagline::before {
    content: "/";
    margin-right: var(--space-3);
    color: var(--color-border-strong);
}

/* Character search lands here; until then it only pushes the meta right. */
.site-header__search {
    flex: 1 1 6.25rem;
    min-width: 0;
}

.site-header__meta {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    color: var(--color-muted);
    font-size: var(--size-sm);
    white-space: nowrap;
}

.site-header__season {
    padding: 1px var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.site-header__langs {
    display: flex;
    align-items: center;
    gap: var(--space-1);
}

.site-header__lang {
    padding: 1px var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    color: var(--color-muted);
    font-size: var(--size-xs);
    letter-spacing: 0.04em;
    text-decoration: none;
}

.site-header__lang:hover {
    color: var(--color-text);
}

.site-header__lang--current {
    border-color: var(--color-accent);
    color: var(--color-text);
    font-weight: 600;
}

.brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3125rem;
    font-size: var(--size-2xl);
    font-weight: 700;
    letter-spacing: -0.02em;
}

.brand__word {
    line-height: 1;
}

.brand__rule {
    width: 100%;
    height: 0.25rem;
    background: var(--color-accent);
}

.page {
    display: grid;
    grid-template-columns:
        minmax(calc(var(--sidebar-width) + var(--gutter-min)), 1fr)
        minmax(0, var(--content-max))
        minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
    flex: 1 0 auto;
    /* The footer starts below the fold on every page, so it never appears to
       move as one page's content grows past another's. */
    min-height: 100vh;
    /* Deeper at the top than at the sides: the page's title is the first thing
       under the header's rule, and at an even gutter it reads as part of it. */
    padding: var(--space-7) var(--space-5) var(--space-5);
}

/* A spec page names four things here — bracket, sibling specs, hero trees and
   all thirteen classes — which overruns 1080px. Pinned without a height, the
   column would keep its tail below the fold until the footer scrolled it up,
   so the last classes could not be clicked at all. It scrolls itself instead. */
.page__sidebar {
    position: sticky;
    top: var(--space-7);
    max-height: calc(100vh - var(--space-7) - var(--space-5));
    overflow-y: auto;
    overscroll-behavior: contain;
    justify-self: end;
    width: var(--sidebar-width);
    scrollbar-width: thin;
    /* `thin` alone leaves the thumb the UA's own grey. This is the one
       scrollbar that sits inside the layout rather than at its edge, so it
       is tinted to the border it runs beside. */
    scrollbar-color: var(--color-border-strong) transparent;
}

.page__content {
    width: 100%;
    max-width: var(--content-max);
    min-width: 0;
    margin: 0 auto;
}

.sidebar__section {
    margin-bottom: var(--space-6);
}

.sidebar__label {
    display: block;
    margin-bottom: var(--space-3);
    color: var(--color-muted);
    font-size: var(--size-xs);
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.bracket-list__item + .bracket-list__item {
    margin-top: 1px;
}

.bracket-list__link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-left: 2px solid transparent;
    font-size: var(--size-md);
}

.bracket-list__link:hover {
    background: var(--color-surface);
}

.bracket-list__link--active {
    border-left-color: var(--color-accent);
    background: var(--color-surface);
    color: var(--color-text);
    font-weight: 600;
}

.bracket-list__link--disabled {
    color: var(--color-muted);
    cursor: default;
}

.bracket-list__link--disabled:hover {
    background: none;
}

.bracket-list__badge {
    padding: 0 var(--space-1);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    color: var(--color-muted);
    font-size: var(--size-xs);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The bar is reserved on every link and only coloured on the current one, the
   way the bracket list's is — lighting it costs no layout, so nothing shifts
   between the states. The tint is the class's own: the sidebar names three
   things at once, and the bar is what says which of them the page is. */
.sidebar-nav__link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0.1875rem var(--space-3);
    border-left: 2px solid transparent;
    color: var(--color-muted);
    font-size: var(--size-base);
}

.sidebar-nav__link:hover {
    background: var(--color-surface);
    color: var(--color-text);
}

.sidebar-nav__link--current {
    border-left-color: var(--class-color, var(--color-accent));
    background: var(--color-surface);
    color: var(--color-text);
    font-weight: 600;
}

.sidebar-nav__link .icon {
    --icon-size: 1.125rem;

    opacity: 0.75;
}

.sidebar-nav__link:hover .icon,
.sidebar-nav__link--current .icon {
    opacity: 1;
}

.content__head {
    margin-bottom: var(--space-6);
}

.content__title {
    font-size: var(--size-2xl);
    font-weight: 700;
    letter-spacing: -0.02em;
}

.content__subtitle {
    margin-top: var(--space-3);
    color: var(--color-text-soft);
    font-size: var(--size-md);
    line-height: 1.6;
}

.site-footer {
    margin-top: var(--space-7);
    border-top: 1px solid var(--color-border);
    background: var(--color-surface);
}

/* The brand takes the slack and the two lists sit against the right edge, so
   the footer reads as one block at each end rather than trailing off. */
.site-footer__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--space-6);
    padding: var(--space-6) var(--space-7);
    color: var(--color-muted);
    font-size: var(--size-sm);
}

.site-footer__brand .brand {
    /* The rule is `width: 100%`, and the footer's column is far wider than the
       header's slot. */
    width: max-content;
    /* The column around it is muted; the mark reads as in the header. */
    color: var(--color-text);
}

.site-footer__tagline {
    margin-top: var(--space-3);
    max-width: 24ch;
}

.site-footer__heading {
    margin-bottom: var(--space-3);
    color: var(--color-text);
    font-size: var(--size-xs);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.site-footer__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.site-footer__list--split {
    display: block;
    columns: 2;
    /* Capped, or the two columns sit at opposite ends of a `1fr` track and
       read as two lists rather than as one split in half. */
    max-width: 22rem;
    column-gap: var(--space-5);
}

.site-footer__list--split li {
    margin-bottom: var(--space-2);
    break-inside: avoid;
}

.site-footer__link {
    color: var(--color-accent);
}

.site-footer__link:hover {
    text-decoration: underline;
}

/* The line the footer ends on: what is not ours, and how fresh this is. */
.site-footer__base {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-6);
    padding: var(--space-4) var(--space-7) var(--space-5);
    border-top: 1px solid var(--color-border);
    color: var(--color-muted);
    font-size: var(--size-xs);
}

/* desktop */
@media (max-width: 80em) {
    .page {
        grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    }

    .page__sidebar {
        justify-self: start;
    }
}

/* Narrow: the sidebar stops being a column and becomes two rows of chips. */
/* tablet */
@media (max-width: 53.75em) {
    .page {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-4);
        padding: 0 0 var(--space-5);
    }

    .page__sidebar {
        position: static;
        max-height: none;
        overflow: visible;
        width: auto;
        justify-self: stretch;
        margin-bottom: var(--space-4);
        border-bottom: 1px solid var(--color-border);
        background: var(--color-bg);
    }

    .page__content {
        padding: 0 var(--space-3);
    }

    .sidebar__section {
        margin: 0;
    }

    .sidebar__label {
        display: none;
    }

    .bracket-list,
    .sidebar-nav {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: var(--space-2);
        padding: var(--space-3);
    }

    .bracket-list__item + .bracket-list__item {
        margin: 0;
    }

    .bracket-list__link,
    .sidebar-nav__link {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: var(--tap-target);
        padding: 0 var(--space-4);
        border: 1px solid var(--color-border);
        border-radius: var(--radius);
        background: var(--color-surface);
        white-space: nowrap;
    }

    .bracket-list__link--active {
        border-color: var(--color-accent);
        border-left-width: 1px;
    }

    .sidebar-nav {
        border-top: 1px solid var(--color-border);
    }

    .sidebar-nav__link {
        gap: var(--space-2);
    }
}

/* narrow */
@media (max-width: 44em) {
    :root {
        --num-column: 3.25rem;
    }

    .page {
        padding: var(--space-4) var(--space-3);
    }

    .site-header__inner,
    /* Three columns do not fit; the brand leads and the two lists sit under it. */
    .site-footer__inner {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        padding: var(--space-5) var(--space-3);
    }

    .site-footer__brand {
        grid-column: 1 / -1;
    }

    .site-footer__base {
        padding: var(--space-4) var(--space-3);
    }

    .page__content {
        padding: 0 var(--space-2);
    }
}

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

.class-list {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
}

.class-card {
    scroll-margin-top: var(--space-5);
}

.class-card + .class-card {
    border-top: 1px solid var(--color-border);
}

.class-card__summary {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-left: 3px solid var(--class-color, transparent);
}

.class-card__summary:hover {
    background: var(--color-surface-raised);
}

.class-card[open] .class-card__summary {
    background: var(--color-surface-raised);
}

.class-card__summary .icon {
    --icon-size: 1.5rem;
}

.class-card__name {
    flex: 1;
    min-width: 0;
    color: var(--class-color, var(--color-text));
    font-size: var(--size-md);
    font-weight: 600;
}

.class-card__caret {
    flex: none;
    width: 0.75rem;
    color: var(--color-muted);
    font-size: var(--size-sm);
    text-align: center;
}

.class-card__caret::after {
    content: "+";
}

.class-card[open] .class-card__caret {
    color: var(--color-text);
}

.class-card[open] .class-card__caret::after {
    content: "\2013";
}

.class-card__specs {
    padding: var(--space-2) 0 var(--space-3);
    border-top: 1px solid var(--color-border);
    border-left: 3px solid var(--class-color, transparent);
}

.class-card__specs--animating {
    overflow: hidden;
}

.spec-table__head,
.spec-table__row {
    display: grid;
    grid-template-columns:
        minmax(0, 1fr)
        var(--num-column) var(--num-column) var(--num-column);
    gap: var(--space-3);
    align-items: center;
    padding: var(--space-2) var(--space-4);
}

.spec-table__head {
    padding-bottom: var(--space-1);
}

.spec-table__heading {
    overflow: hidden;
    color: var(--color-muted);
    font-size: var(--size-xs);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-align: right;
    white-space: nowrap;
}

.spec-table__heading--name {
    text-align: left;
}

.spec-table__row {
    margin-left: -2px;
    border-left: 2px solid transparent;
    font-size: var(--size-base);
}

.spec-table__row:hover {
    border-left-color: var(--class-color, var(--color-border-strong));
    background: var(--color-surface-raised);
}

.spec-table__name {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    overflow: hidden;
    color: var(--color-text-soft);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.spec-table__name .icon {
    --icon-size: 1.25rem;
}

.spec-table__row:hover .spec-table__name {
    color: var(--color-accent);
}

.spec-table__num {
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.spec-table__num--top {
    color: var(--color-text);
}

/* A ladder that never filled up: the cut is not a cut, it is everyone that could
   be read. */
.spec-table__row--thin .spec-table__num--ranked {
    color: var(--color-danger);
}

/* narrow */
@media (max-width: 44em) {
    .spec-table__head,
    .spec-table__row {
        grid-template-columns:
            minmax(0, 1fr)
            var(--num-column) var(--num-column);
    }

    .spec-table__heading--cutoff,
    .spec-table__num--cutoff {
        display: none;
    }
}

/* ----------------------------------------------------------------- ladder */

.spec-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    margin-bottom: var(--space-5);
}

/* Same handover as a section heading's, but the `h1` is twice the size, so the
   pair still needs air between its halves. */
.spec-header:has(+ .spec-lede) {
    margin-bottom: var(--space-4);
}

/* The rule marks the heading, so it has to end with it. On the container it
   grew every time the back link wrapped to a line of its own. */
.spec-header__title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    min-width: 0;
    padding-left: var(--space-4);
    border-left: 3px solid var(--class-color, var(--color-border-strong));
}

.spec-header .icon {
    --icon-size: 2.5rem;
}

.spec-header__name {
    color: var(--class-color, var(--color-text));
    font-size: var(--size-2xl);
    font-weight: 700;
    letter-spacing: -0.01em;
}

.spec-header__back {
    margin-left: auto;
    color: var(--color-accent);
    font-size: var(--size-sm);
}

.spec-header__back:hover {
    text-decoration: underline;
}

/*
 * The header's numbers. They wrap into as many rows as they need rather than
 * scrolling, since a tile is the width of its own value.
 */
.spec-stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-8);
}

.spec-stats__tile {
    flex: 1 1 auto;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
}

.spec-stats__label {
    color: var(--color-muted);
    font-size: var(--size-xs);
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.spec-stats__value {
    margin-top: var(--space-1);
    color: var(--color-text);
    font-size: var(--size-lg);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    white-space: nowrap;
}

/* A season's name and a relative time are prose, not figures. */
.spec-stats__value--words {
    font-size: var(--size-md);
    font-variant-numeric: normal;
    font-weight: 500;
}

.spec-stats__span {
    margin: 0 var(--space-2);
    color: var(--color-muted);
    font-size: var(--size-sm);
    font-weight: 400;
}

/* A section heading, not a table caption — the chips sit right above it, so it
   needs the room to read as the page turning to its list. */
.spec-section {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    margin: var(--space-8) 0 var(--space-5);
    color: var(--color-text);
    font-size: var(--size-xl);
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* The page's own name, repeated at the far end of every section heading: the
   sections are long enough that a reader who scrolled into one has lost the
   `h1`. Hidden from assistive tech, which still has the heading above it. */
.spec-section__scope {
    color: var(--color-muted-dim);
    font-size: var(--size-xs);
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* A rule between one section and the next, across the content column. Not on
   the first heading: there it would divide the page's own lede from the list it
   introduces, which are one unit. The margin is split around it so the gap
   above and below the line stays even. */
.spec-section:not(:first-of-type) {
    padding-top: var(--space-8);
    border-top: 1px solid var(--color-border);
    margin-top: var(--space-8);
}

/* A heading that has a lede hands its bottom margin over: the two are one
   unit, and the gap belongs under the pair rather than between them. */
.spec-section:has(+ .spec-lede) {
    margin-bottom: var(--space-3);
}

/* A line of prose under a section heading — what the block is, and the one
   thing about it a reader cannot get from looking. It runs the content column:
   a shorter measure left a ragged edge halfway across an otherwise full page. */
.spec-lede {
    margin-bottom: var(--space-6);
    color: var(--color-text-soft);
    font-size: var(--size-md);
    line-height: 1.55;
    text-wrap: pretty;
}

/* The page's own, under the `h1` rather than a section heading. It answers the
   tiles right below it, so it sits closer to them than a section lede does. */
.spec-lede--page {
    margin-bottom: var(--space-6);
    font-size: var(--size-lg);
}

/*
 * A row is one grid line but two lines tall: the name sits over its realm in
 * the identity column, and everything that qualifies the standing — race, hero
 * talent, gear, rating — is its own column, centred against the row's whole
 * height rather than hung off the second line. Fixed columns are what keep the
 * icons at the same x on every row; flowing text staircased them.
 *
 * The faction has no column: it is a crest in front of the name, where it reads
 * as part of the identity rather than as a fact of its own. Losing that column
 * is what made room for the race.
 */
.ladder {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
}

/*
 * Every track is a width, not `auto`: a row is its own grid, so an `auto` track
 * is sized by that row's own content and `Highmountain Tauren` pushes the tree a
 * quarter of the row further right than `Orc` does. That is the staircase the
 * columns exist to stop. 10rem holds the longest race name, `Highmountain
 * Tauren`; the two tree names past the tree column wrap onto a second line
 * inside the height the row already has.
 */
.ladder__row {
    display: grid;
    /*
     * The race and the tree are sized to their longest names — `Highmountain
     * Tauren` and `Rider of the Apocalypse`, portrait and gap included — so the
     * one track that gives way is the character's own, which is the only cell
     * with somewhere to put the overflow: its name and realm already end in an
     * ellipsis. A flexible tree column starved instead, and its name spilled
     * over the item level.
     *
     * They are ceilings rather than widths so that a container too narrow for
     * all seven squeezes them instead of overflowing. Every row squeezes by the
     * same amount — the width available is the row's container, not its content
     * — so the staircase does not come back.
     */
    grid-template-columns:
        2ch auto minmax(3rem, 1fr) minmax(0, 9.75rem)
        minmax(0, 10.5rem) minmax(0, 3.25rem) 3rem;
    grid-template-areas: "pos crest who race hero gear rating";
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-left: 2px solid transparent;
}

.ladder__row + .ladder__row {
    border-top: 1px solid var(--color-border);
}

.ladder__row:hover {
    border-left-color: var(--class-color, var(--color-border-strong));
    background: var(--color-surface-raised);
}

/* The pager swaps a window of rows; hiding them is its only side effect. */
.ladder__row--hidden {
    display: none;
}

/* The grid gap alone left the number nearer its name than the row's own edge —
   the box is two digits wide and the value is often one, so the slack all fell
   on the left. */
.ladder__position {
    grid-area: pos;
    margin-right: var(--space-3);
    color: var(--color-muted);
    font-size: var(--size-md);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    text-align: right;
}

.ladder__who {
    grid-area: who;
    min-width: 0;
}

.ladder__name,
.ladder__realm {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ladder__name {
    color: var(--class-color, var(--color-text));
    font-size: var(--size-md);
    font-weight: 600;
}

.ladder__realm {
    color: var(--color-text-soft);
    font-size: var(--size-sm);
}

/* The region qualifies the realm rather than standing beside it — `Ravencrest`
   is a name on both, and the two only ever read together. */
.ladder__region {
    color: var(--color-muted);
}

/* The crest's ring is the faction's colour rather than the class's — every
   character on the page shares the class, not the side. With the word gone the
   ring is the only thing left carrying it, so the tint sits on the row. */
.ladder__crest {
    grid-area: crest;
    --icon-size: 1.5rem;
    --icon-ring: var(--faction-color, var(--color-border-strong));
}

.ladder__row--alliance {
    --faction-color: #4a7fd4;
}

.ladder__row--horde {
    --faction-color: #b8434a;
}

.ladder__race {
    grid-area: race;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    color: var(--color-text-soft);
    font-size: var(--size-sm);
}

/* Nothing in a row may reach into the cell beside it, whatever the API names a
   race or a tree next season. */
.ladder__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ladder__race .icon {
    --icon-size: 1.5rem;
    --icon-ring: var(--faction-color, var(--color-border-strong));
}

/* A bare number beside the rating reads as a second rating, so the unit rides
   with it — small enough not to compete, close enough to belong. */
.ladder__gear {
    grid-area: gear;
    color: var(--color-text-soft);
    font-size: var(--size-md);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

.ladder__gear-unit {
    margin-left: 0.25em;
    color: var(--color-muted);
    font-size: var(--size-xs);
}

.ladder__hero {
    grid-area: hero;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    color: var(--color-text-soft);
    font-size: var(--size-sm);
}

.ladder__hero .icon {
    --icon-size: 1.5rem;
}

/* 3rem is four bold tabular digits, the widest rating there is. Left `auto`
   it was the last track that could still stagger the row: a ladder reaching
   down to two figures made the flexible name column grow, and everything
   between them moved with it. */
.ladder__rating {
    grid-area: rating;
    color: var(--class-color, var(--color-text));
    font-size: var(--size-lg);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    letter-spacing: -0.01em;
    text-align: right;
}

/*
 * The pager is built by app.js and does not exist without it — with the script
 * blocked the whole list is there instead, which is long but never broken.
 */
.pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    margin-top: var(--space-5);
}

.pager__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--tap-target);
    min-height: var(--tap-target);
    padding: 0 var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text-soft);
    font-size: var(--size-base);
    font-variant-numeric: tabular-nums;
}

.pager__link:hover {
    border-color: var(--class-color, var(--color-border-strong));
    color: var(--color-text);
}

.pager__link--current {
    background: var(--color-surface-raised);
    border-color: var(--class-color, var(--color-border-strong));
    color: var(--color-text);
    font-weight: 600;
}

/*
 * Hero talent filter. Each chip is its own page, so the row is plain links —
 * no JavaScript, and a filtered list is a real URL.
 */
.hero-filter {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
    margin-bottom: var(--space-8);
}

.hero-filter__chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--tap-target);
    color: var(--color-text-soft);
    font-size: var(--size-base);
}

.hero-filter__chip .icon {
    --icon-size: 1.25rem;
}

.hero-filter__chip:hover {
    border-color: var(--class-color, var(--color-border-strong));
    color: var(--color-text);
}

.hero-filter__chip--current {
    background: var(--color-surface-raised);
    border-color: var(--class-color, var(--color-border-strong));
    color: var(--color-text);
}

.hero-filter__count {
    color: var(--color-muted);
    font-size: var(--size-sm);
    font-variant-numeric: tabular-nums;
}

/* narrow */
@media (max-width: 44em) {
    .spec-stats__tile {
        flex-basis: 40%;
    }

    .ladder__row {
        padding: var(--space-3);
    }

    /* Seven columns do not fit a narrow screen. The name takes a line of its
       own with the rating, and what qualifies the standing drops under it. */
    .ladder__row {
        grid-template-columns: 2ch auto auto minmax(0, 1fr) auto;
        grid-template-areas:
            "pos crest who  who  rating"
            ".   .     race hero gear";
        row-gap: var(--space-3);
    }

    .ladder__hero {
        justify-self: start;
    }

    /* The portrait says the race on its own, and the name and realm want the
       width more. Hidden from the eye, not from a screen reader — the icon
       beside it is `aria-hidden`, so this is the only thing that names it. */
    .ladder__race .ladder__label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* ------------------------------------------------------------------ tally */

/*
 * The card the gear section and the enchants section are both made of: a ground
 * with a header strip over it, holding rows of name over share track with the
 * count beside them. Only the chrome lives here — what a row puts in its columns
 * is the section's, and so is the panel's content.
 */

/* The panel hangs off a row but is positioned against the section: inside a
   multi-column container, an absolutely positioned box that tall makes the
   column box grow to hold it, and the footer moves down every time one opens. */
.tally {
    position: relative;
    margin-top: var(--space-2);
}

/* A ground of its own with a header strip over it, rather than an outline:
   fourteen frames with a label cut into each read as fourteen boxes, and the
   strip gives the card's name somewhere to stand. */
.tally-card {
    /* A card that split across a column gutter would leave its header in one
       column and half its rows in the other. */
    break-inside: avoid;
    margin-bottom: 18px;
    border-radius: 6px;
    background: var(--color-surface-sunken);
}

.tally-card__name {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 10px 14px;
    border-bottom: 1px solid var(--color-border);
    border-radius: 6px 6px 0 0;
    background: var(--color-surface);
    color: var(--color-muted);
    font-size: var(--size-sm);
    font-weight: 600;
    text-transform: uppercase;
}

/* Ringed the way the rows under it are: the class colour is the page's accent,
   and a strip of fourteen of it would outshout the gear. */
.tally-card__icon {
    --icon-size: 20px;
    --icon-ring: var(--color-border-strong);
}

/* The 14px between two rows is the rows' own padding rather than a gap, so the
   hover highlight has room around its text instead of clipping it at the edge.
   The card's own top and bottom make the same 12px up with the first and last
   row's share of it, so nothing about the card's height moved. */
.tally-card__rows {
    display: flex;
    flex-direction: column;
    padding: 5px 14px;
    list-style: none;
}

/*
 * Icon, then the name over its share track, then the count with the row's
 * marker over it. Three cells rather than two rows of columns: the marker sits
 * in a slot the row reserves whether or not it has one, so a card whose second
 * item is a set piece does not stand a line taller than the rest of it.
 *
 * The pointer lights the whole row rather than the thing under it: the row is
 * the item, and highlighting one of its columns asks the reader to work out
 * which of them belong together. The bar down the left is the class colour, the
 * one the share track already carries.
 *
 * The bleed and the reserved border are what keep it from moving anything: the
 * row pulls out into the card's own padding so the highlight reaches its inner
 * edge, and the bar is declared transparent so lighting it costs no layout.
 */
.tally-row {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) var(--tally-meta);
    grid-template-rows: auto auto;
    gap: var(--space-3) 12px;
    align-items: center;
    margin-inline: -14px;
    padding: 7px 14px;
    border-left: 3px solid transparent;
    border-radius: var(--radius);
    transition:
        background-color var(--motion-fast) var(--ease),
        border-left-color var(--motion-fast) var(--ease);
}

.tally-row:hover {
    border-left-color: var(--class-color, var(--color-border-strong));
    background-color: var(--color-surface-raised);
}

/*
 * Ringed the way a talent tile is: a round icon inside a coloured ring is what
 * a player reads a piece of gear as. The padding is the channel between the
 * two, and the art is clipped to it so its square corners do not stand in the
 * ring. Without `--art` the box stays, empty — a thing Blizzard publishes no
 * icon for still holds the row's shape rather than shifting the name left.
 *
 * Drawn as a background rather than an image element, and offset by a
 * percentage because CSS resolves that against (sheet height - box height),
 * which lands cell N right whatever the box is sized to.
 */
.tally-row__icon {
    grid-area: 1 / 1 / 3 / 2;
    width: 28px;
    height: 28px;
    padding: 2px;
    border-radius: 50%;
    /* An outline, not a border: a border eats into the box and would crop the
       art it is meant to frame. */
    outline: 2px solid var(--color-border-strong);
    background-color: var(--color-track);
    background-clip: content-box;
}

.tally-row__art {
    background-image: var(--item-sheet);
    background-repeat: no-repeat;
    background-origin: content-box;
    background-size: 100% auto;
    background-position: 0 calc(var(--cell) * 100% / (var(--item-cells) - 1));
}

/*
 * An enchant no scroll is sold for has its art in the talent sheet instead —
 * the row is drawn the same way, only the two variables the arithmetic above
 * reads are swapped for their spell counterparts.
 */
.tally-row__art--spell {
    --item-sheet: var(--spell-sheet);
    --item-cells: var(--spell-cells);
}

/*
 * Placed by negative line, so the same four rules hold whether or not the
 * section put an icon in a column of its own: -3/-2 is the column left of the
 * count either way. The marker shares the name's line and the count shares the
 * track's, which is what keeps a count off the middle of a row that has no
 * marker at all.
 */
.tally-row__name {
    grid-area: 1 / -3 / 2 / -2;
    overflow: hidden;
    color: var(--color-text);
    font-size: var(--size-base);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tally-row__track {
    grid-area: 2 / -3 / 3 / -2;
    height: 6px;
    border-radius: 3px;
    background: var(--color-track);
}

.tally-row__fill {
    display: block;
    width: calc(var(--share) * 1%);
    height: 100%;
    border-radius: inherit;
    background: var(--class-color, var(--color-border-strong));
}

/* A chip rather than a word: it names a kind, and set under the name a second
   run of plain text would read as part of it. */
.tally-row__source {
    grid-area: 1 / -2 / 2 / -1;
    justify-self: end;
    padding: 3px 5px;
    border: 1px solid var(--color-border-strong);
    border-radius: 3px;
    color: var(--color-muted);
    font-size: var(--size-2xs);
    font-weight: 700;
    letter-spacing: 0.1em;
    line-height: 1;
    text-transform: uppercase;
}

/* No leading: it shares its row with a 6px track, and the line box a count
   would otherwise carry is what sets that row's height. */
.tally-row__wore {
    grid-area: 2 / -2 / 3 / -1;
    justify-self: end;
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    white-space: nowrap;
}

.tally-row__dot {
    margin: 0 0.25em;
    opacity: 0.6;
}

/*
 * The panel a row opens under the pointer. It can be wider than the card, so it
 * opens to the side rather than over its row and `app.js` places it against the
 * section. `display: none` rather than a fade, because a hidden box still has
 * layout — a panel hanging past the right of a narrow window would give the
 * page a horizontal scrollbar nobody hovered anything to get.
 */
.tally-row__tip {
    position: absolute;
    top: var(--tip-top, 0);
    left: var(--tip-left, 0);
    z-index: 3;
    display: none;
    flex-direction: column;
    gap: var(--space-4);
    /* Narrower than its own width on a screen that cannot hold it. */
    width: min(var(--tip-width), 100%);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface-raised);
    /* A band that paints its own ground out to the frame leaves the corners the
       panel's to round. */
    overflow: hidden;
    pointer-events: none;
}

/* `--measuring` is what app.js reads the shut panel's size through: the box has
   to be laid out to be measured, and `:hover` alone cannot be relied on to have
   matched by the time the event is dispatched. */
.tally-row:hover .tally-row__tip,
.tally-row--measuring .tally-row__tip {
    display: flex;
}

.tally-row--measuring .tally-row__tip {
    visibility: hidden;
}

/* The panel's own icon, twice the row's. Its ring is the same idea and the same
   `--art` rule; only the section says what colour the ring is. */
.tally-row__crest {
    width: 56px;
    height: 56px;
    padding: 2px;
    border-radius: 50%;
    outline: 3px solid var(--color-border-strong);
    background-color: var(--color-border);
    background-clip: content-box;
}

/* ------------------------------------------------------- the panel's bands */

/* The crest spans the text lines beside it, so a long name wraps under itself
   rather than under the art. */
.tally-tip__head {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0 var(--space-4);
    align-items: center;
}

.tally-tip__name {
    color: var(--color-text);
    font-size: var(--size-md);
    font-weight: 600;
}

.tally-tip__share {
    color: var(--color-text-soft);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

/* One label column for the whole panel, so the facts and the bands under them
   read as one list rather than as two blocks with different margins. The value
   is free to wrap: a limit category runs three words. */
.tally-tip__facts {
    display: grid;
    grid-template-columns: var(--gear-tip-label) 1fr;
    gap: var(--space-1) var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
    font-size: var(--size-xs);
}

.tally-tip__label {
    color: var(--color-muted);
}

.tally-tip__value {
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}

/* The same label column, then a block of lines rather than the facts' one
   value — an effect is a sentence and wraps like one. */
.tally-tip__band {
    display: grid;
    grid-template-columns: var(--gear-tip-label) 1fr;
    gap: var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
    font-size: var(--size-xs);
}

/* Its own ground, and the last thing in the panel — so it bleeds through the
   padding to the frame the way a row bleeds to its card's. */
.tally-tip__band--sunken {
    margin-inline: calc(var(--space-5) * -1);
    margin-bottom: calc(var(--space-4) * -1);
    padding-right: var(--space-5);
    padding-bottom: var(--space-4);
    padding-left: var(--space-5);
    background: var(--color-surface);
}

.tally-tip__who {
    display: flex;
    flex-direction: column;
    color: var(--class-color, var(--color-text));
    font-size: var(--size-sm);
    line-height: 1.5;
    list-style: none;
}

.tally__caption {
    margin-top: var(--space-4);
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

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

/* ------------------------------------------------------------------ races */

/*
 * One bar split by race, under the standings it counts. Nothing is drawn
 * inside a segment, so the fill is the race's colour at full strength: there
 * is no text on it whose contrast would swing from race to race. The name and
 * the share live in a tooltip on hover and in the legend under the bar — and
 * the legend is the one that carries them on a touch screen, where no hover
 * ever happens.
 */

.race-bar {
    margin-top: var(--space-2);
}

/*
 * A segment's width is its own count, as a flex grow factor: the track divides
 * itself in the same proportion with no percentage to round, and the last
 * segment cannot leave a sliver of track behind. `--seg` is the only value the
 * template writes inline — a share has no fixed set of values to name a
 * modifier after, unlike a race or a class.
 */
.race-bar__track {
    display: flex;
    gap: 2px;
    height: 2.5rem;
}

.race-bar__seg {
    position: relative;
    flex: var(--seg) 1 0;
    min-width: 3px;
    cursor: default;
}

/*
 * The fill is a child, not the segment's own background, because the hover
 * lifts it: a transform on the segment scales everything inside it, and the
 * tooltip came out stretched.
 */
.race-bar__seg::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--race-color, var(--color-border-strong));
    transition:
        transform var(--motion-fast) var(--ease),
        filter var(--motion-fast) var(--ease);
}

/* The track cannot clip its corners — that would cut the tooltip and the lift
   below — so the rounding sits on whichever segments are on the ends. */
.race-bar__seg:first-child {
    border-radius: var(--radius) 0 0 var(--radius);
}

.race-bar__seg:last-child {
    border-radius: 0 var(--radius) var(--radius) 0;
}

.race-bar__seg:only-child {
    border-radius: var(--radius);
}

/* Everything past the fifth race. Hatched rather than filled: it is a
   remainder, not a race, and on a ladder where no race dominates it is the
   widest segment on the bar. */
.race-bar__seg--rest::before,
.race-bar__swatch {
    background: repeating-linear-gradient(
        45deg,
        var(--color-border-strong) 0 4px,
        var(--color-surface) 4px 8px
    );
}

.race-bar__seg:hover {
    z-index: 1;
}

.race-bar__seg:hover::before {
    transform: scaleY(1.16);
    filter: brightness(1.16) saturate(1.08);
}

.race-bar__tip {
    --tip-x: -50%;

    position: absolute;
    bottom: calc(100% + var(--space-2));
    left: 50%;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface-raised);
    color: var(--color-text);
    font-size: var(--size-base);
    white-space: nowrap;

    /* A tooltip is wider than a thin segment, so it hangs over its neighbours
       and would block the hover that opens theirs. */
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transform: translate(var(--tip-x), var(--space-1));
    transition:
        opacity var(--motion-fast) var(--ease),
        transform var(--motion-fast) var(--ease),
        visibility var(--motion-fast);
}

/* Centred on its segment everywhere but the ends, where a centred tooltip
   would hang off the page and scroll it sideways. The tail is last and is
   usually the narrowest segment, so this is the common case, not the corner. */
.race-bar__seg:first-child .race-bar__tip {
    --tip-x: 0;

    left: 0;
}

.race-bar__seg:last-child .race-bar__tip {
    --tip-x: 0;

    right: 0;
    left: auto;
}

.race-bar__seg:hover .race-bar__tip {
    opacity: 1;
    visibility: visible;
    transform: translate(var(--tip-x), 0);
}

.race-bar__tip .icon {
    --icon-size: 1.25rem;
    --icon-ring: var(--race-color);
}

.race-bar__caption {
    margin-top: var(--space-3);
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

/* Not decoration: on a touch screen there is no hover, so this is where the
   races are read. */
.race-bar__legend {
    display: flex;
    list-style: none;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-3) var(--space-5);
    margin-top: var(--space-4);
}

.race-bar__legend-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-text-soft);
    font-size: var(--size-base);
}

.race-bar__legend-item .icon {
    --icon-size: 1.5rem;
    --icon-ring: var(--race-color);
}

/* The tail's key. Every race entry has a portrait tinted like its segment, so
   without this the hatched segment is the one nothing on the page matches. */
.race-bar__swatch {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    border-radius: 50%;
    box-shadow: inset 0 0 0 2px var(--color-border-strong);
}

.race-bar__legend-item--rest {
    color: var(--color-muted);
}

.race-bar__count {
    color: var(--color-muted);
    font-size: var(--size-base);
    font-variant-numeric: tabular-nums;
}

/* The separator leads nothing and trails nothing — it is spacing with a mark
   in it, so a count and its share never touch. */
.race-bar__dot {
    margin: 0 var(--space-2);
}

/* Motion is decoration here: the bar, the tooltip and the legend all work
   without it. */
@media (prefers-reduced-motion: reduce) {
    .race-bar__seg::before,
    .race-bar__tip {
        transition: none;
    }

    .race-bar__seg:hover::before {
        transform: none;
    }
}

/* ------------------------------------------------------------------ stats */

/*
 * What a ladder gears for, as one scale. A row's distance from the one above
 * it is that stat's shortfall in rating, so the eye reads the priority before
 * it reads a number. Laid out in flow rather than by absolute position: the
 * distance is a top margin, which means the block cannot overflow its own
 * height and two close rows cannot land on top of each other.
 */

.stat-scale {
    margin-top: var(--space-2);
}

.stat-scale__list {
    /* The scale's height when nothing is floored, one row, and the closest two
       rows may sit. `view.rs` holds the same floor as a share of the scale. */
    --stat-track: 13.25rem;
    --stat-row: 1.25rem;
    --stat-step: 1.5rem;

    list-style: none;
    padding-left: var(--space-5);
    border-left: 1px solid var(--color-border);
}

/*
 * `--gap` is the one value the template writes inline: a distance drawn from
 * the data has no fixed set of values to name a modifier after. `max()` is the
 * whole collision fix — two stats a few rating apart still get a full row of
 * clearance, and only the pairs that would have overlapped move at all.
 */
.stat-scale__row {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    height: var(--stat-row);
    margin-top: max(
        calc(var(--stat-step) - var(--stat-row)),
        calc(var(--gap, 0) * var(--stat-track) / 100 - var(--stat-row))
    );
}

.stat-scale__row:first-child {
    margin-top: 0;
}

/* The dot marks the row on the axis. It fades down the order, so the priority
   survives being read at a glance with the numbers out of focus. */
.stat-scale__dot {
    width: 0.5rem;
    height: 0.5rem;
    flex: none;
    border-radius: 50%;
    background: var(--class-color, var(--color-accent));
    opacity: 0.28;
}

.stat-scale__row:nth-child(1) .stat-scale__dot {
    opacity: 1;
}

.stat-scale__row:nth-child(2) .stat-scale__dot {
    opacity: 0.5;
}

.stat-scale__row:nth-child(3) .stat-scale__dot {
    opacity: 0.38;
}

.stat-scale__name {
    font-size: var(--size-base);
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* Only the first stat takes the class colour: the section's answer is which
   stat leads, and a page that tinted all four would not have said it. */
.stat-scale__row:first-child .stat-scale__name {
    color: var(--class-color, var(--color-accent));
    font-size: var(--size-md);
}

/* The rule between a name and its numbers, so the eye crosses the gap on a
   line rather than on nothing. */
.stat-scale__leader {
    flex: 1;
    min-width: var(--space-4);
    height: 1px;
    background: linear-gradient(90deg, var(--color-border), var(--color-surface));
}

.stat-scale__buys {
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.stat-scale__rating {
    min-width: 3.5rem;
    font-size: var(--size-md);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* The shortfall to the row above, sitting in the space that row's distance
   opened. Printed only where there is room for it — see `MIN_STEP`. */
.stat-scale__drop {
    position: absolute;
    right: 0;
    bottom: 100%;
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

.stat-scale__caption {
    margin-top: var(--space-4);
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

/* narrow — the leader gives up its width first, since it is the one part of a
   row carrying nothing. Every number stays. */
@media (max-width: 44em) {
    .stat-scale__list {
        padding-left: var(--space-4);
    }

    .stat-scale__row {
        gap: var(--space-3);
    }

    .stat-scale__rating {
        min-width: 2.75rem;
    }
}

/* --------------------------------------------------------------- talents */

.talents {
    /*
     * Bigger than the 44px tap target the rest of the site uses: Blizzard's
     * sheet is 56px a cell, and the closer the tile sits to that the less the
     * downscale bands. The ceiling is the content column — nine columns, ten
     * gaps and two gutters have to fit 45rem, so the tile and the gap trade
     * against each other, and 38px is what a 28px gap leaves — the gap is what
     * the choice marks live in, so they set it and the tile takes the rest.
     */
    --tile-size: 2.375rem;
    /* The channel between the art and the ring around it, the way a talent
       calculator draws one. */
    --art-inset: 2px;

    margin-top: var(--space-5);
}

/* Centred with the panels it explains, not with the prose above it. */
.talents__legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-3) var(--space-6);
    margin-bottom: var(--space-7);
    list-style: none;
}

.talents__key {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    color: var(--color-text-soft);
    font-size: var(--size-sm);
}

.talents__note {
    color: var(--color-muted);
}

.talents__swatch {
    width: 1rem;
    height: 1rem;
    flex: none;
    border-radius: 50%;
    background-color: var(--color-surface);
    outline: var(--ring-width, 0) solid var(--ring, transparent);
}

.talents__caption {
    margin-top: var(--space-5);
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------------------- panel */

/*
 * Headings, ledes and the legend stay on the left edge with the rest of the
 * page's prose; only the grid centres.
 */
/* A panel is a whole tree, so the next one needs more air under it than the
   gaps inside it: `--space-7` put the class half's last row as close to the
   specialization's heading as two rows of its own grid were. */
.talent-panel {
    margin-bottom: var(--space-8);
}

.talent-panel__head {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-6);
}

.talent-panel__title {
    color: var(--color-text);
    font-size: var(--size-base);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* Kept inline rather than laid out as a flex row: a flex container takes its
   baseline from its first item, so the icon's bottom became the heading's
   baseline and the count beside it dropped half an icon. */
.talent-panel__title .icon {
    --icon-size: 1.25rem;

    margin-right: var(--space-3);
    vertical-align: middle;
}

.talent-panel__count {
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

/*
 * `min-width: min-content` is what makes one container do both jobs: a grid
 * narrower than the screen centres, a wider one stops centring and scrolls.
 * Without it `justify-content: center` clips the left edge out of reach.
 */
/*
 * The widest tree is 652px against a 720px column, so nothing scrolls until the
 * narrow breakpoint — and until then this box does not clip, which is what lets
 * a tooltip stand above the grid or under it without any reserve at all.
 */
.talent-panel__scroll {
    padding-bottom: var(--space-2);
    pointer-events: none;
}

.talent-panel__center {
    display: flex;
    justify-content: center;
    min-width: min-content;
    pointer-events: auto;
}

/*
 * A hero tree is four columns wide, so the trees a page draws sit side by side
 * — stacked they left two thirds of the column empty for as many rows again.
 * Each takes an equal share of the box rather than only its own width: the
 * heading then starts at its share's left edge, and the tree centres inside
 * that share independently. `grid-auto-columns` counts the trees for us, so
 * one tree gets the whole box and centres on the page.
 */
.talent-panel__center--heroes {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: var(--space-7);
}

.talent-hero__center {
    display: flex;
    justify-content: center;
}

/* Narrower than a wide half's, which is what lets two trees centred as a pair
   keep every tooltip inside the box — the edge columns still turn theirs in. */
.talent-tile--hero .talent-tile__tip {
    width: var(--tip-width-hero);
}

/*
 * A gutter each side, so a gate's label has somewhere to sit: right-aligned
 * inside the tiles it lands on top of one whenever that cell is filled. Both
 * sides, or the tiles stop being centred under their heading.
 */
.talent-grid {
    --gate-gutter: 3.5rem;
    /* The ring stands outside the circle and the choice arrows cross it, so the
       gap has to clear both of a facing pair, not just the two circles. */
    --tile-gap: 1.75rem;

    /* Rows need more than columns: the count badge hangs below its tile, and a
       gate's rule has to clear it without crowding the row underneath. */
    --tile-row-gap: var(--space-5);

    display: grid;
    grid-template-columns:
        var(--gate-gutter) repeat(var(--cols), var(--tile-size))
        var(--gate-gutter);
    /* Left implicit, so a gate's own row can collapse to its rule while every
       tile row stands at the height the tile gives it. Sizing them all to
       `--tile-size` would make a gate cost a whole empty row. */
    gap: var(--tile-row-gap) var(--tile-gap);
}

/* The gutters exist to hold a gate's label, so a panel with no gates drops them
   — a hero tree is four columns, and 8rem of empty margin dwarfed it. */
.talent-grid--ungated {
    --gate-gutter: 0;
}

/* A gate holds a row of its own, so the blocks it divides stand two gaps apart
   rather than sharing one — which read as less air than the rows inside a block
   had. The row itself collapses to the rule. */
.talent-grid__gate {
    position: relative;
    grid-row: var(--r);
    grid-column: 1 / -1;
    height: 0;
    border-bottom: 1px dashed var(--color-border);
}

.talent-grid__points {
    position: absolute;
    right: 0;
    bottom: 2px;
    color: var(--color-muted);
    font-size: var(--size-xs);
    letter-spacing: 0.04em;
}

/* ------------------------------------------------------------------ tile */

/*
 * Four tiers, not a gradient: the ladder's agreement is bimodal, and a fill
 * scaled to the percentage draws fifty near-identical tiles.
 */
.talent-tile {
    position: relative;
    grid-row: var(--r);
    grid-column: calc(var(--c) + 1);
    width: var(--tile-size);
    height: var(--tile-size);
    color: var(--color-muted);
}

/*
 * The circle itself, and everything that grows with it on hover — art, ring,
 * count and choice marks. It is a child rather than the tile so the tooltip can
 * stay out of the transform: `app.js` measures the tip's own height to decide
 * which way it opens, and a scaled ancestor would hand it the scaled one.
 */
.talent-tile__disc {
    /* A dark edge inside the circle, so the art meets the ring through a
       shadow rather than flush against it. Declared here and taken by every
       rule that sets `box-shadow`, since the property does not cascade. */
    --edge: inset 0 0 4px 1px rgb(0 0 0 / 55%);

    position: relative;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: var(--color-surface);
    box-shadow: var(--edge);
    transition: transform var(--motion-fast) var(--ease);

    /* An outline, not a border: a border eats into the box and would crop the
       art it is meant to frame. This one sits outside the circle instead. */
    outline: var(--ring-width, 0) solid var(--ring, transparent);
}

.talent-tile:hover .talent-tile__disc {
    transform: scale(1.12);
}

/* Lifted while it is grown, so it passes over its neighbours rather than under
   them. A PvP tile is `position: static` and takes no z-index — its row is
   spaced far enough apart that nothing overlaps. */
.talent-tile:hover {
    z-index: 3;
}

/*
 * Drawn against the content box, so the padding stands as a channel between the
 * art and the ring, and offset by a percentage because CSS resolves that
 * against (sheet height - box height) — which lands cell N right whatever the
 * box is sized to, channel and all. The cell number rides on the tile and is
 * inherited from there.
 */
.talent-tile--art .talent-tile__disc {
    padding: var(--art-inset);
    background-image: var(--spell-sheet);
    background-repeat: no-repeat;
    background-origin: content-box;
    /* Two clips, because the layers want different ones: the art stops at the
       content box, or its square corners stand in the channel, and the colour
       under it takes the last value and fills the channel to the ring. */
    background-clip: content-box, border-box;
    background-color: var(--color-bg);
    background-size: 100% auto;
    background-position: 0 calc(var(--cell) * 100% / (var(--spell-cells) - 1));
}

/* The only tile on its row, centred across the panel rather than left where the
   document's column would put it. */
.talent-tile--alone {
    grid-column: 1 / -1;
    justify-self: center;
}

/*
 * The ring around the art is the tier, and each step is its own colour: 3px
 * spell gold, 3px epic purple, 2px grey, then none at all. Both are the site's
 * own borrowings from the game — the gold a tooltip prints spell text in, and
 * the purple an epic item is named in — so a tile is read in colours that are
 * already on the page. All four sit on the same outer radius, so nothing in
 * the grid moves between states.
 */
.talent-tile--top,
.talents__swatch--top {
    --ring: var(--color-spell);
}

.talent-tile--mid,
.talents__swatch--mid {
    --ring: var(--color-quality-epic);
}

.talent-tile--top,
.talent-tile--mid {
    --ring-width: 3px;
}

.talent-tile--low,
.talents__swatch--low {
    --ring: var(--color-border-strong);
}

.talent-tile--low {
    --ring-width: 2px;
}

/* Nobody took it, so there is nothing to ring: the art stands on its own. */

.talents__swatch--top,
.talents__swatch--mid {
    --ring-width: 2px;
}

.talents__swatch--low {
    --ring-width: 1px;
}

.talents__swatch--zero {
    background-color: var(--color-border);
}

/*
 * What the ladder barely plays is veiled rather than filtered: an inset shadow
 * paints over the art and under the number, so the count stays readable without
 * a second element to dim.
 */
.talent-tile--low .talent-tile__disc {
    box-shadow:
        var(--edge),
        inset 0 0 0 var(--tile-size)
            color-mix(in srgb, var(--color-surface) 15%, transparent);
}

.talent-tile--zero .talent-tile__disc {
    box-shadow:
        var(--edge),
        inset 0 0 0 var(--tile-size)
            color-mix(in srgb, var(--color-surface) 55%, transparent);
}

/*
 * Sat on the ring rather than inside it, so the art keeps the whole circle —
 * and filled with the ring's own colour, so the count reads as part of it. The
 * fill is lifted towards white and the figure darkened from the same hue: on
 * the two darker qualities a flat fill with a dark figure falls under 4.5:1.
 */
.talent-tile__took {
    position: relative;
    z-index: 1;
    min-width: 1.625rem;
    margin-bottom: -0.4rem;
    padding: 0 0.4375rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--ring, var(--color-border)) 82%, #fff);
    color: color-mix(in srgb, var(--ring, var(--color-border)) 22%, #000);
    text-align: center;
    font-size: var(--size-sm);
    font-weight: 700;
    line-height: 1.35;
    font-variant-numeric: tabular-nums;
}

/* The channel pulls the flex box's own bottom edge inwards, and the count is
   anchored to it — so it takes the same number back to stay on the ring. */
.talent-tile--art .talent-tile__took {
    margin-bottom: calc(-0.4rem - var(--art-inset));
}

.talent-tile__took:empty {
    display: none;
}

/*
 * Two talents behind one node, flagged the way the game flags it: a point out
 * of each side, in the ring's own colour, clear of it rather than crossing it.
 * Drawn as a chevron rather than a solid wedge. Its size comes out of the
 * column gap: ring, clearance and mark have to fit twice over, which at 28px
 * leaves 8px of mark a side.
 */
.talent-tile--choice .talent-tile__disc::before,
.talent-tile--choice .talent-tile__disc::after {
    content: "";
    position: absolute;
    top: 50%;
    width: 8px;
    height: 26px;
    background: var(--ring, var(--color-border));
    transform: translateY(-50%);
}

.talent-tile--choice .talent-tile__disc::before {
    right: calc(100% + var(--ring-width, 0px) + 1px);
    clip-path: polygon(100% 0, 0 50%, 100% 100%, 100% 76%, 34% 50%, 100% 24%);
}

.talent-tile--choice .talent-tile__disc::after {
    left: calc(100% + var(--ring-width, 0px) + 1px);
    clip-path: polygon(0 0, 100% 50%, 0 100%, 0 76%, 66% 50%, 0 24%);
}

/* --------------------------------------------------------------- tooltip */

/* Hover-only, like the race bar's — the tile's own number is the readable copy. */
.talent-tile__tip {
    --tip-x: -50%;

    position: absolute;
    bottom: calc(100% + var(--space-2));
    left: 50%;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    width: var(--tip-width);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface-raised);
    color: var(--color-text);
    text-align: left;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transform: translate(var(--tip-x), var(--space-1));
    transition:
        opacity var(--motion-fast) var(--ease),
        transform var(--motion-fast) var(--ease),
        visibility var(--motion-fast);
}

.talent-tile:hover .talent-tile__tip {
    opacity: 1;
    visibility: visible;
    transform: translate(var(--tip-x), 0);
}

/* Flipped under the tile when there is no room over it — app.js measures. */
.talent-tile--below .talent-tile__tip {
    top: calc(100% + var(--space-2));
    bottom: auto;
    transform: translate(var(--tip-x), calc(var(--space-1) * -1));
}

.talent-tile__head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.talent-tile__name {
    color: var(--color-text);
    font-size: var(--size-md);
    font-weight: 700;
    line-height: 1.3;
}

/* The game's own two-column header: cost against range, cast against cooldown. */
.talent-tile__casting {
    display: grid;
    grid-template-columns: auto auto;
    gap: var(--space-1) var(--space-4);
    margin-top: calc(var(--space-2) * -1);
}

.talent-tile__cast {
    color: var(--color-text-soft);
    font-size: var(--size-sm);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
}

.talent-tile__cast--right {
    text-align: right;
}

.talent-tile__text {
    color: var(--color-spell);
    font-size: var(--size-sm);
    line-height: 1.55;
    font-weight: 400;
    white-space: pre-line;
}

.talent-tile__ladder {
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
    color: var(--color-muted);
    font-size: var(--size-xs);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
}

.talent-tile__ladder b {
    color: var(--color-text);
}

.talent-tile__dot {
    padding: 0 0.375em;
}

.talent-bars {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* Two rows: the option's icon and name, then how much of the page took it. */
.talent-bar {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: var(--space-1) var(--space-3);
    align-items: center;
}

/* The sheet again, at the size a tooltip draws it. */
.talent-tile__crest,
.talent-bar__art {
    flex: none;
    border-radius: 50%;
    background-image: var(--spell-sheet);
    background-repeat: no-repeat;
    background-size: 100% auto;
    background-position: 0 calc(var(--cell) * 100% / (var(--spell-cells) - 1));
}

.talent-tile__crest {
    width: 1.75rem;
    height: 1.75rem;
}

.talent-bar__art {
    grid-row: 1;
    width: 1.25rem;
    height: 1.25rem;
}

.talent-bar__name {
    grid-row: 1;
    grid-column: 2 / -1;
    color: var(--color-text);
    font-size: var(--size-sm);
    font-weight: 400;
}

/* A rank has no icon, so nothing holds the first column open and its name
   would sit one gap right of the track under it. */
.talent-bar--rank .talent-bar__name {
    grid-column: 1 / -1;
}

/* A rank is a block of three: its label, its bar, and what the talent reads at
   it. The rule is what stops that text from reading as the next rank's. */
.talent-bar--rank + .talent-bar--rank {
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.talent-bar__track {
    grid-row: 2;
    grid-column: 1 / 3;
    height: 6px;
    border-radius: 3px;
    background: var(--color-border);
}

.talent-bar__fill {
    display: block;
    width: calc(var(--share) * 1%);
    height: 100%;
    border-radius: inherit;
    background: var(--class-color, var(--color-border-strong));
}

.talent-bar__text {
    grid-row: 3;
    grid-column: 1 / -1;
    color: var(--color-spell);
    font-size: var(--size-sm);
    line-height: 1.55;
    white-space: pre-line;
}

.talent-bar__took {
    grid-row: 2;
    color: var(--color-text);
    font-size: var(--size-xs);
    font-variant-numeric: tabular-nums;
}

/* A tooltip is far wider than a tile, so the ends of a row anchor theirs inward
   rather than hanging half of it off the grid. */
.talent-tile--first .talent-tile__tip {
    --tip-x: 0;

    left: 0;
}

.talent-tile--last .talent-tile__tip {
    --tip-x: 0;

    right: 0;
    left: auto;
}

/* ------------------------------------------------------------------- pvp */

/*
 * PvP talents are the same tile with no grid to sit in, so each keeps its name
 * under it: a bare count identifies nothing without a cell to read it from.
 */
.pvp-row {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-start;
    gap: var(--space-5) var(--space-4);
    list-style: none;
}

.pvp-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    width: calc(var(--tile-size) * 2);
}

.pvp-cell__name {
    color: var(--color-text-soft);
    font-size: var(--size-xs);
    line-height: 1.3;
    text-align: center;
    text-wrap: balance;
}

/*
 * The row wraps, so no tile knows whether it sits at an edge the way a grid
 * column does — and a tooltip anchored to one would hang off the page. Taking
 * the positioning off the tile hands it to the row, which the tooltip then
 * spans, so it never leaves the column.
 */
.talent-tile--pvp {
    position: static;
}

/*
 * Anchored to the row rather than the tile, so it can never leave the column —
 * but capped and centred inside it: stretched to the full row a one-sentence
 * talent ran the whole 45rem, which is twice the measure the grid's tooltips
 * read at. `max-width` rather than `width`, so a narrow screen still shrinks it.
 */
.talent-tile--pvp .talent-tile__tip {
    --tip-x: 0px;
    --tip-above: 100%;
    --tip-below: 100%;

    right: auto;
    bottom: calc(var(--tip-above) + var(--space-2));
    left: 0;
    width: max-content;
    max-width: min(calc(var(--tip-width) * 1.5), 100%);
    transform: translate(var(--tip-x), var(--space-1));
}

/*
 * Flipping one of these needs its own rule: `.talent-tile--below` and the rule
 * above have the same specificity, and this one comes later — so the `bottom`
 * here survived the flip and the box was stretched between a `top` and a
 * `bottom` at once, collapsing to a strip its text spilled out of.
 */
.talent-tile--pvp.talent-tile--below .talent-tile__tip {
    top: calc(var(--tip-below) + var(--space-2));
    bottom: auto;
    transform: translate(var(--tip-x), calc(var(--space-1) * -1));
}

/* The open state settles the vertical nudge, and must keep the horizontal one:
   `translateY` alone here threw `--tip-x` away, and the tooltip snapped back to
   the row's left edge the moment it became visible. */
.talent-tile--pvp:hover .talent-tile__tip {
    transform: translate(var(--tip-x), 0);
}

/*
 * The widest tree is 734px, and the content column only reaches that on a
 * window of about 66em. Below it the box has to scroll — and an `overflow-x`
 * box clips both axes, so it also has to carry the room a tooltip needs over
 * the top row. 48rem clears the tallest the site draws, `Rider's Champion` at
 * the narrow tooltip width: 713px. The padding is inert, or it would swallow
 * the hover of the panel it reaches over.
 */
@media (max-width: 66em) {
    .talent-panel__scroll {
        --tip-reserve: 48rem;

        overflow-x: auto;
        margin-top: calc(var(--tip-reserve) * -1);
        padding-top: var(--tip-reserve);
    }

    /* Under the tile a tooltip would fall out of the box the grid scrolls in,
       so at this width it always stands over the tile. */
    .talent-tile--below .talent-tile__tip {
        top: auto;
        bottom: calc(100% + var(--space-2));
    }
}

/* narrow */
@media (max-width: 44em) {
    .talent-panel__center--heroes {
        grid-auto-flow: row;
        grid-auto-columns: auto;
        gap: var(--space-6);
    }

    .talent-panel__scroll {
        margin-right: calc(var(--space-4) * -1);
        margin-left: calc(var(--space-4) * -1);
        padding-right: var(--space-4);
        padding-left: var(--space-4);
    }

    .talent-tile__tip {
        --tip-width: 15rem;
        --tip-width-hero: 13rem;
    }
}

/* The lift is decoration; the tooltip carries everything it says. */
@media (prefers-reduced-motion: reduce) {
    .talent-tile__disc {
        transition: none;
    }

    .talent-tile:hover .talent-tile__disc {
        transform: none;
    }
}

/* ------------------------------------------------------------------- gear */

/*
 * What a ladder wears, one `.tally-card` per slot. The chrome and the icon are
 * in `54-tally.css`; this is the quality colour the gear section adds to them,
 * and what the panel puts in its bands.
 *
 * Columns rather than a grid: the cards are 1 to 5 rows tall, and a grid row is
 * as tall as its tallest card — which left a five-row trinket card sitting
 * beside three rows of nothing. Columns pack them, at the cost of reading down
 * one column and then the other rather than across.
 */
.gear__slots {
    /* The width is the fallback, not the target: two columns of the 45rem
       content column come out at 21.9rem each, and naming that as the minimum
       leaves no room for the gutter — the browser drops to one column. 20rem
       holds two here and one on a phone. */
    columns: 20rem 2;
    column-gap: 18px;
    list-style: none;
}

/* The quality colour is the only colour the section carries, and it is what
   tells a season's PvP piece from whatever the character kept from PvE. */
.gear-item__name {
    color: var(--quality-color, var(--color-text));
}

.gear-item__icon {
    outline-color: var(--quality-color, var(--color-border-strong));
}

/* ------------------------------------------------------------- tooltip */

.gear-item__tip {
    --tip-width: var(--gear-tip-width);
}

.gear-item__tip-crest {
    grid-row: 1 / span 3;
    outline-color: var(--quality-color, var(--color-border-strong));
}

/* The quality colour, over the shared name's plain one. */
.gear-item__tip-name {
    color: var(--quality-color, var(--color-text));
}

/* The slot and the set, between the name and the share. */
.gear-item__tip-where {
    color: var(--color-muted);
    font-size: var(--size-xs);
}

.gear-item__lines {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    list-style: none;
    line-height: 1.55;
    text-wrap: pretty;
}

.gear-item__lines--figures {
    gap: 0;
    font-variant-numeric: tabular-nums;
}

/* Green is the game's own for what an item grants on top of its stat block, and
   it is what tells the secondaries from the primaries in the same list. */
.gear-item__lines--bonus,
.gear-item__line--bonus {
    color: var(--color-bonus);
}


/* ----------------------------------------------------------------- extras */

/*
 * What a ladder adds to the gear it wears. Two sections, both made of
 * `.tally-card`: one card per enchanted slot, then the gems and the
 * embellishments side by side under a heading of their own.
 *
 * They are split because the two halves pack differently — the slot cards run
 * one or two rows each and belong in columns, while a gem list is one tall card
 * that left the columns half empty when it shared them.
 */
.extras__slots {
    columns: 20rem 2;
    column-gap: 18px;
    list-style: none;
}

/* Two side by side, one on a phone: these are long lists rather than the slot
   cards' one or two rows, so they do not pack and are placed instead. */
.extras__pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 18px;
    align-items: start;
}

/* Narrower than a gear panel: it holds a name and a sentence rather than five
   bands, and at the gear width an enchant's line sat in a very wide box. */
.extra-row__tip {
    --tip-width: 21rem;
}

/*
 * The pieces an embellishment is crafted onto, one band a slot — the slot takes
 * the label column the facts use, so the panel stays one list down its left.
 */
.extra-row__piece + .extra-row__piece {
    /* One rule between two slots, not one under every band: they are one block. */
    border-top: 0;
    padding-top: 0;
}

.extra-row__piece-of {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-2) var(--space-3);
    align-items: center;
}

/* The piece is an item and carries a quality, the way a gear row does — which
   is also what tells one `Thalassian Competitor's` piece from the next. */
.extra-row__piece-name {
    grid-column: 1 / -1;
    color: var(--quality-color, var(--color-text-soft));
    line-height: 1.45;
    text-wrap: pretty;
}

/* Small enough to sit in the line of text rather than beside it. */
.extra-row__pip {
    display: inline-block;
    width: 16px;
    height: 16px;
    margin-right: var(--space-2);
    border-radius: 50%;
    outline: 1px solid var(--quality-color, var(--color-border-strong));
    background-color: var(--color-track);
    background-clip: content-box;
    vertical-align: -3px;
}

.extra-row__piece-wore {
    color: var(--color-muted);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    white-space: nowrap;
}

/* A gem is a real item and carries a quality; an enchant does not, and the row
   declares no modifier for one — so both fall back to the plain border rather
   than to `common`, which is white and would have rung an enchant brighter than
   any gem in the list beside it. */
.extra-row__icon,
.extra-row__crest {
    outline-color: var(--quality-color, var(--color-border-strong));
}

.extra-row__name,
.extra-row__tip-name {
    color: var(--quality-color, var(--color-text));
}

/* The crest spans the header's text lines, the way the gear panel's does. */
.extra-row__crest {
    grid-row: 1 / span 2;
}

/* Green is the game's own for what a thing grants on top of an item's stats,
   which is the colour the gear panel's effect lines already carry. */
.extra-row__effect {
    color: var(--color-bonus);
    font-size: var(--size-xs);
    line-height: 1.55;
    text-wrap: pretty;
}

/* ------------------------------------------------------------------ icons */

/*
 * Generated by scripts/icons.sh — edit the script, not this file.
 *
 * One sheet, one column of cells. `background-size: 100% auto` scales it to
 * whatever square the element is, and the offset is a percentage because CSS
 * resolves that against (sheet height - element height) rather than the
 * element — so cell N sits right at every size, from the 16px sidebar to the
 * 40px page header, with no per-size arithmetic.
 *
 * `sprite.05797335.webp` is rewritten to the hashed filename when the site is generated.
 */

.icon {
    display: inline-block;
    flex: none;
    width: var(--icon-size, 1rem);
    height: var(--icon-size, 1rem);
    border-radius: 50%;
    background-image: url("sprite.05797335.webp");
    background-repeat: no-repeat;
    background-size: 100% auto;

    /*
     * The ring is drawn inside the box, not around it: an ancestor that clips
     * its overflow — a name cell truncating a long label — would cut a ring
     * that reached outside, and the icon's box stays exactly --icon-size.
     *
     * It is the class colour unless the icon says otherwise: a faction crest
     * belongs to neither class nor specialization, so it sets --icon-ring.
     *
     * The second shadow is the channel between the art and the ring, painted
     * under the first so only the part the ring does not cover shows. Both are
     * inset, so the art is what shrinks and the box never grows.
     */
    box-shadow:
        inset 0 0 0 2px
            var(--icon-ring, var(--class-color, var(--color-border-strong))),
        inset 0 0 0 calc(2px + var(--icon-channel, 1px)) var(--color-bg);
}

.icon--deathknight {
    background-position: 0 0.0000%;
}

.icon--demonhunter {
    background-position: 0 0.7299%;
}

.icon--druid {
    background-position: 0 1.4599%;
}

.icon--evoker {
    background-position: 0 2.1898%;
}

.icon--hunter {
    background-position: 0 2.9197%;
}

.icon--mage {
    background-position: 0 3.6496%;
}

.icon--monk {
    background-position: 0 4.3796%;
}

.icon--paladin {
    background-position: 0 5.1095%;
}

.icon--priest {
    background-position: 0 5.8394%;
}

.icon--rogue {
    background-position: 0 6.5693%;
}

.icon--shaman {
    background-position: 0 7.2993%;
}

.icon--warlock {
    background-position: 0 8.0292%;
}

.icon--warrior {
    background-position: 0 8.7591%;
}

.icon--deathknight-blood {
    background-position: 0 9.4891%;
}

.icon--deathknight-frost {
    background-position: 0 10.2190%;
}

.icon--deathknight-unholy {
    background-position: 0 10.9489%;
}

.icon--demonhunter-devourer {
    background-position: 0 11.6788%;
}

.icon--demonhunter-havoc {
    background-position: 0 12.4088%;
}

.icon--demonhunter-vengeance {
    background-position: 0 13.1387%;
}

.icon--druid-balance {
    background-position: 0 13.8686%;
}

.icon--druid-feral {
    background-position: 0 14.5985%;
}

.icon--druid-guardian {
    background-position: 0 15.3285%;
}

.icon--druid-restoration {
    background-position: 0 16.0584%;
}

.icon--evoker-augmentation {
    background-position: 0 16.7883%;
}

.icon--evoker-devastation {
    background-position: 0 17.5182%;
}

.icon--evoker-preservation {
    background-position: 0 18.2482%;
}

.icon--hunter-beastmastery {
    background-position: 0 18.9781%;
}

.icon--hunter-marksmanship {
    background-position: 0 19.7080%;
}

.icon--hunter-survival {
    background-position: 0 20.4380%;
}

.icon--mage-arcane {
    background-position: 0 21.1679%;
}

.icon--mage-fire {
    background-position: 0 21.8978%;
}

.icon--mage-frost {
    background-position: 0 22.6277%;
}

.icon--monk-brewmaster {
    background-position: 0 23.3577%;
}

.icon--monk-mistweaver {
    background-position: 0 24.0876%;
}

.icon--monk-windwalker {
    background-position: 0 24.8175%;
}

.icon--paladin-holy {
    background-position: 0 25.5474%;
}

.icon--paladin-protection {
    background-position: 0 26.2774%;
}

.icon--paladin-retribution {
    background-position: 0 27.0073%;
}

.icon--priest-discipline {
    background-position: 0 27.7372%;
}

.icon--priest-holy {
    background-position: 0 28.4672%;
}

.icon--priest-shadow {
    background-position: 0 29.1971%;
}

.icon--rogue-assassination {
    background-position: 0 29.9270%;
}

.icon--rogue-outlaw {
    background-position: 0 30.6569%;
}

.icon--rogue-subtlety {
    background-position: 0 31.3869%;
}

.icon--shaman-elemental {
    background-position: 0 32.1168%;
}

.icon--shaman-enhancement {
    background-position: 0 32.8467%;
}

.icon--shaman-restoration {
    background-position: 0 33.5766%;
}

.icon--warlock-affliction {
    background-position: 0 34.3066%;
}

.icon--warlock-demonology {
    background-position: 0 35.0365%;
}

.icon--warlock-destruction {
    background-position: 0 35.7664%;
}

.icon--warrior-arms {
    background-position: 0 36.4964%;
}

.icon--warrior-fury {
    background-position: 0 37.2263%;
}

.icon--warrior-protection {
    background-position: 0 37.9562%;
}

.icon--hero-aldrachi-reaver {
    background-position: 0 38.6861%;
}

.icon--hero-annihilator {
    background-position: 0 39.4161%;
}

.icon--hero-archon {
    background-position: 0 40.1460%;
}

.icon--hero-chronowarden {
    background-position: 0 40.8759%;
}

.icon--hero-colossus {
    background-position: 0 41.6058%;
}

.icon--hero-conduit-of-the-celestials {
    background-position: 0 42.3358%;
}

.icon--hero-dark-ranger {
    background-position: 0 43.0657%;
}

.icon--hero-deathbringer {
    background-position: 0 43.7956%;
}

.icon--hero-deathstalker {
    background-position: 0 44.5255%;
}

.icon--hero-diabolist {
    background-position: 0 45.2555%;
}

.icon--hero-druid-of-the-claw {
    background-position: 0 45.9854%;
}

.icon--hero-elunes-chosen {
    background-position: 0 46.7153%;
}

.icon--hero-farseer {
    background-position: 0 47.4453%;
}

.icon--hero-fatebound {
    background-position: 0 48.1752%;
}

.icon--hero-fel-scarred {
    background-position: 0 48.9051%;
}

.icon--hero-flameshaper {
    background-position: 0 49.6350%;
}

.icon--hero-frostfire {
    background-position: 0 50.3650%;
}

.icon--hero-hellcaller {
    background-position: 0 51.0949%;
}

.icon--hero-herald-of-the-sun {
    background-position: 0 51.8248%;
}

.icon--hero-keeper-of-the-grove {
    background-position: 0 52.5547%;
}

.icon--hero-lightsmith {
    background-position: 0 53.2847%;
}

.icon--hero-master-of-harmony {
    background-position: 0 54.0146%;
}

.icon--hero-mountain-thane {
    background-position: 0 54.7445%;
}

.icon--hero-oracle {
    background-position: 0 55.4745%;
}

.icon--hero-pack-leader {
    background-position: 0 56.2044%;
}

.icon--hero-rider-of-the-apocalypse {
    background-position: 0 56.9343%;
}

.icon--hero-sanlayn {
    background-position: 0 57.6642%;
}

.icon--hero-scalecommander {
    background-position: 0 58.3942%;
}

.icon--hero-sentinel {
    background-position: 0 59.1241%;
}

.icon--hero-shado-pan {
    background-position: 0 59.8540%;
}

.icon--hero-slayer {
    background-position: 0 60.5839%;
}

.icon--hero-soul-harvester {
    background-position: 0 61.3139%;
}

.icon--hero-spellslinger {
    background-position: 0 62.0438%;
}

.icon--hero-stormbringer {
    background-position: 0 62.7737%;
}

.icon--hero-sunfury {
    background-position: 0 63.5036%;
}

.icon--hero-templar {
    background-position: 0 64.2336%;
}

.icon--hero-totemic {
    background-position: 0 64.9635%;
}

.icon--hero-trickster {
    background-position: 0 65.6934%;
}

.icon--hero-void-scarred {
    background-position: 0 66.4234%;
}

.icon--hero-voidweaver {
    background-position: 0 67.1533%;
}

.icon--hero-wildstalker {
    background-position: 0 67.8832%;
}

.icon--faction-alliance {
    background-position: 0 68.6131%;
}

.icon--faction-horde {
    background-position: 0 69.3431%;
}

.icon--race-blood-elf {
    background-position: 0 70.0730%;
}

.icon--race-dark-iron-dwarf {
    background-position: 0 70.8029%;
}

.icon--race-dracthyr {
    background-position: 0 71.5328%;
}

.icon--race-draenei {
    background-position: 0 72.2628%;
}

.icon--race-dwarf {
    background-position: 0 72.9927%;
}

.icon--race-earthen {
    background-position: 0 73.7226%;
}

.icon--race-gnome {
    background-position: 0 74.4526%;
}

.icon--race-goblin {
    background-position: 0 75.1825%;
}

.icon--race-haranir {
    background-position: 0 75.9124%;
}

.icon--race-highmountain-tauren {
    background-position: 0 76.6423%;
}

.icon--race-human {
    background-position: 0 77.3723%;
}

.icon--race-kul-tiran {
    background-position: 0 78.1022%;
}

.icon--race-lightforged-draenei {
    background-position: 0 78.8321%;
}

.icon--race-maghar-orc {
    background-position: 0 79.5620%;
}

.icon--race-mechagnome {
    background-position: 0 80.2920%;
}

.icon--race-night-elf {
    background-position: 0 81.0219%;
}

.icon--race-nightborne {
    background-position: 0 81.7518%;
}

.icon--race-orc {
    background-position: 0 82.4818%;
}

.icon--race-pandaren {
    background-position: 0 83.2117%;
}

.icon--race-tauren {
    background-position: 0 83.9416%;
}

.icon--race-troll {
    background-position: 0 84.6715%;
}

.icon--race-undead {
    background-position: 0 85.4015%;
}

.icon--race-void-elf {
    background-position: 0 86.1314%;
}

.icon--race-vulpera {
    background-position: 0 86.8613%;
}

.icon--race-worgen {
    background-position: 0 87.5912%;
}

.icon--race-zandalari-troll {
    background-position: 0 88.3212%;
}

.icon--card-back {
    background-position: 0 89.0511%;
}

.icon--card-chest {
    background-position: 0 89.7810%;
}

.icon--card-embellishment {
    background-position: 0 90.5109%;
}

.icon--card-feet {
    background-position: 0 91.2409%;
}

.icon--card-gem {
    background-position: 0 91.9708%;
}

.icon--card-hands {
    background-position: 0 92.7007%;
}

.icon--card-head {
    background-position: 0 93.4307%;
}

.icon--card-legs {
    background-position: 0 94.1606%;
}

.icon--card-main-hand {
    background-position: 0 94.8905%;
}

.icon--card-neck {
    background-position: 0 95.6204%;
}

.icon--card-off-hand {
    background-position: 0 96.3504%;
}

.icon--card-ring {
    background-position: 0 97.0803%;
}

.icon--card-shoulder {
    background-position: 0 97.8102%;
}

.icon--card-trinket {
    background-position: 0 98.5401%;
}

.icon--card-waist {
    background-position: 0 99.2701%;
}

.icon--card-wrist {
    background-position: 0 100.0000%;
}

/* ------------------------- items icons, built from the crawl, per spec */

.items--deathknight-blood {
    --item-sheet: url("items/deathknight-blood.4ae8ad1f.webp");
    --item-cells: 213;
}

.items--deathknight-frost {
    --item-sheet: url("items/deathknight-frost.008ec08c.webp");
    --item-cells: 91;
}

.items--deathknight-unholy {
    --item-sheet: url("items/deathknight-unholy.615eb987.webp");
    --item-cells: 69;
}

.items--demonhunter-devourer {
    --item-sheet: url("items/demonhunter-devourer.aad98a92.webp");
    --item-cells: 121;
}

.items--demonhunter-havoc {
    --item-sheet: url("items/demonhunter-havoc.cbad3060.webp");
    --item-cells: 105;
}

.items--demonhunter-vengeance {
    --item-sheet: url("items/demonhunter-vengeance.29ddca78.webp");
    --item-cells: 153;
}

.items--druid-balance {
    --item-sheet: url("items/druid-balance.9a701b91.webp");
    --item-cells: 89;
}

.items--druid-feral {
    --item-sheet: url("items/druid-feral.0c41df9a.webp");
    --item-cells: 134;
}

.items--druid-guardian {
    --item-sheet: url("items/druid-guardian.583201ad.webp");
    --item-cells: 138;
}

.items--druid-restoration {
    --item-sheet: url("items/druid-restoration.9616d2e9.webp");
    --item-cells: 88;
}

.items--evoker-augmentation {
    --item-sheet: url("items/evoker-augmentation.75e24ab3.webp");
    --item-cells: 140;
}

.items--evoker-devastation {
    --item-sheet: url("items/evoker-devastation.ab81cc87.webp");
    --item-cells: 95;
}

.items--evoker-preservation {
    --item-sheet: url("items/evoker-preservation.e781ff2f.webp");
    --item-cells: 83;
}

.items--hunter-beastmastery {
    --item-sheet: url("items/hunter-beastmastery.5dcf4bff.webp");
    --item-cells: 73;
}

.items--hunter-marksmanship {
    --item-sheet: url("items/hunter-marksmanship.559f681a.webp");
    --item-cells: 100;
}

.items--hunter-survival {
    --item-sheet: url("items/hunter-survival.1885aaea.webp");
    --item-cells: 102;
}

.items--mage-arcane {
    --item-sheet: url("items/mage-arcane.59fe99d4.webp");
    --item-cells: 87;
}

.items--mage-fire {
    --item-sheet: url("items/mage-fire.52db0dee.webp");
    --item-cells: 71;
}

.items--mage-frost {
    --item-sheet: url("items/mage-frost.6806215a.webp");
    --item-cells: 71;
}

.items--monk-brewmaster {
    --item-sheet: url("items/monk-brewmaster.e1315f40.webp");
    --item-cells: 238;
}

.items--monk-mistweaver {
    --item-sheet: url("items/monk-mistweaver.e30d9478.webp");
    --item-cells: 108;
}

.items--monk-windwalker {
    --item-sheet: url("items/monk-windwalker.ed35731b.webp");
    --item-cells: 107;
}

.items--paladin-holy {
    --item-sheet: url("items/paladin-holy.b7ad6712.webp");
    --item-cells: 128;
}

.items--paladin-protection {
    --item-sheet: url("items/paladin-protection.ddd8f063.webp");
    --item-cells: 188;
}

.items--paladin-retribution {
    --item-sheet: url("items/paladin-retribution.f6c7a901.webp");
    --item-cells: 85;
}

.items--priest-discipline {
    --item-sheet: url("items/priest-discipline.2c855e1f.webp");
    --item-cells: 103;
}

.items--priest-holy {
    --item-sheet: url("items/priest-holy.94eec5c7.webp");
    --item-cells: 81;
}

.items--priest-shadow {
    --item-sheet: url("items/priest-shadow.1579f7ab.webp");
    --item-cells: 84;
}

.items--rogue-assassination {
    --item-sheet: url("items/rogue-assassination.00f5604e.webp");
    --item-cells: 77;
}

.items--rogue-outlaw {
    --item-sheet: url("items/rogue-outlaw.62e36538.webp");
    --item-cells: 110;
}

.items--rogue-subtlety {
    --item-sheet: url("items/rogue-subtlety.dd3a6cf8.webp");
    --item-cells: 79;
}

.items--shaman-elemental {
    --item-sheet: url("items/shaman-elemental.66cb990c.webp");
    --item-cells: 83;
}

.items--shaman-enhancement {
    --item-sheet: url("items/shaman-enhancement.16d78ac9.webp");
    --item-cells: 85;
}

.items--shaman-restoration {
    --item-sheet: url("items/shaman-restoration.ff095e7f.webp");
    --item-cells: 93;
}

.items--warlock-affliction {
    --item-sheet: url("items/warlock-affliction.71554472.webp");
    --item-cells: 75;
}

.items--warlock-demonology {
    --item-sheet: url("items/warlock-demonology.f4e5e30c.webp");
    --item-cells: 133;
}

.items--warlock-destruction {
    --item-sheet: url("items/warlock-destruction.fdd422ac.webp");
    --item-cells: 85;
}

.items--warrior-arms {
    --item-sheet: url("items/warrior-arms.8fe92fcd.webp");
    --item-cells: 72;
}

.items--warrior-fury {
    --item-sheet: url("items/warrior-fury.11a0cd73.webp");
    --item-cells: 134;
}

.items--warrior-protection {
    --item-sheet: url("items/warrior-protection.ef7f0121.webp");
    --item-cells: 204;
}

/* ------------------------- spells icons, built from the crawl, per spec */

.talents--deathknight-blood {
    --spell-sheet: url("spells/deathknight-blood.07dd4fc3.webp");
    --spell-cells: 137;
}

.talents--deathknight-frost {
    --spell-sheet: url("spells/deathknight-frost.08ccf79c.webp");
    --spell-cells: 132;
}

.talents--deathknight-unholy {
    --spell-sheet: url("spells/deathknight-unholy.e2273cb7.webp");
    --spell-cells: 132;
}

.talents--demonhunter-devourer {
    --spell-sheet: url("spells/demonhunter-devourer.fd0652bb.webp");
    --spell-cells: 126;
}

.talents--demonhunter-havoc {
    --spell-sheet: url("spells/demonhunter-havoc.defde9b5.webp");
    --spell-cells: 128;
}

.talents--demonhunter-vengeance {
    --spell-sheet: url("spells/demonhunter-vengeance.57004d72.webp");
    --spell-cells: 136;
}

.talents--druid-balance {
    --spell-sheet: url("spells/druid-balance.9174c28f.webp");
    --spell-cells: 150;
}

.talents--druid-feral {
    --spell-sheet: url("spells/druid-feral.5c7117f1.webp");
    --spell-cells: 146;
}

.talents--druid-guardian {
    --spell-sheet: url("spells/druid-guardian.cc496ac4.webp");
    --spell-cells: 153;
}

.talents--druid-restoration {
    --spell-sheet: url("spells/druid-restoration.c063333e.webp");
    --spell-cells: 151;
}

.talents--evoker-augmentation {
    --spell-sheet: url("spells/evoker-augmentation.106a9d61.webp");
    --spell-cells: 143;
}

.talents--evoker-devastation {
    --spell-sheet: url("spells/evoker-devastation.6187fc12.webp");
    --spell-cells: 131;
}

.talents--evoker-preservation {
    --spell-sheet: url("spells/evoker-preservation.4248371f.webp");
    --spell-cells: 135;
}

.talents--hunter-beastmastery {
    --spell-sheet: url("spells/hunter-beastmastery.f48af346.webp");
    --spell-cells: 134;
}

.talents--hunter-marksmanship {
    --spell-sheet: url("spells/hunter-marksmanship.05e96814.webp");
    --spell-cells: 136;
}

.talents--hunter-survival {
    --spell-sheet: url("spells/hunter-survival.5fd24db6.webp");
    --spell-cells: 132;
}

.talents--mage-arcane {
    --spell-sheet: url("spells/mage-arcane.a2818e61.webp");
    --spell-cells: 134;
}

.talents--mage-fire {
    --spell-sheet: url("spells/mage-fire.244c1ffc.webp");
    --spell-cells: 129;
}

.talents--mage-frost {
    --spell-sheet: url("spells/mage-frost.673b69e0.webp");
    --spell-cells: 134;
}

.talents--monk-brewmaster {
    --spell-sheet: url("spells/monk-brewmaster.a279e5c7.webp");
    --spell-cells: 159;
}

.talents--monk-mistweaver {
    --spell-sheet: url("spells/monk-mistweaver.2cfc31ae.webp");
    --spell-cells: 160;
}

.talents--monk-windwalker {
    --spell-sheet: url("spells/monk-windwalker.0d943072.webp");
    --spell-cells: 155;
}

.talents--paladin-holy {
    --spell-sheet: url("spells/paladin-holy.0c5380e6.webp");
    --spell-cells: 157;
}

.talents--paladin-protection {
    --spell-sheet: url("spells/paladin-protection.bac7e241.webp");
    --spell-cells: 154;
}

.talents--paladin-retribution {
    --spell-sheet: url("spells/paladin-retribution.95203cbd.webp");
    --spell-cells: 142;
}

.talents--priest-discipline {
    --spell-sheet: url("spells/priest-discipline.733468d5.webp");
    --spell-cells: 141;
}

.talents--priest-holy {
    --spell-sheet: url("spells/priest-holy.d615e918.webp");
    --spell-cells: 139;
}

.talents--priest-shadow {
    --spell-sheet: url("spells/priest-shadow.71547a78.webp");
    --spell-cells: 143;
}

.talents--rogue-assassination {
    --spell-sheet: url("spells/rogue-assassination.b240f347.webp");
    --spell-cells: 138;
}

.talents--rogue-outlaw {
    --spell-sheet: url("spells/rogue-outlaw.f95032dd.webp");
    --spell-cells: 137;
}

.talents--rogue-subtlety {
    --spell-sheet: url("spells/rogue-subtlety.953db22b.webp");
    --spell-cells: 141;
}

.talents--shaman-elemental {
    --spell-sheet: url("spells/shaman-elemental.aa4a5ecc.webp");
    --spell-cells: 144;
}

.talents--shaman-enhancement {
    --spell-sheet: url("spells/shaman-enhancement.3f179159.webp");
    --spell-cells: 135;
}

.talents--shaman-restoration {
    --spell-sheet: url("spells/shaman-restoration.ebd2a309.webp");
    --spell-cells: 142;
}

.talents--warlock-affliction {
    --spell-sheet: url("spells/warlock-affliction.48b04f36.webp");
    --spell-cells: 128;
}

.talents--warlock-demonology {
    --spell-sheet: url("spells/warlock-demonology.6b2fda01.webp");
    --spell-cells: 129;
}

.talents--warlock-destruction {
    --spell-sheet: url("spells/warlock-destruction.b4a95046.webp");
    --spell-cells: 134;
}

.talents--warrior-arms {
    --spell-sheet: url("spells/warrior-arms.9f845581.webp");
    --spell-cells: 129;
}

.talents--warrior-fury {
    --spell-sheet: url("spells/warrior-fury.26f3365c.webp");
    --spell-cells: 130;
}

.talents--warrior-protection {
    --spell-sheet: url("spells/warrior-protection.0c8ddc8e.webp");
    --spell-cells: 136;
}
