/* =========================================================================
   livecard.css — THE LIVE DINOSAUR CARD. Canonical, owner-neutral.

   The visual reference is the donor island's parking panel (proposed to the
   owner 2026-08-07, his call outstanding): an eyebrow + LIVE pill, the species in display type with a
   growth ring beside it, VITALS as labelled bars, NUTRIENT DIET as three
   bars, and a nested PRIME STATUS block scored "n of 5".

   OWNER-NEUTRAL BY CONSTRUCTION. Not one colour is decided in this file.
   The surfaces map onto the host's own design tokens, so a site gets its
   own look for free; the MEANING colours are named tokens too, emitted by
   webcore/theme.py. There is not one owner name, brand colour or copy
   string here.

   ★ WHY NOT ONE LITERAL, NOT EVEN AS A FALLBACK (2026-08-10). Every colour
     used to be `var(--x, #hex)` so the card could render on a host with no
     tokens at all. The day this sheet was first LOADED BY A PAGE it met the
     rule the rest of this core lives under - theme.css is the only sheet
     allowed to decide a colour, and the assembled-sheet gate proves it - so
     the fallbacks moved into theme.py as real tokens. The trade is stated
     plainly: a host outside this core must publish the same token names
     (they are listed in README_FRONTEND.md) instead of getting a dark
     default for free. In exchange the card is themeable ONCE, in the one
     place an owner already edits colour.

   ★ AND THE TOKEN NAMES ARE THIS CORE'S, WHICH IS NOT COSMETIC. The card was
     written against a donor's names (`--ui`, `--display`, `--moon`,
     `--silver`, `--line`) and this core publishes `--font-ui`,
     `--font-display`, `--fg`, `--fg-dim`, `--border`. Every one of those
     reads was therefore falling through to its fallback: the card would have
     rendered in system-ui on a site that ships two loaded faces, and nothing
     would have looked broken enough to notice. Names matched 2026-08-10.

   THE MEANING COLOURS ARE STILL NOT AN OWNER'S TO SWEEP UP. Health is red
   and thirst is blue on every server in the fleet, because a player reads
   the colour before the label; a palette that recoloured health would make
   the card lie faster than it informs. theme.py therefore sets `--lvc-*` to
   fixed values rather than deriving them, and only an explicit
   `theme.tokens` pin can move one.

   NO LAYOUT DEPENDENCY. The card sizes from its container: it is a grid
   that collapses to one column under 720px and never sets a page width, so
   it drops into a vault page, a live page or a dashboard column unchanged.
   ========================================================================= */

.lvc {
  /* ---- surfaces, from the host site's own tokens ---- */
  --lvc-card:    var(--surface);
  --lvc-nest:    var(--surface-2);
  --lvc-line:    var(--border);
  --lvc-line-2:  var(--border-2);
  --lvc-head:    var(--fg);
  --lvc-text:    var(--fg-dim);
  --lvc-mut:     var(--fg-mute);
  --lvc-track:   var(--surface-sink);

  /* ---- meaning colours: theme.py's fixed `lvc-*` tokens, see header ---- */
  --lvc-ring:    var(--accent);

  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

@media (max-width: 860px) {
  .lvc { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, a full-width SQUARE well pushes every number below the fold on a
     phone. The specimen keeps its place and gives back the height. */
  .lvc-specimen { aspect-ratio: 16 / 10; }
}

/* =========================================================================
   the specimen well — the player's own animal, or a calm empty frame
   ========================================================================= */
.lvc-specimen {
  position: relative;
  border: 1px solid var(--lvc-line);
  border-radius: 12px;
  background: var(--lvc-card);
  aspect-ratio: 1 / 1;
  overflow: hidden;
  /* A specimen that never loads must still be a deliberate-looking frame
     rather than a collapsed box, so the well keeps its shape with no child. */
  min-height: 200px;
}

/* The side column: the animal, and whatever the host lets you DO with it.
   NOT sticky - the specimen renderer draws into the rectangle this element
   reports, and a box that moves under a page-pinned canvas draws a frame
   behind itself on every scroll. A square well that stays put is honest and
   costs nothing. */
.lvc-side { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* The host mounts whatever renderer it owns in here. The card itself never
   loads a mesh, a texture or a 3D library - it only provides the frame and
   tells the host which species class is on screen.

   ★ INSET, AND THE INSET IS THE ROUNDED CORNER (2026-08-10). The specimen is
     drawn by ONE WebGL canvas pinned over the whole viewport (shopview.mjs),
     scissored to the rectangle this element reports. A scissor rectangle is a
     RECTANGLE: it knows nothing about the 12px radius on the well above it, so
     a specimen drawn edge to edge would paint square corners over a rounded
     frame - the one visual artefact this arrangement can produce. A 12px
     radius bulges at most ~3.5px into its own corner, so a 10px inset clears
     it outright with room to spare, and the gutter it leaves is not a
     compromise: an animal that is not touching its own frame is the better
     picture anyway. */
.lvc-specimen-mount { position: absolute; inset: 10px; }

/* ---- the specimen stage, when a host owns one ----
   THE LIVE PAGE DOES NOT SHIP shop.css. `.shopview` is the shared preview
   engine's host contract and its poster states were styled only on the shop's
   own sheet, so a live card that could not load a mesh would have shown an
   unstyled empty box - a state whose only styling lives on another page's
   stylesheet is an unstyled state. These are the same states, in this card's
   own tokens, and they are scoped inside the well so they can never reach a
   shop card on a page that carries both sheets. */
.lvc-specimen-mount .shopview {
  position: absolute; inset: 0;
  height: auto;                  /* shop.css pins a card height; a well fills */
  border: 0; background: transparent;
}

/* idle = the assets are on their way; missing = this box has no directory for
   the species. A calm poster in both, never a blank rectangle and never a
   spinner that outlives its answer. */
.lvc-specimen-mount .shopview[data-view="idle"]::after,
.lvc-specimen-mount .shopview[data-view="missing"]::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 46px; height: 46px; transform: translate(-50%, -50%);
  border-radius: var(--r-round);
  border: 1px solid var(--border-2);
  background: var(--grad-poster);
}
/* The label is the OWNER'S WORD or nothing at all: `content: attr()` on an
   absent attribute renders empty, so a site that wrote no copy shows the
   poster alone rather than an English sentence on a Spanish island. */
.lvc-specimen-mount .shopview[data-view="missing"]::before {
  content: attr(data-missing-label);
  position: absolute; left: 0; right: 0; top: calc(50% + 34px);
  text-align: center;
  font: 500 11px/1 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--lvc-mut);
}

.lvc-specimen-note {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  /* ★ ABOVE THE SHARED CANVAS. That canvas is `position: fixed; z-index: 5`
     on the BODY - it is not a child of this well and it does not respect this
     well's stacking - so a caption without a z-index of its own is a caption
     the dinosaur walks over. 6 is the smallest number that is above it. */
  z-index: 6;
  padding: 10px 12px;
  font: 600 10px/1.4 var(--font-ui);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--lvc-mut);
  background: linear-gradient(to top, var(--lvc-well-scrim), transparent);
  pointer-events: none;
}

/* =========================================================================
   the stats card
   ========================================================================= */
.lvc-card {
  border: 1px solid var(--lvc-line);
  border-radius: 12px;
  background: var(--lvc-card);
  padding: 22px;
  min-width: 0;
}

@media (max-width: 560px) { .lvc-card { padding: 16px; } }

/* ---- header ---- */
.lvc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}

.lvc-head-l { display: flex; align-items: center; gap: 10px; min-width: 0; }

.lvc-eyebrow {
  margin: 0;
  font: 700 10px/1 var(--font-ui);
  letter-spacing: .25em;
  text-transform: uppercase;
  color: var(--lvc-mut);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lvc-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid transparent;
  font: 700 9px/1 var(--font-ui);
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.lvc-pill-live {
  background: var(--ok-halo);
  color: var(--lvc-ok);
  border-color: var(--ok-dim);
}

.lvc-pill-stale {
  background: var(--warn-halo);
  color: var(--warn);
  border-color: var(--warn-dim);
}

.lvc-pill-prime {
  background: var(--lvc-prime-halo);
  color: var(--lvc-prime);
  border-color: var(--lvc-prime-dim);
  box-shadow: 0 0 12px var(--lvc-prime-glow);
}

/* Elder is the rung BELOW prime on the game's own ladder, so it reads as a
   quieter version of the same badge rather than a different kind of thing. */
.lvc-pill-elder {
  background: var(--surface-3);
  color: var(--lvc-head);
  border-color: var(--lvc-line-2);
}

.lvc-pill-plain {
  background: var(--surface-2);
  border-color: var(--lvc-line-2);
  color: var(--lvc-text);
}

.lvc-dot {
  width: 6px; height: 6px;
  border-radius: 999px;
  background: currentColor;
  animation: lvc-pulse 2s ease-in-out infinite;
}

@keyframes lvc-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

/* A refresh control is a real button so it is keyboard reachable. It never
   becomes the only way to get fresh numbers - the card polls on its own. */
.lvc-refresh {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--lvc-mut);
  cursor: pointer;
  transition: color .16s ease, border-color .16s ease, background .16s ease;
}
.lvc-refresh:hover { color: var(--lvc-head); border-color: var(--lvc-line); }
.lvc-refresh:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lvc-refresh[disabled] { opacity: .4; cursor: default; }
.lvc-refresh svg { width: 15px; height: 15px; }
.lvc-refresh.is-busy svg { animation: lvc-spin 1s linear infinite; }

@keyframes lvc-spin { to { transform: rotate(360deg); } }

/* ---- species + growth ring ---- */
.lvc-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 22px;
}

.lvc-species {
  margin: 0;
  min-width: 0;
  font-family: var(--font-display);
  font-size: clamp(24px, 3.4vw, 34px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--lvc-head);
  overflow-wrap: anywhere;
}

.lvc-ring { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.lvc-ring-svg { position: relative; width: 62px; height: 62px; }
.lvc-ring-svg svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.lvc-ring-num {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font: 700 13px/1 var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--lvc-head);
}
.lvc-ring-stage {
  font: 700 9px/1 var(--font-ui);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--lvc-mut);
  white-space: nowrap;
}

/* =========================================================================
   the growth slot — the host's growth control, directly under the ring

   IT IS A FRAME AND NOTHING ELSE. Everything inside is the host's markup
   (the fleet's pause/unpause segments live in site.css), so this block sets
   a surface, a border and a rhythm and never names one of those classes. A
   sheet that styled the host's control would make the card's look depend on
   which host mounted it.

   :empty IS LOAD-BEARING. The slot is emitted before the host's own request
   lands, and on a server with no growth lane it is never filled at all - so
   an unfilled slot has to be no gap rather than a bordered box of nothing.
   ========================================================================= */
.lvc-growth {
  margin: -8px 0 22px;
  padding: 14px 16px;
  border: 1px solid var(--lvc-line-2);
  border-radius: 10px;
  background: var(--lvc-nest);
}

.lvc-growth:empty { display: none; }

@media (max-width: 560px) { .lvc-growth { padding: 12px; } }

/* ---- section label ---- */
.lvc-sec {
  margin: 0 0 10px;
  font: 700 10px/1 var(--font-ui);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--lvc-mut);
  opacity: .8;
}

/* ---- the bar grid ---- */
.lvc-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px 22px;
  margin-bottom: 22px;
}
.lvc-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

@media (max-width: 1080px) { .lvc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px)  { .lvc-grid, .lvc-grid-3 { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 700px)  { .lvc-grid-3 { grid-template-columns: minmax(0, 1fr); } }

.lvc-bar { display: flex; flex-direction: column; gap: 6px; min-width: 0; }

.lvc-bar-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.lvc-bar-label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 700 10px/1.3 var(--font-ui);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--lvc-mut);
  min-width: 0;
}
.lvc-bar-label svg { width: 11px; height: 11px; flex: 0 0 auto; color: var(--lvc-bleed); }

.lvc-bar-value {
  font: 600 11.5px/1.3 var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--lvc-text);
  white-space: nowrap;
}
.lvc-bar-value.is-pending { color: var(--lvc-mut); font-style: italic; font-weight: 500; }

.lvc-bar-track {
  height: 8px;
  border-radius: 999px;
  background: var(--lvc-track);
  overflow: hidden;
}

.lvc-bar-fill {
  /* A <span> is inline by default, and an inline box ignores both height and
     a percentage width - the bar rendered as an invisible sliver until this
     line existed. */
  display: block;
  height: 100%;
  border-radius: 999px;
  /* A width transition is the ONLY motion here. The card repaints on a poll,
     and animating anything else would make a steady reading look like it is
     changing when it is not. */
  transition: width .7s cubic-bezier(.22,.61,.36,1);
}
.lvc-bar.is-low .lvc-bar-fill { animation: lvc-pulse 1.6s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .lvc-bar-fill { transition: none; }
  .lvc-dot, .lvc-bar.is-low .lvc-bar-fill, .lvc-refresh.is-busy svg { animation: none; }
}

/* =========================================================================
   prime status
   ========================================================================= */
.lvc-prime {
  border: 1px solid var(--lvc-line-2);
  border-radius: 12px;
  background: var(--lvc-nest);
  padding: 16px;
}

.lvc-prime-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.lvc-prime-score {
  font: 600 11px/1 var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--lvc-text);
  white-space: nowrap;
}

.lvc-prime-rows { display: flex; flex-direction: column; }

.lvc-prime-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 5px 0;
  font: 400 12px/1.4 var(--font-ui);
  color: var(--lvc-text);
}

.lvc-prime-state {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 700;
  font-size: 11.5px;
  white-space: nowrap;
}
.lvc-prime-state svg { width: 13px; height: 13px; }
.lvc-prime-state.is-met  { color: var(--lvc-ok); }
.lvc-prime-state.is-unmet { color: var(--lvc-no); }
.lvc-prime-state.is-na, .lvc-prime-state.is-pending { color: var(--lvc-mut); }

.lvc-prime-note {
  margin: 10px 0 0;
  font: 400 10.5px/1.5 var(--font-ui);
  color: var(--lvc-mut);
}

/* =========================================================================
   footer chips + freshness
   ========================================================================= */
.lvc-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--lvc-line-2);
}

.lvc-foot-when {
  margin-left: auto;
  font: 400 10.5px/1.4 var(--font-ui);
  color: var(--lvc-mut);
}

/* =========================================================================
   the empty states — a rendered answer, never a spinner
   ========================================================================= */
.lvc-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 34px 16px;
}
.lvc-empty svg { width: 34px; height: 34px; color: var(--lvc-mut); opacity: .5; }
.lvc-empty h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(19px, 2.6vw, 24px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--lvc-head);
}
.lvc-empty p { margin: 0; max-width: 44ch; font-size: 13.5px; color: var(--lvc-text); }
/* WIDTH 100%, so the slot can hold more than a button. The empty state is a
   centred column, and a content-sized slot squeezes anything richer than two
   buttons - a host's action RAIL collapsed to a thin strip down the middle of
   a wide card (seen live, 2026-08-10). `justify-content: center` still centres
   one or two buttons exactly as before; a full-width child now fills. */
.lvc-empty-act { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 6px; width: 100%; }

/* The skeleton is shown ONCE, before the first answer of the session ever
   lands. Every later state is a real answer, so nothing here can be left on
   screen by a failed poll. */
.lvc-skel { display: flex; flex-direction: column; gap: 12px; animation: lvc-pulse 1.6s ease-in-out infinite; }
.lvc-skel-row { height: 34px; border-radius: 10px; background: var(--surface-2); }
.lvc-skel-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; }
.lvc-skel-grid > span { display: block; height: 34px; border-radius: 10px; background: var(--surface-sink); }
@media (max-width: 1080px) { .lvc-skel-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
