/* The log-game flow, the victory toast, and every responsive breakpoint. */

/* ---------------------------------------------------------------- overlay */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--s5);
  background: rgba(4, 6, 8, 0.82);
  opacity: 0;
  transition: opacity 140ms var(--ease);
  overflow-y: auto;
}

.overlay.is-open {
  opacity: 1;
}

.overlay.is-closing {
  opacity: 0;
}

.modal {
  width: min(880px, 100%);
  transform: translateY(10px) scale(0.99);
  transition: transform 180ms var(--ease);
}

.is-open .modal {
  transform: none;
}

.modal > .panel__in {
  padding: 0;
}

.modal__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border-bottom: var(--fw) solid var(--edge);
  background: linear-gradient(var(--panel-2), var(--panel));
}

.modal__title {
  font-size: 15px;
  letter-spacing: 0.08em;
  color: var(--gold);
}

.modal__sub {
  font-size: 8px;
  line-height: 1.7;
  color: var(--ink-faint);
}

.modal__body {
  padding: var(--s5);
}

/* ---------------------------------------------------------------- stepper */

.stepper {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) var(--s5);
  background: var(--bg-deep);
  border-bottom: 2px solid var(--panel-2);
}

.stepper__step {
  display: flex;
  align-items: center;
  gap: var(--s2);
  color: var(--ink-faint);
}

.stepper__step:disabled {
  cursor: default;
}

.stepper__num {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  font-family: var(--font-display);
  font-size: 8px;
  background: var(--panel-2);
  color: var(--ink-faint);
}

.stepper__label {
  font-family: var(--font-display);
  font-size: 8px;
  letter-spacing: 0.08em;
}

.stepper__step.is-active .stepper__num {
  background: var(--gold);
  color: #23180a;
}

.stepper__step.is-active .stepper__label {
  color: var(--gold);
}

.stepper__step.is-done .stepper__num {
  background: var(--gold-dark);
  color: var(--gold);
}

.stepper__step.is-done .stepper__label {
  color: var(--ink-dim);
}

.stepper__link {
  flex: 1;
  height: 3px;
  background: repeating-linear-gradient(90deg, var(--panel-3) 0 4px, transparent 4px 8px);
}

/* ---------------------------------------------------------------- steps */

.step {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

.step__prompt {
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink);
}

.step__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  padding-top: var(--s2);
}

.step__tally {
  font-family: var(--font-display);
  font-size: 11px;
  color: var(--gold);
}

.step__hint {
  font-size: 8px;
  color: var(--ink-faint);
}

/* Every tile in the flow shares this shape: notched, accent-washed, with a
 * keyboard hint in the corner and a clear selected state. */
.game-tile,
.player-tile,
.winner-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  padding: var(--s4) var(--s2);
  background:
    linear-gradient(170deg, color-mix(in srgb, var(--deep) 55%, var(--panel)), transparent 70%),
    var(--panel-2);
  box-shadow: inset 0 0 0 2px var(--panel-3);
  clip-path: polygon(
    6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px),
    calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px
  );
  transition: transform 120ms var(--ease), box-shadow 120ms;
}

.game-tile:hover,
.player-tile:hover,
.winner-tile:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
}

.game-tile.is-selected,
.player-tile.is-selected,
.winner-tile.is-selected {
  box-shadow: inset 0 0 0 3px var(--accent);
}

.game-tile__key,
.player-tile__key {
  position: absolute;
  top: 6px;
  left: 7px;
  font-family: var(--font-display);
  font-size: 7px;
  color: var(--ink-faint);
}

.game-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s2);
}

.game-tile__name {
  font-family: var(--font-display);
  font-size: 10px;
  color: var(--accent);
}

.game-tile__realm {
  font-family: var(--font-display);
  font-size: 7px;
  line-height: 1.7;
  color: var(--ink-faint);
}

.player-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: var(--s2);
}

.player-tile__check {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  /* A drawn tick needs no font metrics, so `place-items: center` is exact. */
  line-height: 0;
  background: transparent;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0, 100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%, 0 calc(100% - 4px), 0 4px
  );
  transition: background-color 120ms var(--ease);
}

.player-tile.is-selected .player-tile__check {
  background: var(--accent);
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.35);
}

.player-tile__name,
.winner-tile__name {
  font-family: var(--font-display);
  font-size: 11px;
  color: var(--accent);
}

.winner-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s2);
}

.winner-tile__crown {
  opacity: 0;
  transition: opacity 120ms;
}

.winner-tile:hover .winner-tile__crown,
.winner-tile.is-selected .winner-tile__crown {
  opacity: 1;
}

/* ---------------------------------------------------------------- summary */

/* A single centred column, divided into bands. The winner is the hero of the
 * card; everything else is supporting detail underneath it. */
.summary > .panel__in {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 0;
  text-align: center;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--deep) 60%, var(--panel)), transparent 55%),
    linear-gradient(var(--panel-2), var(--panel) 42%);
}

/* Each band is full width with a rule between, so the card reads top to bottom
 * instead of as loose clusters. */
.summary > .panel__in > * {
  width: 100%;
  padding: var(--s4) var(--s4);
}

.summary > .panel__in > * + * {
  border-top: 2px solid var(--panel-3);
}

.summary__game {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
}

.summary__game-name {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 400;
  color: var(--accent);
}

.summary__realm {
  font-size: 8px;
  line-height: 1.7;
  color: var(--ink-faint);
}

/* ---- winner ---- */

.summary__winner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
}

.summary__crown {
  margin-bottom: calc(var(--s2) * -1);
}

.summary__winner-art {
  padding: var(--fw);
  background: linear-gradient(#ffeec0, var(--edge) 42%, var(--edge-dim));
  clip-path: polygon(
    6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px),
    calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px
  );
  box-shadow: 0 0 22px rgba(242, 206, 86, 0.22);
}

.summary__winner-name {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 400;
  color: var(--gold);
}

/* ---- also played ---- */

.summary__losers {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
}

.summary__losers-row {
  display: flex;
  gap: var(--s4);
  flex-wrap: wrap;
  justify-content: center;
}

.summary__loser {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-family: var(--font-display);
  font-size: 8px;
  color: var(--ink-faint);
}

/* ---- note + date ---- */

.summary__notes {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
}

.summary__notes-text {
  width: 100%;
  padding: var(--s2) var(--s3);
  font-size: 9px;
  line-height: 1.9;
  color: var(--ink);
  background: var(--well);
  box-shadow: inset 0 0 0 2px var(--panel-3);
}

.summary__date {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
}

/* Was rendering in the browser's own UI font at native size -- every other
 * field in the flow (notes, the whole pixel-font vocabulary) is deliberate
 * about typography, and a native date input dropped in unstyled just read as
 * unfinished next to them. */
.date-input {
  padding: 10px var(--s3);
  min-width: 132px;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--ink);
  background: var(--well);
  border: 0;
  box-shadow: inset 0 0 0 2px var(--panel-3);
  color-scheme: dark;
  transition: box-shadow 140ms var(--ease);
}

.date-input:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--gold);
}

/* The native calendar-picker glyph is a flat black/white icon regardless of
 * color-scheme in some browsers -- dim it to sit quietly instead of glowing
 * white against the dark well. */
.date-input::-webkit-calendar-picker-indicator {
  filter: opacity(0.6);
}

/* ---------------------------------------------------------------- notes step */

.step__aside {
  max-width: 62ch;
  margin-top: calc(var(--s2) * -1);
  font-size: 13px;
  color: var(--ink-faint);
}

.notes-field {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.notes-input {
  width: 100%;
  padding: var(--s3);
  font-size: 10px;
  line-height: 1.9;
  color: var(--ink);
  background: var(--well);
  border: 0;
  box-shadow: inset 0 0 0 2px var(--panel-3);
  resize: vertical;
  transition: box-shadow 140ms var(--ease);
}

.notes-input::placeholder {
  color: var(--ink-faint);
}

.notes-input:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--gold);
}

.notes-count {
  align-self: flex-end;
  font-family: var(--font-display);
  font-size: 7px;
  color: var(--ink-faint);
}

.summary__notes {
  grid-column: 1 / -1;
}

.summary__notes-text {
  margin-top: var(--s2);
  padding: var(--s2) var(--s3);
  font-size: 9px;
  line-height: 1.9;
  color: var(--ink);
  background: var(--well);
  box-shadow: inset 0 0 0 2px var(--panel-3);
}

/* ---------------------------------------------------------------- victory */

.victory {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: var(--s5);
  /* Opaque, not translucent. The page behind is a wall of bright gold-framed
   * cards, and even a few percent of bleed reads through and competes with the
   * celebration. The warm glow sits on top of a solid base. */
  background:
    radial-gradient(58% 46% at 50% 44%, rgba(64, 48, 8, 0.55), transparent 70%),
    #05070c;
  opacity: 0;
  transition: opacity 200ms var(--ease);
  cursor: pointer;
  overflow: hidden;
}

.victory.is-open {
  opacity: 1;
}

.victory__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s3);
  text-align: center;
  /* Punches in past its final size and settles, which reads as impact. */
  transform: scale(0.7);
  transition: transform 320ms cubic-bezier(0.2, 1.5, 0.4, 1);
}

.victory.is-open .victory__inner {
  transform: scale(1);
}

/* The winner's own animated victory pose, full-bleed -- no frame. The static
 * portrait crop this replaced was a 48px sprite blown up in a gold box; this
 * is real per-character art, so the gold glow now comes from a drop-shadow
 * on the character's own silhouette instead of a panel around it. */
.victory__pose {
  width: clamp(160px, 34vh, 260px);
  height: auto;
  image-rendering: pixelated;
  filter: drop-shadow(0 0 32px rgba(242, 206, 86, 0.45));
}

.victory__title {
  /* 8 characters of a very wide pixel face; any larger and it touches both edges
   * of a 375px screen. */
  font-size: clamp(24px, 8.6vw, 54px);
  letter-spacing: 0.06em;
  color: var(--gold);
  text-shadow:
    0 4px 0 #3a2a08,
    0 0 30px rgba(242, 206, 86, 0.5);
}

.victory__banner {
  padding: var(--s2) var(--s5);
  background: linear-gradient(var(--edge-bright), var(--edge) 45%, var(--edge-dim));
  clip-path: polygon(6px 0, calc(100% - 6px) 0, 100% 50%, calc(100% - 6px) 100%, 6px 100%, 0 50%);
}

.victory__name {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: 0.04em;
  color: #241a05;
}

.victory__sub {
  font-size: 10px;
  color: var(--ink-dim);
}

/* ---- confetti ---- */

.victory__confetti {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.confetti {
  position: absolute;
  top: -16px;
  left: var(--x);
  width: var(--size);
  height: var(--size);
  background: var(--hue);
  /* Square, unrounded, stepped: falling pixels rather than paper. */
  animation: confetti-fall var(--fall) steps(12, end) var(--delay) forwards;
}

@keyframes confetti-fall {
  0% {
    opacity: 0;
    transform: translate(0, 0) rotate(0deg);
  }
  10% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate(var(--drift), 100vh) rotate(calc(var(--spin) * 540deg));
  }
}

@media (prefers-reduced-motion: reduce) {
  .victory__confetti {
    display: none;
  }

  .victory__inner,
  .victory.is-open .victory__inner {
    transform: none;
  }
}

/* ---------------------------------------------------------------- responsive */

@media (max-width: 1080px) {
  .ladder-row .panel__in {
    grid-template-columns: 44px auto minmax(0, 1fr) auto;
  }

  /* The strongest-game column is the first thing to go: it is the least
   * important of the four and the widest. */
  .ladder-row__best {
    display: none;
  }
}

/* ------------------------------------------------------------ tablet + phone
 *
 * At this width the top bar cannot hold a wordmark, four nav items and a button
 * on one line. Rather than let it wrap into three stacked rows -- which ate
 * ~180px of a 812px screen before you saw any content -- the navigation moves to
 * a fixed bottom tab bar and the primary action becomes a floating button just
 * above it. Both are within thumb reach, and the top bar shrinks to a title. */
@media (max-width: 860px) {
  .topbar__inner {
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    /* Nav and the action button both go to fixed position below, leaving the
     * banner alone in this row -- it should sit centred, not pinned left with
     * empty space beside it. */
    justify-content: center;
  }

  .title-banner__line {
    font-size: 28px;
  }

  .view {
    /* Bottom padding clears the fixed action bar, which the removed footer used
     * to account for. */
    padding: var(--s4) var(--s4) calc(var(--s7) + env(safe-area-inset-bottom));
  }

  /* --- bottom action bar ------------------------------------------------ */

  /* Nav tabs and the primary action share one fixed bar. A floating button was
   * the other option, but a FAB necessarily sits on top of content -- it was
   * covering a leaderboard card's win rate. In the bar, everything is inside
   * thumb reach and nothing is obscured. */
  .nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    gap: 0;
    margin: 0;
    /* Reserve the right end for the action button -- matched to its actual
     * width (see .topbar__actions below); a wider reservation than the button
     * itself left a sliver of bare bar between the last tab and the button. */
    padding: 0 96px 0 0;
    background: linear-gradient(#0d1019, var(--bg-deep));
    border-top: var(--fw) solid var(--edge);
    box-shadow: 0 -4px 18px rgba(0, 0, 0, 0.6);
  }

  .nav__item {
    flex: 1;
    flex-direction: column;
    justify-content: center;
    gap: 0;
    padding: 13px var(--s1) calc(12px + env(safe-area-inset-bottom));
    font-size: 7px;
    letter-spacing: 0.02em;
    text-align: center;
    border-bottom: 0;
    /* The active marker moves to the top edge, since the bar now sits at the
     * bottom of the screen and a bottom border would fall off it. */
    border-top: var(--fw) solid transparent;
  }

  /* Icons only. The label stays in the DOM for screen readers and as the
   * button's title, but four pixel-font words do not fit a phone, and the icons
   * carry the meaning on their own. */
  .nav__icon {
    display: block;
  }

  .nav__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .nav__item .sprite {
    filter: grayscale(0.5) brightness(0.8);
  }

  .nav__item.is-active {
    border-top-color: var(--gold);
    background: rgba(242, 206, 86, 0.1);
    color: var(--gold);
  }

  .nav__item.is-active .sprite {
    filter: none;
  }

  /* The primary action is deliberately the loudest thing in the bar: it breaks
   * the bar's top line, carries the gold frame on three sides, and shows the
   * die. Everything else in the bar is a flat icon by comparison. */
  .topbar__actions {
    position: fixed;
    right: 0;
    bottom: 0;
    z-index: 46;
    width: 96px;
  }

  .topbar__actions .cta-wrap {
    display: block;
    width: 100%;
    height: 100%;
  }

  .topbar__actions .pixel-btn {
    width: 100%;
    height: 100%;
    padding: 3px 0 0;
    clip-path: none;
    background: linear-gradient(#ffeec0, var(--edge) 50%);
    box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.55);
  }

  .topbar__actions .pixel-btn__face {
    flex-direction: column;
    gap: 3px;
    height: 100%;
    padding: 10px var(--s1) calc(9px + env(safe-area-inset-bottom));
    font-size: 7px;
    letter-spacing: 0.04em;
    clip-path: none;
    box-shadow:
      inset 0 0 0 2px var(--hero-deep),
      inset 0 3px 0 var(--hero-bright);
  }

  /* Scaled for the bar; any larger and the ornament crowds the label. */
  .topbar__actions .pixel-btn__face::after {
    inset: 1px;
    background-size: 6px 6px;
  }

  /* Sized up now that the icon is the button's only content -- at its
   * default size it read as a small icon lost in a tall gold box. */
  .topbar__actions .pixel-btn__face .sprite {
    display: block;
    width: 26px;
  }

  /* Keep the end of the page clear of the bar. */
  
  /* --- content ---------------------------------------------------------- */

  .world-hero .panel__in {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    padding: var(--s4);
  }

  .world-hero__figures {
    justify-content: center;
  }

  .mastery__row,
  .h2h__row {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      'who figures'
      'bar bar';
    gap: var(--s2);
  }

  .mastery__game,
  .h2h__who {
    grid-area: who;
  }

  .mastery__bar,
  .h2h__bar {
    grid-area: bar;
  }

  .mastery__figures,
  .h2h__score {
    grid-area: figures;
  }

  .h2h__verdict {
    display: none;
  }

  .standings__row {
    grid-template-columns: 28px 1fr auto;
    grid-template-areas:
      'pos who figures'
      'bar bar bar';
    gap: var(--s2) var(--s3);
  }

  .standings__pos {
    grid-area: pos;
  }

  .standings__who {
    grid-area: who;
  }

  .standings__bar {
    grid-area: bar;
  }

  .standings__figures {
    grid-area: figures;
  }
}

/* ------------------------------------------------------------------- phone */
@media (max-width: 620px) {
  /* --- the vault ---------------------------------------------------------- */

  .vault__name {
    font-size: clamp(40px, 15vw, 64px);
  }

  /* Smaller again on a phone, on top of the bottom-anchored position above --
   * a phone's viewport is short relative to its width, so the same vh-based
   * size that reads well on a laptop leaves next to no headroom for the name
   * stacked above it. */
  .vault__figure {
    width: clamp(120px, 34vh, 260px);
    bottom: 2%;
  }

  /* The fixed tab bar already sits at the foot of the screen; a second thing
   * down there competing with it was one too many. The room ending at the
   * bar is cue enough that the page continues. */
  .vault__scroll {
    display: none;
  }

  /* --- top bar ---------------------------------------------------------- */

  .title-banner__line {
    font-size: 26px;
  }

  /* --- headings --------------------------------------------------------- */

  .section-head {
    flex-wrap: wrap;
    gap: var(--s2) var(--s3);
  }

  .section-head__label {
    font-size: 11px;
  }

  .section-head__rule {
    display: none;
  }

  /* --- one column everywhere -------------------------------------------- */

  /* Every card grid collapses to a single column. Two-up cards at 375px meant
   * ~150px of usable width, which forced tiny type and mid-word wrapping. Full
   * width instead, with each card's contents reflowed sideways so the extra
   * width buys density rather than height. */
  .overview,
  .sheet-stats,
  .game-grid {
    grid-template-columns: 1fr;
    gap: var(--s2);
  }

  /* Stat tiles, all ten identical: label on the left, figure right-aligned
   * opposite it, supporting line beneath the figure. */
  .stat-tile > .panel__in {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--s3);
    row-gap: 0;
    padding: var(--s3);
  }

  .stat-tile__top {
    grid-column: 1;
    grid-row: 1 / span 2;
  }

  .stat-tile__label {
    font-size: 7px;
  }

  .stat-tile__value {
    grid-column: 2;
    grid-row: 1;
    font-size: 16px;
    text-align: right;
    overflow-wrap: anywhere;
  }

  .stat-tile__foot {
    grid-column: 2;
    grid-row: 2;
    font-size: 7px;
    text-align: right;
  }

  /* The two tiles that carry a game icon use the same grid; the icon simply
   * joins the figure in the right-hand cell, so every tile in the strip lines
   * up on the same two axes. */
  .stat-tile--wide > .panel__in {
    display: grid;
    align-items: center;
  }

  .champion-tile__row {
    grid-column: 2;
    grid-row: 1 / span 2;
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    gap: var(--s2);
    text-align: right;
  }

  .champion-tile__row .stat-tile__value {
    font-size: 13px;
    text-align: right;
  }

  .champion-tile__row .stat-tile__foot {
    text-align: right;
  }

  /* --- leaderboard ------------------------------------------------------ */

  /* Rank, portrait and identity share the top row -- the earlier stacked
   * version left a wide empty gap beside the portrait. Figures get their own
   * full-width row underneath, divided off like a character sheet. */
  .ladder-row .panel__in,
  .ladder-row.is-champion .panel__in {
    grid-template-columns: 30px 76px minmax(0, 1fr);
    grid-template-areas:
      'rank portrait identity'
      'figures figures figures';
    gap: var(--s2) var(--s3);
    align-items: center;
    padding: var(--s3);
  }

  .ladder-row__rank {
    grid-area: rank;
  }

  .ladder-row__portrait {
    grid-area: portrait;
  }

  .ladder-row__identity {
    grid-area: identity;
    align-self: center;
  }

  .ladder-row__figures {
    grid-area: figures;
    justify-content: space-between;
    padding-top: var(--s3);
    border-top: 2px solid var(--panel-3);
  }

  .ladder-row__rank-num,
  .is-champion .ladder-row__rank-num {
    font-size: 18px;
  }

  .ladder-row__name,
  .is-champion .ladder-row__name {
    font-size: 15px;
  }

  .ladder-row__title {
    font-size: 8px;
  }

  /* The XP bar and its counter stack rather than sitting side by side in a
   * ~200px column. */
  .ladder-row__xp {
    flex-wrap: wrap;
    gap: var(--s1);
  }

  .ladder-row__xp-text {
    font-size: 6px;
  }

  .figure__value,
  .figure--primary .figure__value {
    font-size: 15px;
  }

  .ribbon {
    font-size: 7px;
    padding: 3px 7px;
  }

  /* Small enough that the level tag and the streak badge sit on one line in a
   * ~220px identity column instead of stacking. */
  .badge {
    font-size: 6px;
    padding: 3px 5px;
    gap: 4px;
  }

  .level-tag {
    font-size: 7px;
    padding: 3px 5px;
  }

  /* --- games cards ------------------------------------------------------- */

  /* A framed icon beside its identity on top, then a single divided row of
   * figures underneath. The two-column head-and-count version this replaces
   * put a big number in the middle of the card with nothing to balance it. */
  .game-card > .panel__in {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      'head head'
      'count champ';
    align-items: center;
    /* No column gap: the two cells' top borders have to meet to read as one
     * continuous rule across the card rather than two broken segments. */
    row-gap: var(--s3);
    column-gap: 0;
    padding: var(--s3);
  }

  .game-card__head {
    grid-area: head;
    flex-direction: row;
    align-items: center;
    gap: var(--s3);
  }

  .game-card__count,
  .game-card__champ {
    /* One continuous rule across the card, drawn by both cells. */
    padding-top: var(--s3);
    border-top: 2px solid var(--panel-3);
  }

  .game-card__count {
    grid-area: count;
    align-items: baseline;
    align-self: stretch;
  }

  .game-card__champ {
    grid-area: champ;
    margin-top: 0;
    align-self: stretch;
    padding-left: var(--s3);
  }

  /* Hug the right edge instead of stretching the name to fill the cell. */
  .game-card__champ-row {
    justify-content: flex-end;
  }

  .game-card__champ-name {
    flex: 0 1 auto;
  }

  /* The count already says MATCHES; a BEST PLAYER caption above a name would
   * make the two halves of the row different heights. */
  .game-card__champ > .micro-label {
    display: none;
  }

  .game-card__champ-row {
    margin-top: 0;
  }

  .game-card__name {
    font-size: 13px;
  }

  .game-card__count strong {
    font-size: 16px;
  }

  /* --- character sheet -------------------------------------------------- */

  .world-hero__name {
    font-size: 22px;
  }

  /* The hero was pushing the standings most of a screen down. A smaller crest
   * and tighter gaps get the actual content above the fold. */
  .world-hero .panel__in {
    gap: var(--s3);
  }

  .world-hero__crest {
    padding: var(--s2);
  }

  .world-hero__crest .sprite {
    width: 56px;
  }

  .world-hero__figures {
    gap: var(--s4);
    margin-top: var(--s3);
  }

  .world-hero__figures strong {
    font-size: 14px;
  }

  .world-hero__blurb {
    font-size: 8px;
  }

  .mastery .panel__in,
  .h2h .panel__in,
  .standings .panel__in {
    padding: var(--s1) var(--s3);
  }

  /* --- matches ---------------------------------------------------------- */

  /* Three stacked bands: the match, then the winner, then everyone else.
   *
   * Flex with explicit `order` rather than grid areas, because the winner and the
   * also-played list are nested inside .match-card__roster and so cannot be
   * placed from the card's own grid. Ordering lets the outcome badge join the
   * timestamp on the top line instead of being absolutely positioned over the
   * roster -- which is what forced the old 74px padding reservation and made the
   * names wrap at strange points. */
  .match-card > .panel__in {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--s2) var(--s3);
    padding: var(--s3);
  }

  /* Framed like every other image in the app, so the card has the same anchor
   * in its top-left corner that a roster or game card does. */
  .match-card__game .sprite {
    padding: 5px;
    background: var(--well);
    box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 34%, transparent);
  }

  .match-card__game {
    order: 1;
    flex: 1 1 auto;
    min-width: 0;
  }

  .match-card__outcome {
    order: 2;
    flex: 0 0 auto;
    align-self: center;
    font-size: 7px;
  }

  .match-card__aside {
    order: 3;
    flex: 0 0 auto;
    align-self: center;
    gap: var(--s2);
  }

  /* The absolute date sits directly above in the game block, so the relative time
   * is redundant here -- and at this width it pushed the top line to wrap. */
  .match-card__ago {
    display: none;
  }

  /* Winner on its own line, everyone else on the next. */
  .match-card__roster {
    order: 4;
    flex: 1 1 100%;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s3);
    padding-top: var(--s3);
    border-top: 2px solid var(--panel-3);
  }

  .match-card__winner {
    gap: var(--s2);
  }

  .match-card__winner-name {
    font-size: 11px;
  }

  /* A lighter rule than the band divider above: this is a subdivision of the
   * roster, not a new section of the card. */
  .match-card__losers {
    gap: var(--s2) var(--s4);
    padding-top: var(--s3);
    border-top: 2px solid var(--panel-2);
  }

  .match-card__loser {
    gap: var(--s2);
  }


  /* --- filters ---------------------------------------------------------- */

  /* Each group scrolls sideways instead of wrapping: four wrapped groups of
   * chips ran taller than the phone viewport on their own. */
  .filters .panel__in {
    gap: var(--s3);
    padding: var(--s3);
  }

  /* One labelled row per group, divided from the next, so the card reads as a
   * tidy list rather than four clusters of different widths. */
  .filters > .panel__in {
    flex-direction: column;
    gap: 0;
    padding: var(--s1) var(--s3) var(--s3);
  }

  .filters__group {
    flex: 1 1 100%;
    width: 100%;
    padding-top: var(--s3);
  }

  .filters__group + .filters__group {
    margin-top: var(--s3);
    border-top: 2px solid var(--panel-2);
  }

  .chip {
    padding: 6px 8px;
    font-size: 7px;
  }

  /* --- log-game flow --------------------------------------------------- */

  /* Full-screen on phones: this is the one flow that has to be fast on a couch,
   * mid-argument about who actually won. */
  .overlay {
    padding: 0;
    place-items: stretch;
    /* An auto-sized grid column takes its width from the widest item, and the
     * sticky footer's negative margins made that wider than the screen -- the
     * modal was rendering 471px wide inside a 375px viewport. minmax(0, 1fr)
     * pins the column to the overlay instead. */
    grid-template-columns: minmax(0, 1fr);
  }

  /* Card-style overlays (the passcode gate) aren't the full-screen log-game
   * flow the rule above is for -- they stay a centred card on phones too. */
  .overlay--card {
    padding: var(--s5);
    place-items: center;
    grid-template-columns: none;
  }

  .modal {
    width: 100%;
    max-width: 100%;
    min-height: 100%;
    clip-path: none;
    padding: 0;
  }

  .modal > .panel__in {
    min-height: 100vh;
    clip-path: none;
    box-shadow: none;
    /* Clips the sticky footer's intentional 16px bleed on each side, which is
     * padding only -- its button still lines up with the body content. */
    overflow-x: hidden;
  }

  .modal__head,
  .modal__body,
  .stepper {
    padding-left: var(--s4);
    padding-right: var(--s4);
  }

  .stepper__label {
    display: none;
  }

  .stepper__step.is-active .stepper__label {
    display: inline;
  }

  .game-picker,
  .player-picker,
  .winner-picker {
    grid-template-columns: repeat(2, 1fr);
  }

  .step__foot {
    position: sticky;
    bottom: 0;
    /* Tally above, action below: the button belongs nearest the thumb. */
    flex-direction: column;
    align-items: stretch;
    margin: 0 calc(var(--s4) * -1) calc(var(--s5) * -1);
    padding: var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
    background: linear-gradient(transparent, var(--panel) 40%);
  }

  .step__tally {
    text-align: center;
  }

  .notes-input {
    font-size: 11px;
  }

  /* Tighter bands on a phone; the structure is already a single column. */
  .summary > .panel__in > * {
    padding: var(--s3);
  }

  .summary__winner-name {
    font-size: 16px;
  }

  .summary__game-name {
    font-size: 13px;
  }

  .step {
    gap: var(--s3);
  }

  /* --- footer ---------------------------------------------------------- */

  

  .data-card > .panel__in {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s3);
  }

  .data-card .pixel-btn,
  .data-card .pixel-btn__face {
    width: 100%;
  }

  /* --- victory --------------------------------------------------------- */

  .victory__name {
    font-size: 14px;
  }

  .victory__banner {
    padding: var(--s2) var(--s4);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    /* Without this, a looping animation is not stopped by the line above -- it is
     * made to thrash through its keyframes as fast as the compositor allows. */
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
