/*
 * The Cartel primitives. Every value here comes from tokens.css; a raw colour
 * or font stack below is a defect. /styleguide renders each one in every state.
 */

/* ── Base ──────────────────────────────────────────────────────────────── */

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

html {
  text-size-adjust: 100%;

  /*
   * No double-tap-to-zoom, anywhere (COMBAT-61). `manipulation` keeps panning
   * and pinch-zoom and drops the double-tap gesture, which is the one that
   * actually misfires: installed to a home screen there is no browser chrome to
   * aim at, so a thumb double-tapping a fight log or a card lurches the whole
   * page to 200% with no obvious way back. It also takes the ~300ms the browser
   * spends waiting to see whether a tap was the first of two.
   *
   * On the root rather than on the controls, because a reader double-taps text —
   * `manipulation` is resolved up the ancestor chain, so this covers the
   * document without touching any element individually.
   *
   * Pinch-zoom deliberately survives: it is what WCAG 1.4.4 is about, and
   * nothing here needs it gone. `user-scalable=no` would take it with the
   * double tap and buy nothing.
   */
  touch-action: manipulation;

  /*
   * And no scroll chaining: it is what makes the fixed marquee and ringside feel
   * like they slide, and what turns a thumb reading to the top of a log into a
   * pull-to-refresh.
   */
  overscroll-behavior: none;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  margin: 0;
  background-color: var(--ink);
  color: var(--texto);
  font: 400 var(--text-body) / var(--leading-body) var(--font-body);
  text-wrap: pretty;
}

body::before {
  content: "";
  position: fixed;
  z-index: -1;
  inset: 0;
  background-image: var(--poster);
  background-repeat: no-repeat;
  pointer-events: none;
}

/* Keeps the footer at the bottom of a short page instead of mid-viewport. */
main {
  flex: 1;
}

/* Everything in this system stacks with a flex gap, never with a margin. */
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

img {
  max-width: 100%;
}

/* Every component here sets its own display, which would outrank [hidden]. */
[hidden] {
  display: none !important;
}

:focus-visible {
  outline: var(--border) solid var(--amarillo);
  outline-offset: 2px;
}

::selection {
  background: var(--amarillo);
  color: var(--ink);
}

/* The replay animation must have a still path. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* ── Layout ────────────────────────────────────────────────────────────── */

.shell {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: var(--shell);
  margin-inline: auto;
  padding: var(--space-4) var(--space-3) var(--space-7);
}

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-2);
}

.masthead > * {
  min-width: 0;
}

.section {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}


/* ── Type roles ────────────────────────────────────────────────────────── */

.wordmark {
  font-family: var(--font-display);
  font-size: var(--text-body);
  font-weight: 400;
  letter-spacing: var(--track-wordmark);
  text-transform: uppercase;
  color: var(--amarillo);
}

.wordmark__tld {
  color: var(--rosa);
}

.display {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 400;
  line-height: var(--leading-tight);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  text-wrap: balance;
  overflow-wrap: anywhere;
}

.display--hero {
  font-size: var(--text-hero);
}

.title {
  font-size: var(--text-title);
  font-weight: 800;
  line-height: var(--leading-tight);
  text-wrap: balance;
}

.eyebrow {
  font-variant-numeric: tabular-nums;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--dim);
}

/*
 * Every HP value, token count, level, price and table column — and the
 * addresses and slugs that read as data too. Bungee and Archivo both set
 * proportional figures by default, so numbers that have to line up wear this
 * role rather than the display or body face. Monospace breaks at neither a dot
 * nor an at-sign, so the role wraps anywhere or an email pushes a 320px card
 * off the screen.
 */
.data {
  font-family: var(--font-data);
  font-size: var(--text-data);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/*
 * The address a code was sent to, or the one you are about to sign in as. It is
 * the whole content of those two screens rather than a field on them, so the
 * prototype prints it centred in the marquee colour — a receipt, not a label.
 * `.data` already wraps it anywhere, which is what keeps a long address inside a
 * 320px panel.
 */
.address {
  margin: 0;
  color: var(--amarillo);
  text-align: center;
}

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

/* What a postura, a piece of equipment or a level gap moves. The sign carries
   the meaning; colour only doubles it. */
.delta {
  font-family: var(--font-data);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.delta--up {
  color: var(--menta);
}

.delta--down {
  color: var(--rosa);
}

/* ── Card ──────────────────────────────────────────────────────────────── */

/*
 * A slab, not a card (ADR 0015). The radius is gone, the border is ink rather
 * than line, and the shadow is a hard offset with no blur: a blur reads as
 * elevation and a printed poster has none — a panel is a second sheet of paper
 * laid on the first. The halftone is a background layer rather than an
 * ::before, because this is a flex column and a pseudo-element would take a
 * slot and a gap of its own.
 */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-3);
  background-color: var(--card);
  background-image: var(--halftone);
  background-size: var(--halftone-grid);
  border: var(--border-poster) solid var(--ink);
  box-shadow: var(--shadow-poster);
}

/* The marquee border bills what it holds the way a printed cartel does: the
   landing's fixture fight, the arena's Reto del Día, the billing above a
   scouting sheet, and a jauría's week once it is won. */
.card--marquee {
  border-color: var(--amarillo);
}

/* Consequence, on the panels that carry it: the same reading .flash--alert and
   .chip--alert already establish. */
.card--alert {
  border-color: var(--rosa);
}

/* What the banner says about itself rather than about the panel: a count, a
   price, a rareza. It never competes with the billing beside it. */
.card__note {
  margin-inline-start: auto;
  font-family: var(--font-data);
  font-weight: 400;
  letter-spacing: var(--track-micro);
  white-space: nowrap;
}

/*
 * The banner: the slab's own header, printed across it in two passes of the same
 * ink. It bleeds to the panel's edge, so it cancels the padding the card sets
 * and takes its own.
 *
 * Two spellings of one object. Eleven surfaces write `.card__banner` out; the
 * rest already opened with an `.eyebrow` as the card's first child, and under
 * Cabina that label *is* the banner — so it is banded here rather than rewritten
 * thirty times. The markup was already saying the right thing.
 *
 * Every panel in the app wears one, operational surfaces included: the order
 * book and the moderation queue are billed as loudly as the Reto del Día. That
 * reverses the quiet-operational-copy rule Cartel shipped with, and
 * .impeccable.md records the reversal rather than leaving the two in conflict.
 *
 * Loud is the default, stripes included. The banded eyebrow started out quiet
 * with `.card--marquee` opting into the stripes, which had the ratio backwards:
 * of the 94 banners in the approved prototype 53 are striped amarillo and only
 * 24 are quiet. Defaulting to quiet made most of the app whisper and left "loud
 * everywhere" true only of the surfaces that spell the banner out by hand.
 *
 * A card whose first child is something else keeps its eyebrows inline, which is
 * right — the moderation card opens on a row of chips, and the labels further
 * down are sub-headings inside a panel, not the panel's own billing.
 */
.card__banner,
.card > .eyebrow:first-child {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: calc(var(--space-4) * -1) calc(var(--space-3) * -1) 0;
  padding: var(--space-2) var(--space-3);
  background-image: var(--stripe-amarillo);
  color: var(--ink);
  font: var(--text-micro) / var(--leading-tight) var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

/*
 * The three tones a band comes in, on either spelling of it. A banded eyebrow
 * takes the same suffix the banner does — they are the same object, and a
 * surface should not have to know which way it was written to say "this one is
 * a count, not a billing".
 */
.card__banner--rosa,
.card > .eyebrow--rosa:first-child {
  background-image: var(--stripe-rosa);
  color: var(--texto);
}

.card__banner--menta,
.card > .eyebrow--menta:first-child {
  background-image: var(--stripe-menta);
  color: var(--ink);
}

/*
 * The one band that does not shout. A panel whose header is a label rather than
 * a billing — a count, a period, a table's name — takes this instead of a
 * colour, so the striped ones keep meaning something.
 */
.card__banner--quiet,
.card > .eyebrow--quiet:first-child {
  background-image: none;
  background-color: var(--well);
  color: var(--dim);
  font-family: var(--font-data);
  font-weight: 700;
  letter-spacing: var(--track-micro);
}

/*
 * The sticker treatment, kept from the rejected Álbum direction for the pet
 * card alone: it makes a photo feel like an object you own, which is the
 * identity mechanic immutable stats depend on (ADR 0010).
 */
.pet-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  background: var(--card);
  border: var(--sticker-border) solid var(--texto);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-sticker);
}

.pet-card__photo {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  overflow: hidden;
  background: var(--well);
  border-radius: var(--radius-xs);
  font-size: 3rem;
  line-height: 1;
}

.pet-card__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The jauría a pet runs with, worn on the sticker itself: a chip is inline and
   every other child of the card is not. */
.pet-card__pack {
  align-self: center;
  max-width: 100%;
}

/* Every podio a pet has finished on (COMBAT-31), worn beside the jauría's chip.
   A pet can collect one a season, so they wrap rather than push the sticker
   wide. */
.pet-card__badges {
  justify-content: center;
  max-width: 100%;
}

.pet-card__name {
  font-family: var(--font-display);
  font-size: var(--text-data);
  line-height: var(--leading-tight);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

/* ── Shelf ─────────────────────────────────────────────────────────────── */

/*
 * One slot of the shop, opened and closed by its own header. A native
 * <details> rather than a controller: the toggle, its keyboard handling and
 * what a screen reader announces all come with the element.
 */
.shelf {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* A closed block still lays out its content box, so the row gap outlives what
   it was spacing and a collapsed header carries 12px of nothing. */
.shelf:not([open]) {
  gap: 0;
}

/*
 * The shelf is the shop's panel, so it composes `.card` and its own header is
 * the banner (ADR 0015). The pieces on it are rows inside, not thirty nested
 * cards: a card in a card doubles every border, and it was what made the shop
 * unreadable (COMBAT-61). Banding each piece would be billing the stock rather
 * than the shelf.
 *
 * It bleeds like `.card__banner` because it is one — the element is a
 * `<summary>` so the shelf can open and close with no controller at all.
 */
.shelf__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap);
  margin: calc(var(--space-4) * -1) calc(var(--space-3) * -1) 0;
  padding: var(--space-2) var(--space-3);
  background-image: var(--stripe-amarillo);
  color: var(--ink);
  list-style: none;
  cursor: pointer;
}

/* The count and the chevron sit on the amarillo band, where --dim is 2.1:1. */
.shelf__head .eyebrow {
  color: var(--ink);
}

/* ── Piece ─────────────────────────────────────────────────────────────── */

/*
 * One thing on a shelf. A ruled row rather than a panel, so a shelf of three
 * reads as a shelf of three and not as three products stacked in a window.
 * The rule is where the one above it ended.
 */
.piece {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block-start: var(--space-3);
  border-block-start: var(--border) solid var(--ink);
}

/* Name left, price right: a shelf is scanned down its right edge, which is the
   whole reason the price is billed here as well as in the chips below. */
.piece__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.piece__name {
  font-family: var(--font-display);
  font-size: var(--text-body);
  line-height: var(--leading-tight);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

.piece__cost {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--text-body);
  font-variant-numeric: tabular-nums;
  color: var(--amarillo);
}

/* Safari draws its own triangle unless this pseudo-element is dismissed too. */
.shelf__head::-webkit-details-marker {
  display: none;
}

.shelf__name {
  flex: 1;
  min-width: 0;
}

.shelf__mark {
  color: var(--amarillo);
  transition: transform var(--motion) var(--ease);
}

.shelf[open] .shelf__mark {
  transform: rotate(90deg);
}

/* ── Billing ───────────────────────────────────────────────────────────── */

/* Two fighters abreast with the mark between them: the landing's intro, the
   arena's, and the replay. Promoted out of the landing when the replay became
   its second caller. The corners stretch to a shared height rather than
   centring, so whatever a 20-character name does to one of them, the two cards
   stay billed on the same line. */
.vs {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--space-2);
}

.vs > * {
  min-width: 0;
}

/* Two cards abreast leave a name 80px at 320px, and the pet-card specimen is
   drawn at 120px; at the card's own size an 8-character name breaks mid-word.
   Billing type is smaller than the card type it is borrowed from. */
.vs .pet-card__name {
  font-size: var(--text-micro);
}

.vs__mark {
  font-family: var(--font-display);
  font-size: var(--text-display);
  letter-spacing: var(--track-display);
  color: var(--rosa);
}

/* ── Button ────────────────────────────────────────────────────────────── */

/*
 * A pressed button, in the literal sense: it sits on a hard offset shadow and
 * a tap drives it into that shadow instead of nudging it a pixel. Poster type
 * rather than body type — a call to action on a cartel is set in the display
 * face, and these labels are ours and short.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: var(--space-2) var(--space-4);
  border: var(--border-poster) solid var(--ink);
  max-width: 100%;
  font: var(--text-micro) / var(--leading-tight) var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;

  /* A button label can carry a name a player authored: a pet's, a jauría's. */
  overflow-wrap: anywhere;
  cursor: pointer;
  box-shadow: var(--shadow-cast);
  transition: transform var(--motion-fast) var(--ease),
    box-shadow var(--motion-fast) var(--ease), filter var(--motion) var(--ease);
}

/* Travelling exactly the shadow's own offset is what makes it read as the
   button meeting the paper rather than as a shrink. */
.btn:active {
  transform: translate(var(--space-1), var(--space-1));
  box-shadow: var(--shadow-cast-pressed);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.btn--primary {
  background: var(--amarillo);
  color: var(--ink);
}

/*
 * The one button that rings the bell: committing to a fight, and nothing else.
 * It is the only control in the system wearing the damage colour as a fill, so
 * a screen may never offer two of them.
 */
.btn--bell {
  min-height: calc(var(--tap) + var(--space-4));
  background: var(--rosa);
  color: var(--texto);
  font-size: var(--text-body);
}

/*
 * The quiet one. No shadow and no poster type: a page has one or two calls and
 * a great many ways out, and if every one of them is stamped on the paper then
 * none of them is.
 */
.btn--ghost {
  background: var(--card);
  border-width: var(--border);
  border-color: var(--line);
  color: var(--texto);
  font: 700 var(--text-data) / var(--leading-tight) var(--font-data);
  letter-spacing: var(--track-micro);
  box-shadow: none;
}

.btn--ghost:active {
  transform: translateY(1px);
  box-shadow: none;
}

/* Consequence, in the damage colour — the reading .flash--alert and
   .chip--alert already establish. It never outranks the primary: on the one
   page that offers an irreversible action, the safe way out keeps the amarillo
   weight and this one only has to stop reading like a nav link. */
.btn--danger {
  background: var(--card);
  border-width: var(--border);
  border-color: var(--rosa);
  color: var(--rosa);
  box-shadow: none;
}

.btn--danger:active {
  transform: translateY(1px);
  box-shadow: none;
}

/* An amarillo ring on the amarillo button reads as a halo, not a focus state. */
.btn--primary:focus-visible {
  outline-color: var(--texto);
}

/* Patchy signal is the median session; every action can be in flight. */
.btn[aria-busy="true"] {
  cursor: progress;
  pointer-events: none;
}

.btn[aria-busy="true"]::before {
  content: "";
  width: 1em;
  height: 1em;
  border: var(--border) solid currentcolor;
  border-block-start-color: transparent;
  border-radius: var(--radius-pill);
  animation: btn-spin 600ms linear infinite;
}

@keyframes btn-spin {
  to {
    transform: rotate(1turn);
  }
}

.btn--block {
  width: 100%;
}

@media (hover: hover) {
  .btn--primary:hover:not(:disabled),
  .btn--bell:hover:not(:disabled) {
    filter: brightness(1.08);
  }

  .btn--ghost:hover:not(:disabled) {
    border-color: var(--dim);
  }
}

/* ── HP bar ────────────────────────────────────────────────────────────── */

.bar {
  height: 11px;
  overflow: hidden;
  background: var(--well);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-pill);
}

.bar__fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--menta);
  transform: scaleX(var(--hp, 1));
  transform-origin: left center;
  transition: transform var(--motion) var(--ease);
}

.bar__fill--hurt {
  background: var(--amarillo);
}

.bar__fill--critical {
  background: var(--rosa);
}

/* A jauría's weekly goal borrows the track and fills toward a target instead
   of draining from one, so it carries its own fraction and the marquee yellow
   that bills a win everywhere else. */
.bar__fill--goal {
  background: var(--amarillo);
  transform: scaleX(var(--progress, 0));
}

/* The same track carrying the walk to the next level. It reads --xp rather than
   --hp so a replay can never scale a progress bar by accident. */
.bar__fill--xp {
  background: var(--amarillo);
  transform: scaleX(var(--xp, 0));
}

/* ── Stat bar ──────────────────────────────────────────────────────────── */

/*
 * How a rival is read on the scouting sheet: five bars against the lifetime cap,
 * so a build is a shape before it is five numbers. This is what the approved
 * prototype scouts with, and the prototype is the reference (ADR 0015).
 *
 * Two fills on one track. The kit's sits behind and reads the total, the
 * allocated points sit in front — so what a piece of equipment adds is the strip
 * of menta showing past the lila, which is the same two-number reading
 * `.boost` gives beside the figure.
 */
.statbar {
  display: grid;

  /* Each row is its own grid, so the figure's column needs a floor or a
     two-digit atributo shortens that row's track and the five bars stop
     lining up with each other. */
  grid-template-columns: minmax(0, 5.5rem) 1fr minmax(2.75rem, auto);
  align-items: center;
  gap: var(--space-2);
}

.statbar__key {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--dim);
}

.statbar__track {
  position: relative;
  height: 12px;
  overflow: hidden;
  background: var(--well);
  border: var(--border-hair) solid var(--line);
}

.statbar__fill {
  position: absolute;
  inset: 0;
  background: var(--lila);
  transform: scaleX(var(--stat, 0));
  transform-origin: left center;
  transition: transform var(--motion) var(--ease);
}

.statbar__fill--kit {
  background: var(--menta);
}

.statbar__value {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: var(--space-1);
  font-family: var(--font-data);
  font-size: var(--text-data);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ── Fight log ─────────────────────────────────────────────────────────── */

/*
 * One modifier per resolver event type (CONTEXT.md): exchange, hit, dodge,
 * counter, snack, trait, end. A critical is a flagged hit, not its own event.
 * The emoji markers are game content, not chrome.
 */
.log {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font: 400 var(--text-log) / var(--leading-log) var(--font-data);
  font-variant-numeric: tabular-nums;
}

/*
 * A ruled stack (.impeccable.md), with the rule doing the colour-coding as well
 * as the words: the prototype marks every moment with a bar down its left edge,
 * which is what makes an asalto scannable without reading it. The hues are the
 * prototype's, which is the reference (ADR 0015) — a crítico is amarillo, not
 * rosa, because amarillo is what the last line of a won fight already wears.
 */
.log-line {
  padding-inline-start: var(--space-3);
  border-inline-start: var(--border) solid transparent;
  overflow-wrap: anywhere;
}

.log-line--exchange {
  font-size: var(--text-micro);
  color: var(--dim);
  border-inline-start-color: var(--line);
}

.log-line--hit {
  color: var(--texto);
  border-inline-start-color: var(--rosa);
}

.log-line--hit.is-crit {
  font-weight: 700;
  color: var(--amarillo);
  border-inline-start-color: var(--amarillo);
}

.log-line--dodge {
  color: var(--azul);
  border-inline-start-color: var(--azul);
}

.log-line--counter {
  font-weight: 700;
  color: var(--lila);
  border-inline-start-color: var(--lila);
}

.log-line--snack {
  font-weight: 700;
  color: var(--naranja);
  border-inline-start-color: var(--naranja);
}

.log-line--trait {
  font-weight: 700;
  color: var(--menta);
  border-inline-start-color: var(--menta);
}

.log-line--end {
  font-weight: 700;
  color: var(--amarillo);
}

/* ── Verdict ───────────────────────────────────────────────────────────── */

/*
 * The card a fight ends on: what happened, in the biggest type the system has,
 * over the pet it happened to. Amarillo for a win and rosa for a loss are the
 * two colours the fight log already spends on those same two meanings, so the
 * border carries the result before the word is read. A win therefore wears the
 * marquee's colour without being a billing, and it is the only amarillo border
 * its page is allowed.
 */
.verdict {
  align-items: center;
  padding-block: var(--space-6);
  text-align: center;
  box-shadow: var(--shadow-slab);

  /* The bands are the ink laid over the fill, so they are mixed from --sombra
     rather than declared: the same overlay has to work on amarillo and rosa. */
  background-image: repeating-linear-gradient(
    var(--stripe-angle),
    color-mix(in srgb, var(--sombra) 9%, transparent) 0 14px,
    transparent 14px 28px
  );
  animation: verdict-stamp var(--beat) var(--ease-slam);
}

.verdict--won {
  background-color: var(--amarillo);
  border-color: var(--ink);
  color: var(--ink);
}

.verdict--lost {
  background-color: var(--rosa);
  border-color: var(--ink);
  color: var(--texto);
}

/* On a solid fill the sticker's white edge disappears; ink is what holds the
   photo off the poster it is printed on. */
.verdict .pet-card {
  border-color: var(--ink);
}

.verdict__call {
  font-size: var(--text-verdict);
}

/* The reason under the call — por nocaut, por decisión — reads as a caption of
   the fill it sits on, not as body copy that happens to be dark. */
.verdict__why {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  opacity: 0.75;
}

/* Landing past the mark and settling back: the fight is over and the page says
   so in one movement rather than fading it in. */
@keyframes verdict-stamp {
  from {
    opacity: 0;
    transform: scale(1.14);
  }
}

/* The photo is the art (.impeccable.md), and the only thing on this card
   competing with the call above it, so it is billed wider than the arena's
   list thumbnail and narrower than the card the pet page gives it. */
.verdict__pet {
  width: 9.5rem;
  max-width: 100%;
}

/* ── Payout ────────────────────────────────────────────────────────────── */

/* What a fight paid, one line per payout. The numbers are set at display size
   because this is the screen the daily ritual ends on and they have to read at
   arm's length — but in the data face, which is the only one of the three that
   sets tabular figures (ADR 0010). The label beside them stays quiet. */
.payout {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.payout__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.payout__value {
  font-family: var(--font-data);
  font-size: var(--text-display);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--amarillo);
}

.payout__value--xp {
  color: var(--menta);
}

/*
 * The two numbers a fight paid, billed side by side (ADR 0015). This is the
 * screen the daily ritual ends on and they are the reward, so they are boxed
 * and centred rather than listed — a payout read down a column of labels is an
 * invoice.
 *
 * `column-reverse` puts the number above its label without reordering the
 * markup, so a screen reader still hears "Fichas, +12" in that order.
 *
 * The figures are set in the display face, which is what the prototype bills
 * them in. Tabular numerals are what `.payout__value` normally exists for, and
 * they are not needed here: each figure stands alone in its own centred box,
 * with nothing above or below it to line up against.
 */
.payout--pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}

.payout--pair .payout__line {
  flex-direction: column-reverse;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-2);
  background: var(--ink);
  border: var(--border) solid var(--line);
  text-align: center;
}

.payout--pair .payout__value {
  font-family: var(--font-display);
  letter-spacing: var(--track-display);
}

/* The label under a billed number is a caption of it, not a row header. */
.payout--pair .payout__line > :first-child {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--dim);
}

/* ── Form field ────────────────────────────────────────────────────────── */

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

.field__label {
  font-size: var(--text-data);
  font-weight: 600;
}

/* A field is a hole cut in the poster: sunken ground, a thick ink edge, no
   radius. The boundary keeps --dim rather than --ink, because on a control the
   border is the whole affordance and has to clear the 3:1 non-text floor. */
.field__input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--space-3);
  background: var(--well);
  border: var(--border-poster) solid var(--dim);
  color: var(--texto);
  font: 400 var(--text-body) / 1 var(--font-body);
}

.field__input::placeholder {
  color: var(--dim);
}

.field__hint {
  font-size: var(--text-data);
  color: var(--dim);
}

.field__error {
  font-size: var(--text-data);
  font-weight: 600;
  color: var(--rosa);
}

/* A field under a cooldown. The hint beside it carries the date it opens again
   (ADR 0013); the dash is the half of that signal colour does not carry. A
   readonly field still holds the pet's real name and stays in the tab order, so
   it keeps body contrast and its 3:1 boundary — only the genuinely inactive
   file input dims. Both sit above .field--invalid, which has the same
   specificity and has to win when a locked field comes back with an error. */
.field__input[readonly] {
  border-style: dashed;
  cursor: not-allowed;
}

.field__input:disabled {
  border-color: var(--line);
  border-style: dashed;
  color: var(--dim);

  /* iOS Safari paints its own colour on a disabled control and ignores `color`. */
  -webkit-text-fill-color: var(--dim);
  cursor: not-allowed;
}

.field--invalid .field__input {
  border-color: var(--rosa);
}

/* The shorthand above sets a leading of 1, which clips a file input's button. */
.field__input[type="file"] {
  padding-block: var(--space-2);
  line-height: var(--leading-body);
}

/* Same shorthand, same problem from the other side: a leading of 1 stacks the
   lines of a multi-line field on top of each other. Qualified by element the
   way the file input is, so no paragraph field can be added without it. */
textarea.field__input {
  padding-block: var(--space-2);
  line-height: var(--leading-body);
  resize: vertical;
}

/* The six digits copied out of a sign-in mail. It wears the data role because
   that is what it is, and it is set large and tracked out because it is read
   off one screen and typed into another — on a phone, one handed, usually with
   the mail app still half covering the field. */
.field__input--code {
  font-family: var(--font-data);
  font-size: var(--text-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-display);
  text-align: center;
}

.fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* A legend is not a flex item — it sits above the anonymous content box — so
   the fieldset's row-gap never reaches it and the space has to be its own. */
.fieldset > legend {
  padding: 0;
  margin-block-end: var(--space-3);
}

/* Reparto libre reveals its own fields with no JavaScript, so they never paint
   before a controller can hide them, and the screen still works without one.
   `:has()` is already a requirement (allow_browser versions: :modern). */
.fieldset--spread {
  display: none;
}

form:has(.choice__input--free:checked) .fieldset--spread {
  display: flex;
}

/* ── Choice ────────────────────────────────────────────────────────────── */

/*
 * A radio stretched over its own panel: the whole card is the tap target, the
 * control stays a real radio for the keyboard and the screen reader, and the
 * check mark means colour is never the only signal of what is chosen.
 */
.choice {
  position: relative;
  display: block;
}

.choice__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.choice__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--card);
  border: var(--border) solid var(--line);
  transition: border-color var(--motion) var(--ease);
}

.choice__input:checked + .choice__body {
  border-color: var(--amarillo);
}

.choice__input:focus-visible + .choice__body {
  outline: var(--border) solid var(--amarillo);
  outline-offset: 2px;
}

/* An amarillo ring on the amarillo border reads as a halo, not a focus state,
   and the checked card is the first one a keyboard lands on. */
.choice__input:checked:focus-visible + .choice__body {
  outline-color: var(--texto);
}

.choice__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.choice__name {
  font-family: var(--font-display);
  font-size: var(--text-body);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

.choice__check {
  color: var(--amarillo);
  opacity: 0;
}

.choice__input:checked + .choice__body .choice__check {
  opacity: 1;
}

/* ── Point spread ──────────────────────────────────────────────────────── */

.spread {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.spread__value {
  width: 5rem;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.spread__left {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Overspent is the only state the number cannot explain on its own. */
.spread__remaining.is-over {
  font-weight: 700;
  color: var(--rosa);
}

/* ── Flash ─────────────────────────────────────────────────────────────── */

/* One line of consequence after a redirect: signed out, link expired. The
   border carries the meaning, so the message stays readable at --texto. */
.flash-region {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: var(--shell);
  margin-inline: auto;
  padding: var(--space-4) var(--space-3) 0;
}

.flash {
  padding: var(--space-3);
  background: var(--card);
  border: var(--border-poster) solid var(--ink);
  box-shadow: var(--shadow-cast);
  font-weight: 600;
}

.flash--notice {
  border-color: var(--menta);
}

.flash--alert {
  border-color: var(--rosa);
}

/* ── Chip ──────────────────────────────────────────────────────────────── */

/* Square, like everything the poster prints: a pill is a soft shape and this
   system stopped having those (ADR 0015). The boundary thickens to match. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  background: var(--well);
  border: var(--border) solid var(--line);
  font: 700 var(--text-micro) / 1 var(--font-data);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--dim);
}

/* A state to find on a scrolling page, not a decoration: the moderation queue
   is scanned before it is read. The label says the same thing without it. */
.chip--alert {
  border-color: var(--rosa);
  color: var(--rosa);
}

/* A podio a closed season left on a pet (COMBAT-31), and the one chip that is a
   reward rather than a reading. Menta rather than amarillo, the victory colour,
   because amarillo on this same card is already the padrino's. A season is named
   in `config/seasons.yml`, so the label is as long as somebody authored it. */
.chip--temporada {
  max-width: 100%;
  border-color: var(--menta);
  color: var(--menta);
}

/*
 * The two readings a shelf is scanned for. What a piece adds wears the colour
 * `.boost` already spends on exactly that, and what it costs wears the marquee
 * colour every ficha in the app wears — so a row of three chips is read by
 * colour before it is read by word.
 */
.chip--boost {
  border-color: var(--menta);
  color: var(--menta);
}

.chip--cost {
  border-color: var(--amarillo);
  color: var(--amarillo);
}

/* ── Rank ──────────────────────────────────────────────────────────────── */

/* Rango is the one thing on a pet that is purely earned and purely cosmetic
   (CONTEXT.md), so it gets the marquee treatment the numbers never do. One
   treatment for all five: the name is the reward, and a colour per rank would
   spend the palette's meanings on decoration. */
.rank {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--well);
  border: var(--border) solid var(--amarillo);
  font: var(--text-body) / var(--leading-tight) var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--amarillo);
}

/* ── Glossary ──────────────────────────────────────────────────────────── */

/*
 * A term and what it does, stacked. The shape the prototype uses everywhere the
 * app explains itself — the landing's five stats, the guide and the archetypes at
 * level-up — and all three of those were two-column tables here.
 *
 * A table is the wrong instrument for it: the term is one word and the gloss is a
 * sentence, so the sentence wraps to three lines in its cell while the header
 * column sits nearly empty. `.impeccable.md` says density belongs in tables;
 * this is not density, it is prose with a label on it.
 */
.glossary {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin: 0;
}

.glossary__term {
  font: var(--text-micro) / var(--leading-tight) var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--texto);
}

.glossary__gloss {
  margin: var(--space-1) 0 0;
  font-size: var(--text-body);
}

/* ── Table ─────────────────────────────────────────────────────────────── */

/* Dense surfaces scroll inside their own box; the page never does. */
.table-scroll {
  overflow-x: auto;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-data);
}

/* Ink rules rather than hairlines: a printed table is ruled, and at this weight
   the grid survives the halftone under it. */
.table th,
.table td {
  padding: var(--space-2);
  border-block-end: var(--border) solid var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: left;
}

.table th {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--dim);
}

/* The head is the band the banner is elsewhere, at a table's scale. */
.table thead th {
  background: var(--well);
}

/*
 * The reader's own row, inverted rather than tinted. A board is scanned for one
 * line and a wash of amarillo at 7% is not what finds it; the row that matters
 * is printed in the marquee colour and everything in it takes the ink.
 */
.table tr[aria-current] th,
.table tr[aria-current] td {
  background: var(--amarillo);
  border-block-end-color: var(--amarillo-print);
  color: var(--ink);
}

.table .num {
  font-family: var(--font-data);
  text-align: right;
  white-space: nowrap;
}

/* Four columns of tracked mono caps and a row header as long as "Inteligencia"
   do not fit 320px at the padding above. A comparison the screen exists for
   may not be the thing that scrolls sideways. */
.table--dense th,
.table--dense td {
  padding-inline: var(--space-1);
}

/* A button that lives in a row rather than under a card. `.btn` breaks a label
   anywhere so a pet's or a jauría's name can wrap inside one; an action column
   is about 90px on a 320px phone, where that turns "Comprar" into two lines and
   the row into 54px. These labels are ours and each is one short word, so here
   the word stays whole and the row keeps a 44px tap target. */
.table .btn {
  padding-inline: var(--space-2);
  overflow-wrap: normal;
}

/* ── Stat boost ────────────────────────────────────────────────────────── */

/* What a pet's equipment adds to a stat, kept beside the number instead of
   folded into it: the 20 points are permanent and this half is not. It carries
   its own figures, so it stays aligned outside a table cell too. */
.boost {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--menta);
}

/* ── Padrino ───────────────────────────────────────────────────────────── */

/*
 * What a one-time supporter tier buys, and the whole of what it buys: nothing
 * below moves a number anywhere (ADR 0011). Tier 1 gilds the name on the
 * sticker, tier 2 swaps the sticker's white border for the holographic sweep
 * .impeccable.md reserves for it. Both ride on the pet card, so every surface
 * that bills a pet wears them from the same rule.
 */
.pet-card--padrino .pet-card__name {
  color: var(--amarillo);
}

.pet-card--holo {
  background:
    linear-gradient(var(--card), var(--card)) padding-box,
    var(--holo) border-box;
  border-color: transparent;
}

/* The badge, on the two pages a pet is the subject of. Marquee yellow, which is
   what a victory already wears — but a chip, never `.rank`, because the rango
   is earned and this was bought (CONTEXT.md). It centres itself the way the
   jauría tag does: the card is a stretching column, so an inline pill left to
   itself goes full-bleed. */
.chip--padrino {
  align-self: center;
  max-width: 100%;
  border-color: var(--amarillo);
  color: var(--amarillo);
}

/* What a tier includes, read before a price is paid, with the tick outside the
   text column. `list-style: none` costs a list its semantics in VoiceOver, so
   the markup carries `role="list"` to put them back. */
.includes {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);

  /* The UA indent is 40px, which is 15% of the content column at 320px and
     leaves every line hanging off the label above it. */
  margin: 0;
  padding: 0;
  list-style: none;
}

.includes__item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

/*
 * The lema de entrada a tier-2 padrino wrote, announced before the bell the way
 * a cartel bills a luchador. Quoted rather than shouted: the log is what the
 * page exists for, and sixty characters of somebody else's words must not
 * out-bill it.
 */
.lema {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-data);
  line-height: var(--leading-body);
  color: var(--dim);
  overflow-wrap: anywhere;
}

.lema__who {
  font-family: var(--font-display);
  font-size: var(--text-micro);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--amarillo);
}

/* ── The case against a verdict ────────────────────────────────────────── */

/*
 * What a hidden player wrote to the one human who hid them, read on the
 * moderation card and nowhere else. Prose rather than a table row: it is the
 * thing on that card which has to be read in full, at whatever length its
 * author chose, and nothing stops five hundred characters arriving without a
 * space in them.
 */
.case {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  line-height: var(--leading-body);
  overflow-wrap: anywhere;
}

.case__when {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--dim);
}

/* ── Empty state ───────────────────────────────────────────────────────── */

/* Empty states teach the next action; they never just say "nothing here". */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  text-align: center;
}

.empty__body {
  color: var(--dim);
}

/* ── Segmented control ─────────────────────────────────────────────────── */

/* One choice out of a few, all of them worth showing at once: a board's week
   against its season, the globe against a country. How many options there are
   is data rather than layout — a country earns its place by having players —
   so the track scrolls inside its own box and never wraps. Wrapped pills stop
   reading as one choice, and three rows of them push the thing the page is for
   below the fold. The options are links, so focus scrolls the track and it
   needs no tabindex of its own. */
.segmented {
  display: flex;
  overflow-x: auto;
  background: var(--well);
  border: var(--border-poster) solid var(--ink);
}

.segmented__option {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding-inline: var(--space-4);
  font: 700 var(--text-micro) / 1 var(--font-data);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  color: var(--dim);
  transition: color var(--motion) var(--ease);
}

.segmented__option[aria-current="page"] {
  background: var(--amarillo);
  color: var(--ink);
}

/* An amarillo ring on the amarillo fill reads as a halo, not a focus state. */
.segmented__option[aria-current="page"]:focus-visible {
  outline-color: var(--texto);
}

@media (hover: hover) {
  .segmented__option:not([aria-current="page"]):hover {
    color: var(--texto);
  }
}

/* ── Locale switch ─────────────────────────────────────────────────────── */

.site-footer {
  display: flex;
  justify-content: center;
  padding: var(--space-4) var(--space-3) var(--space-6);
}

/* The hairline is decoration elsewhere; here it is the whole affordance, so
   the boundary takes --dim and clears the 3:1 non-text floor. */
.locale-switch {
  display: flex;
  background: var(--well);
  border: var(--border) solid var(--dim);
}

/*
 * Equal halves, not two labels sized to their words: "Español" is shorter than
 * "English", and with the pill's gap gone the two butting together at different
 * widths read as one broken control rather than as a choice (COMBAT-61). A
 * switch has as many equal seats as it has options.
 */
.locale-switch__option {
  flex: 1;
  min-height: var(--tap);
  padding-inline: var(--space-4);
  background: none;
  border: 0;
  color: var(--dim);
  font: 700 var(--text-micro) / 1 var(--font-data);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  cursor: pointer;
  transition: color var(--motion) var(--ease);
}

.locale-switch__option[aria-current="true"] {
  background: var(--amarillo);
  color: var(--ink);
}

/* An amarillo ring on the amarillo fill reads as a halo, not a focus state. */
.locale-switch__option[aria-current="true"]:focus-visible {
  outline-color: var(--texto);
}

@media (hover: hover) {
  .locale-switch__option:not([aria-current="true"]):hover {
    color: var(--texto);
  }
}

/* ── Install prompt ────────────────────────────────────────────────────── */

.install-prompt {
  position: fixed;
  z-index: 1;
  inset-inline: var(--space-3);
  inset-block-end: var(--space-3);
  max-width: var(--shell);
  margin-inline: auto;
  border-color: var(--amarillo);
}

/* Fixed, so the page has to grow past the banner's tallest state or it buries
   the last log line of the fight it is congratulating. */
body:has(.install-prompt:not([hidden])) {
  padding-block-end: 15rem;
}

/* ── Visually hidden ───────────────────────────────────────────────────── */

/*
 * Read, never seen. For the word behind an abbreviation a component shows for
 * room's sake — `.statbar` bills "FUE" in 88px and owes a reader "Fuerza".
 *
 * `clip-path` rather than `display: none` or `width: 0`, both of which take the
 * text out of the accessibility tree along with the layout.
 */
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Icon ──────────────────────────────────────────────────────────────── */

/* IconHelper#icon is the only sanctioned glyph; shapes carry currentColor. */
.ic {
  flex: none;
  fill: none;
  vertical-align: -0.15em;
}
