diff --git a/cmd_chat/cardex.py b/cmd_chat/cardex.py
index d8b5ae6..8458ccb 100644
--- a/cmd_chat/cardex.py
+++ b/cmd_chat/cardex.py
@@ -221,6 +221,7 @@ class Card:
stats: dict = field(default_factory=dict) # HP/Attack/Defense/SpAtk/Speed/SpDef
subscores: dict = field(default_factory=dict)
shiny: bool = False # deterministic "holo pull"
+ status: str = "" # VM build status (done / in_progress / …)
flavor: str = ""
def to_dict(self) -> dict:
@@ -280,8 +281,10 @@ def card_from_entry(entry: dict, manifest: dict | None = None) -> Card:
flavor = (f"A {rarity.lower()} {ty}-type born from {purpose.rstrip('.')}. "
f"Base stat total {sum(stats.values())}.")
+ status = (entry.get("status") or "unknown").strip().lower()
return Card(label=label, name=name, dex=_dex(label), rarity=rarity, power=power,
- types=types, stats=stats, subscores=subs, shiny=shiny, flavor=flavor)
+ types=types, stats=stats, subscores=subs, shiny=shiny,
+ status=status, flavor=flavor)
# ── registry I/O + CLI ────────────────────────────────────────────────────────
diff --git a/cmd_chat/cardimg.py b/cmd_chat/cardimg.py
index 33a8be3..cdd7976 100644
--- a/cmd_chat/cardimg.py
+++ b/cmd_chat/cardimg.py
@@ -281,6 +281,10 @@ def render_card_svg(card: Card, art_data_uri: str | None = None) -> str:
f'No.{card.dex:03d} · {_esc(card.label)}{holo}')
p.append(f'PWR {card.power}')
+ # status pill on the rarity ribbon's left (done = green check, else amber)
+ st_done = card.status in ("done", "complete", "completed")
+ st_col, st_txt = (("#3fae5a", f"✓ {card.status}") if st_done
+ else ("#d8a23a", f"● {card.status or 'unknown'}"))
# type badges (top-right under PWR)
bx = W - PAD - 6
for ty in reversed(card.types):
@@ -307,6 +311,12 @@ def render_card_svg(card: Card, art_data_uri: str | None = None) -> str:
p.append(f''
f'{card.rarity.upper()}')
+ # status pill (top-left corner of the art window)
+ sw = 22 + len(st_txt) * 7
+ p.append(f'')
+ p.append(f'{_esc(st_txt)}')
# stat bars
y0 = ART_Y + ART_H + 26
bw = W - 2 * (PAD + 8)
@@ -368,6 +378,51 @@ def render_card(card: Card, out_dir: Path, backend: str, fmt: str) -> Path:
return svg_path
+# ── browser gallery ───────────────────────────────────────────────────────────
+RARITY_ORDER = {"Legendary": 0, "Epic": 1, "Rare": 2, "Uncommon": 3, "Common": 4}
+
+
+def build_gallery(cards: list[Card], out_dir: Path, backend: str) -> Path:
+ """Render every card as an inline SVG and lay them out in one index.html."""
+ out_dir.mkdir(parents=True, exist_ok=True)
+ cards = sorted(cards, key=lambda c: (RARITY_ORDER.get(c.rarity, 9), -c.power))
+ tally: dict[str, int] = {}
+ cells = []
+ for c in cards:
+ art_uri = None
+ if backend != "sigil":
+ try:
+ art_uri = fetch_art_data_uri(c, backend)
+ except Exception as e:
+ print(f" ! {c.label}: {backend} art failed ({e}); sigil", file=sys.stderr)
+ tally[c.rarity] = tally.get(c.rarity, 0) + 1
+ cells.append(f'
{render_card_svg(c, art_uri)}
')
+ print(f" rendered {c.rarity:<10} {c.label}")
+ spread = " · ".join(f"{n}× {r}" for r, n in
+ sorted(tally.items(), key=lambda kv: RARITY_ORDER.get(kv[0], 9)))
+ html = (
+ ''
+ ''
+ f'hack-house VM Cardex
'
+ f'
{len(cards)} saved VMs · {spread}
'
+ f'{"".join(cells)}
'
+ '')
+ idx = out_dir / "index.html"
+ idx.write_text(html)
+ return idx
+
+
# ── CLI ───────────────────────────────────────────────────────────────────────
def main(argv=None) -> int:
p = argparse.ArgumentParser(prog="hh-cardimg",
@@ -381,6 +436,8 @@ def main(argv=None) -> int:
p.add_argument("--out", default="cards", help="output directory")
p.add_argument("--prompt", action="store_true",
help="just print the AI-art prompt(s) and exit")
+ p.add_argument("--gallery", action="store_true",
+ help="emit a single browser index.html grid of all cards")
args = p.parse_args(argv)
entries = load_entries(Path(args.registry))
@@ -400,6 +457,10 @@ def main(argv=None) -> int:
return 0
out = Path(args.out)
+ if args.gallery:
+ idx = build_gallery(cards, out, args.art)
+ print(f"gallery → {idx}")
+ return 0
for c in cards:
path = render_card(c, out, args.art, args.format)
print(f"{c.rarity:<10} {c.label:<22} → {path}")