/* Playit — the CRT / Atari-TV ASSET CARD component (shared).
   One card design for every PICTURE and every SOUND, used by more than one place (the
   wizard's ASSETS step, the IDE's ASSETS tab and My Inventory), so it lives here rather than in any
   one page's file. Linked by base.html on every page, since My Inventory opens from the header. Its
   markup is the <template>s in html/partial/asset_cards.html; a page's JS clones those and drives the
   card through its states (a sound's screen — the PLAY chip + waveform — is sound_card.js).

   The card's whole state API is ONE attribute on .tv-screen:
     data-state="" | "done" | "failed" | "skipped"
   CSS reads it and decides what shows: the loading shimmer + skeleton, the finished picture, or the
   dimmed "FAIL" screen. A page that never fails an item simply never sets those values.

   Page-specific chrome (the wizard's progress bar, its cover-card tweak, the AI-prompt modal) stays in
   that page's own stylesheet — only the card itself is here. */

/* ---- the card's layout floor ----
   The narrowest a card should ever be laid out at: below this the footer (name + coin chip + the
   sparkle/trash buttons) starts to crowd. Every grid of these cards (the wizard's ASSETS step, the
   IDE's ASSETS tab) reads this one number, so the cards are the same size everywhere. */
:root { --tv-card-min: 212px; }

/* ---- a grid of cards (the wizard's ASSETS step, My Inventory) ----
   Two cards per row on phones (a pair fills a phone nicely). From tablet width up, as many cards
   per row as fit at the card's shared floor — four across the wizard's 940px manifest on desktop,
   so a card is a ~226px tile rather than a half-page slab. The breakpoint is the wizard's own
   small-screen line (wizard.css, 760px). */
.tv-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
@media (min-width: 760px) {
  .tv-grid { grid-template-columns: repeat(auto-fill, minmax(var(--tv-card-min), 1fr)); }
}

/* ---- the card frame ---- */
.tv-card {
  border: 3px solid var(--color-ink);
  background: linear-gradient(#221e40, #15122f);
  border-radius: 12px;
  padding: 7px 7px 6px;
  box-shadow: 0 4px 0 rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .06);
}
.tv-screen {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 2px solid #000;
  border-radius: 16px / 12px;
  box-shadow: inset 0 0 10px rgba(0, 0, 0, .6);
}
.tv-screen-art { background: #05040f; }

/* the glass: scanlines + a vignette, always on top of whatever the screen shows */
.tv-scan {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background-image: repeating-linear-gradient(0deg, rgba(0, 0, 0, .28) 0 2px, transparent 2px 4px);
}
.tv-vign {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(125% 115% at 50% 44%, transparent 62%, rgba(0, 0, 0, .4) 100%);
}

/* shimmer while an item is still rendering; hidden once it's done */
.tv-shimmer {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(100deg, #1c1a44 30%, #2e2a63 50%, #1c1a44 70%);
  background-size: 200% 100%;
  animation: tvShimmer 1.3s linear infinite, tvSpriteGlitch 1.7s steps(2) infinite;
}
.tv-screen[data-state="done"] .tv-shimmer { display: none; }
/* a finished picture invites a click to enlarge it (the page wires the lightbox) */
.tv-screen[data-state="done"] { cursor: zoom-in; }
/* the finished picture fills the screen (kept whole — sprites are transparent cut-outs) */
.tv-art {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
  animation: tvOn .5s ease both;
}
/* An animation SHEET's card shows the shared animated one-frame preview (sheet_preview.js)
   instead of the raw 4-up strip — a canvas, so the same contain-fit rules as .tv-art apply. */
.tv-screen .sheet-preview {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: contain;
  animation: tvOn .5s ease both;
}
/* The game COVER is a normal artwork card, but its picture is a full 16:9 illustration (not a pixel
   sprite) — fill the 4:3 card frame edge-to-edge and render it smooth instead of pixelated. Part of
   the card component (both the wizard and the IDE add .is-cover to the cover card). */
.tv-card.is-cover .tv-art { object-fit: cover; image-rendering: auto; }
/* "rendering…" tag while working; hidden when done */
.tv-rendering {
  position: absolute;
  top: 5px;
  right: 6px;
  z-index: 4;
  font-family: var(--font-body);
  font-size: 13px;
  color: #c7c1ff;
  animation: blink 1.1s step-end infinite;
}
.tv-screen[data-state="done"] .tv-rendering { display: none; }
/* a couldn't-make-it / out-of-coins item dims and keeps its reason label */
.tv-screen[data-state="failed"],
.tv-screen[data-state="skipped"] { filter: grayscale(.5) brightness(.75); }
.tv-screen[data-state="failed"] .tv-rendering,
.tv-screen[data-state="skipped"] .tv-rendering { color: var(--color-pink-light); animation: none; }
/* a settled-but-not-done card isn't rendering anymore — stop the loading shimmer + its glitch */
.tv-screen[data-state="failed"] .tv-shimmer,
.tv-screen[data-state="skipped"] .tv-shimmer { display: none; }

/* ---- an UPLOAD's progress ring (asset_library.setProgress) ----
   A ring on the screen's upper side that fills clockwise to --tv-progress (0-100, set from JS — the
   fill circle's pathLength is 100, so the number IS the dash length), with "NN%" inside. While it
   shows, the "rendering…" tag steps aside: the ring already says the card is working. At 100% the
   bytes have all arrived and the server is saving, so the ring becomes a spinning quarter-arc. */
.tv-progress {
  position: absolute;
  top: 14%;
  left: 50%;
  z-index: 4;
  width: 58px;
  height: 58px;
  transform: translateX(-50%);
  display: grid;
  place-items: center;
}
.tv-progress-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg); /* start filling from 12 o'clock */
}
.tv-progress-track { fill: rgba(11, 10, 36, .82); stroke: #3a3670; stroke-width: 5; }
.tv-progress-fill {
  fill: none;
  stroke: var(--color-cyan);
  stroke-width: 5;
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--tv-progress, 0));
  transition: stroke-dashoffset .2s linear;
}
.tv-progress-num {
  position: relative;
  font-family: var(--font-display);
  font-size: 9px;
  color: var(--color-text);
}
.tv-progress.is-saving .tv-progress-fill { stroke-dashoffset: 75; transition: none; }
.tv-progress.is-saving .tv-progress-ring { animation: tvProgressSpin .9s linear infinite; }
.tv-screen.is-uploading .tv-rendering { display: none; }
@keyframes tvProgressSpin {
  from { transform: rotate(-90deg); }
  to { transform: rotate(270deg); }
}

/* big arcade "FAIL" in the middle of a couldn't-make-it screen (hidden unless the card failed) */
.tv-fail {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: none;
  place-items: center;
  font-family: var(--font-display);
  font-size: 30px;
  color: var(--color-pink);
  letter-spacing: 3px;
  text-shadow: 3px 3px 0 #000, 0 0 14px rgba(255, 61, 154, .55);
  animation: tvFailGlitch 1.4s steps(2) infinite;
}
.tv-screen[data-state="failed"] .tv-fail { display: grid; }

/* the small action buttons beside the name: the trash (when the page lets the user wipe an unused
   asset), then the sparkle (a picture) or the PLAY chip (a sound) */
.tv-actions { flex: none; display: flex; align-items: center; gap: 6px; }
/* the trash — the sparkle's shape in the site's delete colour (pink face, ink glyph, pink ledge);
   the save-to-inventory chest is the same button in gold, and My Inventory's "add to game" in green */
.tv-trash,
.tv-keep,
.tv-add {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  cursor: pointer;
  color: var(--color-ink);
  background: var(--color-pink);
  border: 2px solid var(--color-ink);
  box-shadow: 0 3px 0 var(--color-pink-shadow);
  transition: transform .1s, box-shadow .1s;
}
.tv-trash:hover,
.tv-keep:hover,
.tv-add:hover { transform: translateY(-1px); }
.tv-trash svg,
.tv-keep svg,
.tv-add svg { width: 14px; height: 14px; display: block; }
.tv-keep { background: var(--color-gold); box-shadow: 0 3px 0 var(--color-gold-shadow); }
.tv-add { background: var(--color-green); box-shadow: 0 3px 0 var(--color-green-shadow); }

/* Each small card button sits in a tooltip slot (.ide-dock-slot) with its .ide-tip right after it.
   A slot whose button is hidden takes no room at all (else the row keeps an empty gap for it), and
   the tip opens UPWARD, right-aligned — the buttons hug the card's bottom-right edge. */
.tv-slot:has(> button[hidden]) { display: none; }
.tv-tip { left: auto; right: 0; width: 230px; }

/* per-card sparkle button that opens the AI-prompt modal
   (cyan face, ink sparkle + border, purple on hover). The page's JS decides when to show it. */
.tv-regen {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  cursor: pointer;
  color: var(--color-ink); /* the sparkle inherits this (currentColor) */
  background: var(--color-cyan);
  border: 2px solid var(--color-ink);
  box-shadow: 0 3px 0 var(--color-cyan-shadow);
  transition: transform .1s, background .1s, box-shadow .1s;
}
.tv-regen:hover { background: #9b6cff; box-shadow: 0 3px 0 #5b3fb0; transform: translateY(-1px); }
.tv-regen svg { width: 15px; height: 15px; display: block; }

/* the coin-cost badge, tucked into the bottom-left of the screen */
.tv-cost {
  position: absolute;
  left: 5px;
  bottom: 5px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 3px;
  background: rgba(11, 10, 36, .82);
  border: 1px solid #3a3670;
  padding: 1px 5px 1px 3px;
  border-radius: 4px;
}
.tv-cost-coin { display: inline-flex; transform: scale(.72); }

/* the "In the game" tag, tucked into the top-left of the screen — the coin badge's sibling chip.
   Shown by asset_library.js only where a page opted in (the IDE assets tab) and the game's code
   really loads the picture. */
.tv-used {
  position: absolute;
  left: 5px;
  top: 5px;
  z-index: 4;
  background: rgba(11, 10, 36, .82);
  border: 1px solid #3a3670;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--color-green);
  line-height: 1;
}
.tv-cost-num {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--color-gold);
  line-height: 1;
}

/* ---- the SOUND screen: a voice-bubble on the CRT — PLAY chip left, waveform filling the rest ---- */
.tv-screen-sound { background: #05040f; }
/* the PLAY/STOP chip sits under the screen (in .tv-actions) — the sparkle's twin: cyan face,
   ink glyph, cyan ledge; pink with a square while this card's sound is playing */
.tv-play {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  cursor: pointer;
  color: var(--color-ink); /* the glyph inherits this (currentColor) */
  background: var(--color-cyan);
  border: 2px solid var(--color-ink);
  box-shadow: 0 3px 0 var(--color-cyan-shadow);
  transition: transform .1s, background .1s, box-shadow .1s;
}
.tv-play:hover { transform: translateY(-1px); }
.tv-play svg { width: 14px; height: 14px; display: block; }
.tv-play-go, .tv-play-stop { display: grid; place-items: center; }
.tv-play-go svg { margin-left: 2px; } /* optical centre: a triangle sits a hair left */
.tv-play-stop { display: none; }
.tv-card.is-playing .tv-play-go { display: none; }
.tv-card.is-playing .tv-play-stop { display: grid; }
.tv-card.is-playing .tv-play {
  background: var(--color-pink);
  box-shadow: 0 3px 0 var(--color-pink-shadow);
}
/* the waveform: bars drawn by sound_card.js — unplayed in the dim line colour, played in cyan */
.tv-wave {
  position: absolute;
  left: 14px;
  top: 50%;
  z-index: 1;
  height: 46%;
  width: calc(100% - 28px);
  transform: translateY(-50%);
  display: block;
  pointer-events: none;
}
/* a sound this browser can't decode: one gentle line where the bars would be */
.tv-sound-note {
  position: absolute;
  left: 14px;
  right: 14px;
  text-align: center;
  top: 50%;
  z-index: 4;
  transform: translateY(-50%);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.1;
  color: var(--color-muted);
}
/* the bottom-right corner: a sound's format (MP3 / OGG) beside its length, or a model's "3D" —
   small pills in one row, the coin badge's opposite corner */
.tv-corner {
  position: absolute;
  right: 5px;
  bottom: 5px;
  z-index: 4;
  display: flex;
  gap: 4px;
}
.tv-ext,
.tv-dur,
.tv-3d {
  background: rgba(11, 10, 36, .82);
  border: 1px solid #3a3670;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--color-muted);
  line-height: 1;
}
.tv-dur { color: #c7c1ff; }
/* while rendering, a sound's pills wait for the finished file */
.tv-screen-sound:not([data-state="done"]) .tv-ext { display: none; }

/* ---- a 3D MODEL on the art card ---- */
/* the same corner pill a sound wears for its format — here it says "3D" (asset_library.js shows
   it on a model item), once the model is done */
.tv-screen-art:not([data-state="done"]) .tv-3d { display: none; }
/* the cube stand-in, centred on the dark screen, for a model with no preview picture — the
   global [hidden] rule keeps it away on every other card */
.tv-cube {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  color: var(--color-muted);
}
.tv-cube svg { width: 44%; height: 44%; }

/* ---- the name / description row under the screen ---- */
.tv-meta {
  margin-top: 6px;
  padding: 0 3px 1px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 6px;
}
/* the name/desc/skeleton column; min-width:0 lets its text ellipsis inside the flex row */
.tv-meta-text { flex: 1; min-width: 0; }
.tv-name {
  font-family: var(--font-body);
  font-size: 17px;
  color: #eae7ff;
  line-height: 1.05;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tv-desc {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--color-muted-2);
  line-height: 1.05;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* meta skeleton bars — shown instead of the name/desc while an artwork item is still rendering */
.tv-skel-bar {
  background: linear-gradient(100deg, #1c1a44 30%, #2e2a63 50%, #1c1a44 70%);
  background-size: 200% 100%;
  animation: tvShimmer 1.3s linear infinite;
}
.tv-skel-bar-1 { height: 12px; width: 70%; margin: 2px 0 5px; }
.tv-skel-bar-2 { height: 9px; width: 46%; }

/* while still rendering: hide the real name/desc, show the skeleton. Once SETTLED (done OR failed):
   the reverse — a failed card shows its real name and drops the shimmering skeleton (nothing about it
   should still look like it's loading). */
.tv-meta .tv-name, .tv-meta .tv-desc { display: none; }
.tv-screen[data-state="done"] ~ .tv-meta .tv-name,
.tv-screen[data-state="failed"] ~ .tv-meta .tv-name,
.tv-screen[data-state="skipped"] ~ .tv-meta .tv-name,
.tv-screen[data-state="done"] ~ .tv-meta .tv-desc,
.tv-screen[data-state="failed"] ~ .tv-meta .tv-desc,
.tv-screen[data-state="skipped"] ~ .tv-meta .tv-desc { display: block; }
.tv-screen[data-state="done"] ~ .tv-meta .tv-meta-skel,
.tv-screen[data-state="failed"] ~ .tv-meta .tv-meta-skel,
.tv-screen[data-state="skipped"] ~ .tv-meta .tv-meta-skel { display: none; }

/* ---- keyframes (blink is global; these belong to the card) ---- */
@keyframes tvShimmer {
  from { background-position: -200% 0; }
  to { background-position: 200% 0; }
}
@keyframes tvSpriteGlitch {
  0%   { opacity: .85; filter: none; clip-path: inset(0 0 0 0); }
  15%  { opacity: .35; filter: hue-rotate(120deg) saturate(3); clip-path: inset(30% 0 24% 0); }
  30%  { opacity: .9;  filter: none; clip-path: inset(0 0 0 0); }
  45%  { opacity: .25; filter: hue-rotate(-140deg) saturate(2); clip-path: inset(8% 0 62% 0); }
  60%  { opacity: .85; filter: none; }
  75%  { opacity: .3;  filter: hue-rotate(80deg); clip-path: inset(56% 0 14% 0); }
  100% { opacity: .7;  filter: none; clip-path: inset(0 0 0 0); }
}
@keyframes tvOn {
  0% { transform: scaleY(.04); filter: brightness(3); opacity: 0; }
  60% { transform: scaleY(1); filter: brightness(1.4); opacity: 1; }
  100% { filter: brightness(1); }
}
/* the big "FAIL" jitters like a broken CRT — subtle, so it reads as arcade, not seizure-y */
@keyframes tvFailGlitch {
  0%   { transform: translate(0, 0); opacity: 1; }
  20%  { transform: translate(-2px, 1px); opacity: .8; }
  40%  { transform: translate(2px, -1px); opacity: 1; }
  60%  { transform: translate(-1px, -1px); opacity: .85; }
  100% { transform: translate(0, 0); opacity: 1; }
}

/* users who ask for less motion get a still card — the state still reads, nothing pulses */
@media (prefers-reduced-motion: reduce) {
  .tv-shimmer,
  .tv-skel-bar,
  .tv-art,
  .tv-rendering,
  .tv-fail,
  .tv-progress.is-saving .tv-progress-ring { animation: none; }
}

/* ---- MENTION: the checkbox top-right, and the card it picks ----
   Ticking it (or tapping the card's frame — the screen still opens the enlarged view) points the
   build agent at this asset in the next chat message (ide_composer.js holds the list). A picked
   card glows cyan. At the limit (10 in one message) the unpicked boxes dim and stop answering. */
.tv-card[data-mentionable] { cursor: pointer; }
.tv-card[data-mentionable] .tv-screen { cursor: zoom-in; }
.tv-pick {
  position: absolute;
  top: 5px;
  right: 5px;
  z-index: 5;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  background: rgba(11, 10, 36, .82);
  border: 2px solid #6f6ab0;
  border-radius: 6px;
  color: transparent;
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
/* a bigger invisible hit area, so the small box is still an easy tap */
.tv-pick::after { content: ""; position: absolute; inset: -10px; }
.tv-pick:hover { border-color: var(--color-cyan); }
.tv-pick[aria-checked="true"] {
  background: var(--color-cyan);
  border-color: var(--color-cyan);
  color: var(--color-ink);
}
.tv-pick[aria-disabled="true"] { opacity: .35; cursor: not-allowed; }
.tv-card.is-mentioned {
  border-color: var(--color-cyan);
  box-shadow: 0 4px 0 rgba(0, 0, 0, .55), 0 0 0 2px rgba(36, 229, 255, .35), 0 0 16px rgba(36, 229, 255, .25);
}
