/* ─────────────────────────────────────────────────────────────────────────
   THE ROLE LAYER — layer 2 of token → role → surface.

   The contract (docs/memory/clients/web/design-system.md → "Website semantic role system"):

     1. TOKENS are the only place a literal colour/size/weight is written.
     2. ROLES are semantic classes that consume tokens and nothing else.
        This file. "A name looks like this" is decided here, once.
     3. SURFACES apply role classes. A page may set layout — grid, spacing,
        order. A page may NOT set font, colour, weight or size.

   Classes are named for what a thing IS, never for where it lives. The
   codebase grew accidental roles named after their birthplace —
   `.study-player-name` styles event cards, the annotated index and bios —
   which is exactly why things drifted apart.

   NO ALIASES (owner call, 2026-08-04: "you didn't assign everything to a
   class first like we discussed, and these are all hot fixes"). An earlier
   version of this file listed legacy class names alongside each role so the
   style was single-sourced without touching templates. That is the same
   hot-fixing in a new file: it leaves the template silent about what a thing
   is, so the next surface gets missed and the next fix is another selector.
   Every element carries its role class in the template, and this file
   selects ONLY role classes. If a legacy name appears in a selector here,
   that is the bug.

   The test this file must pass: any sentence the owner might say — "names
   shouldn't be blue", "counts are too small", "results should be brighter" —
   is one edit, here. If a change needs two places, a role is missing or a
   surface is cheating.

   Scope: linked only for the charcoal theme, so the Space theme is
   untouched by construction. Selectors are prefixed `body.site-theme-charcoal`
   anyway, both to out-specify the per-surface rules this layer replaces and
   so the file can never repaint Space if it is ever linked more widely.
   ───────────────────────────────────────────────────────────────────────── */


/* ══ page-title / page-kicker ══════════════════════════════════════════════
   The hero lockup. The thin weight-300 display treatment belongs to the
   CLASSIC CHESS wordmark alone; every other page title is a scan-tier title
   that happens to be large. Treating them as one thing is what produced
   pages of oversized thin grey headings. */

/* The title is solid ink and nothing else.

   Five animated treatments were tried here and all five were rejected: a
   slow sweep, a fast sweep, a radial glint, per-letter glinting, and a pair
   of counter-drifting washes. The last version rested at plain white and lit
   a clear crest as it passed, which fixed the ambiguity but not the verdict:
   "hard to look at as i'm still squinting to see the colors". A title that
   asks to be studied to see whether it changed is costing the reader
   attention it does not repay, and this is the page's most-read element.

   Do not re-add an animated title without the owner asking for one. The
   title earns its emphasis from size, weight and scan-tier ink against body
   grey — which is what the colour law asks for: emphasis where it means
   something, not motion for its own sake. */
body.site-theme-charcoal .page-title {
  font-size: var(--fs-page-title);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: 0;
  overflow-wrap: anywhere;
  text-wrap: balance;
  color: var(--title-ink);

  /* The default theme paints these titles as gradient-clipped text — a
     cyan→gold prism on .header-name, a green ramp on .htb-title, a
     cyan→gold on .bio-name. Solid ink is only reachable by undoing the clip
     as well as the colour, or the glyphs stay filled by the gradient. */
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  -webkit-text-fill-color: currentColor;
  text-shadow: none;
  animation: none;
  filter: none;
}

/* The wordmark alone keeps the deployed thin display treatment. Its full
   production scale is part of the homepage identity; structure below it must
   step down instead of shrinking the mark to solve the composition. */
body.site-theme-charcoal .wordmark {
  font-size: var(--fs-wordmark);
  font-weight: 300;
  line-height: 1.22;
}
html[data-device="desktop"] body.home-page.site-theme-charcoal .wordmark {
  color: var(--tier-scan);
}

/* The kicker under a title. De-golded (0.2 — gold is punctuation, not a
   font) and deliberately NOT dropped into body grey (0.2a): it steps UP to
   scan tier and keeps a warm gold mark, so the region gains contrast rather
   than losing a colour. Tracking comes down from 8px, which was built for a
   condensed display face and reads as thin wire in Noto Sans. */
body.site-theme-charcoal .page-kicker {
  font-size: var(--fs-label);
  font-weight: 600;
  color: var(--tier-scan);
  letter-spacing: 3px;
  text-transform: uppercase;
}
body.site-theme-charcoal .page-kicker::before {
  content: '\25C6';
  color: var(--gold);
  font-size: 0.72em;
  margin-right: 0.7em;
  vertical-align: 0.1em;
  letter-spacing: 0;
}


/* The hero's third line — the one-sentence description under the divider.

   0.8: it carried a pulsing green dot, a decorative bullet that means
   nothing on a static page and reads as a live-status indicator that is not
   indicating anything. The dot is hidden rather than deleted from the
   templates because those templates also serve the Space theme, where the
   pulsing dot is part of the look. Live-status dots elsewhere are untouched.

   It also inherits weight 300 at 2px tracking from the Space display face,
   which on Noto Sans reads as thin wire. */
body.site-theme-charcoal .page-tagline {
  font-size: var(--fs-body);
  font-weight: 400;
  color: var(--tier-read);
  letter-spacing: 0.2px;
}
body.site-theme-charcoal .page-tagline .pulse-dot {
  display: none;
}


/* ══ entity-name ═══════════════════════════════════════════════════════════
   The name of a person, event, series, collection, notebook, or pairing.

   THE RULE IS MECHANICAL — is this element a link? (owner call)
     · a name that is NOT a link  → --name lilac, bold. It is identity, and
       it sits beside its own controls (the /players/ card is not clickable;
       it has dedicated star / info / arrow buttons).
     · a name that IS a link      → link cyan, unchanged. Nothing about link
       styling changes anywhere on the site.

   The test is applied by the CSS itself rather than per template, so it can
   never be answered inconsistently on a surface someone forgot. */

body.site-theme-charcoal .entity-name {
  color: var(--name);
  font-size: var(--fs-name);
  font-weight: 700;
  letter-spacing: 0.2px;
  line-height: 1.3;
  /* The name is whatever the DOM says it is. Without this, /about-the-creator/
     shouted "FIDE GLOBAL" from `.profile-platform` and said "GitHub" from
     `.social-name` two cards below — one idea, two voices, because each
     surface had made its own casing decision. */
  text-transform: none;
}
body.site-theme-charcoal a .entity-name,
body.site-theme-charcoal .entity-name a {
  color: var(--cyan);
}
/* The display form, for the one place a name IS the heading of its block
   rather than a line inside a card — the /stats/ record, which is about one
   player and says so at heading size. Same modifier the figure tiles use, so
   "the big form of this role" is one word everywhere. */
body.site-theme-charcoal .entity-name.is-display {
  font-size: clamp(1.5rem, 3vw, 2rem);
  line-height: 1.2;
}
/* The lead form, for a list whose items ARE the page — My Library's own
   shelves. At `--fs-name` the thing a reader made sat at 1.08rem under a
   2.9rem page title and beside a 900px search field, so the loudest objects
   on /library/imported/ were two controls and the quietest was the game
   (owner: "its literally nothing"). One step up is what makes the list the
   thing you look at. Not `is-display`: that is a heading, and there are many
   of these on a page. */
body.site-theme-charcoal .entity-name.is-lead {
  font-size: 1.32rem;
  line-height: 1.25;
}
body.site-theme-charcoal .pairing.is-lead .entity-name {
  font-size: 1.32rem;
  font-weight: 700;
  line-height: 1.25;
}

/* ══ surface-link ══════════════════════════════════════════════════════════
   An anchor that IS a surface — a whole card, tile or portrait frame — rather
   than a destination picked out of a line of text.

   The mechanical rule above gives a name the link voice, which is right when
   the name is the link inside a sentence or a table cell. It is wrong when the
   link wraps the card: every name, figure and label inside it takes the link
   voice at once and the card stops sounding like a card (owner, 2026-08-05:
   "they all went back to the wrong color and fonts", after the /players/ card
   text became one big anchor). The wrapping surface supplies the affordance:
   a card may use its boundary and hover, while a frameless portrait tile uses
   the photograph's own hard edge and hover. The contents keep whatever voice
   their own role gives them; a border is not required merely to justify this
   role. */

body.site-theme-charcoal a.surface-link {
  color: inherit;
  text-decoration: none;
}
body.site-theme-charcoal a.surface-link .entity-name {
  color: var(--name);
}

/* A pairing — "White vs Black" in a game table. It is a link, so the names
   in it take the link voice from the mechanical rule above with no special
   case. Two things are set here: the type scale (0.8rem tracked micro-type
   is the Space metric leaking in), and the fact that a name inside a pairing
   is a compact table cell, not a card headline, so it does not take the
   full name size. */
body.site-theme-charcoal .pairing {
  font-size: var(--fs-meta);
  letter-spacing: 0;
}
body.site-theme-charcoal .pairing .entity-name {
  font-size: inherit;
  font-weight: 600;
}
/* The word between the two names. It is not a name, so it steps down to the
   read tier — and at lead size the names run into it without the space, which
   made "Mohishunder vs Cochrane" read as one string. */
body.site-theme-charcoal .pairing-vs {
  color: var(--tier-ignore);
  font-size: var(--fs-meta);
  font-weight: 400;
  padding: 0 0.5em;
}


/* A letter standing in for an icon — the A/S/C/K and A/S/N/P squares on the
   /android/ and /about/ feature grids.

   0.8: those squares borrowed the brand chips from the creator page, so a
   feature called "Archive" wore lichess white, "Analysis" wore FIDE cyan and
   "Cast" wore chess.com green. The hues were picked by which brand class was
   nearest, which is decoration wearing the costume of meaning: cyan on a
   non-interactive square is the link colour spent on nothing, and green on a
   feature name is the win colour spent on nothing.

   A letter mark is punctuation, so it takes the gold that punctuation takes,
   on a neutral surface. The real brand chips on /about-the-creator/ keep
   their brand colours — those hues identify a real thing. */
body.site-theme-charcoal .letter-mark {
  background: var(--glass);
  color: var(--gold);
  font-weight: 700;
  font-size: 1.15rem;
}
body.site-theme-charcoal .letter-mark::after {
  border-color: var(--border);
  opacity: 1;
}


/* ══ section-title / section-count ═════════════════════════════════════════
   The heading that introduces a group of things on a page — "Historic
   Series", an era band, an about-page section — and the count beside it.
   These had no class of their own anywhere: they were bare <h2> elements
   styled by descendant selectors off whatever container they sat in, which
   is why every page's section heading was a different size and colour and
   why they all ended up smaller than the cards they introduce. A header that
   is quieter than its contents is not a header. */

body.site-theme-charcoal .section-title {
  font-size: var(--fs-section);
  font-weight: 700;
  color: var(--tier-scan);
  letter-spacing: 0.4px;
  text-transform: none;
}
/* Prose headings retain the production documentation gap and visible rules
   at phone widths as well as desktop widths. */
.section-title.is-prose { gap: 12px; }
.section-title.is-prose::before,
.section-title.is-prose::after { min-width: 12px; }

body.site-theme-charcoal .section-title.is-unruled::before,
body.site-theme-charcoal .section-title.is-unruled::after {
  background: none;
}
/* No ◆ mark here. `section-title` already spends BOTH its pseudo-elements on
   the hairlines that flank the heading, so a role that claims ::before does
   not add a mark — it replaces the left rule with a glyph that then inherits
   `flex: 1` and floats in the middle of the gap it was supposed to fill
   (owner, 2026-08-05: "that orange dot is weirdly placed"). The rules are
   already the mark. Gold stays punctuation elsewhere, where there is a slot
   for it. */
body.site-theme-charcoal .section-count {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.6px;
  color: var(--tier-ignore);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-transform: none;
}


/* ══ entry-title ═══════════════════════════════════════════════════════════
   The bold line that titles an entry whose subject is not an entity — a
   capability, a step, a row in a feature list. Distinct from `entity-name` on
   purpose: lilac means "this is the name of a thing", and a sentence like
   "Your own games, on the same board" is not the name of anything. Wearing
   the name colour there would make the rule dishonest, and the rule is only
   useful while it stays true (owner, 2026-08-05, preferring production's
   near-white on these tiles).

   Named for the entry, not for a card, because the entry is the thing that
   persists: /about/'s capabilities lost their cards the day after they
   gained them, and the title line did not change. */

body.site-theme-charcoal .entry-title {
  font-size: 1.06rem;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: 0;
  color: var(--tier-scan);
  text-transform: none;
  margin: 0;
}
/* The one entry whose subject is destruction. Settings' delete section used to
   say so by tinting its whole panel red — a box making the point. The heading
   is where the warning belongs, and it is the role that decides the colour so
   the next destructive section does not invent its own red. */
body.site-theme-charcoal .entry-title.is-danger {
  color: #e07575;
}


/* ══ work-surface-title / setting roles ════════════════════════════════════
   Account Settings is a working surface, not a poster masthead. Its title is
   therefore one step below page-title, while the names and controls beneath it
   stay at readable UI scale. These roles replace the old surface-specific
   Orbitron and tracked micro-label rules. */

body.site-theme-charcoal .work-surface-title {
  color: var(--title-ink);
  font-size: var(--fs-work-title);
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: 0;
  text-align: center;
  text-transform: none;
  width: 100%;
}
body.site-theme-charcoal .setting-name {
  color: var(--tier-read);
  font-size: var(--fs-body);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
}
body.site-theme-charcoal .setting-option-label {
  color: var(--tier-read);
  font-size: var(--fs-meta);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
body.site-theme-charcoal .setting-control,
body.site-theme-charcoal .setting-value-control {
  color: var(--tier-scan);
  font-family: inherit;
  font-size: var(--fs-meta);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
body.site-theme-charcoal .setting-control-action,
body.site-theme-charcoal .choice-control {
  font-family: inherit;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}
body.site-theme-charcoal .setting-help,
body.site-theme-charcoal .settings-save-status {
  color: var(--tier-ignore);
  font-size: var(--fs-label);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
body.site-theme-charcoal .setting-error {
  color: var(--danger);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
body.site-theme-charcoal .danger-navigation {
  color: var(--danger);
}


/* ══ entity-description ════════════════════════════════════════════════════
   The sentence or two a card gives about the thing it names. Read tier: it
   is prose, it is not a label, and it is not the name. */

body.site-theme-charcoal .entity-description {
  font-size: var(--fs-meta);
  line-height: 1.55;
  color: var(--tier-read);
  letter-spacing: 0;
  text-transform: none;
}


/* ══ entity-figure-line ════════════════════════════════════════════════════
   A line that exists to carry counts ("80 CURATED GAMES", "50 EVENTS ·
   1030 GAMES"). Distinct from entity-meta, which qualifies a name. */

/* The crown is identity punctuation: the visible SVG is brass, while the
   template carries the kind-specific accessible name. */
body.site-theme-charcoal .entity-crown {
  color: var(--gold);
}
body.site-theme-charcoal .entity-crown svg {
  width: 18px;
  height: 18px;
}

body.site-theme-charcoal .entity-figure-line {
  font-size: var(--fs-meta);
  letter-spacing: 0;
  text-transform: none;
  color: var(--tier-read);
}


/* ══ entity-portrait ═══════════════════════════════════════════════════════
   The framed image or monogram that identifies an entity. "Framed" is the
   whole point (owner: every bio's portrait must be framed): a dark mat, a
   visible edge, and an inner shadow so the image sits IN something rather
   than floating as a loose rectangle on the page. The /players/ card
   portraits already had the mat; the bio hero did not, which is why one
   looked framed and the other did not. */

body.site-theme-charcoal .entity-portrait {
  padding: 4px;
  background: #15171a;
  border: 1px solid #4a5158;
  border-radius: 4px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 2px 5px rgba(0, 0, 0, 0.45);
  color: var(--tier-ignore);
}
body.site-theme-charcoal .entity-portrait img {
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.75);
}


/* ══ entity-kind ═══════════════════════════════════════════════════════════
   The type label above a name — TOURNAMENT, COLLECTION, PRIVATE · OWNER. */

/* Ignore tier, not scan. A kind label sits directly above a name, and at scan
   weight in scan grey with a gold mark in front of it, it was reading as a
   second headline: "◆ SOVIET FINALS" over "USSR Championships" is "2 things
   competing to be the title" (owner, 2026-08-05). The category is context for
   the name, so it steps back and lets the name win. The gold ◆ stays — that
   is the mark, and dropping both the colour and the mark is exactly the grey
   wall 0.2a forbids. */
body.site-theme-charcoal .entity-kind {
  display: inline-block;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--tier-ignore);
}
body.site-theme-charcoal .entity-kind::before {
  content: '\25C6';
  color: var(--gold);
  font-size: 0.7em;
  margin-right: 0.55em;
  vertical-align: 0.1em;
  letter-spacing: 0;
  font-style: normal;
  font-weight: 400;
}

/* An epithet is a kind label with a voice: "The Combination Artist" is a
   phrase, not a category, so it drops the small-caps tracking and reads as
   a brighter italic. It was gold running text with a glow — gold used as a
   font — and de-golding it steps the line UP to scan tier rather than down
   into body grey, keeping the warm voice as the mark (0.2a).

   It restates the colour because the base role stepped down to ignore tier to
   stop categories competing with names. An epithet is not competing with a
   name — it is the hero's second line, and it is the only text on that line. */
body.site-theme-charcoal .entity-kind.is-epithet {
  color: var(--tier-scan);
  font-size: 1.02rem;
  font-style: italic;
  font-weight: 500;
  letter-spacing: 0.3px;
  text-transform: none;
}


/* ══ entity-meta ═══════════════════════════════════════════════════════════
   The qualifying line under a name — lifespan, dates, venue, epithet,
   visibility. Out of tracked micro-caps and into something readable. */

body.site-theme-charcoal .entity-meta {
  font-size: var(--fs-meta);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--tier-ignore);
  line-height: 1.45;
}
/* The separator inside a meta run is the gold mark's smaller sibling: it is
   punctuation, which is exactly what gold is for. */
body.site-theme-charcoal .entity-meta .sep,
body.site-theme-charcoal .meta-sep {
  color: var(--gold);
  padding: 0 0.15em;
}


/* ══ data-cell ═════════════════════════════════════════════════════════════
   A cell of tabular data — a date, a move count, an event name, an opening.
   The columns of a games table, in other words, which is the surface a reader
   spends the most time actually reading on this site.

   Read tier, and this is the whole point of the role. Those cells inherited
   `color: var(--white)` from `.games-table td`, and charcoal maps `--white`
   down to the ignore tier, so every date, move count and event name on every
   game list, book, beginner table and MasterDB search result rendered as
   caption grey at 0.85rem. Two of them were dimmer still, pushed there by
   inline `style="color: var(--data-secondary)"` and `font-size: 0.78rem`
   attributes that outrank every theme layer (owner, 2026-08-05: "the font of
   games needs to be improved, all game interfaces/game-search etc, dates,
   moves etc should all be easier to read").

   Ignore tier is for labels and captions. A date in a games table is neither:
   it is the answer to "when", which is one of the four questions the table
   exists to answer. */

body.site-theme-charcoal .data-cell {
  color: var(--tier-read);
  font-size: var(--fs-meta);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
/* The column heading above it. Ignore tier is right here — a heading names the
   column and then gets out of the way — but 0.75rem tracked out to 2px with a
   cyan glow behind it is below the 0.9rem interface floor and reads as wire.
   The glow goes too: text-shadow on a label is the Space display treatment. */
body.site-theme-charcoal .column-label {
  color: var(--tier-ignore);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  text-shadow: none;
}
/* ══ field-label ═══════════════════════════════════════════════════════════
   The label above a form control — SOURCE, PGN, VISIBILITY.

   Same job as `column-label`: name the thing below and get out of the way, so
   it takes the same ignore tier and the same interface floor.

   No mark. The owner asked for the gold ◆ on these (2026-08-05, image 23) and
   took it back on sight ("i changed my minds about the diamonds on the import
   box"). It stays a role of its own even though it now resolves the same as
   `column-label`, because "form labels are too small" and "column headings are
   too small" are different sentences, and the whole point of the role system
   is that each is one edit that does not touch the other. */
body.site-theme-charcoal .field-label {
  display: inline-block;
  color: var(--tier-ignore);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  text-shadow: none;
}

/* Digits that line up in a column need to line up. */
body.site-theme-charcoal .data-cell.is-numeric {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* A cell that names a thing rather than measuring one — the event a game was
   played at. It is the widest column and the one a reader skims, so it stays
   at the same size as its neighbours rather than being shrunk to fit. */
body.site-theme-charcoal .data-cell.is-name {
  color: var(--tier-read);
}


/* ══ entity-figure ═════════════════════════════════════════════════════════
   A count or statistic presented as a number. A figure is DATA, so it is
   scan tier — bright and weighty. It is not display type, and the thin muted
   weight-300 treatment must never reach it. */

body.site-theme-charcoal .entity-figure {
  font-size: 1.45rem;
  font-weight: 700;
  color: var(--tier-scan);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: 0;
}
body.site-theme-charcoal .entity-figure-label {
  display: block;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--tier-ignore);
}
/* The inline form: a count sitting in a line of text ("80 CURATED GAMES").
   The number is the data and reads first; the unit is a label. Splitting
   them is what turns a grey run-on into something glanceable. */
body.site-theme-charcoal .entity-figure-value {
  color: var(--tier-scan);
  font-weight: 700;
  /* Relative so the figure steps up from whatever line it sits in, but never
     below the 0.9rem interface floor (0.1): the /events/ series toplines set
     a ~10px kicker size, and an em-relative role inherits its way straight
     through the floor unless the floor is written into the role. */
  font-size: max(1.06em, var(--fs-label));
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
/* The display form: a big number over its label, used where the figures ARE
   the section ("by the numbers"). Layout stays with the surface; only the
   step in size lives here. */
body.site-theme-charcoal .figure-tile.is-display .entity-figure-value {
  font-size: 1.5rem;
  line-height: 1.1;
}
/* Same step for the block figure. A rating tile is nothing but its number:
   the label says which rating and the meter says how far along it is, so if
   the figure does not dominate the tile there is nothing left to look at. */
body.site-theme-charcoal .figure-tile.is-display .entity-figure {
  font-size: 2.3rem;
}
body.site-theme-charcoal .entity-figure-unit {
  color: var(--tier-ignore);
  font-weight: 500;
  letter-spacing: 0.8px;
}

/* The meaning bar (0.5): differentiation moves off the number and onto a
   coloured rule, so a row of figures is one hue per meaning instead of three
   identical golds. */
body.site-theme-charcoal .entity-figure-bar {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: var(--gold);
}
/* No margin here: the gap between a tile's bar, label and figure is layout,
   which the surface owns. */
body.site-theme-charcoal .entity-figure-bar.is-interactive { background: var(--cyan); }
body.site-theme-charcoal .entity-figure-bar.is-positive    { background: var(--mint); }
body.site-theme-charcoal .entity-figure-bar.is-negative    { background: var(--danger); }
body.site-theme-charcoal .entity-figure-bar.is-neutral     { background: var(--border); }


/* ══ result-chip ═══════════════════════════════════════════════════════════
   A game result.

   Colour on a result means VALENCE, and valence needs a subject (0.3a).
   Where a row has no subject, "1-0" is not good news or bad news — it is
   just a result — so the chip is NEUTRAL. And player archives are wins-only,
   so a red 0-1 there would actively contradict the page: the eye reads a
   loss, the reader checks the names, discovers a win, and the reconciliation
   reads as a bug. A colour must not contradict what the content says.

   Net: a table result cell is neutral unless its surface names a subject.
   Colour is also real in the viewer's per-player chips (both outcomes side by
   side, in game-board.css, canonical and unchanged) and in the /stats/ record
   figures, which use entity-figure-bar above. */

body.site-theme-charcoal .result-chip {
  /* Matches `pairing` in the same row rather than inheriting the table's
     0.85rem: a result is scan-tier data sitting next to the names it belongs
     to, and it was reading a step below them and under the 0.9rem floor. */
  font-size: var(--fs-meta);
  color: var(--tier-scan);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
}
/* `is-scored` is how a surface names its subject. One surface has one:
   Christian's own OTB archive, where every row is his game, so 0-1 is not a
   neutral fact — it is him losing (owner, 2026-08-06: "for my personal otb
   games, i like the red for losses and coloring ... this is the only place
   where this coloring makes sense to use to0").

   The subject is the player, never the colour he happened to have: blue is a
   Walls win and red is a Walls loss whether he was White or Black (owner:
   "blue = christian walls wins, red is i lose, not blue for white wins and
   red for black wins"). `protagonist_result` decides that server-side; the
   chip only paints what it is told.

   These are the hues the archive has always used, restored unchanged. Without
   `is-scored` the outcome classes carry no colour at all, so a curated
   wins-only player archive cannot end up contradicting itself. */
body.site-theme-charcoal .result-chip.is-scored.result-win  { color: var(--cyan); }
body.site-theme-charcoal .result-chip.is-scored.result-loss { color: var(--danger); }
body.site-theme-charcoal .result-chip.is-scored.result-draw { color: var(--gold); }


/* ══ result-distribution ═══════════════════════════════════════════════════
   The win/draw/loss bar. It existed twice — `.stats-opening-*` in style.css
   and `.opening-explorer-*` in game-board.css — and the two copies had
   already drifted apart on height (9px vs 8px), track alpha (0.34 vs 0.36)
   and all three segment alphas, while naming the same segments
   win/draw/loss on one surface and white/draw/black on the other. Nobody
   would ever catch that, which is the whole problem.

   One definition, three surfaces: /stats/ repertoire, the viewer's explorer
   panel, and the standalone /opening-explorer/. Colours are unchanged by
   owner call — the point is that changing them later is one edit.

   Recorded exception: draw is amber HERE while it is neutral in a result
   chip. These segments are segment identity — white's share, draws, black's
   share — not a judgement about anyone, and a three-segment bar needs three
   visible segments. This is the only place draw carries a hue. */

body.site-theme-charcoal .result-distribution {
  height: 9px;
  border-radius: 3px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.34);
  display: flex;
}
body.site-theme-charcoal .result-distribution-win {
  background: rgba(var(--mint-rgb), 0.76);
}
body.site-theme-charcoal .result-distribution-draw {
  background: rgba(var(--amber-rgb), 0.72);
}
body.site-theme-charcoal .result-distribution-loss {
  background: rgba(var(--danger-rgb), 0.72);
}


/* ══ mark ══════════════════════════════════════════════════════════════════
   The gold ▸/◆ punctuation. Gold is punctuation, not a font: it marks, it
   separates, it numbers — it is never the colour of a sentence. Marks are
   the survivors of the icon sweep because they are type, not controls. */

body.site-theme-charcoal .mark {
  color: var(--gold);
  font-weight: 400;
}
body.site-theme-charcoal .mark-rule {
  border: 0;
  border-top: 1px solid rgba(var(--gold-rgb), 0.45);
  width: 44px;
  margin: 18px 0;
}


/* ══ prose ═════════════════════════════════════════════════════════════════
   Long-form reading: about sections, bios, event narratives, legal pages,
   /api/.

   THERE IS NO `prose-surface`, AND THERE MUST NOT BE ONE. It existed for one
   round: every long-prose block got a fill, a border and an elevation shadow.
   The owner's verdict (2026-08-05, on the Staunton bio and /api/):
   "everything in a box ... this style of everything in a box was my goal to
   move away from, not towards", and "'break things up' means 'put everything
   in its own box' ... makes the issue far worse - not better".

   The reasoning, so this is not rediscovered as a good idea:

   - A box is not contrast. Giving every block the same container makes them
     all the SAME object, which is the flattening this overhaul exists to
     undo. The eye lands on whatever differs, and nothing differs.
   - What actually breaks prose up is typographic: weight, italics, colour,
     size, and the marks between beats. Those cost no ceremony and they can
     differ per beat, which a repeated rectangle cannot.
   - Boxes also spend the page's one scarce signal — "this is a distinct
     object" — on paragraphs, leaving nothing for the objects that really are
     distinct (cards, tiles, controls).

   Prose gets `prose-lede`, `prose-body` and the emphasis voice below. It does
   not get a container. Pages that were already carded before the role system
   (/about/, /about-the-creator/, /android/) keep their own card and are not
   given a second one. */

/* The container for a run of prose. On charcoal it draws NOTHING — that is
   the whole point of the role. Several about pages wrap their copy in a
   `card`, which is the same box by another name, so those containers carry
   this and give the box back. Prose sits on the page. */
body.site-theme-charcoal .prose-column {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  backdrop-filter: none;
}

/* No measure cap. An optimal-measure rule (~68ch) is good typography in the
   abstract and wrong here: it left half the content column empty and made
   the page look like it had stopped using the space it already owns (owner,
   2026-08-04: "somehow the full page isn't being used anymore like it is on
   prod"). The container's own width is the measure. */
body.site-theme-charcoal .prose-lede {
  font-size: 1.12rem;
  line-height: 1.65;
  color: var(--tier-scan);
  font-weight: 400;
}
body.site-theme-charcoal .prose-body {
  font-size: var(--fs-prose);
  line-height: 1.78;
  color: var(--tier-read);
}
body.site-theme-charcoal .prose-body + .prose-body {
  margin-top: 1.05em;
}

/* ══ ONE EMPHASIS VOICE ════════════════════════════════════════════════════
   Brighter plus a step of weight, italics preserved, never a hue. Blue on
   text that cannot be clicked is a fake link, and fake links are the single
   most common way this theme broke its own affordance rule. */
body.site-theme-charcoal .prose-emphasis,
body.site-theme-charcoal .prose-body strong,
body.site-theme-charcoal .prose-body em,
body.site-theme-charcoal .prose-lede strong,
body.site-theme-charcoal .prose-lede em {
  color: var(--tier-scan);
  font-weight: 600;
}
body.site-theme-charcoal .prose-body em,
body.site-theme-charcoal .prose-lede em {
  font-style: italic;
  font-weight: 500;
}
/* A real link inside prose is still a link. */
body.site-theme-charcoal .prose-body a,
body.site-theme-charcoal .prose-lede a {
  color: var(--cyan);
}


/* ══ pull-quote ════════════════════════════════════════════════════════════
   A quotation lifted out of the prose — the five on a player bio, the ones on
   an event page.

   They were cards. Five identical bordered rectangles stacked down a page is
   the failure the no-box law above names exactly: every quote became the same
   object, so none of them was the one worth reading. A quote is already
   marked by being a quote. What it needs is an edge to hang from, not a
   container, so the figure keeps its `card` class and this role gives the box
   back the same way `prose-column` does.

   The rule is the mark, and it carries the one distinction the page actually
   has: `is-voice` is the subject speaking and takes the gold rule and scan
   tier; a quote ABOUT the subject takes a quiet rule and read tier. That
   replaces `.bio-quote-by { color: var(--gold) }`, which painted two whole
   paragraphs and their attributions brass — gold is punctuation, never the
   colour of a sentence, and with the attribution in the same brass as the
   quote there was nothing left to separate the voice from its source. */

body.site-theme-charcoal .pull-quote {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  /* The same edge the portrait frame draws. `--border` is the fill separator
     and disappears against the page at two pixels wide, which left the quiet
     quotes reading as indented prose rather than as quotations. */
  border-left: 2px solid #4a5158;
  padding: 2px 0 2px 20px;
  margin: 0;
}
body.site-theme-charcoal .pull-quote.is-voice {
  border-left-color: var(--gold);
}
body.site-theme-charcoal .pull-quote-text {
  font-size: var(--fs-prose);
  line-height: 1.7;
  font-style: italic;
  font-weight: 400;
  color: var(--tier-read);
}
body.site-theme-charcoal .pull-quote.is-voice .pull-quote-text {
  color: var(--tier-scan);
}
/* The attribution is a caption, so it steps back — but it steps back to the
   interface floor, not through it. It was 0.76rem tracked out to 2px, which
   is the Space wire treatment, and on the `is-voice` quotes it was the same
   brass as the sentence it was attributing. */
body.site-theme-charcoal .pull-quote-source {
  margin-top: 10px;
  font-size: var(--fs-label);
  letter-spacing: 0;
  text-transform: none;
  color: var(--tier-ignore);
}


/* ══ documentation ═════════════════════════════════════════════════════════
   Reference pages (/api/). The owner's models are GitLab's and Supabase's
   REST docs (2026-08-05): "less boxes", and "colors when meaningful — for
   like key words - syntax highlight for api docs".

   Both of those are one idea. A reference page is long and repetitive, so
   every card it draws is a card it draws thirty times, and thirty identical
   rectangles carry no information. Rules and whitespace separate entries for
   free. The colour that would have gone into chrome goes into the code
   instead, where each hue answers a real question about the text. */

/* An endpoint entry: separated by a hairline and air, not by a card. */
body.site-theme-charcoal .doc-entry {
  box-shadow: none;
  background: none;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding: 24px 0 0;
  margin: 0 0 36px;
}

/* Contents: a band between two rules, the way a reference page's index sits
   above the body rather than floating in a panel. */
body.site-theme-charcoal .doc-toc {
  background: none;
  border: 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 16px 0;
}

/* A persistent index within a full-height rail. The column owns its divider;
   the sticky navigation must not shorten the rule to the length of its links. */
body.site-theme-charcoal .doc-toc.is-sidebar {
  border: 0;
  padding: 0 20px 0 0;
  font-size: var(--fs-meta);
  line-height: 1.5;
}
@media (max-width: 960px) {
  body.site-theme-charcoal .doc-toc.is-sidebar {
    border-right: 0;
    border-bottom: 1px solid var(--border);
    padding: 0 0 20px;
  }
}

/* The small caps label that introduces a block ("EXAMPLE", "RESPONSE") or
   names the term in a definition list. No margin: spacing is layout, and a
   role that sets it outranks the surface trying to place it — /about/'s
   capability rail needs the label flush with its title, /api/ needs air above
   it, and neither is the role's business. */
body.site-theme-charcoal .block-label {
  display: block;
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--tier-ignore);
}

body.site-theme-charcoal .code-inline {
  border: 0;
  background: rgba(var(--tint-rgb), 0.09);
  padding: 1px 5px;
}

/* Reference identifiers share one readable size in prose and tables, without
   chips nested inside rows. Gallery filenames retain their declared variant. */
body.site-theme-charcoal .code-inline:not(.is-gallery-detail) {
  font-size: var(--fs-meta);
  font-weight: 400;
  text-align: start;
  white-space: normal;
  overflow-wrap: anywhere;
  background: none;
  box-shadow: none;
}

/* A code block DOES keep a container — it is genuinely a different medium
   from the prose around it, and both reference designs keep a quiet one. */
body.site-theme-charcoal .code-block {
  background: #16181b;
  border: 1px solid var(--border);
  border-left: 2px solid rgba(var(--tint-rgb), 0.5);
  border-radius: 6px;
}

/* ── syntax tokens ─────────────────────────────────────────────────────────
   Each hue answers one question, and the answers are consistent with the
   rest of the theme: a name is lilac wherever it appears, a value is mint,
   a figure is gold, and anything you are meant to skip is ignore tier. */
body.site-theme-charcoal .tok-key     { color: var(--name); }
body.site-theme-charcoal .tok-str     { color: var(--mint); }
body.site-theme-charcoal .tok-num     { color: var(--gold); }
body.site-theme-charcoal .tok-lit     { color: var(--cyan); }
body.site-theme-charcoal .tok-cmd     { color: var(--tier-scan); font-weight: 700; }
body.site-theme-charcoal .tok-flag    { color: var(--gold); }
body.site-theme-charcoal .tok-punct   { color: var(--tier-ignore); }
body.site-theme-charcoal .tok-comment { color: var(--tier-ignore); font-style: italic; }


/* ══ status-chip ═══════════════════════════════════════════════════════════
   One component for state labels. Settings currently shows VERIFIED as an
   outlined green pill and ENABLED as plain blue text that reads as a link
   and is not one — two treatments for the same idea on one page. */

body.site-theme-charcoal .status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  border: 1px solid var(--border);
  background: rgba(var(--tint-rgb), 0.06);
  color: var(--tier-scan);
}
body.site-theme-charcoal .status-chip.is-positive {
  border-color: rgba(var(--mint-rgb), 0.55);
  background: rgba(var(--mint-rgb), 0.12);
  color: #93c25a;
}
body.site-theme-charcoal .status-chip.is-caution {
  border-color: rgba(var(--amber-rgb), 0.55);
  background: rgba(var(--amber-rgb), 0.12);
  color: #e0a848;
}
body.site-theme-charcoal .status-chip.is-negative {
  border-color: rgba(var(--danger-rgb), 0.55);
  background: rgba(var(--danger-rgb), 0.12);
  color: #e07575;
}


/* ══ form-error ════════════════════════════════════════════════════════════
   What a form says when it will not accept what you typed. It is the one
   message on the page a reader has to be able to read, and it was the
   smallest text on it: 0.72rem tracked to 1px, below the interface floor and
   below the label it belongs to.

   Red is right here and it is not a valence judgement about a game (0.3a):
   it is the error colour on a control, which is the one thing danger red is
   unambiguously for. */

body.site-theme-charcoal .form-error {
  color: var(--danger);
  font-size: var(--fs-meta);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
  opacity: 1;
}


/* ══ actions ═══════════════════════════════════════════════════════════════
   The button ladder. Depth carries affordance: if it looks raised it must be
   pressable, if it is pressable it must look raised.

   Sized to the label, never stretched to the container (spec item 18).
   Full-width is a mobile action-bar decision, taken by a surface's layout,
   and it is the only exception. */

/* A conversion ask inside existing chrome. It inherits that chrome's exact
   type, height, padding and corners; only the accented face distinguishes the
   ask. Using the full page-primary key here made Donate look like a different
   component bolted onto the navigation. */
body.site-theme-charcoal .action-prominent {
  background:
    linear-gradient(180deg, rgba(var(--cyan-rgb), 0.24), rgba(var(--cyan-rgb), 0.12)),
    var(--surface);
  border-color: rgba(var(--cyan-rgb), 0.62);
  color: var(--tier-scan);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.15),
    0 2px 3px rgba(0, 0, 0, 0.55);
}
body.site-theme-charcoal .action-prominent:hover,
body.site-theme-charcoal .action-prominent:focus-visible {
  background:
    linear-gradient(180deg, rgba(var(--cyan-rgb), 0.34), rgba(var(--cyan-rgb), 0.18)),
    var(--surface);
  border-color: var(--cyan);
  color: #ffffff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 2px 4px rgba(0, 0, 0, 0.6);
}

body.site-theme-charcoal .action-primary,
body.site-theme-charcoal .action-secondary,
body.site-theme-charcoal .action-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  width: auto;
  max-width: 100%;
  /* `width: auto` alone does not keep a button off its container's edges: a
     grid item still stretches to fill its track, which is how the /import/
     submit ends up spanning the form. `justify-self` is ignored by flex
     containers, so this only bites where the stretch actually happens. A
     mobile action bar that wants the full width overrides it on the surface,
     which is the one exception spec item 18 allows. */
  justify-self: start;
  padding: 9px 18px;
  border-radius: var(--radius-control);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}

/* Compact: the same control at the size a small panel or a row can carry. The
   full control is sized for a page's own action area; dropped into a popover
   it becomes the largest thing in the panel and therefore reads as what the
   panel is for (owner, 2026-08-06, of Delete collection in the collection
   editor: "delete is way too large"). Deleting is not what that panel is for.
   The tracked-out caps go with the padding — they are half of what makes a
   short label wide. */
body.site-theme-charcoal .action-primary.is-compact,
body.site-theme-charcoal .action-secondary.is-compact,
body.site-theme-charcoal .action-danger.is-compact {
  padding: 5px 12px;
  letter-spacing: 0.3px;
  text-transform: none;
}

/* Primary: the pressed blue key. One per page.

   This is the canonical key — a light-to-dark blue face, a bright top bevel,
   a deep bottom shade and a real drop shadow, so it reads as pressed metal
   rather than a flat blue slab. It is the same treatment the hero actions and
   the bio CURATED GAMES button already wear; carrying it here rather than in
   a per-surface list is what lets those lists be deleted. A weaker copy in
   this file would lose anyway: `body.site-theme-charcoal .x button` scores
   (0,2,2) against a role's (0,2,1). */
body.site-theme-charcoal .action-primary {
  background: linear-gradient(180deg, #55a9f4, #2673c8);
  border: 1px solid rgba(0, 0, 0, 0.6);
  color: #ffffff;
  text-shadow: 0.5px 0.5px 0 rgba(0, 0, 0, 0.8);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.5),
    inset 0 3px 7px -2px rgba(220, 238, 255, 0.95),
    inset 0 -8px 12px -4px rgba(10, 58, 106, 1),
    0 3px 5px rgba(0, 0, 0, 0.5);
}
body.site-theme-charcoal .action-primary:hover {
  background: linear-gradient(180deg, #61b1f6, #3182d8);
  border-color: rgba(0, 0, 0, 0.6);
  color: #ffffff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 3px 7px -2px rgba(220, 238, 255, 0.95),
    inset 0 -8px 12px -4px rgba(10, 58, 106, 1),
    0 3px 5px rgba(0, 0, 0, 0.5);
}
/* The key physically travels when pressed: it drops the 1px its drop shadow
   gives up, and the bottom shade shallows out as the face flattens. */
body.site-theme-charcoal .action-primary:active {
  transform: translateY(1px);
  background: linear-gradient(180deg, #3d8fdd, #2b7cd0);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.25),
    inset 0 2px 5px -2px rgba(10, 58, 106, 0.8),
    inset 0 -4px 8px -4px rgba(10, 58, 106, 0.9),
    0 1px 2px rgba(0, 0, 0, 0.45);
}

/* Secondary: metal, one visible step above whatever it sits on. */
body.site-theme-charcoal .action-secondary {
  background: var(--key-metal-face);
  border: 1px solid var(--key-metal-edge);
  color: var(--tier-scan);
  box-shadow:
    var(--key-metal-catch),
    var(--key-metal-seat);
}
body.site-theme-charcoal .action-secondary:hover {
  background: var(--key-metal-face-hover);
  color: var(--key-metal-ink-active);
}
/* Selected: the metal face plus a filled blue underline bar. This is the
   selection language the theme already speaks on filter buttons, action tabs
   and search modes, so the role adopts it verbatim rather than inventing a
   second one — blue is the selection accent, and a selected control must not
   render pixel-identical to an unselected one. */
body.site-theme-charcoal .action-secondary.is-active {
  background: var(--key-metal-face);
  border-color: var(--key-metal-edge);
  color: var(--key-metal-ink-active);
  box-shadow:
    inset 0 -2px 0 var(--cyan),
    var(--key-metal-catch),
    var(--key-metal-seat);
}

/* The one deliberate exception to sizing-to-label, made explicit so it has to
   be asked for: a segmented control (an era filter row, a mobile action bar)
   whose keys each own a track and are meant to be equal. Spec item 18 bans
   the accidental stretch, not this. */
body.site-theme-charcoal .action-primary.is-block,
body.site-theme-charcoal .action-secondary.is-block,
body.site-theme-charcoal .action-danger.is-block {
  justify-self: stretch;
  width: 100%;
}

/* A `<details>` disclosure that is styled as a button must lose the browser's
   triangle marker, or the control reads as a text link with a caret (0.6:
   every control that performs an action looks like a control). */
body.site-theme-charcoal summary.action-primary,
body.site-theme-charcoal summary.action-secondary {
  list-style: none;
}
body.site-theme-charcoal summary.action-primary::-webkit-details-marker,
body.site-theme-charcoal summary.action-secondary::-webkit-details-marker {
  display: none;
}

/* Danger: destructive intent is carried by colour and confirmation, not by
   an identical grey button wearing a different word. */
body.site-theme-charcoal .action-danger {
  background: rgba(var(--danger-rgb), 0.14);
  border: 1px solid rgba(var(--danger-rgb), 0.5);
  color: #e07575;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
body.site-theme-charcoal .action-danger:hover {
  background: rgba(var(--danger-rgb), 0.24);
  border-color: rgba(var(--danger-rgb), 0.8);
  color: #f0a0a0;
}

/* Icon control: a square target for one shared SVG icon. `currentColor`
   is the point — the icon inherits its button's state instead of carrying a
   private colour that has to be kept in sync. */
body.site-theme-charcoal .action-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: var(--radius-control);
  background: linear-gradient(180deg, #454c54, #333940);
  border: 1px solid rgba(0, 0, 0, 0.55);
  color: var(--tier-scan);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 1px 2px rgba(0, 0, 0, 0.5);
  cursor: pointer;
}
body.site-theme-charcoal .action-icon:hover {
  background: linear-gradient(180deg, #525a63, #3e454c);
  color: #ffffff;
}
body.site-theme-charcoal .action-icon.is-active {
  color: var(--gold);
}
body.site-theme-charcoal .action-icon svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}

/* ══ action-quiet ══════════════════════════════════════════════════════════
   An action that is a word rather than a button.

   Every action role above is a raised metal face, which is correct when the
   control is the point of the surface it sits on. It is wrong where the
   surface is meant to be barely there — a dismissible reminder, an inline
   undo, a tertiary escape hatch. Two metal faces inside a floating toast made
   the toast read as a panel with controls bolted to it rather than a passing
   note (owner, 2026-08-05: "thats not a transparent gentle reminder").

   No border and no fill until hover: the affordance is the word and the
   pointer, which is enough when the action is optional. Sentence case, unlike
   the tracked uppercase of the button roles — this one is not raising its
   voice. */

body.site-theme-charcoal .action-quiet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.4em;
  padding: 4px 8px;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  box-shadow: none;
  color: var(--tier-read);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}
body.site-theme-charcoal .action-quiet:hover {
  color: var(--tier-scan);
  background: rgba(255, 255, 255, 0.07);
}
body.site-theme-charcoal .action-quiet:focus-visible {
  outline: 2px solid rgba(var(--tint-rgb), 0.7);
  outline-offset: 1px;
}
/* Wordless drawer tabs share the shell's metal surface, with explicit selection. */
body.site-theme-charcoal .drawer-panel-chooser {
  background: var(--key-metal-face);
  border-color: var(--key-metal-edge);
  box-shadow: var(--key-metal-seat);
}

/* Icon form: a square target, sized to the same 28px as the word's line box
   so a row of quiet actions does not step. */
body.site-theme-charcoal .action-quiet.is-icon {
  width: 28px;
  height: 28px;
  padding: 0;
}
body.site-theme-charcoal .action-quiet svg {
  width: 14px;
  height: 14px;
}

/* Button-styled links never carry a text underline — the depth says it is a
   control, and an underline says it is a sentence. */
body.site-theme-charcoal a.action-primary,
body.site-theme-charcoal a.action-secondary,
body.site-theme-charcoal a.action-danger,
body.site-theme-charcoal a.action-icon,
body.site-theme-charcoal a.action-quiet {
  text-decoration: none;
}

/* Layout modifier for an action row. A page may set layout, and this is
   layout — but it is centring a flex row, which is the same everywhere, so
   it is single-sourced here rather than written per page. Used on /android/,
   whose action row has no hero portrait to align against. */
body.site-theme-charcoal .is-centered {
  justify-content: center;
}

/* Text labels use the same explicit alignment variant as centered action rows. */
.block-label.is-centered {
  text-align: center;
}


/* ══ listing-card / listing-open ═══════════════════════════════════════════
   A card that presents one entity in a grid. The card itself is NOT a link:
   it carries dedicated controls, the way the /players/ card carries star /
   info / arrow. That is what lets `entity-name` stay a name — a whole-card
   anchor would make every name link text, which is exactly how event and
   series names ended up painted the same blue as a hyperlink. */

body.site-theme-charcoal .listing-card {
  cursor: default;
}
body.site-theme-charcoal .listing-open {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
}
body.site-theme-charcoal .listing-open .study-arrow-icon {
  width: 17px;
  height: 17px;
}

/* These custom properties name the gallery's three intentional anatomy
   contexts for the conformance registry. They do not paint or resize the
   roles; gallery.css remains the sole owner of page layout. */
body.site-theme-charcoal .is-gallery-card {
  --role-anatomy-context: gallery-card;
}
body.site-theme-charcoal .is-gallery-detail {
  --role-anatomy-context: gallery-detail;
}
body.site-theme-charcoal .is-gallery-summary {
  --role-anatomy-context: gallery-summary;
}


/* ══ listing-row ═══════════════════════════════════════════════════════════
   The same entity as `listing-card`, presented in a list instead of a grid.
   A row is separated from its neighbours by one hairline and nothing else:
   no border box, no fill, no left accent bar, no drop shadow.

   This exists because the library was built entirely out of cards and read as
   nothing else (owner, 2026-08-05: "its literally nothing except boxes"), and
   because the durable rule this theme is built on says the same thing — a box
   around every item is what the charcoal work is moving away from, and bold,
   colour and type are what should be breaking the list up instead. A row
   gives the eye a title in the name colour, a figure in chrome and a meta
   line in the ignore tier, which is three levels of contrast without drawing
   a single rectangle.

   Hover tints the row rather than lighting a border, for the same reason: a
   border that appears on hover is a box that was hiding. */

body.site-theme-charcoal .listing-row {
  border-bottom: 1px solid var(--border);
  transition: background 0.14s ease;
}
/* The last row would otherwise draw a rule under the list with nothing below
   it, which reads as the bottom edge of a container that does not exist. */
body.site-theme-charcoal .listing-row:last-child {
  border-bottom: 0;
}
body.site-theme-charcoal .listing-row:hover,
body.site-theme-charcoal .listing-row:focus-within {
  background: rgba(255, 255, 255, 0.028);
}


/* ══ empty-state ═══════════════════════════════════════════════════════════
   One component, and it always contains the action that resolves it. Four
   separate empty-state treatments across the library is three too many. */

/* No border and no fill: an empty state is the absence of content, and
   drawing a box around nothing is the most literal version of the thing this
   theme is moving away from. The heading and the action carry it. */
body.site-theme-charcoal .empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  padding: 34px 0 40px;
  color: var(--tier-read);
  font-size: var(--fs-body);
}
body.site-theme-charcoal .empty-state-title {
  font-size: var(--fs-section);
  font-weight: 600;
  color: var(--tier-scan);
}

/* Page search is a single centred control anatomy outside the navbar. Local
   archive filters may update while typing; MasterDB searches retain their
   explicit submit action. Those data contracts do not change the geometry. */
body.site-theme-charcoal .site-search-form {
  display: grid;
  align-items: normal;
  gap: 8px;
  width: min(620px, 100%, calc(100vw - 28px));
  margin: 0 auto 28px;
}
body.site-theme-charcoal .site-search-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
  gap: 10px;
}
body.site-theme-charcoal .site-search-input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 0 13px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: rgba(0, 0, 0, 0.2);
  color: var(--tier-scan);
  font-family: inherit;
  font-size: var(--fs-body);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  outline: none;
}
body.site-theme-charcoal .site-search-input.has-inline-help {
  /* Reserve the existing 32px help target, its 8px inset, and text clearance. */
  padding-right: 46px;
}
body.site-theme-charcoal .site-search-input:focus {
  border-color: var(--cyan);
  box-shadow: 0 0 0 2px rgba(var(--tint-rgb), 0.16);
}
body.site-theme-charcoal .site-search-row .action-primary,
body.site-theme-charcoal .site-search-row .action-secondary {
  grid-column: 2;
  min-height: 44px;
}
body.site-theme-charcoal .site-search-region {
  width: min(620px, 100%, calc(100vw - 28px));
  margin: 0 auto 34px;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
}
body.site-theme-charcoal .site-search-region .site-search-form {
  margin-bottom: 0;
}
body.site-theme-charcoal .site-search-region.site-submit-search {
  width: min(760px, 100%, calc(100vw - 28px));
  padding: 0;
}
body.site-theme-charcoal .site-submit-search .site-search-form {
  width: min(760px, 100%, calc(100vw - 28px));
}
body.site-theme-charcoal .site-search-row.is-balanced-submit {
  grid-template-columns: 96px minmax(0, 1fr) 96px;
  justify-items: stretch;
  gap: 10px;
}
body.site-theme-charcoal .site-search-row.is-balanced-submit > :first-child {
  grid-column: 2;
  width: 100%;
}
body.site-theme-charcoal .site-search-row.is-balanced-submit > .action-primary {
  grid-column: 3;
  justify-self: stretch;
}
@media (max-width: 720px) {
  body.site-theme-charcoal .site-search-row.is-balanced-submit {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  body.site-theme-charcoal .site-search-row.is-balanced-submit > :first-child {
    grid-column: 1;
  }
  body.site-theme-charcoal .site-search-row.is-balanced-submit > .action-primary {
    grid-column: 2;
  }
}

/* A failed query is feedback tied to the centred search, not a second card or
   an orphaned sentence at the page edge. It states the fact once with the
   site's subordinate outlined mark and no title/help/action pile. */
body.site-theme-charcoal .no-results {
  display: block;
  width: min(620px, 100%, calc(100vw - 28px));
  margin: 16px auto 42px;
  padding: 0;
  color: var(--tier-read);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.55;
  letter-spacing: 0;
  text-align: center;
}
body.site-theme-charcoal .no-results .mark.is-no-results {
  display: inline-block;
  margin-right: 9px;
}
body.site-theme-charcoal [hidden] {
  display: none;
}


/* ══ SURFACE APPLICATIONS ══════════════════════════════════════════════════
   Legacy classes pointed at the role they always were. Everything below is
   an alias or a layout consequence — no new colours or sizes are invented
   here, they all come from the roles above.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── The bio hero ──────────────────────────────────────────────────────── */

/* The "random green dot" (owner). A pulsing mint dot in front of a career
   record means nothing — mint is the positive/live voice, and a championship
   reign is neither live nor a positive outcome. It is replaced by the gold ◆
   that marks structure everywhere else. Scoped to the bio crown, so genuine
   live-status dots elsewhere are untouched. */
body.site-theme-charcoal .entity-highlight .pulse-dot {
  display: none;
}
body.site-theme-charcoal .entity-highlight {
  font-size: var(--fs-meta);
  letter-spacing: 0;
  color: var(--tier-read);
}
body.site-theme-charcoal .entity-highlight::before {
  content: '\25C6';
  color: var(--gold);
  font-size: 0.72em;
  margin-right: 0.55em;
  vertical-align: 0.12em;
}

/* Vitals: labels to ignore tier at a readable size (they were 0.66rem with
   2px tracking), values to scan tier, and the tile becomes a real surface
   instead of a flat outline. */
body.site-theme-charcoal .figure-tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.04),
    0 2px 4px rgba(0, 0, 0, 0.32);
}
/* Padding is deliberately NOT set here. Each tile sizes itself — a vitals
   tile, a by-the-numbers tile and a rating tile carry different amounts of
   content — and padding is layout, which is the surface's to own. */

/* 0.5: a figure in a tile is DATA, so it is scan tier. The default theme
   paints these numbers in the tile's category hue (cyan/mint/gold on the
   rating cards), which puts link-blue on a number that cannot be clicked and
   spends three hues saying nothing. The hue moves to the tile's accent rule
   and its meter fill, where one hue per tile distinguishes the categories;
   the number itself goes near-white everywhere. */
body.site-theme-charcoal .figure-tile .entity-figure {
  color: var(--tier-scan);
  font-weight: 700;
  filter: none;
}

/* Section headings, card names, card descriptions, kickers, counts and
   figures are NOT listed here. They carry `section-title`, `section-count`,
   `entity-name`, `entity-description`, `entity-kind`, `entity-figure-value`
   and `entity-figure-line` in the templates, and the role blocks above are
   the only place they are styled. Re-adding a rule like
   `.event-series-card-topline span:last-child` would put a surface back in
   charge of type and colour, which is the exact drift this layer exists to
   end. If something looks wrong, fix its role or give it the right one. */


/* ══ fit-to-content and no mid-word truncation ═════════════════════════════
   Spec items 18 and 19. Truncation is a layout failure, not a feature: text
   never ellipsizes mid-word ("THE PAWNS ARE THE SOUL OF C…"). A container
   either fits its content or clamps on a word boundary with whole words
   visible. */

body.site-theme-charcoal .clamp-1,
body.site-theme-charcoal .clamp-2,
body.site-theme-charcoal .clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;
}
body.site-theme-charcoal .clamp-1 { -webkit-line-clamp: 1; }
body.site-theme-charcoal .clamp-2 { -webkit-line-clamp: 2; }
body.site-theme-charcoal .clamp-3 { -webkit-line-clamp: 3; }

/* The card epithet was `white-space: nowrap` + `text-overflow: ellipsis`,
   which cuts wherever the pixel runs out: "THE PAWNS ARE THE SOUL OF C…".
   Wrapping first and clamping second puts the break on a word boundary, and
   two lines is enough for nearly every epithet to land whole. Overridden
   here rather than in style.css because that rule also serves the Space
   theme, which this overhaul leaves untouched. */
body.site-theme-charcoal .entity-meta.is-epithet {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  /* One line, so the card keeps the compact height it has in production —
     letting the epithet wrap to two lines fixed the truncation but fattened
     every card in the grid, which is a worse trade (owner, 2026-08-04: "the
     cards are too thick"). Clamping still breaks on a word boundary, so the
     mid-word cut ("THE PAWNS ARE THE SOUL OF C…") does not come back. */
  -webkit-line-clamp: 1;
  overflow: hidden;
  white-space: normal;
  text-overflow: clip;
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;
}
/* Phones list one card per row, so a second line no longer forces a
   neighbouring card in the same row to match it. The height argument for the
   one-line clamp is a desktop-grid argument; the truncation it buys is a cost
   with nothing on the other side of the trade here. */
@media (max-width: 720px) {
  body.site-theme-charcoal .entity-meta.is-epithet {
    -webkit-line-clamp: 2;
  }
}

/* Public distribution and destination cards share one anatomy. */
.promotion-card,
body.site-theme-charcoal .promotion-card {
  --promotion-accent: var(--gold);
  display: flex;
  flex-direction: column;
  align-items: normal;
  min-width: 0;
  gap: 18px;
  padding: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--promotion-accent);
  border-radius: var(--radius-card, 8px);
  box-shadow: none;
  text-decoration: none;
}
.promotion-card.is-interactive-accent,
body.site-theme-charcoal .promotion-card.is-interactive-accent { --promotion-accent: var(--cyan); }
.promotion-label,
body.site-theme-charcoal .promotion-label {
  margin: 0;
  font-size: var(--fs-meta, .92rem);
  color: var(--tier-ignore, var(--muted));
  text-align: center;
  line-height: 1.5;
}
.promotion-title,
body.site-theme-charcoal .promotion-title {
  display: block;
  margin: 0;
  color: var(--tier-scan, var(--white));
  font-size: var(--fs-promotion-title, 1.5rem);
  font-weight: 650;
  line-height: 1.3;
  text-align: center;
  text-wrap: balance;
}
a.promotion-card .promotion-title,
body.site-theme-charcoal a.promotion-card .promotion-title { color: var(--cyan); }
.promotion-description,
body.site-theme-charcoal .promotion-description {
  display: block;
  margin: 0;
  color: var(--tier-read, var(--muted));
  font-size: var(--fs-prose, 1.05rem);
  font-weight: 400;
  line-height: 1.65;

}
.promotion-footer,
body.site-theme-charcoal .promotion-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-top: auto;
  padding-top: 20px;
  font-size: var(--fs-body, 1rem);
  line-height: 1.5;
  text-align: center;
  overflow-wrap: anywhere;
}
.promotion-footer p,
body.site-theme-charcoal .promotion-footer p { margin: 0; }

a.promotion-card .promotion-footer,
body.site-theme-charcoal a.promotion-card .promotion-footer,
.promotion-footer a:not(.action-primary),
body.site-theme-charcoal .promotion-footer a:not(.action-primary) { color: var(--cyan); }
.promotion-card.is-profile,
body.site-theme-charcoal .promotion-card.is-profile { padding: 0; }
.promotion-card:has(> a:focus-visible),
body.site-theme-charcoal .promotion-card:has(> a:focus-visible),
a.promotion-card:focus-visible,
body.site-theme-charcoal a.promotion-card:focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }

.promotion-footer::before,
body.site-theme-charcoal .promotion-footer::before {
  content: '';
  display: block;
  width: 100%;
  border-top: 1px solid var(--border);
  margin-bottom: 10px;
}

.entity-meta.is-inline { display: inline; }

/* Platform cards retain the owner-selected production download anatomy. */
.app-package {
  min-width: 0; padding: clamp(22px, 3vw, 34px);
  border: 1px solid var(--border);
  background: linear-gradient(145deg, rgba(var(--panel-rgb), .9), rgba(var(--deep-rgb), .62));
  text-align: left;
}
.app-package-heading { display: flex; align-items: center; gap: 14px; min-width: 0; }
.app-package-icon { display: flex; align-items: center; justify-content: center; flex: 0 0 42px; height: 42px; border: 1px solid rgba(var(--tint-rgb), .3); background: rgba(var(--tint-rgb), .12); color: var(--cyan); }
.app-package-icon.is-neutral { color: var(--white); }
.app-package h3 { margin: 0 0 2px; color: var(--white); font-size: 1.4rem; line-height: 1.4; text-align: left; }
.app-package-platform { margin: 0; color: var(--muted); font-size: var(--fs-label, .9rem); line-height: 1.4; }
.app-package-size { color: inherit; font-size: var(--fs-label, .9rem); font-weight: 400; line-height: 1.6; white-space: nowrap; }
.app-package-download {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  min-height: 55px; padding: 10px 16px; background: var(--cyan); color: rgb(var(--deep-rgb));
  font-size: var(--fs-body, 1rem); font-weight: 700; line-height: 1.5; text-decoration: none;
}
.app-package-download:hover { background: var(--white); color: rgb(var(--deep-rgb)); }
.app-package-download[aria-disabled="true"] { background: var(--surface); color: var(--muted); }
.app-package-option { color: var(--cyan); font-size: var(--fs-label, .9rem); line-height: 1.6; text-decoration: none; }
.app-package-option:hover { text-decoration: underline; }
.app-package-option:focus-visible, .app-package-download:focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }
.app-package-note { margin: 16px 0 0; color: var(--muted); font-size: var(--fs-label, .9rem); line-height: 1.55; }
body:not(.site-theme-charcoal) .app-package .action-secondary {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
  min-height: 44px; padding: 9px 18px; border: 1px solid var(--tint);
  background: rgba(var(--tint-rgb), .08); color: var(--white); font: inherit;
  font-size: var(--fs-label, .9rem); cursor: pointer;
}
body:not(.site-theme-charcoal) .app-package .action-secondary:is(:hover, :focus-visible, .is-active) { color: var(--cyan); border-color: var(--cyan); }
body:not(.site-theme-charcoal) .app-package .action-secondary.is-active { box-shadow: inset 0 -2px 0 var(--cyan); }
.code-block:is(.is-terminal, .is-source) pre { font-size: var(--fs-label, .9rem); line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.code-block:is(.is-terminal, .is-source) pre, .code-block:is(.is-terminal, .is-source) pre * { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', monospace !important; }
.mark.is-prose { font-size: inherit; line-height: inherit; }
.mark.is-inline { display: inline; font-size: inherit; line-height: inherit; }
.entity-meta.is-centered, .action-primary.is-centered { text-align: center; }
.product-demo { box-sizing: border-box; width: 100%; padding: 7px; border: 1px solid var(--border); background: rgb(var(--deep-rgb)); }
.product-demo video { display: block; width: 100%; height: auto; }
.product-demo figcaption { margin: 12px 0 0; text-align: center; }
@media (max-width: 720px) { .product-demo { padding: 4px; } }
.entry-title.is-centered { text-align: center; }

.page-title.is-compact,
body.site-theme-charcoal .page-title.is-compact {
  font-size: 2.25rem;
  line-height: 1.2;
  letter-spacing: 0;
}
