/* ==========================================================================
   dashboard.css — layout for the reorganized dashboard: League Champion
   hero, First Achievements trophy row, Recent Activity + Leaderboard.
   Base card/hero/trophy-card visuals live in app.css and firsts.css;
   this file only adds dashboard-specific structure (avatars, section
   headers, the two-column activity/standings split).
   ========================================================================== */

/* ==========================================================================
   PRESENTATION TIERS — the contract every dashboard widget implements
   ==========================================================================

   A widget's presentation is chosen by the width of the CELL IT LANDS IN, not
   by the span name it was configured with. A "quarter" widget is ~306px wide on
   a 1600px board but only ~226px on a 1024px one — the same name, two different
   design problems. So every widget styles itself with `@container` queries
   against its own `.dash-cell` and never against the viewport.

   Three tiers, in ascending order of available width:

     compact   — identity + the single most important number. Lists over cards,
                 no charts, no secondary columns.
     standard  — the widget's normal reading: primary content plus the most
                 useful secondary detail.
     expanded  — the full showcase: every category, full tables, hero framing.

   Each widget declares its own pixel thresholds (they differ — a table needs
   more room to earn a column than a portrait strip needs to earn a tile) both
   here in its CSS block and in `tracker/dashboard_widgets.py` WIDGET_TYPES, so
   the registry and the stylesheet can be checked against each other. Reference
   cell widths at the standard gap, for picking thresholds:

     span   1600px board   1440px board   1024px board
     3      306            266            226
     4      414            363            280
     6      646            556            476
     8      856            749            642
     9      966            846            745
     12     1296           1136           976

   Container queries need no fallback here: the target is modern evergreen
   browsers, so there are deliberately NO media-query duplicates of tier logic.
   Viewport media queries below are for the SHELL only — how many grid columns a
   span occupies once the sidebar collapses.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Config-driven widget grid — a 12-column reflowing layout. Each widget cell
   spans .dash-wN columns and is a query container for its own contents.
   -------------------------------------------------------------------------- */
.dash-admin-bar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-3);
}
.dash-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-5);
  align-items: start;
}
.dash-cell {
  min-width: 0;
  container-type: inline-size;
  container-name: widget;
}
.dash-w1  { grid-column: span 1; }
.dash-w2  { grid-column: span 2; }
.dash-w3  { grid-column: span 3; }
.dash-w4  { grid-column: span 4; }
.dash-w5  { grid-column: span 5; }
.dash-w6  { grid-column: span 6; }
.dash-w7  { grid-column: span 7; }
.dash-w8  { grid-column: span 8; }
.dash-w9  { grid-column: span 9; }
.dash-w10 { grid-column: span 10; }
.dash-w11 { grid-column: span 11; }
.dash-w12 { grid-column: span 12; }

/* Shell collapse (viewport, not container). Below 56rem the 12-column board is
   too tight to be read as columns, so spans fold into halves; below 40rem
   everything stacks. Widgets don't need to know — they just see a narrower
   container and drop to a lower tier. */
@media (max-width: 55.99rem) {
  .dash-grid { gap: var(--space-4); }
  .dash-w1, .dash-w2, .dash-w3, .dash-w4, .dash-w5, .dash-w6 { grid-column: span 6; }
  .dash-w7, .dash-w8, .dash-w9, .dash-w10, .dash-w11, .dash-w12 { grid-column: span 12; }
}
@media (max-width: 40rem) {
  .dash-grid { grid-template-columns: 1fr; }
  .dash-cell { grid-column: auto !important; }
}

/* --------------------------------------------------------------------------
   Standardised widget chrome — one header, one border, one padding rhythm and
   one actions slot for every widget, so a board of eight widgets reads as one
   surface instead of eight bespoke ones.
   -------------------------------------------------------------------------- */
.widget-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin: 0 0 var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.widget-head h2 {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: 1.2;
}
.widget-head .widget-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}
.widget-link {
  font-size: var(--fs-sm);
  color: var(--color-gold);
  text-decoration: none;
  white-space: nowrap;
}
.widget-link:hover { text-decoration: underline; }

/* At compact widths the "view all" link would wrap under a truncated title and
   cost a whole line; the title alone still names the widget. */
@container widget (max-width: 300px) {
  .widget-head h2 { font-size: var(--fs-md); }
  .widget-head .widget-link { display: none; }
}

/* Touch sizing is a device concern, not a container one — a 300px cell on a
   desktop board doesn't need thumb-sized controls, a 300px phone screen does.
   So this one stays a viewport media query: every standalone control inside a
   widget clears the 44px minimum on phones. Inline links inside a sentence
   (a player's name mid-row) are exempt under WCAG 2.5.8 and stay as they are. */
@media (max-width: 40rem) {
  .dash-admin-bar .btn,
  .dash-grid .btn,
  .dash-grid .widget-link,
  .dash-grid .gchar-link {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
  }
  .dash-grid .gchar-link { display: flex; }
  .dash-grid .widget-link { padding-block: var(--space-2); }
}

/* Widgets that carry their own section header shouldn't double the top gap. */
.dash-cell .section-header { margin-top: 0; }
.dash-cell .kpi-row { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Section headers ("First Achievements", "Recent Activity", "Leaderboard")
   -------------------------------------------------------------------------- */

.section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}

.section-header h2 {
  margin: 0;
}

/* --------------------------------------------------------------------------
   Shared with pages outside the dashboard — kept here because that's where
   they were authored, not because the widgets own them.
   -------------------------------------------------------------------------- */

/* Winner avatar row inside an achievement card (partials/achievement_card). */
.trophy-winner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 var(--space-3);
}
.trophy-winner .trophy-holder {
  font-weight: 700;
}
.trophy-winner .trophy-holder a {
  text-decoration: none;
}
.trophy-winner .trophy-holder a:hover {
  text-decoration: underline;
}

/* Plain activity list — the standalone activity feed and the achievement
   detail page. The dashboard's activity WIDGET has its own tiered
   presentation (.act) and no longer shares these. */
.activity-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.activity-item {
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.activity-item:last-child { border-bottom: none; }
.activity-item .member-avatar {
  vertical-align: middle;
  margin-right: var(--space-2);
}
.activity-meta {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
}

/* ==========================================================================
   WIDGET: champion — tiers by container width
     compact   <360px   portrait, name, crown, points
     standard  360px    portrait, name, primary statistics
     expanded  >=820px  hero banner: portrait, identity, statistics, chasing
                        pack and league clock filling the full width
   ========================================================================== */

.champ {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "portrait identity"
    "stats    stats";
  align-items: center;
  gap: var(--space-4) var(--space-5);
  margin-bottom: 0;
  padding: var(--space-5);
}
.champ-portrait { grid-area: portrait; }
.champ-identity { grid-area: identity; }
.champ-stats { grid-area: stats; }
.champ-aside { grid-area: aside; }
.champ-portrait {
  position: relative;
  display: block;
  line-height: 0;
  flex-shrink: 0;
}
.champ-crown {
  position: absolute;
  top: calc(var(--space-2) * -1);
  left: calc(var(--space-2) * -1);
  font-size: var(--fs-md);
  line-height: 1;
  color: var(--color-gold-bright);
  filter: drop-shadow(0 0 6px rgba(130, 228, 201, 0.5));
}
.champ-identity { min-width: 0; }
.champ-identity h2 { margin: 0; overflow-wrap: anywhere; }
.champ-character {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-parchment-dim);
}

.champ-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin: 0;
}
.champ-stat dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-parchment-dim);
}
.champ-stat dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  color: var(--color-parchment);
}
.champ-stat--lead dd { color: var(--color-gold-bright); font-size: var(--fs-2xl); }

.champ-aside { display: none; }
.champ-aside-title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-parchment-dim);
}
.champ-chase {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--fs-sm);
}
.champ-chase li {
  display: grid;
  grid-template-columns: 1.5rem 1fr auto;
  gap: var(--space-2);
  align-items: baseline;
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}
.champ-chase li:last-child { border-bottom: none; }
.champ-chase-rank { color: var(--color-parchment-dim); font-variant-numeric: tabular-nums; }
.champ-chase li a { text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.champ-chase-points {
  font-variant-numeric: tabular-nums;
  color: var(--color-parchment);
}
.champ-clock {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-xs);
  color: var(--color-parchment-dim);
}
.champ-clock strong { color: var(--color-gold); }

/* compact — only identity and the headline number survive. */
@container widget (max-width: 359px) {
  .champ { padding: var(--space-4); gap: var(--space-3) var(--space-4); }
  .champ-character { display: none; }
  .champ-stats { gap: var(--space-4); }
  .champ-stat:not(.champ-stat--lead) { display: none; }
  .champ-stat--lead dd { font-size: var(--fs-xl); }
  .champ .class-portrait--lg { width: 3.25rem; height: 3.25rem; }
}

/* expanded — the banner. Statistics move up beside the identity and the aside
   claims the right-hand third, so full width carries content instead of air. */
@container widget (min-width: 820px) {
  .champ {
    grid-template-columns: auto minmax(12rem, 1fr) minmax(14rem, 20rem);
    grid-template-areas:
      "portrait identity aside"
      "portrait stats    aside";
    align-content: center;
    gap: var(--space-3) var(--space-6);
    padding: var(--space-6);
  }
  .champ-identity { align-self: end; }
  .champ-stats { align-self: start; }
  .champ-aside {
    display: block;
    align-self: center;
    padding-left: var(--space-5);
    border-left: 1px solid var(--color-border);
  }
  .champ .class-portrait--lg { width: 7rem; height: 7rem; }
  .champ-crown { font-size: var(--fs-xl); }
  .champ-stat--lead dd { font-size: var(--fs-2xl); }
}

.hero-empty {
  margin-bottom: 0;
}

/* ==========================================================================
   WIDGET: characters (Guild characters) — tiers by container width
     compact   <360px   a short character list; a grid here would mean
                        postage-stamp portraits, so we don't render one
     standard  360px    portrait cards, player + class, level on the art
     expanded  >=700px  showcase cards — taller portrait plus character name,
                        level and the build metadata we hold
   ========================================================================== */

.gchars {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.gchar-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  text-decoration: none;
  border: 1px solid transparent;
  transition: border-color 0.12s ease, background-color 0.12s ease;
}
.gchar-link:hover {
  border-color: var(--color-gold-dim);
  background: rgba(255, 255, 255, 0.02);
}
.gchar-media {
  position: relative;
  flex: 0 0 auto;
  width: 2.5rem;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border: 1px solid var(--color-gold-dim);
  background: var(--color-bg);
}
.gchar-art { width: 100%; height: 100%; object-fit: cover; object-position: center top; }
.gchar-crest {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: var(--fs-md); font-weight: 700;
  color: hsl(var(--hue, 42) 70% 72%);
  background: radial-gradient(circle at 50% 40%, hsl(var(--hue, 42) 40% 22%), var(--color-bg) 78%);
}
.gchar-level {
  position: absolute; bottom: 0; right: 0; z-index: 2;
  min-width: 1.15rem; padding: 0 3px;
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-xs);
  color: var(--color-parchment); text-align: center;
  background: rgba(2, 7, 7, 0.85);
  border-left: 1px solid var(--color-gold-dim);
  border-top: 1px solid var(--color-gold-dim);
}
.gchar-body { min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--space-2); }
.gchar-player {
  font-weight: 600;
  color: var(--color-gold-bright);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gchar-name,
.gchar-class,
.gchar-meta {
  font-size: var(--fs-xs);
  color: var(--color-parchment-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gchar-class { font-variant: small-caps; letter-spacing: 0.04em; }
.gchar-sync { color: var(--color-gold); }

/* compact — the list. Only the player, class and level survive; the level
   moves out of the art corner into the row so the icon can stay small. */
@container widget (max-width: 359px) {
  .gchar-name,
  .gchar-meta { display: none; }
  .gchar-media { width: 2rem; }
}

/* standard — cards. The portrait becomes the subject; character name and the
   metadata line stay out of it so the card doesn't get chatty at this size. */
@container widget (min-width: 360px) {
  .gchars {
    grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
    gap: var(--space-3);
  }
  .gchar-link {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    padding: 0;
    border-color: var(--color-border);
    background: var(--color-bg-card);
  }
  .gchar-link:hover { border-color: var(--color-gold); }
  .gchar-media { width: 100%; aspect-ratio: 4 / 3; border: none; border-bottom: 1px solid var(--color-border); }
  .gchar-level { font-size: var(--fs-sm); min-width: 1.5rem; }
  .gchar-body { flex-direction: column; gap: 0; padding: 0 var(--space-3) var(--space-3); }
  .gchar-player,
  .gchar-name,
  .gchar-class,
  .gchar-meta { max-width: 100%; }
  .gchar-name,
  .gchar-meta { display: none; }
}

/* expanded — the showcase. Taller portraits, and the character name, level and
   sync state come back because there's room to read them. */
@container widget (min-width: 700px) {
  .gchars { grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: var(--space-4); }
  .gchar-level { font-size: var(--fs-md); min-width: 1.75rem; }
  .gchar-name { display: block; color: var(--color-parchment); font-size: var(--fs-sm); }
  .gchar-meta {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-1);
    padding-top: var(--space-1);
    border-top: 1px solid var(--color-border);
  }
}

/* ==========================================================================
   WIDGET: firsts (First Achievements) — tiers by container width
     compact   <400px   icon / name / winner list, proof link kept
     standard  400px    trophy cards, fewer of them and less secondary detail
     expanded  >=720px  every category — the unclaimed ones included
   The item cap at the narrow tiers is deliberate truncation (registry
   overflow: "truncate"): a quarter-width cell showing the admin's full count
   would be a mile-long column next to a two-line neighbour.
   ========================================================================== */

.tro {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.tro-item {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  min-height: 0;
}
.tro-icon { display: inline-flex; }
.tro-body { min-width: 0; }
.tro-label {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-base);
  line-height: 1.2;
  color: var(--color-gold-bright);
}
.tro-winner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
  font-size: var(--fs-sm);
}
.tro-open {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--color-parchment-dim);
}
.tro-proof { white-space: nowrap; }

/* Unclaimed categories are expanded-tier only — a quiet, unframed variant so
   the claimed trophies stay the loud thing on the board. */
.tro-item--open {
  display: none;
  background: transparent;
  border: 1px dashed var(--color-border-strong);
  box-shadow: none;
}
.tro-item--open .tro-label { color: var(--color-parchment-dim); }
.tro-item--open .tro-icon { opacity: 0.5; filter: grayscale(1); }

/* compact — flat rows, no card skin and no corner ribbon (it needs a card
   corner to sit in). Icon, name, winner, proof. */
@container widget (max-width: 399px) {
  .tro-item {
    background: none;
    border: none;
    border-bottom: 1px solid var(--color-border);
    box-shadow: none;
    padding: var(--space-2) 0;
  }
  .tro-item:last-child { border-bottom: none; }
  .tro-item .trophy-ribbon { display: none; }
  /* The lg medallion and its art are both sized in crests.css; scale the seal
     AND its contents or the art overflows the row. */
  .tro-item .medallion { width: 2.5rem; height: 2.5rem; }
  .tro-item .achv-art { width: 1.6rem; height: 1.6rem; }
  .tro-item .achievement-icon { font-size: 1.25rem; }
  .tro-label { font-size: var(--fs-sm); }
  .tro-item { gap: var(--space-2) var(--space-3); padding: var(--space-3) 0; }
  .tro-winner .member-avatar { display: none; }
  .tro-proof {
    font-size: var(--fs-xs);
    padding: var(--space-1) var(--space-2);
  }
  .tro-item:nth-child(n + 6) { display: none; }
}

/* standard — cards. Fewer of them, and the winner avatar drops out so each one
   carries less secondary weight than at full width. */
@container widget (min-width: 400px) {
  .tro { grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: var(--space-4); }
  .tro-item {
    grid-template-columns: 1fr;
    align-items: start;
    align-content: start;
    gap: var(--space-2);
    overflow: hidden;
    padding: var(--space-4);
    padding-right: 2.5rem; /* clear the corner ribbon */
    min-height: 9rem;
  }
  .tro-proof { justify-self: start; margin-top: auto; }
  .tro-winner .member-avatar { display: none; }
  .tro-item:nth-child(n + 5) { display: none; }
}

/* expanded — the full board: every claimed trophy, the winner's avatar back,
   and the categories still up for grabs. */
@container widget (min-width: 720px) {
  .tro { grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); }
  .tro-label { font-size: var(--fs-md); }
  .tro-winner .member-avatar { display: inline-flex; }
  .tro-item:nth-child(n + 5) { display: grid; }
  .tro-item--open { display: grid; }
}

/* ==========================================================================
   WIDGET: leaderboard — tiers by container width
     compact   <340px   rank, player, points
     standard  340px    + completions and abbreviated medal labels
     expanded  >=720px  + full medal / efficiency labels (matches the registry)
   Columns are dropped whole, never squeezed: the old behaviour gave the table
   a 26rem floor and let it scroll sideways inside a quarter-width cell, which
   is how the Medals column ended up clipped off the edge of the board.
   ========================================================================== */

/* Fixed layout below the expanded tier: a narrow cell must not be widened by a
   header's min-content width ("Completions" alone is ~90px). Headers wrap
   instead, and the table always fits the cell it was given. */
.lb {
  width: 100%;
  min-width: 0;
  table-layout: fixed;
}
.lb .lb-player { overflow-wrap: anywhere; }

/* Narrow tiers show the abbreviated header but keep the full word in the
   accessibility tree, so the column is still named for a screen reader. */
.lb-h-full {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.lb .lb-rank { width: 2.75rem; }
.lb .lb-points { width: 3.5rem; font-variant-numeric: tabular-nums; }
.lb .lb-col-secondary { width: 4.5rem; }
.lb .lb-col-medals { width: 3.5rem; }

/* compact — rank, player, points. Completions and medals leave the table. */
.lb-col-medals,
.lb-col-secondary { display: none; }
.lb-medal-full { display: none; }
.lb .medal-row { display: none; }
.lb-medal-count {
  font-variant-numeric: tabular-nums;
  color: var(--color-parchment-dim);
}

/* standard — completions, plus the medals as a single count. Chips at this
   width wrap into a tall ragged stack and push the table past the cell, which
   is exactly the squeezing this widget is meant to avoid. */
@container widget (min-width: 340px) {
  .lb-col-secondary { display: table-cell; }
  .lb-col-medals { display: table-cell; }
}

/* expanded — the count gives way to the medal chips, labels spelled out. */
@container widget (min-width: 720px) {
  .lb { table-layout: auto; }
  .lb .lb-rank,
  .lb .lb-points,
  .lb .lb-col-secondary,
  .lb .lb-col-medals { width: auto; }
  .lb-h-full {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
  }
  .lb-h-abbr { display: none; }
  .lb-medal-count { display: none; }
  .lb .medal-row { display: flex; flex-wrap: wrap; gap: var(--space-1); }
  .lb-medal-abbr { display: none; }
  .lb-medal-full { display: inline; }
  .lb .medal { font-size: var(--fs-sm); }
}

/* ==========================================================================
   WIDGET: activity (Recent activity) — tiers by container width
     compact   <340px   player + achievement over two lines; no avatar, no pill
     standard  340px    avatar and one line per submission, time underneath
     expanded  >=640px  status and time get their own columns
   ========================================================================== */

.act {
  list-style: none;
  margin: 0;
  padding: 0;
}
.act-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  gap: 0 var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.act-item:last-child { border-bottom: none; }
.act-avatar { grid-row: span 2; align-self: center; }
.act-avatar .member-avatar { vertical-align: middle; }
.act-body { min-width: 0; }
.act-player { font-weight: 600; }
.act-verb { color: var(--color-parchment-dim); }
.act-status { justify-self: start; }
.act-time {
  grid-column: 2;
  font-size: var(--fs-sm);
  color: var(--color-parchment-dim);
  white-space: nowrap;
}

/* compact — the avatar and status pill are the first things to go; who did
   what to which achievement is the whole message. */
@container widget (max-width: 339px) {
  .act-avatar,
  .act-status { display: none; }
  .act-item { grid-template-columns: minmax(0, 1fr); }
  .act-time { grid-column: 1; font-size: var(--fs-xs); }
  .act-item:nth-child(n + 7) { display: none; }
}

/* expanded — status and time stop stacking under the sentence and become
   right-hand columns, so the list reads as a table of events. */
@container widget (min-width: 640px) {
  .act-item {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-2);
  }
  .act-avatar { grid-row: auto; }
  .act-time { grid-column: auto; text-align: right; }
}

/* ==========================================================================
   WIDGET: kpis (Stat tiles) — tiers by container width
     compact   <360px   two tiles abreast, smaller figures
     standard  360px    two abreast at full size
     expanded  >=720px  all four abreast
   .stat-grid's own viewport breakpoints in app.css don't know how wide this
   cell is, so the widget restates the column count against its container.
   ========================================================================== */

.kpi-row { margin-bottom: 0; }
.dash-cell .kpi-row { grid-template-columns: repeat(2, 1fr); }

@container widget (max-width: 359px) {
  .dash-cell .kpi-row .stat-tile { padding: var(--space-2) var(--space-3); }
  .dash-cell .kpi-row .value { font-size: var(--fs-xl); }
}
@container widget (min-width: 720px) {
  .dash-cell .kpi-row { grid-template-columns: repeat(4, 1fr); }
}

/* ==========================================================================
   WIDGET: progression (Progression trend) — tiers by container width
     compact   <320px   figures only; the chart is REMOVED, not shrunk
     standard  320px    sparkline above the figures
     expanded  >=640px  taller chart with the figures beside it
   ========================================================================== */

.prog-title {
  margin: 0 0 var(--space-1);
  font-family: var(--font-display);
  font-variant: small-caps;
  letter-spacing: 0.06em;
  color: var(--color-gold-bright);
}
.prog-chart {
  display: block;
  width: 100%;
  height: 3rem;
  margin: var(--space-2) 0;
}
.prog-line {
  fill: none;
  stroke: var(--color-gold);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.prog-area { fill: rgba(201, 162, 74, 0.12); stroke: none; }
.prog-figures { margin: 0; font-size: var(--fs-sm); color: var(--color-parchment-dim); }
.prog-figures strong { color: var(--color-gold-bright); }
.prog-range { white-space: nowrap; }

@container widget (max-width: 319px) {
  .prog-chart { display: none; }
  .prog-range { display: block; }
}
@container widget (min-width: 640px) {
  .prog {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "title   figures"
      "chart   chart";
    align-items: baseline;
    column-gap: var(--space-4);
  }
  .prog-title { grid-area: title; }
  .prog-figures { grid-area: figures; text-align: right; }
  .prog-chart { grid-area: chart; height: 5rem; margin-bottom: 0; }
  .prog .empty-state { grid-column: 1 / -1; }
}

/* ==========================================================================
   WIDGET: progress_ring (League progress) — tiers by container width
     compact   <260px   the ring alone; its aria-label still reads the figures
     standard  260px    ring beside a short legend
     expanded  >=480px  a larger ring with the legend
   ========================================================================== */

.progress-card {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.progress-ring {
  --p: 0%;
  flex: 0 0 auto;
  width: 6rem;
  height: 6rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background:
    radial-gradient(closest-side, var(--color-bg-card) 76%, transparent 77%),
    conic-gradient(var(--color-gold) 0 var(--p), var(--color-border-strong) var(--p) 100%);
}
.progress-ring-value {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
  color: var(--color-gold-bright);
}
.progress-legend-title {
  margin: 0 0 var(--space-1);
  font-family: var(--font-display);
  font-variant: small-caps;
  letter-spacing: 0.06em;
  color: var(--color-gold-bright);
}
.progress-legend-meta { margin: 0; font-size: var(--fs-sm); color: var(--color-parchment-dim); }
.progress-legend-meta strong { color: var(--color-parchment); }

@container widget (max-width: 259px) {
  .progress-card { justify-content: center; }
  .progress-legend { display: none; }
}
@container widget (min-width: 480px) {
  .progress-ring { width: 7.5rem; height: 7.5rem; }
  .progress-ring-value { font-size: var(--fs-xl); }
}
