/* ---------- The material system ----------
   The dashboard's surfaces, its light direction and its radius scale,
   in one place. Three surfaces, each a visible step lighter than the one
   under it: the floor the page sits on, a panel raised off the floor, a
   tile raised off a panel. Every raised thing is lit from above by the
   same edge and drops the same shadow, which is the recipe css/buttons.css
   already uses on every button.

   The warm trio in css/style.css (--surface-sunken, --surface, and
   --surface-hero) stays as it is for the rest of the site; these are the
   cool equivalents this page is built from, and they keep the blue the
   charts and signals already carry. --glow-accent, --elev-1, --elev-2 and
   --edge-lit are reused from there rather than restated. */
:root {
  /* The five material tokens (--surface-floor, --surface-panel,
     --surface-raised, --edge-top, --edge) moved to css/surface-tokens.css,
     which every panel page loads. Same values; see that file. */

  /* ---------- THE SEVEN RARITIES, AS BAR COLOURS ----------
     The same hues the thumbnail plates already wear, taken from their
     --tint-edge rather than their --tint. The tints run 0.08 to 0.3
     alpha because they sit behind artwork as a glow; as a 4px bar on a
     dark row they would be all but invisible. The edges are the
     saturated half of the same pair, which is why they are the ones
     lifted here.

     Two values each rather than one plus a color-mix: the soft end is
     the left of the bar's gradient and the solid end is its right, and
     writing both out keeps this file free of a function it does not
     use anywhere else. The pairing mirrors --tint / --tint-edge above. */
  --rar-gold:       rgb(255, 211, 107);  --rar-gold-soft:       rgba(255, 211, 107, 0.5);
  --rar-covert:     rgb(235, 75, 75);    --rar-covert-soft:     rgba(235, 75, 75, 0.5);
  --rar-classified: rgb(211, 44, 230);   --rar-classified-soft: rgba(211, 44, 230, 0.5);
  --rar-restricted: rgb(136, 71, 255);   --rar-restricted-soft: rgba(136, 71, 255, 0.5);
  --rar-milspec:    rgb(75, 105, 255);   --rar-milspec-soft:    rgba(75, 105, 255, 0.5);
  --rar-industrial: rgb(94, 152, 217);   --rar-industrial-soft: rgba(94, 152, 217, 0.5);
  --rar-consumer:   rgb(176, 195, 217);  --rar-consumer-soft:   rgba(176, 195, 217, 0.5);

  /* One light, from above. The panel lifts further off the floor than a
     tile lifts off its panel, which is the whole of the hierarchy. */
  --lift-panel:
    inset 0 1px 0 var(--edge-top),
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 12px 32px -16px rgba(0, 0, 0, 0.9);
  --lift-tile:
    inset 0 1px 0 var(--edge-top),
    0 1px 1px rgba(0, 0, 0, 0.4),
    0 6px 16px -10px rgba(0, 0, 0, 0.8);

  /* Radius by size, so a container's scale is legible before it is read. */
  --r-panel: 18px;
  --r-tile:  12px;

  /* The page background, for this page only: style.css keeps its own. */
  --bg: var(--surface-floor);
}

/* ============================================================
   SkinSignal - THE DASHBOARD PAGE

   The frame dashboard.html needs on top of css/style.css: the header
   (who is signed in, or the Sign in with Steam nudge for somebody who is
   not), the loading state, the honest empty and locked states, the value
   history slot on the hero, and the holdings as cards on a phone.

   The dashboard sections themselves (rank hero, ladder, stats, signals,
   holdings, item detail) are still styled in css/style.css, and the
   unlock cascade there too. The premium redesign is Phase 2; this is the
   honest, working version, on the site's tokens.
   ============================================================ */

/* ---------- The header ---------- */

/* Signed out only: the one thing a visitor with no inventory to load can
   do here. Green, because it is the way in. */
.dash-signin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin-top: clamp(18px, 2vw, 26px);
}

.dash-signin[hidden] { display: none; }

.dash-signin__note {
  max-width: 44ch;
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

/* The status line: what is loading, what loaded, what went wrong. The
   error state reads at full brightness, the same rule the site uses
   everywhere, so red stays for losses. */
.dash-note {
  max-width: 62ch;
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--muted);
}

.dash-note:empty { display: none; }

.dash-note.hero__note--error { color: var(--text); }
.dash-note.hero__note--ok    { color: var(--muted); }

/* ---------- Loading ----------
   While an inventory is read, whatever is underneath (the sample, on
   arrival) is dimmed, so it is never mistaken for the answer. */
.herorank,
main > .section {
  transition: opacity var(--dur-lift) var(--ease);
}

.dash-loading .herorank,
.dash-loading main > .section {
  opacity: 0.4;
}

/* ---------- The gain since the last visit ----------
   The user's own number, under the Portfolio Value figure. Green up, red
   down, quiet when flat or on Day 1. */
.progression__gain {
  margin: 8px 0 0;
  font-size: 12.5px;
  letter-spacing: 0.01em;
  color: var(--muted);
}

.progression__gain[hidden] { display: none; }
.progression__gain.is-up   { color: var(--gain); }
.progression__gain.is-down { color: var(--risk); }

/* ---------- The value chart ----------
   The dashboard's hero, under the Portfolio Value figure (js/app.js, THE
   VALUE CHART). Blue is the recorded value. Gold is the climb: the next
   rank's goal line, the rank-ups, and the all time high when it breaks.
   Built like the pricing cards: a gradient edge, a lit top, two quiet
   lights in the corners. */
.vchart {
  --vchart-gold: 240, 180, 41;

  position: relative;
  margin-top: clamp(16px, 2vw, 22px);
  padding: 14px 16px 12px;
  border: 1px solid transparent;
  border-radius: var(--r-tile);
  background:
    radial-gradient(90% 70% at 100% 0%, rgba(var(--vchart-gold), 0.06), rgba(var(--vchart-gold), 0) 60%) padding-box,
    radial-gradient(80% 90% at 0% 100%, rgba(var(--glow-accent), 0.08), rgba(var(--glow-accent), 0) 60%) padding-box,
    linear-gradient(180deg, var(--surface-raised) 0%, var(--surface-panel) 100%) padding-box,
    linear-gradient(180deg,
      rgba(var(--glow-accent), 0.32) 0%,
      rgba(245, 242, 236, 0.05) 45%,
      rgba(var(--vchart-gold), 0.18) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(168, 196, 255, 0.06),
    0 18px 40px -26px rgba(0, 0, 0, 0.9);
}

.vchart__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px 16px;
}

.vchart__eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-family: var(--font-display);
  font-stretch: var(--display-label);
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.vchart__tag {
  padding: 3px 7px 2px;
  border: 1px solid var(--line-warm);
  border-radius: 999px;
  font-size: 9px;
  letter-spacing: 0.14em;
  color: var(--text);
}

.vchart__tag[hidden] { display: none; }

.vchart__caption {
  max-width: 62ch;
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
}

.vchart__ranges {
  display: inline-flex;
  gap: 3px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-sunken);
}

.vchart__ranges[hidden] { display: none; }

.vchart__range {
  padding: 5px 10px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--dur-swift) ease, background-color var(--dur-swift) ease;
}

.vchart__range:hover { color: var(--text); }
.vchart__range[aria-pressed="true"] { background: rgba(var(--glow-accent), 0.18); color: #cfe0ff; }
.vchart__range:focus-visible { outline: 2px solid rgba(var(--glow-accent), 0.7); outline-offset: 2px; }

.vchart__plot {
  position: relative;
  height: clamp(180px, 21vw, 248px);
  margin-top: 10px;
  touch-action: pan-y;
  cursor: crosshair;
}

.vchart__svg,
.vchart__marks {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.vchart__svg { overflow: visible; }
.vchart__marks { pointer-events: none; }

/* The SVG layers. Strokes stay a constant width however the plot
   stretches. */
.vchart__area { fill: url(#vchartArea); }

.vchart__line,
.vchart__glowline,
.vchart__gapline,
.vchart__daytick,
.vchart__estimate,
.vchart__goalline,
.vchart__goalglow,
.vchart__athline {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.vchart__line     { stroke: #6aa2ff; stroke-width: 2; }
.vchart__glowline { stroke: #3d7fff; stroke-width: 8; stroke-opacity: 0.16; }
.vchart__gapline  { stroke: #6aa2ff; stroke-width: 1.6; stroke-opacity: 0.6; stroke-dasharray: 2 6; }
.vchart__daytick  { stroke: #6aa2ff; stroke-width: 1; stroke-opacity: 0.3; }
.vchart__estimate { stroke: #a8c4ff; stroke-width: 1.4; stroke-opacity: 0.65; stroke-dasharray: 2 5; }
.vchart__goalgap  { fill: url(#vchartGap); }
.vchart__goalglow { stroke: rgb(var(--vchart-gold)); stroke-width: 8; stroke-opacity: 0.14; }
.vchart__goalline { stroke: #f5c451; stroke-width: 1.5; }
.vchart__athline  { stroke: #d9d5cb; stroke-width: 1; stroke-opacity: 0.4; stroke-dasharray: 5 6; }
.vchart--newhigh .vchart__athline { stroke: #f5c451; stroke-opacity: 0.9; }

/* The marks over the plot. */
.vchart__goal {
  position: absolute;
  right: 8px;
  transform: translateY(calc(-100% - 6px));
  padding: 4px 10px 3px;
  border: 1px solid rgba(var(--vchart-gold), 0.5);
  border-radius: 999px;
  background: rgba(12, 10, 5, 0.86);
  font-family: var(--font-mono);
  font-size: 11px;
  color: #ffd36b;
  white-space: nowrap;
  box-shadow: 0 0 18px -4px rgba(var(--vchart-gold), 0.6);
}

.vchart__athlabel {
  position: absolute;
  left: 8px;
  transform: translateY(calc(-100% - 5px));
  font-family: var(--font-display);
  font-stretch: var(--display-label);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.vchart__athlabel.is-new { color: #ffd36b; }

.vchart__dot {
  position: absolute;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: #6aa2ff;
  box-shadow: 0 0 0 2px #0b0c10;
}

.vchart__today {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: #dbe7ff;
  box-shadow: 0 0 0 3px rgba(var(--glow-accent), 0.45), 0 0 22px 4px rgba(var(--glow-accent), 0.75);
}

.vchart__today::after {
  content: '';
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  border: 1px solid rgba(106, 162, 255, 0.75);
  opacity: 0;
}

/* The one loop: only while the chart is on screen and the tab visible. */
.vchart.is-live:not(.is-paused) .vchart__today::after {
  animation: vchartPulse 2.4s var(--ease) infinite;
}

@keyframes vchartPulse {
  0%   { opacity: 0.9; transform: scale(0.5); }
  100% { opacity: 0;   transform: scale(1.9); }
}

/* Day 1, the launch state: the one point gets all the light. */
.vchart[data-phase="day1"] .vchart__today {
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  box-shadow: 0 0 0 4px rgba(var(--glow-accent), 0.4), 0 0 32px 8px rgba(var(--glow-accent), 0.7);
}

.vchart__now {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border: 2px solid #6aa2ff;
  border-radius: 50%;
  background: #0b0c10;
}

.vchart__nowlabel {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 10.5px;
  color: var(--muted);
  white-space: nowrap;
}

.vchart__rankup {
  position: absolute;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid rgba(var(--vchart-gold), 0.85);
  border-radius: 50%;
  background: #0b0b0d;
  box-shadow: 0 0 14px -2px rgba(var(--vchart-gold), 0.7);
}

.vchart__rankup img {
  width: 20px;
  height: 20px;
  object-fit: contain;
}

.vchart__x {
  position: absolute;
  bottom: 2px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--faint);
}

.vchart__x--start { left: 2%; }
.vchart__x--end   { right: 2%; }

.vchart__newhigh {
  position: absolute;
  left: 50%;
  top: 2px;
  transform: translateX(-50%);
  padding: 5px 12px 4px;
  border-radius: 999px;
  background: linear-gradient(180deg, #ffe7a3 0%, #f0b429 70%, #cf9418 100%);
  font-family: var(--font-display);
  font-stretch: var(--display-label);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #1a1102;
  white-space: nowrap;
  box-shadow: 0 0 0 1px rgba(var(--vchart-gold), 0.4), 0 10px 30px -6px rgba(var(--vchart-gold), 0.8);
  animation: vchartHigh 900ms var(--ease) both;
}

@keyframes vchartHigh {
  0%   { opacity: 0; transform: translate(-50%, 8px) scale(0.9); }
  60%  { opacity: 1; transform: translate(-50%, 0) scale(1.06); }
  100% { opacity: 1; transform: translate(-50%, 0) scale(1); }
}

.vchart__empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  margin: 0;
  font-size: 13px;
  color: var(--faint);
}

.vchart__cross {
  position: absolute;
  top: 0;
  bottom: 20px;
  width: 1px;
  background: rgba(245, 242, 236, 0.28);
  pointer-events: none;
}

.vchart__tip {
  position: absolute;
  top: 4px;
  z-index: 3;
  display: grid;
  gap: 3px;
  min-width: 124px;
  padding: 8px 10px;
  border: 1px solid var(--line-warm);
  border-radius: 9px;
  background: rgba(14, 14, 17, 0.96);
  box-shadow: var(--elev-2);
  transform: translateX(-50%);
  pointer-events: none;
  font-size: 11.5px;
}

.vchart__cross[hidden],
.vchart__tip[hidden] { display: none; }

.vchart__tipdate  { color: var(--muted); }
.vchart__tipvalue { font-size: 14px; color: var(--text); }

.vchart__tiprank {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-stretch: var(--display-label);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #ffd36b;
}

.vchart__tiprank img {
  width: 18px;
  height: 18px;
  object-fit: contain;
}

.vchart__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  margin-top: 10px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--faint);
}

.vchart__foot:empty { display: none; }

.vchart__key {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.vchart__key::before {
  content: '';
  width: 16px;
  border-top: 2px solid #6aa2ff;
}

.vchart__key--goal::before     { border-top-color: #f5c451; }
.vchart__key--ath::before      { border-top: 1px dashed rgba(217, 213, 203, 0.6); }
.vchart__key--estimate::before { border-top: 2px dotted #a8c4ff; }

.vchart__fact b {
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--text);
}

.vchart__fact b.pos { color: var(--gain); }
.vchart__fact b.neg { color: var(--risk); }

.vchart__lock {
  color: #a8c4ff;
  text-decoration: none;
  border-bottom: 1px solid rgba(var(--glow-accent), 0.35);
}

.vchart__lock:hover { color: var(--text); }

/* The reveal: once, the first time the chart scrolls into view. */
.vchart.is-drawing .vchart__svg   { animation: vchartReveal 1300ms var(--ease) both; }
.vchart.is-drawing .vchart__marks { animation: vchartFade 600ms var(--ease) 700ms both; }

@keyframes vchartReveal {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

@keyframes vchartFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* The label under the Portfolio heading: the change cards are a
   different measure from the chart. */
.section__note {
  max-width: 72ch;
  margin: -6px 0 18px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--faint);
}

@media (max-width: 560px) {
  .vchart { padding: 12px 12px 10px; }
  .vchart__goal { padding: 3px 8px 2px; font-size: 10px; }
  .vchart__nowlabel { display: none; }
}

/* ---------- No wall of n/a ----------
   Until there is history to compare against, js/app.js renders this one
   line instead of three n/a cards. It spans the whole stats grid. */
.stats__empty {
  grid-column: 1 / -1;
  margin: 0;
  padding: 18px 20px;
  border: 1px dashed var(--line-warm);
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted);
}

/* ---------- Locked, not empty ----------
   A free or signed out read: the change exists and is part of
   membership. A clear panel with a way in, never an error or a blank. */
.stats__locked {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 6px 24px;
  padding: 20px 22px;
  border: 1px solid rgba(var(--glow-accent), 0.28);
  border-radius: 12px;
  background:
    linear-gradient(180deg, rgba(var(--glow-accent), 0.07), rgba(var(--glow-accent), 0) 70%),
    var(--surface);
  box-shadow: var(--edge-lit), var(--elev-1);
}

.stats__locked-title {
  margin: 0;
  font-family: var(--font-display);
  font-stretch: var(--display-header);
  font-weight: 600;
  font-size: 16px;
  line-height: 1.35;
  color: var(--text);
}

.stats__locked-text {
  grid-column: 1;
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

.stats__locked-cta {
  grid-column: 2;
  grid-row: 1 / span 2;
}

/* ---------- Phones ---------- */

@media (max-width: 560px) {
  .dash-signin .cta { width: 100%; }

  .stats__locked { grid-template-columns: minmax(0, 1fr); }
  .stats__locked-text,
  .stats__locked-cta { grid-column: 1; grid-row: auto; }
  .stats__locked-cta { justify-self: start; margin-top: 8px; }
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  .herorank,
  main > .section { transition: none; }

  /* The chart stands still: drawn, no reveal, no pulse, no burst. */
  .vchart.is-drawing .vchart__svg,
  .vchart.is-drawing .vchart__marks,
  .vchart.is-live .vchart__today::after,
  .vchart__newhigh {
    animation: none !important;
  }

  .vchart__range { transition: none; }
}

/* ============================================================
   ONE BOARD
   The free dashboard brought up to the chart, the ladder and the
   signals. Everything below reuses what is already here: the value
   chart's panel, the signal row's thumbnail well, meter and pill, the
   locked-teaser tone, and the site's tokens. Nothing loops.
   ============================================================ */

/* ---------- The status strip ---------- */

.dash-strip {
  position: relative;
  padding-top: clamp(12px, 1.5vw, 18px);
}

.dash-strip__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 28px;
  padding-bottom: clamp(12px, 1.4vw, 16px);
  border-bottom: 1px solid transparent;
  border-image: linear-gradient(90deg, rgba(var(--glow-accent), 0.3), rgba(245, 242, 236, 0.07) 38%, rgba(245, 242, 236, 0) 100%) 1;
}

.dash-strip__lead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  min-width: 0;
  flex: 1 1 460px;
}

.dash-strip__live {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 4px;
  border: 1px solid var(--line-warm);
  border-radius: 999px;
  background: var(--surface-sunken);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.dash-strip__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--faint);
}

/* Live is lit, not animated: a still glow says "on" without a loop. */
.dash-strip__live[data-state="live"] {
  border-color: rgba(var(--glow-accent), 0.4);
  background: rgba(var(--glow-accent), 0.07);
  color: #cfe0ff;
}
.dash-strip__live[data-state="live"] .dash-strip__dot {
  background: #8fb6ff;
  box-shadow: 0 0 0 3px rgba(var(--glow-accent), 0.18), 0 0 10px rgba(var(--glow-accent), 0.8);
}
.dash-strip__live[data-state="loading"] .dash-strip__dot { background: var(--info); }

.dash-strip__title {
  margin: 0;
  font-family: var(--font-display);
  font-stretch: var(--display-header);
  font-weight: 600;
  font-size: 17px;
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: nowrap;
}

.dash-strip__sub {
  margin: 0;
  min-width: 0;
  flex: 1 1 260px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

/* THE FACTS WRAP RATHER THAN RUN OFF THE SIDE. This row is nowrap by
   default, which is right while the facts fit. At 320 they do not: three
   cells need 312px inside a 280px measure, and the last one ended 12px
   past the viewport, which is the whole of the page's horizontal scroll
   at that width. Wrapping is what the scale strip, modelled on this
   element, already does. One line whenever one line fits. */
.dash-strip__facts {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
}

.dash-strip__facts:empty { display: none; }

.dash-strip__fact {
  padding: 0 18px;
  border-left: 1px solid var(--line);
}

.dash-strip__fact:first-child { padding-left: 0; border-left: 0; }

.dash-strip__fact dt {
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.dash-strip__fact dd {
  margin: 3px 0 0;
  font-size: 13px;
  color: var(--text);
  white-space: nowrap;
}

/* Signed out: the way in sits on one line under the lead, button then
   note, so the nudge does not turn the strip back into a header block. */
.dash-strip .dash-signin {
  flex: 1 1 100%;
  margin-top: 2px;
  gap: 8px 16px;
}

.dash-strip .dash-signin__note { max-width: 78ch; font-size: 12.5px; }

.dash-strip .dash-note {
  flex: 1 1 100%;
  margin: 0;
}

/* A successful load is already said by the facts beside it, so its
   sentence is kept for screen readers only. Errors stay on screen. */
.dash-strip .dash-note.hero__note--ok {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- The hero, closer to the top ----------
   Same hero, less air: the strip carries what the space above it used
   to announce, and the board starts right under the ladder. */
.herorank {
  padding: clamp(16px, 2vw, 26px) 0 clamp(14px, 1.8vw, 24px);
}

/* ---------- One section rhythm ---------- */

main > .section.dash-section {
  padding: clamp(20px, 2.6vw, 34px) 0;
  border-top: 0;
}

.dash-sechead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px 24px;
  margin: 0 0 14px;
}

.dash-sechead__title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--font-display);
  font-stretch: var(--display-header);
  font-weight: 600;
  font-size: 19px;
  letter-spacing: -0.015em;
  color: var(--text);
}

/* A short silver-to-blue tick, the chart panel's edge in miniature. */
.dash-sechead__title::before {
  content: "";
  width: 3px;
  height: 15px;
  border-radius: 2px;
  background: linear-gradient(180deg, #dfe5ef, rgba(var(--glow-accent), 0.75));
}

.dash-sechead__meta {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

.dash-sechead__meta:empty { display: none; }
.dash-sechead__meta.is-live { color: #9fb8e6; }

/* ---------- The panel ----------
   The value chart's container, for everything else: the gradient edge
   (blue at the top, gold at the foot), the two corner glows, the inner
   highlight and the deep soft shadow. Same recipe as .vchart. */
.dpanel {
  --dpanel-gold: 240, 180, 41;

  position: relative;
  border: 1px solid transparent;
  border-radius: var(--r-panel);
  background:
    radial-gradient(90% 70% at 100% 0%, rgba(var(--dpanel-gold), 0.05), rgba(var(--dpanel-gold), 0) 60%) padding-box,
    radial-gradient(80% 90% at 0% 100%, rgba(var(--glow-accent), 0.07), rgba(var(--glow-accent), 0) 60%) padding-box,
    linear-gradient(180deg, var(--surface-raised) 0%, var(--surface-panel) 100%) padding-box,
    linear-gradient(180deg,
      rgba(var(--glow-accent), 0.32) 0%,
      rgba(245, 242, 236, 0.05) 45%,
      rgba(var(--dpanel-gold), 0.18) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(168, 196, 255, 0.06),
    0 18px 40px -26px rgba(0, 0, 0, 0.9);
}

/* ---------- Your book ---------- */

/* THE PANEL, AND ONLY THIS PANEL. .dpanel above is shared with the
   chart and the desk; this overrides it for the book alone, which works
   because .book is declared after it and both are one class deep. The
   material is the collection module's, so the two big panels on the
   page are made of the same thing. */
.book {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 16px;
  /* The base glow is a child, so it has to be clipped to the plate's
     radius. overflow only ever clips descendants, never the element's
     own shadow or border, so the drop shadow and the lit top edge below
     are unaffected by this. */
  overflow: hidden;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.006));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.055),
    0 20px 50px rgba(0, 0, 0, 0.42);
}

/* ---------- THE WHISPER ----------
   One glow, anchored below the bottom left corner and gone long before
   the far side: the ellipse is 620x240 centred at 16% 118%, so its
   brightest point is off the plate entirely and what reaches the panel
   is the tail. It resolves to transparent rather than to a colour,
   which is the difference between a glow and a wash: a gradient that
   ends on a grey leaves every pixel of the panel carrying some of it. */
.book::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(620px 240px at 16% 118%,
    rgba(61, 127, 255, 0.07), transparent 68%);
}

/* The columns sit over it. Without this the glow would paint on top of
   the content rather than under it. */
.book > * { position: relative; z-index: 1; }

.book__col {
  min-width: 0;
  padding: 18px 20px 20px;
}

.book__col + .book__col { border-left: 1px solid rgba(245, 242, 236, 0.06); }

.book__eyebrow {
  margin: 0 0 16px;
  padding-left: 10px;
  border-left: 3px solid var(--accent);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The figure leads and the sentence follows it, rather than the two
   sharing a size. baseline, so a 32px number and 13px words sit on the
   same line rather than the words floating in the middle of the digit. */
.book__lead {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin: 0 0 16px;
  font-size: 13.5px;
  color: var(--muted);
}

.book__leadpct {
  margin-right: 0;
  font-family: var(--font-mono);
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 22px rgba(61, 127, 255, 0.22);
}

.book__bar {
  display: flex;
  height: 14px;
  border-radius: 7px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
  /* The inset groove it always had, plus the faintest lift off the
     panel. A flat rgba here rather than var(--seg): the summary bar
     carries four categories at once, so it has no one colour, and the
     accent is what the page uses for "all of it". */
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.6),
    0 0 20px -6px rgba(61, 127, 255, 0.16);
}

/* NO MIN-WIDTH. It used to floor every segment at 3px, which is a lie
   about the two smallest: graffiti is 0.25% of this book and drawing it
   at the same width as something four times bigger is the one thing a
   proportion bar must not do. The legend under it names both, with the
   figure, so nothing is lost by letting them be as small as they are.

   The width lives in --w and CSS owns the property, so the reveal can
   animate from 0 without js/app.js knowing anything about motion. */
.book__seg {
  position: relative;
  flex: 0 0 auto;
  width: var(--w, 0%);
  height: 100%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), transparent 55%), var(--seg);
}

/* The join, as an inset rather than a flex gap: a real gap would add
   6px to a row whose parts already add to exactly 100%, and the last
   segment would be clipped by the overflow. */
.book__seg + .book__seg { box-shadow: inset 2px 0 0 rgba(8, 9, 12, 0.95); }

/* Hover polish on the summary bar. The brightness is out here because a
   colour lifting is not a movement, and taking it from a reader with
   reduced motion would cost them the feedback without sparing them
   anything. The scale that goes with it is in the motion query. */
.book__seg:hover { filter: brightness(1.25); }

/* ---------- THE LEDGER ----------
   Four columns, in the order the eye asks the questions in: what it is,
   how much of the book, what it is worth, what share. The track in the
   middle is the same proportion the stacked bar draws, per row, so a
   reader comparing two lines does not have to measure two slices of one
   bar against each other. */
/* THE PRICED TOTAL, and only the priced total. It is the denominator
   the four percentages are shares of, summed from the same parts the
   rows draw, which is why it is computed in renderBook from those
   parts rather than added to bookComposition: the facts rail reads
   that return shape and nothing should change under it.

   It is not the inventory's worth and is not labelled as if it were.
   Anything without a price is outside it, and the note under the
   legend already says how many that is. */
.book__total {
  margin: 10px 0 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.book__total b { font-weight: 600; color: var(--text); }

.book__legend {
  display: grid;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}

/* THE BAR TAKES THE MIDDLE, NOT THE NAME. The name used to hold the
   flexible column and the bar a fixed 140px, which on a 1101px panel
   left a canyon between the two: a name that needs 130px stretched to
   600, the bar sat in a fifth of the row, and the eye had to cross
   empty space to get from what a thing is to how much of the book it
   is. Swapping which column flexes closes the gap with the one element
   that has a reason to be wide, and the name gets the 212px it
   actually needs for the longest of the six labels. */
.book__part {
  display: grid;
  grid-template-columns: 212px minmax(0, 1fr) 104px 54px;
  align-items: center;
  gap: 20px;
  padding: 13px 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: 8px;
  font-size: 13px;
}

.book__part:first-child { border-top: 0; }
.book__part:hover { background: rgba(255, 255, 255, 0.015); }

.book__idn {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

/* THE GLOW IS A BLURRED COPY, NOT A box-shadow. --seg is a gradient for
   knives and a flat colour for everything else, and box-shadow cannot
   take a gradient: the declaration would be dropped and gold alone
   would lose its glow. A blurred duplicate behind the dot takes either. */
.book__swatch {
  position: relative;
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--seg);
}

.book__swatch::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  background: var(--seg);
  filter: blur(4px);
  opacity: 0.75;
}

.book__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.book__count {
  margin-left: 8px;
  padding: 2px 7px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 20px;
  font-size: 10.5px;
  font-weight: 400;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* The row's own share of the book, at the same scale as every other
   row: --w is the true percentage, never rescaled against the largest,
   and never floored. Two of these are nubs on this account and that is
   the honest drawing of 0.63% and 0.25%. The channel around them stays
   visible at full width either way, so a nub reads as a nub in a
   measure rather than as a bar that failed to render. */
/* OVERFLOW IS VISIBLE, AND THE FILL STILL CLIPS ITSELF.
   The channel used to clip with overflow:hidden, which also cut the
   fill's halo off at the track's edge: a glow that stops dead on a
   straight line is not a glow. It is visible now, and nothing is lost,
   because the fill was never relying on this to shape it. The fill
   carries its own border-radius and its own width, and its width is a
   percentage that cannot exceed the track, so its rounded ends are its
   own and the track's background still shows to the right of it. */
.book__track {
  height: 10px;
  border-radius: 5px;
  overflow: visible;
  background: rgba(255, 255, 255, 0.045);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* The sheen is a background layer rather than a third pseudo element,
   because both of the ones this has are carrying the bloom. */
.book__trackfill {
  position: relative;
  display: block;
  width: var(--w, 0%);
  height: 100%;
  border-radius: 5px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.22), transparent 45%), var(--seg);
}

/* ---------- THE BLOOM, IN TWO LAYERS ----------

   NOT A box-shadow, AND THIS IS THE REASON. --seg is
   linear-gradient(90deg, #d99a1e, #ffd36b) for knives and a flat hex
   for the other three. box-shadow cannot take a gradient: the whole
   declaration is invalid and dropped, so a raw
   `box-shadow: 0 0 14px -3px var(--seg)` would glow for three
   categories and silently do nothing for the one the eye goes to
   first. Both layers are blurred copies of the fill instead, which
   takes a gradient and a colour alike.

   ::before is the wide halo and ::after the tight edge. Both sit over
   the fill rather than behind it: a negative z-index would send them
   behind the track's own background and the halo would disappear
   again. Over a sharp fill, a blurred copy of it reads as bloom. */
.book__trackfill::before {
  content: '';
  position: absolute;
  inset: -9px -5px;
  border-radius: 10px;
  pointer-events: none;
  background: var(--seg);
  filter: blur(13px);
  opacity: 0.18;
}

.book__trackfill::after {
  content: '';
  position: absolute;
  inset: -2px 0;
  border-radius: 5px;
  pointer-events: none;
  background: var(--seg);
  filter: blur(6px);
  opacity: 0.38;
}

.book__part:hover .book__trackfill::after { opacity: 0.6; }
.book__part:hover .book__trackfill::before { opacity: 0.3; }

.book__value {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.book__pct {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.book__note,
.book__empty {
  margin: 10px 0 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--faint);
}

.book__empty { margin: 0; color: var(--muted); }

/* ---------- THE BOOK'S MOTION ----------
   OPT IN, the same way the collection module does it. The resting
   stylesheet above already shows the finished state: every segment and
   every fill is at var(--w), and the figure is in the markup at its
   real value. A reader with reduced motion, or with no JavaScript, gets
   the answer rather than a frame of an animation that will not run.

   The from-zero start lives here and nowhere else, and it is gated on
   .is-revealed, which js/app.js adds once per page load. */
@media (prefers-reduced-motion: no-preference) {
  .book__comp:not(.is-revealed) .book__seg,
  .book__comp:not(.is-revealed) .book__trackfill { width: 0; }

  .book__comp .book__seg {
    transition: width 900ms cubic-bezier(0.16, 0.84, 0.34, 1);
  }

  /* ---------- THE CASCADE ----------
     A row rises and its bar fills on the SAME beat: --d is the row's
     own delay, set inline by js/app.js as index * 0.09s, and it is the
     delay on both the transform and the width. Two halves of one row
     arriving at different times would read as two events.

     The count-up in js/app.js uses the same 90ms step, so the figure a
     row is counting to lands with the bar that is drawing it. */
  .book__comp:not(.is-revealed) .book__part {
    transform: translateY(9px);
    opacity: 0;
  }

  .book__part {
    transition:
      transform 500ms cubic-bezier(0.16, 0.84, 0.34, 1),
      opacity 500ms cubic-bezier(0.16, 0.84, 0.34, 1),
      background 180ms ease;
    transition-delay: var(--d, 0s), var(--d, 0s), 0s;
  }

  /* ---------- THE PULSE IS A TRANSITION, NOT AN ANIMATION ----------

     MEASURED, AND THE ANIMATION FAILED. `animation: book-fillbloom ...`
     on .is-revealed was specified as one shot on the grounds that the
     class is state gated. That reasoning holds for a transition and not
     for an animation, and the difference is what renderBook does: it
     rewrites innerHTML, so every fill is a BRAND NEW element on every
     render. A transition needs a value to change on an element that was
     already there, so a new element born at its final value simply
     renders at it. An animation has no such requirement: it plays from
     the top whenever a matching element appears. Sampled across a
     currency switch, the fills pulsed again, 12 distinct brightness
     values where there should have been one.

     So the bloom is a transition on filter, on exactly the mechanism
     the width beside it already uses and which is already proven not to
     replay. The shape changes with it: the bar now arrives hot and
     cools over the same 1150ms rather than brightening after it lands.
     That is the only shape a transition can make, and it is the better
     one anyway, because the bloom is then part of the bar drawing
     rather than an event that happens to it afterwards. */
  .book__comp:not(.is-revealed) .book__trackfill {
    filter: brightness(1.3) saturate(1.08);
  }

  .book__comp .book__trackfill {
    transition:
      width 900ms cubic-bezier(0.16, 0.84, 0.34, 1),
      filter 1150ms ease;
    transition-delay: var(--d, 0s);
  }

  /* The segment grows out of the bar's floor rather than from its
     middle, so the bar looks pressed rather than inflated. */
  .book__seg { transition: filter 180ms ease, transform 180ms ease; }
  .book__seg:hover { transform: scaleY(1.35); transform-origin: bottom; }
}

/* Gold for knives and gloves, the site's legendary colour. Silver for
   weapons, blue for stickers, and quiet greys for the rest. Green and
   red stay for gains and risk. */
[data-cat="knives"]   { --seg: linear-gradient(90deg, #d99a1e, #ffd36b); }
[data-cat="weapons"]  { --seg: #c9d1de; }
[data-cat="stickers"] { --seg: #6aa2ff; }
[data-cat="graffiti"] { --seg: #7c8aa6; }
[data-cat="cases"]    { --seg: #56607a; }
[data-cat="other"]    { --seg: #3d424e; }

/* The change column inside the panel: no second box around it. */
.book .stats {
  gap: 8px;
  background: none;
  border: 0;
  border-radius: 0;
  overflow: visible;
}

/* A cell on the desk is a tile: raised off its panel, lit from above. */
.book .stat {
  padding: 14px 14px 13px;
  border: 1px solid var(--edge);
  border-radius: var(--r-tile);
  background: var(--surface-raised);
  box-shadow: var(--lift-tile);
}

.book .stats__empty { border-radius: 10px; }

.book .stats__locked {
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
}

.book .stats__locked-text,
.book .stats__locked-cta { grid-column: 1; grid-row: auto; }
.book .stats__locked-cta { justify-self: start; margin-top: 4px; }

.stats__tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin: 10px 0 8px;
  padding: 0;
  list-style: none;
}

/* Labels only. Dashed and blue-lit, the locked-teaser tone, with the
   period named and nothing in it that could be read as a figure. */
.stats__tile {
  padding: 12px 12px 11px;
  border: 1px dashed rgba(var(--glow-accent), 0.3);
  border-radius: 10px;
  background: rgba(var(--glow-accent), 0.04);
}

.stats__tile-label {
  display: block;
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.stats__tile-lock {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-family: var(--font-display);
  font-stretch: var(--display-label);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: #9fb8e6;
}

.book .section__note { margin: 12px 0 0; }

/* ---------- Holdings ---------- */

.holdings__summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-bottom: 1px solid rgba(245, 242, 236, 0.06);
}

.hsum {
  min-width: 0;
  padding: 16px 20px 15px;
}

.hsum + .hsum { border-left: 1px solid rgba(245, 242, 236, 0.06); }

.hsum__label {
  margin: 0;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}

.hsum__value {
  margin: 8px 0 0;
  font-size: clamp(19px, 2vw, 24px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--text);
}

.hsum__sub {
  margin: 6px 0 0;
  overflow: hidden;
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.holdings__filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 12px 20px;
  border-bottom: 1px solid rgba(245, 242, 236, 0.06);
}

.holdings__filters:empty { display: none; }

.hchip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px 5px;
  border: 1px solid var(--line-warm);
  border-radius: 999px;
  background: rgba(245, 242, 236, 0.02);
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  transition:
    color var(--dur-swift) var(--ease),
    background-color var(--dur-swift) var(--ease),
    border-color var(--dur-swift) var(--ease);
}

.hchip:hover { color: var(--text); border-color: #36363f; }

.hchip[aria-pressed="true"] {
  border-color: rgba(var(--glow-accent), 0.55);
  background: rgba(var(--glow-accent), 0.15);
  color: #e6eeff;
}

.hchip:focus-visible { outline: 2px solid rgba(var(--glow-accent), 0.7); outline-offset: 2px; }

.hchip__count { font-size: 10.5px; color: var(--faint); }
.hchip[aria-pressed="true"] .hchip__count { color: #9fb8e6; }

.hrow {
  position: relative;
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) minmax(120px, 210px) minmax(92px, auto);
  align-items: center;
  gap: 18px;
  padding: 12px 20px;
  border-top: 1px solid rgba(245, 242, 236, 0.045);
  cursor: pointer;
  transition: background-color var(--dur-swift) var(--ease);
}

.holdings__list > .hrow:first-child { border-top: 0; }

.hrow:hover { background: rgba(245, 242, 236, 0.028); }
.hrow:focus-visible { outline: 2px solid rgba(var(--glow-accent), 0.7); outline-offset: -2px; }
.hrow.is-sample { cursor: default; }
.hrow.is-sample:hover { background: transparent; }

/* The signal row's thumbnail well, tinted from underneath by rarity.
   Knives and gloves get the gold edge. */
.hrow__thumb {
  position: relative;
  --tint: rgba(245, 242, 236, 0);
  --tint-edge: rgba(245, 242, 236, 0);
  background:
    radial-gradient(90% 70% at 50% 100%, var(--tint), rgba(0, 0, 0, 0) 72%),
    var(--surface-sunken);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5), inset 0 -1px 0 var(--tint-edge);
}

.hrow__thumb[data-rarity="gold"]       { --tint: rgba(240, 180, 41, 0.3);  --tint-edge: rgba(255, 211, 107, 0.6); border-color: rgba(240, 180, 41, 0.4); }
.hrow__thumb[data-rarity="covert"]     { --tint: rgba(235, 75, 75, 0.16);  --tint-edge: rgba(235, 75, 75, 0.45); }
.hrow__thumb[data-rarity="classified"] { --tint: rgba(211, 44, 230, 0.15); --tint-edge: rgba(211, 44, 230, 0.42); }
.hrow__thumb[data-rarity="restricted"] { --tint: rgba(136, 71, 255, 0.15); --tint-edge: rgba(136, 71, 255, 0.42); }
.hrow__thumb[data-rarity="milspec"]    { --tint: rgba(75, 105, 255, 0.15); --tint-edge: rgba(75, 105, 255, 0.4); }
.hrow__thumb[data-rarity="industrial"] { --tint: rgba(94, 152, 217, 0.12); --tint-edge: rgba(94, 152, 217, 0.32); }
.hrow__thumb[data-rarity="consumer"]   { --tint: rgba(176, 195, 217, 0.08); --tint-edge: rgba(176, 195, 217, 0.22); }

.hrow__glyph {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 18px;
  color: var(--faint);
}

.hrow--gold .hrow__glyph { color: #d9b25a; }

.hrow__body { min-width: 0; }

.hrow__head {
  display: flex;
  align-items: center;
  gap: 6px 12px;
  min-width: 0;
}

.hrow__name {
  min-width: 0;
  overflow: hidden;
  font-size: 14.5px;
  color: var(--text);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.hrow--gold .hrow__name { color: #f3e4bd; }

/* ---------- THE FLEX BADGES ----------
   Small, earned, and capped at two a row by js/app.js so a good row
   reads as a good row rather than as a wall of stickers.

   NO ENTRANCE ANIMATION, DELIBERATELY. A keyframed pop cannot live in
   this half of the file: tools/test-dashboard-free.js slices from the
   ONE BOARD marker to the end and asserts the slice declares no
   keyframe rule and nothing that loops. A transition was the other
   route and it is worse here, because the class it would key off,
   motion.js's .is-revealing, is added and then removed again, so the
   badges would play on the way in and play again on the way out, on
   every price batch of a sweep. Static and correct beats animated and
   strobing. The hover lift below is a transition on a state only a
   pointer can enter, so it cannot fire on a re-render.

   (This comment deliberately avoids writing either banned word out in
   full: the assertion is a plain substring scan over the raw file, so
   a comment that names them trips it. Measured, not assumed.) */
.hrow__flex {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 5px;
  vertical-align: middle;
}

.hrow__flex-b {
  --bc: var(--muted);

  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px 2px 5px;
  border: 1px solid;
  border-color: color-mix(in srgb, var(--bc) 38%, transparent);
  border-radius: 4px;
  background: color-mix(in srgb, var(--bc) 13%, transparent);
  box-shadow: 0 0 14px -5px var(--bc);
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: var(--bc);
}

.hrow__flex-b svg { flex: none; }

@media (prefers-reduced-motion: no-preference) {
  .hrow__flex-b { transition: transform 160ms ease, box-shadow 160ms ease; }
  .hrow:hover .hrow__flex-b { transform: translateY(-1px); box-shadow: 0 0 16px -4px var(--bc); }
}

/* THE HUES ARE THE PAGE'S OWN. Gold is the rarity gold the thumbnail
   plates and the top of the ladder already wear, the two market badges
   are the exact green and red the index rows use for a rise and a fall,
   and the rest are lifted from the rarity scale rather than picked. */
.hrow__flex-b--bag     { --bc: var(--rar-gold); }
.hrow__flex-b--grail   { --bc: var(--rar-gold); }
.hrow__flex-b--ath     { --bc: var(--rar-gold); }
.hrow__flex-b--allin   { --bc: var(--rar-classified); }
.hrow__flex-b--cooking { --bc: #57d38c; }
.hrow__flex-b--rugged  { --bc: #f26569; }
.hrow__flex-b--clean   { --bc: var(--rar-milspec); }
.hrow__flex-b--warvet  { --bc: var(--r-legendary); }
.hrow__flex-b--dust    { --bc: var(--muted); }

/* The group handle's Dust badge sits after the label text. */
.hgroup__label .hrow__flex { margin-left: 8px; }

.hrow__pill { display: inline-flex; flex: 0 0 auto; }
.hrow__pill .signal__pill { padding: 3px 7px 2px; font-size: 9px; }

.hrow__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  margin-top: 6px;
}

.hrow__wear {
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--faint);
  white-space: nowrap;
}

.hrow__qty { font-size: 11px; color: var(--muted); }

/* The wear scale: Factory New at the bright end, Battle-Scarred at the
   dark, the bands at 0.07, 0.15, 0.38 and 0.45, and a tick where the
   float sits. */
.hwear {
  position: relative;
  flex: 0 0 auto;
  width: 60px;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    #b9cff5 0 7%, #8faad6 7% 15%, #6d7c96 15% 38%, #555c69 38% 45%, #3c4048 45% 100%);
}

.hwear i {
  position: absolute;
  top: -3px;
  width: 2px;
  height: 10px;
  margin-left: -1px;
  border-radius: 1px;
  background: #f5f2ec;
  box-shadow: 0 0 6px rgba(245, 242, 236, 0.55);
}

.hrow__share {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.hrow__share .signal__track { flex: 1 1 auto; }

.hrow__share .signal__fill {
  background: linear-gradient(90deg, rgba(var(--glow-accent), 0.55), #a8c4ff);
  opacity: 1;
}

.hrow--gold .hrow__share .signal__fill {
  background: linear-gradient(90deg, rgba(240, 180, 41, 0.65), #ffd36b);
}

/* ---------- THE BAR WEARS THE ITEM'S RARITY ----------
   The thumbnail plate beside it already carries the grade as a ring, so
   the row said it once and then drew a bar in the same blue as every
   other row. It is the same colour in both places now, which is the
   point: a column of bars becomes a column you can read the grade off
   without going back to the thumbnails.

   SPECIFICITY IS DELIBERATE. .hrow[data-rarity] .hrow__share
   .signal__fill is (0,4,0), which clears both the default fill above at
   (0,3,0) and the gold override at (0,3,0) without either of them
   having to move. A row whose rarityId came back null matches none of
   this and keeps the default fill untouched, which is the correct
   answer for an item Steam gave no grade we recognise.

   box-shadow IS SAFE HERE, unlike the book's fills: every one of these
   is a solid colour, never a gradient, so the declaration is valid. */
.hrow[data-rarity="gold"]       .hrow__share .signal__fill { --rar: var(--rar-gold);       --rar-soft: var(--rar-gold-soft); }
.hrow[data-rarity="covert"]     .hrow__share .signal__fill { --rar: var(--rar-covert);     --rar-soft: var(--rar-covert-soft); }
.hrow[data-rarity="classified"] .hrow__share .signal__fill { --rar: var(--rar-classified); --rar-soft: var(--rar-classified-soft); }
.hrow[data-rarity="restricted"] .hrow__share .signal__fill { --rar: var(--rar-restricted); --rar-soft: var(--rar-restricted-soft); }
.hrow[data-rarity="milspec"]    .hrow__share .signal__fill { --rar: var(--rar-milspec);    --rar-soft: var(--rar-milspec-soft); }
.hrow[data-rarity="industrial"] .hrow__share .signal__fill { --rar: var(--rar-industrial); --rar-soft: var(--rar-industrial-soft); }
.hrow[data-rarity="consumer"]   .hrow__share .signal__fill { --rar: var(--rar-consumer);   --rar-soft: var(--rar-consumer-soft); }

.hrow[data-rarity] .hrow__share .signal__fill {
  background: linear-gradient(90deg, var(--rar-soft), var(--rar));
  box-shadow: 0 0 12px -3px var(--rar);
}

.hrow__sharepct {
  min-width: 46px;
  font-size: 11.5px;
  color: var(--muted);
  text-align: right;
}

.hrow__figure { text-align: right; }

.hrow__value {
  font-size: clamp(15px, 1.5vw, 18px);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--text);
  white-space: nowrap;
}

/* ---------- The folded group ---------- */

.hgroup { border-top: 1px solid rgba(245, 242, 236, 0.045); }

.hgroup__toggle {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) auto 18px;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 12px 20px;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dur-swift) var(--ease);
}

.hgroup__toggle:hover { background: rgba(245, 242, 236, 0.028); }
.hgroup__toggle:focus-visible { outline: 2px solid rgba(var(--glow-accent), 0.7); outline-offset: -2px; }

.hgroup__stack {
  position: relative;
  width: 56px;
  height: 56px;
  border: 1px dashed var(--line-warm);
  border-radius: 8px;
  background: var(--surface-sunken);
}

.hgroup__stack img {
  position: absolute;
  width: 30px;
  height: 30px;
  object-fit: contain;
  opacity: 0.75;
}

.hgroup__stack img:nth-child(1) { left: 4px;  top: 5px; }
.hgroup__stack img:nth-child(2) { left: 20px; top: 20px; }
.hgroup__stack img:nth-child(3) { left: 11px; top: 13px; opacity: 0.9; }

.hgroup__text { min-width: 0; }
.hgroup__label { display: block; font-size: 14px; color: var(--text); }
.hgroup__hint { display: block; margin-top: 4px; font-size: 11.5px; color: var(--faint); }
.hgroup__value { font-size: 15px; color: var(--muted); white-space: nowrap; }

.hgroup__chev {
  justify-self: center;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--dur-swift) var(--ease);
}

.hgroup.is-open .hgroup__chev { transform: translateY(2px) rotate(-135deg); }

.hgroup__rows {
  background: rgba(0, 0, 0, 0.2);
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.45);
}

.hgroup__rows .hrow { padding-left: 32px; }
.hgroup__rows > .hrow:first-child { border-top: 0; }

.holdings__empty {
  margin: 0;
  padding: 22px 20px;
  font-size: 13px;
  color: var(--muted);
}

.holdings__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 20px;
  border-top: 1px solid rgba(245, 242, 236, 0.08);
  border-radius: 0 0 13px 13px;
  background: rgba(0, 0, 0, 0.18);
}

.holdings__foot:empty { display: none; }

.hfoot__label {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}

.hfoot__value { font-size: 17px; color: var(--text); }

.holdings + .table-note { margin-top: 12px; }

/* ---------- Tablet and phone ---------- */

@media (max-width: 720px) {
  .dash-strip__facts { width: 100%; }

  .book { grid-template-columns: minmax(0, 1fr); }
  .book__col + .book__col { border-left: 0; border-top: 1px solid rgba(245, 242, 236, 0.06); }

  .holdings__summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hsum { padding: 14px 14px 13px; }
  .hsum:nth-child(3) {
    grid-column: 1 / -1;
    border-left: 0;
    border-top: 1px solid rgba(245, 242, 236, 0.06);
  }

  /* Swipe the chips rather than stacking three rows of them. */
  .holdings__filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding: 10px 14px;
    scrollbar-width: none;
  }
  .holdings__filters::-webkit-scrollbar { display: none; }
  .hchip { flex: 0 0 auto; }

  /* Each row a compact card line: thumbnail, name and value across the
     top, the share of the total under them. */
  .hrow {
    grid-template-columns: 48px minmax(0, 1fr) auto;
    gap: 8px 12px;
    padding: 12px 14px;
  }
  .hrow__thumb { grid-column: 1; grid-row: 1 / span 2; width: 48px; height: 48px; }
  .hrow__body { grid-column: 2; grid-row: 1; }
  .hrow__figure { grid-column: 3; grid-row: 1; align-self: start; }
  .hrow__share { grid-column: 2 / -1; grid-row: 2; }
  .hrow__share:empty { display: none; }
  /* A pill beside a long name would squeeze the name to "★ ...", so on a
     phone the pill drops under it instead. */
  .hrow__head { flex-wrap: wrap; }
  .hrow__name { max-width: 100%; font-size: 14px; }
  .hrow__value { font-size: 15px; }
  .hgroup__rows .hrow { padding-left: 20px; }

  .hgroup__toggle {
    grid-template-columns: 48px minmax(0, 1fr) auto 14px;
    gap: 12px;
    padding: 12px 14px;
  }
  .hgroup__stack { width: 48px; height: 48px; }

  .holdings__foot { padding: 12px 14px; }
}

@media (max-width: 560px) {
  .dash-strip__fact { padding: 0 12px; }
  /* Wrapped cells need vertical room between the rows they now make,
     and the first cell of a wrapped row should sit flush to the left
     edge the same way the first cell of the first row does. */
  .dash-strip__facts { row-gap: 10px; }
  .dash-strip__sub { flex-basis: 100%; }
  .book__col { padding: 16px 14px 18px; }
  /* THE TRACK IS THE FIRST THING TO GO. Under 560px the four cells
     cannot all have a useful size, and of them the track is the one
     whose job the percentage beside it already does in words. The old
     10px first column was the bare swatch; the swatch lives inside the
     name cluster now, so that column would have been 10px holding the
     whole of it. */
  .book__part { grid-template-columns: minmax(0, 1fr) auto 46px; gap: 10px; padding: 12px 4px; }
  .book__track { display: none; }
  .stats__tile { padding: 10px 9px 9px; }
  .stats__tile-lock { font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .hchip,
  .hrow,
  .hgroup__toggle,
  .hgroup__chev,
  /* Appended at the end on purpose: tools/test-dashboard-free.js
     matches this rule by the first two selectors and their order. */
  .hrow__flex-b { transition: none; }
}

/* ---------- A saved copy, and trying again ----------
   Amber, the info tone: not an error, not live. It says the items are a
   real saved copy and how old, and the page carries on around it. */
.dash-stale {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  flex: 1 1 100%;
  gap: 8px 16px;
  padding: 10px 14px;
  border: 1px solid rgba(224, 165, 58, 0.36);
  border-radius: 10px;
  background: rgba(224, 165, 58, 0.06);
}

.dash-stale[hidden],
.dash-retryrow[hidden] { display: none; }

.dash-stale__text {
  flex: 1 1 320px;
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: #f0d9ad;
}

.dash-retryrow { flex: 1 1 100%; }

.dash-retry {
  display: inline-flex;
  align-items: center;
  padding: 7px 14px 6px;
  border: 1px solid var(--line-warm);
  border-radius: 999px;
  background: rgba(245, 242, 236, 0.04);
  font-family: var(--font-body);
  font-size: 12.5px;
  color: var(--text);
  cursor: pointer;
  transition:
    border-color var(--dur-swift) var(--ease),
    background-color var(--dur-swift) var(--ease);
}

.dash-retry:hover { border-color: #3a3a44; background: rgba(245, 242, 236, 0.08); }
.dash-retry:focus-visible { outline: 2px solid rgba(var(--glow-accent), 0.7); outline-offset: 2px; }

.dash-strip__live[data-state="stale"] {
  border-color: rgba(224, 165, 58, 0.4);
  background: rgba(224, 165, 58, 0.07);
  color: #f0d9ad;
}
.dash-strip__live[data-state="stale"] .dash-strip__dot { background: var(--info); }

@media (prefers-reduced-motion: reduce) {
  .dash-retry { transition: none; }
}

/* ============================================================
   THE LOCKED DOOR
   What a free user has earned and cannot read. In steel, not gold:
   gold on this page is kept for two moments, the status hero
   (css/flex.css) and the close, and gold on every lock meant gold
   meant nothing. The locked figures are there and blurred, and
   nothing flashes or loops.
   ============================================================ */

.lock-icon { flex: 0 0 auto; }

/* ---------- The teaser, in steel ---------- */

.signal--locked {
  border-color: rgba(168, 186, 214, 0.34);
  border-left-color: rgba(168, 186, 214, 0.7);
  background:
    linear-gradient(180deg, rgba(168, 186, 214, 0.08), rgba(168, 186, 214, 0) 62%),
    var(--surface);
}

/* ---------- Signals behind the lock ----------
   Placeholder rows, one per signal a member would read, up to three.
   The bars and the figure are drawn, not data: nothing is under them. */
.signals__locked {
  display: grid;
  gap: 8px;
  padding: 14px;
  border: 1px dashed rgba(168, 186, 214, 0.32);
  border-radius: 12px;
  background: rgba(168, 186, 214, 0.025);
}

.signals__locked-head {
  margin: 0 0 4px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

.signals__locked-head b { font-weight: 600; color: #c9d2e3; }

.signal--ghost {
  --signal-tone: rgba(168, 186, 214, 0.6);
  --signal-wash: rgba(168, 186, 214, 0.05);
  padding-top: 16px;
  padding-bottom: 16px;
  pointer-events: none;
  user-select: none;
}

.signal--ghost:hover { transform: none; }

.signal__thumb--ghost {
  background:
    radial-gradient(80% 70% at 50% 100%, rgba(168, 186, 214, 0.14), rgba(0, 0, 0, 0) 72%),
    var(--surface-sunken);
}

.signal__pill--locked { color: #c9d2e3; border-color: rgba(168, 186, 214, 0.55); }

.ghost__bar {
  display: block;
  height: 8px;
  border-radius: 4px;
  background: linear-gradient(90deg, rgba(245, 242, 236, 0.12), rgba(245, 242, 236, 0.04));
  filter: blur(1.5px);
}

.ghost__bar--name  { width: min(220px, 40%); }
.ghost__bar--text  { width: 72%; margin-top: 12px; }
.ghost__bar--short { width: 46%; margin-top: 7px; }

.ghost__fig {
  color: #a8c4ff;
  filter: blur(5px);
}

.signals__locked-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  margin-top: 4px;
  font-size: 12.5px;
  color: var(--faint);
}

/* ---------- Locked figures and chips ---------- */

.stats__tile {
  border-color: rgba(168, 186, 214, 0.32);
  background: rgba(168, 186, 214, 0.035);
}

.stats__tile-fig {
  display: block;
  margin-top: 8px;
  font-size: 19px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: #a8c4ff;
  filter: blur(4px);
  user-select: none;
}

.stats__tile-lock { color: #c9d2e3; }

.hchip--locked {
  border-color: rgba(168, 186, 214, 0.45);
  background: rgba(168, 186, 214, 0.08);
  color: #c9d2e3;
  text-decoration: none;
}

.hchip--locked:hover { border-color: rgba(168, 186, 214, 0.75); color: #e9edf5; }
.hchip--locked .hchip__count { color: #dce7ff; }

.vchart__lock {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: #c9d2e3;
}

.vchart__lock:hover { color: #e9edf5; }

@media (max-width: 720px) {
  .earned { border-radius: 16px; }
  .earned__tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .earned__tile { padding: 14px 12px 12px; }
  .earned__actions .cta { width: 100%; justify-content: center; }
}

@media (max-width: 480px) {
  .earned__value { font-size: 28px; }
  .earned__detail { font-size: 11px; }
}

@media (max-width: 340px) {
  .earned__tiles { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   THE CLOSER
   The landing page's two paid cards, styled by css/pricing.css exactly
   as they are there. This only places them: centred under a heading
   that picks up the earned panel's promise, two across on a wide
   screen, with the same static gold and blue light behind them. The
   landing's breathing glow and live dot only run inside its own
   section, so nothing here loops.
   ============================================================ */

main > .section.dash-membership {
  position: relative;
  padding: clamp(40px, 4.6vw, 68px) 0 clamp(48px, 5.2vw, 76px);
  text-align: center;
  scroll-margin-top: 96px;
}

.dash-membership::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(30% 50% at 62% 62%, rgba(240, 180, 41, 0.1), rgba(240, 180, 41, 0.03) 50%, rgba(240, 180, 41, 0) 76%),
    radial-gradient(24% 38% at 36% 62%, rgba(61, 127, 255, 0.07), rgba(61, 127, 255, 0) 72%);
}

.dash-membership > .container {
  position: relative;
  z-index: 1;
}

.dash-membership .close__head {
  margin-bottom: clamp(30px, 3.6vw, 50px);
  scroll-margin-top: 96px;
}

.dash-membership__title {
  margin: 12px 0 0;
  font-family: var(--font-display);
  font-stretch: var(--display-hero);
  font-weight: 700;
  font-size: clamp(32px, 4.4vw, 58px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  background: linear-gradient(180deg, #fff6dc 0%, #ffe3a3 45%, #f5c451 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.dash-membership__body {
  max-width: 56ch;
  margin: 14px auto 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--muted);
}

.dash-membership__earned {
  display: inline-flex;
  margin: 16px auto 0;
  padding: 8px 15px 7px;
  border: 1px solid rgba(240, 180, 41, 0.42);
  border-radius: 999px;
  background: rgba(240, 180, 41, 0.08);
  font-size: 13.5px;
  line-height: 1.45;
  color: #ffe3a3;
}

.dash-membership__earned[hidden] { display: none; }

/* Two cards, not the landing's pair and a hero: Monthly and a slightly
   wider Lifetime, side by side for as long as they fit. Below 821px the
   landing's own stack takes over: Lifetime first, then Monthly. */
@media (min-width: 1101px) {
  .tiers--paid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.14fr);
    max-width: 1000px;
  }
}

@media (max-width: 1100px) and (min-width: 821px) {
  .tiers--paid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.14fr);
    max-width: 940px;
    gap: 22px;
  }

  .tiers--paid .tier--lifetime {
    order: 0;
    justify-self: stretch;
    max-width: none;
  }
}

/* ============================================================
   SHARE YOUR PROFILE
   A member's way to their public profile, in the strip (renderShareSlot
   in js/app.js). A free account has it in the status hero instead.
   ============================================================ */
.dash-share {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  margin-top: 12px;
}

.dash-share[hidden] { display: none; }

.dash-share__view { font-size: 13px; color: #a8c4ff; text-decoration: none; }
.dash-share__view:hover { color: #dce7ff; }
.dash-share__btn.is-copied { --cta-ink: #7ef0b4; }

.dash-share__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ============================================================
   THE SIGN IN GATE (signed out only, showSignedOut in js/app.js)

   While it shows, the strip and the dashboard are not drawn behind it.
   The gate itself is drawn by css/dash-gate.css.
   ============================================================ */
.dash--gated .dash-strip,
.dash--gated main { display: none; }

.dash-gate[hidden] { display: none; }
