/* =========================================================================
   cards.css — THE CARD FACE, AND THE AUCTION HOUSE BUILT ON IT.

   ★★★★★ WHY THIS FILE EXISTS AT ALL.
   `WC.cards.cardEl` is the ONE place a card face is built and TWO pages call
   it: the collection (cards.js) and the auction board (auction.js). Its CSS
   lived in a `<style>` block inside `fragments/cards.html` — so the renderer
   shipped to both pages and its appearance shipped to one. On Jurassic Land's
   live auction page, measured 2026-08-14 through Cloudflare, `.cf`, `.cf-mono`,
   `.cf-sheen` and `.cardt-name` were ALL ABSENT from the served bytes: every
   lot rendered as an unstyled `<div>` with a zero-height face, a bare `<b>` and
   a row of undressed pills. The board answered 200, the payload was healthy,
   every gate was green, and the animal was invisible.

   A RENDERER'S APPEARANCE BELONGS TO THE RENDERER, NOT TO ONE PAGE THAT
   HAPPENS TO USE IT. A `<style>` block in a fragment is scoped to the page
   that fragment builds; the moment a second page calls the same JavaScript it
   inherits the behaviour and none of the look. So the face moved here, into a
   sheet the assembler ships with the SCRIPT rather than with one page.

   NO COLOUR IS DECIDED HERE. Every value is a role token from theme.css or a
   color-mix() of one, and `run_all.py::TestNoRawColours` refuses this file if
   a literal ever appears. Every plate this sheet draws is named in
   terminal.css's SURFACE REGISTER (section 6b), every radius is the house's,
   every interior heading takes one of the four `--tm-h-*` steps, and the four
   `--cf-*` custom properties are written per-element by cards.js from the
   owner's OWN palette file — which is why a species this island has never
   heard of renders a monogram rather than a broken image.

   ★★★★★ EVERY `--tm-*` READ CARRIES A FALLBACK, AND THAT IS NOT BELT-AND-
   BRACES. terminal.css says of itself that the house look "degrades to the
   plain shell if it is ever dropped", and a sheet built on it has to degrade
   with it or that promise is only true of the sheets written before this one.
   The failure is silent and total: an undefined custom property with no
   fallback is INVALID AT COMPUTED-VALUE TIME and CSS drops the WHOLE
   declaration - so `border: 1px solid var(--tm-hair)` falls back to
   `currentColor` and draws a full-ink line, and a `font-size` disappears out
   of its rule. It is not hypothetical either: Jurassic Land's served tree
   carries NO terminal.css at all (measured 2026-08-14, 3,111 files), because
   that owner has not been re-assembled since the house-look wave. Without
   these fallbacks, shipping this sheet there would have re-created the exact
   bug it was written to fix.
   ========================================================================= */


/* =========================================================================
   1. THE FACE — four palette slots and nothing fetched.
   ========================================================================= */

/* ★ THE FALLBACKS ARE LOAD-BEARING. `var(--cf-body)` with no fallback is
     invalid at computed-value time on any card whose species the palette does
     not carry, and CSS drops the WHOLE `background` declaration - so the one
     case this design exists to handle (an unknown species) would be the one
     case that renders nothing. Each slot falls back to a surface token. */
.cf {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-xs);
  overflow: hidden;
  border: 1px solid var(--tm-hair, var(--border));
  background:
    radial-gradient(120% 82% at 50% 6%,
      var(--cf-detail, var(--surface-3)) 0%, transparent 60%),
    linear-gradient(180deg,
      var(--cf-body, var(--surface-2)) 0%,
      var(--cf-flank, var(--surface)) 56%,
      var(--cf-belly, var(--surface-sink)) 100%);
}

/* the markings, as the animal's own stripe rather than as a texture */
.cf::before {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(118deg,
    var(--cf-mark, transparent) 0 5px, transparent 5px 26px);
  opacity: .3;
}

/* ★ THE SPECIMEN RULE. A registration hairline across the plate, so a face
     built from two gradients reads as a MEASURED specimen on a register sheet
     rather than as a coloured rectangle. It is the house's own grammar
     (terminal.css section 2) applied to the one object that had none. */
.cf::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: linear-gradient(90deg,
    var(--tm-hair, var(--border)) 0 34%, transparent 34%);
}

.cf-mono {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 600;
  line-height: 1;
  color: var(--fg-mute);
  letter-spacing: .04em;
}

/* The sheen is the LAST child and an overlay only. It never puts a transform,
   a filter or a perspective on the card itself. */
.cf-sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(128deg, transparent 34%,
    var(--accent-wash-2) 50%, transparent 66%);
}

/* ★ GROWTH IS A MEASUREMENT, SO IT IS DRAWN AS ONE. The number is still in
     the pill row; this is the same fact at a glance, on the plate, filled to
     the fraction the server sent. `--cf-grow` is written by cards.js as a
     percentage; with none written the bar is empty rather than full, which is
     the honest direction for a missing reading. */
.cf-grow {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--tm-hair-soft, var(--border));
}
.cf-grow > i {
  display: block;
  height: 100%;
  width: var(--cf-grow, 0%);
  background: var(--rule-accent);
}

/* ★ THE SHOWCASE STAGE (2026-08-18, Jurassic Den: "it show you the dino and
     skin it got on"). Fills the SAME plate the palette gradient already
     draws - deliberately no background of its own, so the plate is the
     honest ground while the specimen loads and stays visible at the canvas's
     transparent edges once it has. `.cf-stage` only sizes the well; the
     `.shopview` class alongside it is shopview.mjs's own selector AND what
     lets this well share the idle/missing visual language shop.mjs's cards
     already use - written HERE, self-contained, because cards.css rides on
     the auction page and shop.css does not (the fleet law right above this
     block: a renderer's look ships in ITS sheet, never a borrowed one).
     Ahead of `.cf-stamp`/`.cf-grow`/`.cf-sheen` in the DOM, so the specimen
     paints BELOW the band, the growth bar and the foil sheen - none of a
     card's existing readings may be hidden behind the animal now standing
     on it. */
.cf-stage {
  position: absolute;
  inset: 0;
}
.cf-stage[data-view="idle"]::after,
.cf-stage[data-view="missing"]::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 34px; height: 34px; transform: translate(-50%, -50%);
  border-radius: var(--r-round, 999px);
  border: 1px solid var(--tm-hair, var(--border));
  background: var(--surface-3);
}
.cf-stage[data-view="missing"]::before {
  content: attr(data-missing-label);
  position: absolute; left: 0; right: 0; top: calc(50% + 26px);
  text-align: center;
  font: 500 10px/1.3 var(--font-mono);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-mute);
}

/* the band, stamped in the corner of the plate rather than only listed below */
.cf-stamp {
  position: absolute;
  top: 0; left: 0;
  padding: 3px 7px 4px;
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  line-height: 1;
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-1);
  background: var(--veil-strong);
  border-right: 1px solid var(--tm-hair, var(--border));
  border-bottom: 1px solid var(--tm-hair, var(--border));
  border-bottom-right-radius: var(--r-xs);
}


/* =========================================================================
   2. THE CARD — a stamped record holding one animal.
   ========================================================================= */
.cardt {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px;
  border: 1px solid var(--tm-hair, var(--border));
  border-radius: var(--r);
  background-color: var(--surface);
  background-image: var(--grain), var(--tm-face, var(--grad-card));
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color .16s;
}
/* ★ NOT-CLICKABLE IS A CURSOR, NOT AN ARIA STATE. The plain variant used to
     carry `aria-disabled="true"`, which says nothing on a `<div>` and, once a
     lot became ONE card holding its own bid button, told a screen reader that a
     live control was disabled. A real browser agreed and refused to type into
     the amount field. The pointer belongs to the variant that is a button. */
.cardt { cursor: default; }
button.cardt { cursor: pointer; }
button.cardt:hover { border-color: var(--accent); }
.cardt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cardt-name {
  display: block;
  margin-top: 8px;
  font-family: var(--font-display);
  font-size: var(--tm-h-item, 14px);
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--ink-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cardt-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 6px;
}

/* the decision-screen variant: a bigger name and every mutation, not a capped
   list — `full` only ever ADDS. */
.cardt-full .cf { aspect-ratio: 3 / 4; }
.cardt-full .cardt-name { font-size: var(--tm-h-sec, clamp(17px, 2.2vw, 23px)); }

/* ★ THE DIALOG'S PLATE IS WIDE, NOT TALL. `cardt-full` was written for a card
     shown at collection width; inside a 440px decision screen the portrait
     ratio makes the face 550px and pushes the two money rows - the numbers
     somebody is about to agree to - under the fold. The face is a colour band,
     so widening it costs nothing and buys the decision its own screen. */
.au-dialog .cardt-full .cf { aspect-ratio: 16 / 10; }

/* ---- the pill vocabulary, one meaning per colour --------------------------
   `.pill` itself belongs to the house look and is not re-described here: these
   are its variants, and each says WHAT KIND of fact it carries. Growth is a
   reading, so it is tabular mono like every other reading on the site. */
.pill-grow {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.pill-band { color: var(--ink-1); border-color: var(--tm-hair, var(--border)); }
.pill-foil { color: var(--accent-hi); border-color: var(--edge-accent); }
.pill-diet { color: var(--ink-2); }
.pill-mine { color: var(--accent-hi); border-color: var(--edge-accent); }
/* the showcase's two facts, said in words beside the picture (2026-08-18) */
.pill-glitch { color: var(--fg-mute); border-color: var(--tm-hair, var(--border)); }
.pill-skin { color: var(--accent-hi); border-color: var(--edge-accent); }


/* =========================================================================
   3. MUTATIONS — the fact the auction board never showed.

   Owner, 2026-08-14: "mutations dont show". They were never in the payload
   and never in the renderer, on a board where they are most of what a bidder
   is buying: the one animal listed on the board that surfaced it carried
   FOUR of them, and the card said nothing about any of them.

   ★ THE THREE FAMILIES ARE NOT ONE LIST. `own` is what the animal grew,
     `parent` is inherited and `elder` is entombed - the vault has drawn them
     apart since 2026-08-10 and a flat list here would have re-merged them.
     The chip carries the family in a data attribute so a reader can tell an
     inherited buff from an earned one WITHOUT relying on colour, which fails
     roughly one reader in twelve.
   ========================================================================= */
.cardt-muts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 7px 0 0;
  padding: 0;
  list-style: none;
}
.cardt-mut {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  padding: 3px 7px;
  border: 1px solid var(--tm-hair, var(--border));
  border-radius: var(--r-xs);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  line-height: 1.4;
  letter-spacing: .02em;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the family, said in the mark as well as in the tooltip */
.cardt-mut::before {
  content: attr(data-fam);
  flex: none;
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.cardt-mut[data-family="own"] { border-color: var(--edge-accent); color: var(--ink-1); }
.cardt-mut[data-family="elder"] { border-color: var(--warn-dim); color: var(--warn); }
/* the overflow counter, when a card carries more than the plate can hold */
.cardt-mut-more { color: var(--ink-3); }
.cardt-mut-none { color: var(--ink-3); border-style: dashed; }


/* =========================================================================
   4. THE COLLECTION PAGE — the shelf, the grid, the pack tiles.
   Lifted from fragments/cards.html unchanged in meaning; every literal it
   carried is now a token.
   ========================================================================= */
.cards-shelf {
  display: grid;
  gap: 15px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.cards-grid {
  display: grid;
  gap: 13px;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
}
.cards-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.cards-bar .dim { margin-right: auto; }
.cards-specials-row {
  display: flex;
  gap: 11px;
  overflow-x: auto;
  padding-bottom: 6px;
  scrollbar-width: thin;
}
.cards-specials-row > * { flex: 0 0 168px; }

.packt { display: flex; flex-direction: column; gap: 9px; }
.packt-odds {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-2);
  color: var(--ink-3);
}
.packt-odds li { display: flex; justify-content: space-between; gap: 8px; }
.packt-buy { display: flex; gap: 8px; align-items: center; margin-top: auto; }
.packt-buy input { width: 72px; }
.cards-err { color: var(--bad); font-size: var(--fs-2); }


/* =========================================================================
   5. THE AUCTION HOUSE.

   ★ ONE PLATE PER LOT. The board used to be `.card` wrapping `.cardt` - a
     record inside a record, two hairlines, two grounds, and the animal pushed
     into the middle of it. A lot IS the card: the face, the name, the facts
     and the actions are all one stamped entry, so the plate count on this page
     went from two-per-lot to one and the animal is the biggest thing on it.
   ========================================================================= */
.au-board {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
  align-items: stretch;
}

.au-lot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 11px;
}
.au-lot .cardt-name { margin-top: 9px; }

/* ★ THE LOT'S PLATE IS A BAND, NOT A PORTRAIT. A collection card is portrait
     because a trading card is; a LOT is a listing, and the thing a bidder
     needs above the fold is the price, the clock and the mutations. The face
     here carries a species colour and a growth reading - it is not artwork and
     it cannot hold three hundred pixels without becoming the page. Measured in
     a browser at 1440: the portrait ratio pushed every price below the fold
     and turned the board into three coloured rectangles. */
.au-lot .cf { aspect-ratio: 16 / 9; }
.au-pick .cf { aspect-ratio: 3 / 2; }

/* ★ THE STATE IS SAID BY THE EDGE AND ALSO BY A PILL AND ALSO BY THE CLOCK.
     Three signals, none of them only colour, because "you are winning" and
     "you have been outbid" is the one distinction on this page a player must
     never misread. The edge is the HAIRLINE, recoloured - never a thick
     coloured strip down one side. */
.au-lot[data-state="leading"] { border-color: var(--ok-dim); }
.au-lot[data-state="outbid"] { border-color: var(--bad-dim); }
.au-lot[data-state="mine"] { border-color: var(--edge-accent); }

.au-tag-lead { color: var(--ok); border-color: var(--ok-dim); }
.au-tag-out { color: var(--bad); border-color: var(--bad-dim); }

/* ---- the readings ---------------------------------------------------- */
.au-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2px 10px;
  margin: 2px 0 0;
  padding-top: 9px;
  border-top: 1px solid var(--tm-hair-soft, var(--border));
}
.au-facts > div { min-width: 0; }
.au-facts dt {
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  line-height: 1.5;
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.au-facts dd {
  margin: 1px 0 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  font-size: var(--tm-h-item, 14px);
  color: var(--ink-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.au-price { color: var(--accent-hi); }

/* ★ THE CLOCK IS THE ONLY THING ON THIS PAGE THAT MOVES, so it is the only
     thing allowed to change colour on its own. Warn under a minute, bad under
     fifteen seconds - the same ladder the server's own extension window sits
     in. */
.au-clock.warn { color: var(--warn); }
.au-clock.hot { color: var(--bad); }
.au-clock.done { color: var(--ink-3); }

/* one authored moment: the price re-stamps itself when it changes under you */
@keyframes au-restamp {
  from { background: var(--tm-warm, var(--accent-wash)); }
  to { background: transparent; }
}
.au-price.au-moved { animation: au-restamp .5s ease-out; border-radius: var(--r-xs); }
@media (prefers-reduced-motion: reduce) {
  .au-price.au-moved { animation: none; }
}

/* ---- the actions ------------------------------------------------------ */
.au-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: auto;
  padding-top: 2px;
}
.au-acts > .btn { flex: 1 1 auto; }
.au-bidfield { display: flex; gap: 7px; flex: 1 1 100%; }
.au-bidfield input { flex: 1 1 auto; min-width: 0; }
.au-bidfield .btn { flex: none; }
.au-afford {
  flex: 1 1 100%;
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.au-afford[data-short="true"] { color: var(--warn); }

/* ---- the toolbar: tabs, sort, search ---------------------------------- */
.au-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}
.au-toolbar .tabs { margin-right: auto; }
.au-find { width: min(220px, 100%); }
.au-sort { width: auto; min-width: 150px; }
.au-count {
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---- the wallet strip -------------------------------------------------- */
.au-wallet {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
  align-items: baseline;
  margin-bottom: 18px;
  padding: 12px 15px;
  border: 1px solid var(--tm-hair, var(--border));
  border-radius: var(--r);
  background-color: var(--surface);
  background-image: var(--grain), var(--tm-face, var(--grad-card));
}
.au-wallet > div { display: flex; gap: 8px; align-items: baseline; }
.au-wallet dt {
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.au-wallet dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--tm-h-item, 14px);
  color: var(--ink-1);
}
.au-wallet .au-held { color: var(--accent-hi); }

/* ---- loading: a skeleton board, never a spinner in the middle ---------- */
.au-skel {
  min-height: 236px;
  border: 1px solid var(--tm-hair-soft, var(--border));
  border-radius: var(--r);
  background-color: var(--surface);
  background-image: linear-gradient(100deg,
    transparent 20%, var(--tm-lift-soft, var(--accent-wash-2)) 46%, transparent 72%);
  background-size: 260% 100%;
  animation: au-sweep 1.4s linear infinite;
}
@keyframes au-sweep {
  from { background-position: 130% 0; }
  to { background-position: -130% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .au-skel { animation: none; }
}

/* =========================================================================
   6. SELLING — pick the animal, then price it.

   ★ THE PICKER AND THE DROPDOWN USED TO BE TWO CONTROLS FOR ONE DECISION.
     The panel drew a grid of the player's animals AND a `<select>` listing the
     same animals, and only the select decided anything - so the grid was
     decoration and the thing that mattered was a name in a menu. The tiles ARE
     the control now: they are radios, the chosen one is stated three ways
     (checked, ringed, and a mark), and the select is gone.
   ========================================================================= */
.au-sell-list {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  margin-bottom: 16px;
  padding: 0;
  border: 0;
}
.au-sell-list legend { padding: 0; }

.au-pick { position: relative; display: block; cursor: pointer; }
.au-pick input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  margin: 0;
  padding: 0;
}
.au-pick input:focus-visible ~ .cardt {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.au-pick input:checked ~ .cardt {
  border-color: var(--accent);
  background-color: var(--surface-2);
}
/* the third signal: a stamped mark, so "chosen" is not carried by colour */
.au-pick input:checked ~ .cardt .cf-pick { opacity: 1; }
.cf-pick {
  position: absolute;
  top: 0; right: 0;
  padding: 3px 7px 4px;
  opacity: 0;
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  line-height: 1;
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--on-primary);
  background: var(--accent-hi);
  border-bottom-left-radius: var(--r-xs);
}

.au-sell-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
}
.au-sell-form label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1 1 150px;
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.au-sell-form .btn { flex: 0 0 auto; }

/* ★ WHAT THE SELLER ACTUALLY RECEIVES, before they commit to anything. The
     house takes a snapshotted percentage and the old panel never said so -
     a seller found out at settlement. This is the same arithmetic the server
     runs (integer floor, in the seller's favour) shown before the button. */
.au-payout {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 20px;
  margin: 0;
  padding: 11px 13px;
  border: 1px solid var(--tm-hair-soft, var(--border));
  border-radius: var(--r-xs);
  background: var(--surface-2);
}
.au-payout > div { display: flex; gap: 8px; align-items: baseline; }
.au-payout dt {
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.au-payout dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-1);
}
.au-payout .au-net { color: var(--ok); }


/* =========================================================================
   7. THE DECISION SCREEN.

   "the accept dialog renders FULL faces" — the dialog and the board build the
   card through the SAME renderer, and the dialog asks for the full variant. A
   decision screen showing less than the browse screen did is a player deciding
   on less information than the page already had, which is the exact shape of a
   swap scam.
   ========================================================================= */
.au-dialog {
  position: fixed;
  inset: 0;
  /* ★ THE FALLBACK IS FOR A BOX WHOSE theme.css PREDATES THE TOKEN. `--z-modal`
     is emitted by theme.py as of 2026-08-14 (value 120: above the sticky nav
     at 60 and the live bar at 90, below the skip link at 200). Its predecessor
     asked for `var(--z-modal, 60)` against a token this core had NEVER
     emitted, so every auction dialog on the fleet resolved to the fallback and
     sat at exactly the nav's own layer - a tie decided by document order,
     which is not a decision anybody made. */
  z-index: var(--z-modal, 120);
  display: grid;
  place-items: center;
  padding: 20px;
  background: var(--scrim-modal);
}
.au-dialog-card {
  width: min(440px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  padding: 20px;
  border-radius: var(--r);
  border: 1px solid var(--tm-hair, var(--border));
  background-color: var(--surface);
  background-image: var(--grain), var(--tm-face, var(--grad-card));
}
.au-dialog-h {
  font-family: var(--font-display);
  font-size: var(--tm-h-sec, clamp(17px, 2.2vw, 23px));
  font-weight: 600;
  letter-spacing: -.014em;
  color: var(--ink-1);
}
.au-dialog-lines {
  margin: 14px 0;
  padding: 0;
  list-style: none;
}
.au-dialog-lines > div {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--tm-hair-soft, var(--border));
}
.au-dialog-lines dt {
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}
.au-dialog-lines dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-1);
  text-align: right;
}
.au-dialog-acts { display: flex; gap: 9px; justify-content: flex-end; }
.au-dialog-acts .btn { flex: 1 1 auto; }

.au-err {
  color: var(--bad);
  font-size: var(--fs-2);
  min-height: 1.4em;
}
.au-note { color: var(--ink-3); font-size: var(--fs-2); }


/* =========================================================================
   8. WHAT THE HOUSE HAS SOLD — the record a bidder prices against.
   ========================================================================= */
.au-hist { display: grid; gap: 7px; }
/* ★ TWO COLUMNS, NOT THREE. At three the timestamp was the column that lost,
     and a stamp clipped mid-word is worse than one on its own line. The name
     and its age share the left cell; the price keeps the right. */
.au-hist-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 16px;
  align-items: baseline;
  padding: 10px 13px;
  border: 1px solid var(--tm-hair-soft, var(--border));
  border-radius: var(--r-xs);
  background: var(--surface-2);
}
/* ★ EVERY CELL IS PLACED. Naming only one of them let the others auto-flow
     into whatever was left, which put the price in the name's cell and the
     name on the right-hand edge - a record that reads backwards. */
.au-hist-row .au-hist-name { grid-column: 1; grid-row: 1; }
.au-hist-row .au-hist-when { grid-column: 1; grid-row: 2; }
.au-hist-row .au-hist-price {
  grid-column: 2; grid-row: 1 / span 2; align-self: center;
}
.au-hist-name {
  font-family: var(--font-display);
  font-size: var(--tm-h-item, 14px);
  font-weight: 600;
  color: var(--ink-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.au-hist-price {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--accent-hi);
}
.au-hist-when {
  font-family: var(--font-mono);
  font-size: var(--tm-h-micro, 10.5px);
  letter-spacing: var(--tm-track, .1em);
  text-transform: uppercase;
  color: var(--ink-3);
}


/* =========================================================================
   9. RESPONSIVE — structural, not fluid type.
   ========================================================================= */
@media (max-width: 640px) {
  .cards-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .au-board { grid-template-columns: minmax(0, 1fr); }
  .au-sell-list { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
  .au-toolbar .tabs { margin-right: 0; flex: 1 1 100%; }
  .au-find, .au-sort { flex: 1 1 auto; width: auto; }
  .au-hist-row { grid-template-columns: minmax(0, 1fr) auto; }
}
