pxg-tiny-site / index.html
Tarul's picture
Upload index.html with huggingface_hub
febc853 verified
Raw
History Blame Contribute Delete
26.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PXG-Tiny — 483k-param offline text→pixel-sprite model</title>
<meta name="description" content="PXG-Tiny turns plain English into 16×16 pixel-art game sprites fully offline. 483,040 parameters, 38 verified classes, pure NumPy runtime — CPU-only, no GPU, no network. By Chowdhury Tarul Ahsan.">
<style>
:root{
--bg:#0d0f1a; --bg2:#141830; --panel:#1a1f3a; --panel2:#20264a;
--ink:#e8eaf6; --dim:#9aa3c7; --faint:#5a6288;
--gold:#ffd24a; --green:#5df08a; --blue:#5db8ff; --purple:#b48cff;
--red:#ff6b6b; --line:#2a3060;
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
--sans:"Inter",-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.6;
background-image:radial-gradient(circle at 15% -10%,#1b2150 0%,transparent 45%),
radial-gradient(circle at 90% 0%,#231a4a 0%,transparent 40%);}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px}
a{color:var(--blue);text-decoration:none}
a:hover{text-decoration:underline}
code,pre,.mono{font-family:var(--mono)}
.px{image-rendering:pixelated;image-rendering:crisp-edges}
/* ---------- nav ---------- */
nav{position:sticky;top:0;z-index:50;background:rgba(13,15,26,.85);backdrop-filter:blur(10px);
border-bottom:1px solid var(--line)}
nav .wrap{display:flex;align-items:center;gap:22px;height:60px}
nav .logo{font-family:var(--mono);font-weight:700;font-size:17px;color:var(--gold);letter-spacing:.5px}
nav .logo span{color:var(--ink)}
nav .links{margin-left:auto;display:flex;gap:20px;font-size:14px}
nav .links a{color:var(--dim)} nav .links a:hover{color:var(--ink);text-decoration:none}
@media(max-width:760px){nav .links{display:none}}
/* ---------- hero ---------- */
header{padding:76px 0 60px;text-align:center}
.badge-row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:26px}
.badge{font-family:var(--mono);font-size:12px;padding:5px 12px;border:1px solid var(--line);
border-radius:999px;color:var(--dim);background:var(--bg2)}
.badge b{color:var(--gold);font-weight:600}
h1{font-family:var(--mono);font-size:clamp(34px,6vw,58px);line-height:1.08;letter-spacing:-1px}
h1 .t{color:var(--gold)}
.sub{max-width:760px;margin:20px auto 0;color:var(--dim);font-size:18px}
.sub b{color:var(--ink);font-weight:600}
.cta{margin-top:34px;display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
.btn{font-family:var(--mono);font-size:14px;padding:12px 22px;border-radius:10px;border:1px solid var(--line);
color:var(--ink);transition:.15s;display:inline-block}
.btn:hover{text-decoration:none;transform:translateY(-2px)}
.btn.primary{background:var(--gold);color:#1a1400;border-color:var(--gold);font-weight:700}
.btn.primary:hover{box-shadow:0 6px 24px rgba(255,210,74,.35)}
.btn.ghost{background:var(--bg2)}
.hero-strip{margin:44px auto 0;max-width:940px;background:var(--panel);border:1px solid var(--line);
border-radius:14px;padding:20px;display:flex;gap:6px;justify-content:center;flex-wrap:wrap}
.hero-strip img{width:64px;height:64px}
.hero-cap{font-family:var(--mono);font-size:12px;color:var(--faint);margin-top:10px}
/* ---------- sections ---------- */
section{padding:64px 0;border-top:1px solid var(--line)}
.kicker{font-family:var(--mono);font-size:12px;letter-spacing:2px;text-transform:uppercase;
color:var(--green);margin-bottom:10px}
h2{font-family:var(--mono);font-size:clamp(24px,3.4vw,34px);margin-bottom:14px;letter-spacing:-.5px}
.lead{color:var(--dim);max-width:820px;margin-bottom:34px}
.lead b{color:var(--ink)}
/* ---------- gallery grid ---------- */
.grid38{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:14px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;
display:flex;gap:14px;align-items:center;transition:.15s}
.card:hover{border-color:var(--gold);transform:translateY(-2px)}
.card img{width:72px;height:72px;flex:none;background:#0a0c16;border-radius:8px;padding:4px;border:1px solid var(--line)}
.card .cls{font-family:var(--mono);font-size:11px;color:var(--green);letter-spacing:1px;text-transform:uppercase}
.card .prompt{font-size:13px;color:var(--dim);margin-top:3px;line-height:1.4}
.verified{display:inline-block;margin-top:6px;font-family:var(--mono);font-size:10px;color:var(--green)}
/* ---------- attribute pairs ---------- */
.pair-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px}
.pair{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px;text-align:center}
.pair img{width:96px;height:96px;margin:6px auto}
.pair .prompt{font-family:var(--mono);font-size:12.5px;color:var(--gold);min-height:38px;display:flex;align-items:center;justify-content:center}
.pair img:hover{transform:scale(1.06)}
/* ---------- tables / misc ---------- */
.tbl{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--line);
border-radius:12px;overflow:hidden;font-size:14.5px}
.tbl th{background:var(--panel2);font-family:var(--mono);font-size:12px;letter-spacing:1px;
text-transform:uppercase;color:var(--dim);text-align:left;padding:12px 16px}
.tbl td{padding:12px 16px;border-top:1px solid var(--line);color:var(--dim)}
.tbl td:first-child{color:var(--ink);font-weight:600}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px;margin:30px 0}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px;text-align:center}
.stat .n{font-family:var(--mono);font-size:26px;font-weight:700;color:var(--gold)}
.stat .l{font-size:12px;color:var(--dim);margin-top:4px}
pre{background:#0a0c16;border:1px solid var(--line);border-radius:12px;padding:18px;overflow-x:auto;
font-size:13.5px;line-height:1.65;margin:16px 0}
pre .c{color:var(--faint)} pre .s{color:var(--green)} pre .k{color:var(--purple)}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:22px}
@media(max-width:860px){.two-col{grid-template-columns:1fr}}
.note{background:var(--panel);border-left:3px solid var(--gold);border-radius:0 10px 10px 0;
padding:14px 18px;font-size:14px;color:var(--dim);margin:18px 0}
.note b{color:var(--ink)}
ul.feat{list-style:none;display:grid;gap:12px;margin-top:10px}
ul.feat li{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:13px 16px;
color:var(--dim);font-size:14.5px}
ul.feat li b{color:var(--ink)}
ul.feat li .tick{color:var(--green);font-family:var(--mono);margin-right:8px}
.warn li .tick{color:var(--red)}
/* ---------- footer ---------- */
footer{border-top:1px solid var(--line);padding:44px 0 60px;color:var(--faint);font-size:14px}
footer .wrap{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
footer b{color:var(--dim)}
.platforms{display:flex;gap:12px;flex-wrap:wrap;margin-top:14px}
.chip{font-family:var(--mono);font-size:12px;border:1px solid var(--line);border-radius:8px;
padding:7px 12px;color:var(--dim);background:var(--bg2)}
.chip:hover{color:var(--ink);border-color:var(--gold);text-decoration:none}
</style>
</head>
<body>
<nav>
<div class="wrap">
<div class="logo">PXG<span>-Tiny</span></div>
<div class="links">
<a href="#gallery">38 classes</a>
<a href="#control">control</a>
<a href="#benchmarks">benchmarks</a>
<a href="#quickstart">quickstart</a>
<a href="#limits">limitations</a>
<a href="play.html" target="_blank">live demo ↗</a>
</div>
</div>
</nav>
<header>
<div class="wrap">
<div class="badge-row">
<span class="badge"><b>483,040</b> parameters</span>
<span class="badge"><b>38</b> verified classes</span>
<span class="badge"><b>CPU-only</b> · no GPU</span>
<span class="badge"><b>~1.8 MB</b> fp32 bundle</span>
<span class="badge"><b>0</b> network calls</span>
</div>
<h1>plain English →<br><span class="t">pixel sprites</span> — fully offline</h1>
<p class="sub">PXG-Tiny is a deliberately miniature text-to-sprite model: <b>483k parameters</b>,
a pure <b>NumPy</b> inference runtime, and a built-in verifier that checks every sprite against
your prompt before showing it. Runs on <b>any laptop CPU</b> in ~0.6&nbsp;s per sprite.</p>
<div class="cta">
<a class="btn primary" href="play.html" target="_blank">▶ Try it live (HF Space)</a>
<a class="btn ghost" href="https://github.com/tarulahsan/PXG-Tiny-AI" target="_blank">GitHub</a>
<a class="btn ghost" href="https://huggingface.co/Tarul/pxg-tiny" target="_blank">Hugging Face</a>
<a class="btn ghost" href="https://www.modelscope.ai/models/tarulahsan/pxg-tiny" target="_blank">ModelScope</a>
</div>
<div class="hero-strip" id="heroStrip"></div>
<div class="hero-cap">every sprite on this page was generated by the final v0.4 model — prompt &amp; seed recorded in <a href="https://github.com/tarulahsan/PXG-Tiny-AI">manifest.json</a></div>
</div>
</header>
<section id="gallery">
<div class="wrap">
<div class="kicker">full acceptance grid</div>
<h2>All 38 classes, verified 100%</h2>
<p class="lead">The final acceptance run tested <b>every class with two prompt styles × two seeds through the
exact shipped user path</b>: <b>152/152 passed (100%)</b>. A second, independent sweep with 39
never-seen phrasings scored <b>99.1%</b>. Each card below is a real accepted render —
hover any sprite to zoom.</p>
<div class="grid38" id="grid38"></div>
</div>
</section>
<section id="control">
<div class="wrap">
<div class="kicker">attribute grounding</div>
<h2>Prompt words actually change pixels</h2>
<p class="lead">The corpus is built from a procedural rasterizer with <b>counterfactual twin pairs</b>
identical geometry, one changed word — so the model learns word→pixel causality, not vibes.</p>
<div class="pair-grid" id="pairGrid"></div>
</div>
</section>
<section id="benchmarks">
<div class="wrap">
<div class="kicker">measured, not marketed</div>
<h2>Benchmarks</h2>
<div class="stats">
<div class="stat"><div class="n">100%</div><div class="l">38-class grid<br>152/152 prompts</div></div>
<div class="stat"><div class="n">99.1%</div><div class="l">fresh-phrasing sweep<br>116/117</div></div>
<div class="stat"><div class="n">31/31</div><div class="l">showcase gallery<br>shipped pipeline</div></div>
<div class="stat"><div class="n">98.89%</div><div class="l">val top-1<br>teacher-forced</div></div>
<div class="stat"><div class="n">100%</div><div class="l">INT8↔fp32 parity<br>max |Δlogit| 2e-05</div></div>
</div>
<table class="tbl">
<tr><th>Protocol</th><th>Result</th><th>What it measures</th></tr>
<tr><td>Full 38-class grid</td><td>152/152 = 100%</td><td>2 prompt styles × 2 seeds per class, exact shipped pipeline (verifier-guided retries on)</td></tr>
<tr><td>Fresh-phrasing sweep</td><td>116/117 = 99.1%</td><td>39 novel phrasings × 3 seeds, same pipeline — guards against overfitting to test prompts</td></tr>
<tr><td>Raw held-out (verifier off)</td><td>59.4% mean · 50% strict</td><td>46 adversarial rephrasings, single free-run sample — the honest backbone-only number</td></tr>
<tr><td>Parity gate</td><td>PASS</td><td>NumPy runtime vs trained torch model, bit-level teacher-forced comparison</td></tr>
</table>
<div class="note"><b>Why the raw number is low and the shipped number is 100%:</b> the 483k backbone knows
every class (perfect teacher-forced accuracy) but autoregressive sampling drifts on hard prompts.
The verifier-guided escalation — palette bias → spatial priors → canonical anchor — closes that gap
for the user path. Both numbers are published on purpose.</div>
</div>
</section>
<section id="quickstart">
<div class="wrap">
<div class="kicker">5-minute quickstart</div>
<h2>Run it on your laptop</h2>
<p class="lead">No GPU. No PyTorch at inference. No internet. Python ≥ 3.9 with <code>numpy</code> and <code>pillow</code>.</p>
<div class="two-col">
<div>
<pre><span class="c"># 1. get the bundle</span>
git clone https://github.com/tarulahsan/PXG-Tiny-AI
<span class="k">cd</span> PXG-Tiny-AI
pip install numpy pillow pytest
<span class="c"># 2. single sprite</span>
python3 cli.py <span class="s">"a golden sword"</span> -o sword.png
<span class="c"># 3. four variations on one sheet</span>
python3 cli.py <span class="s">"an iron chest"</span> --variations 4 \
--sheet chest_variants.png
<span class="c"># 4. batch from a prompt list</span>
python3 cli.py --sheet-from-prompts prompts.txt \
--outdir sprites/</pre>
</div>
<div>
<pre><span class="c"># Python API</span>
<span class="k">from</span> pxg_tiny.pipeline <span class="k">import</span> PXGPipeline
pipe = PXGPipeline()
grid, meta = pipe.generate_pixels(
<span class="s">"a ruby potion that glows"</span>, seed=7)
<span class="k">if</span> grid <span class="k">is</span> <span class="k">None</span>:
<span class="k">print</span>(meta[<span class="s">"message"</span>]) <span class="c"># clarify / refuse</span>
<span class="k">else</span>:
pipe.generate_png(<span class="s">"a ruby potion that glows"</span>,
<span class="s">"ruby_potion.png"</span>)
<span class="c"># ask-first gate (rule-based, offline)</span>
PXGPipeline.ask(<span class="s">"render a 3d photo"</span>)
<span class="c"># -> "i only make 16x16 pixel-art sprites..."</span></pre>
</div>
</div>
<ul class="feat">
<li><span class="tick"></span><b>Ask-first behavior</b> — asks clarifying questions on unknown objects or
conflicting words ("tiny huge sword"), refuses photos/3D/animation requests instead of hallucinating.</li>
<li><span class="tick"></span><b>Verifier-guided sampling</b> — every candidate sprite is checked against the
parsed prompt; failed seeds escalate through self-guidance (8 attempts) automatically.</li>
<li><span class="tick"></span><b>Deterministic seeds</b> — same prompt + seed ⇒ same sprite, forever.</li>
<li><span class="tick"></span><b>22 automated tests</b> — tokenizer, gates, NumPy kernels, end-to-end: <code>pytest tests/ -q</code></li>
</ul>
</div>
</section>
<section id="limits">
<div class="wrap">
<div class="kicker">honest limitations</div>
<h2>What it won't do</h2>
<ul class="feat warn">
<li><span class="tick"></span><b>16×16 only</b> — larger canvases, spritesheets and tilemaps are refused by
design; scaling to 32×32 would 4× the sequence length and require a full redesign.</li>
<li><span class="tick"></span><b>38-class vocabulary</b> — anything outside the trained families gets a
clarifying question, never a hallucinated blob.</li>
<li><span class="tick"></span><b>Binary transparency</b> — master-palette indices mean no soft edges/anti-aliasing.</li>
<li><span class="tick"></span><b>Impossible combos downgrade</b> — "emerald dagger" renders a default dagger
(the recipe set has no such training pair) instead of failing.</li>
<li><span class="tick"></span><b>No animation / multi-view</b> — roadmap items from the main PXG program.</li>
</ul>
</div>
</section>
<section id="how">
<div class="wrap">
<div class="kicker">under the hood</div>
<h2>Architecture in 30 seconds</h2>
<pre>caption ids (32 chars) ──► <span class="k">intent encoder</span> (bi-attention, fp32) ──► 8 prefix vectors
[8 prefixes | 256 visual tokens] ──► 4× pre-LN causal blocks (d=96, 4 heads, FFN 256)
└─► 32-way softmax over <span class="s">master-palette indices</span> (0 = transparent)
teacher-forced top-1 98.89% · KV-cached decode ~0.6 s/sprite on 2 CPU cores
training corpus: 97,005 fully procedural rows (CC0), counterfactual twins + paraphrase engine</pre>
<ul class="feat">
<li><span class="tick"></span><b>Built-in visual "tokenizer"</b> — sprites are grids of 32 master-palette indices,
so decoding is byte-exact and alpha transparency is exact by construction.</li>
<li><span class="tick"></span><b>Self-teaching recipe</b> — no external dataset: the procedural rasterizer makes
captions honest by construction; twins teach causality; paraphrases teach invariance.</li>
<li><span class="tick"></span><b>Verifiable</b> — quality gates calibrated so the GT corpus passes 100%; parity
harness proves the shipped NumPy runtime matches the torch training reference bit-for-bit.</li>
</ul>
</div>
</section>
<footer>
<div class="wrap">
<div>
<b>PXG-Tiny v0.4</b> — offline text → pixel-sprite model<br>
Created by <b>Chowdhury Tarul Ahsan</b> · <a href="mailto:tarulahsan@gmail.com">tarulahsan@gmail.com</a><br>
Code: MIT · Weights &amp; corpus: CC0 (self-generated)
</div>
<div>
<div class="platforms">
<a class="chip" href="https://github.com/tarulahsan/PXG-Tiny-AI" target="_blank">github ↗</a>
<a class="chip" href="https://huggingface.co/Tarul/pxg-tiny" target="_blank">hugging face ↗</a>
<a class="chip" href="play.html" target="_blank">live demo ↗</a>
<a class="chip" href="https://www.modelscope.ai/models/tarulahsan/pxg-tiny" target="_blank">modelscope ↗</a>
</div>
<div style="margin-top:14px;font-family:var(--mono);font-size:11px">
every sprite on this page: generated by PXG-Tiny v0.4, accepted by its own verifier
</div>
</div>
</div>
</footer>
<script>
const M = {
"showcase": [
{
"cls": "wizard",
"prompt": "a wizard with a purple hat",
"seed": 0,
"attempt": 0,
"file": "wizard.png",
"gate": "accept"
},
{
"cls": "knight",
"prompt": "a knight in iron armor",
"seed": 2026,
"attempt": 2,
"file": "knight.png",
"gate": "accept"
},
{
"cls": "archer",
"prompt": "an archer with a green hood",
"seed": 0,
"attempt": 0,
"file": "archer.png",
"gate": "accept"
},
{
"cls": "skeleton",
"prompt": "a skeleton warrior",
"seed": 6078,
"attempt": 6,
"file": "skeleton.png",
"gate": "accept"
},
{
"cls": "zombie",
"prompt": "an undead with green skin",
"seed": 0,
"attempt": 0,
"file": "zombie.png",
"gate": "accept"
},
{
"cls": "cat",
"prompt": "a small gray cat",
"seed": 0,
"attempt": 0,
"file": "cat.png",
"gate": "accept"
},
{
"cls": "dog",
"prompt": "a brown dog",
"seed": 0,
"attempt": 0,
"file": "dog.png",
"gate": "accept"
},
{
"cls": "bird",
"prompt": "a small blue bird",
"seed": 0,
"attempt": 0,
"file": "bird.png",
"gate": "accept"
},
{
"cls": "fish",
"prompt": "a golden fish",
"seed": 0,
"attempt": 0,
"file": "fish.png",
"gate": "accept"
},
{
"cls": "deer",
"prompt": "a brown deer with antlers",
"seed": 6078,
"attempt": 6,
"file": "deer.png",
"gate": "accept"
},
{
"cls": "mouse",
"prompt": "a little gray mouse",
"seed": 1013,
"attempt": 1,
"file": "mouse.png",
"gate": "accept"
},
{
"cls": "bat",
"prompt": "a purple bat with wings",
"seed": 0,
"attempt": 0,
"file": "bat.png",
"gate": "accept"
},
{
"cls": "sword",
"prompt": "a golden sword",
"seed": 0,
"attempt": 0,
"file": "sword.png",
"gate": "accept"
},
{
"cls": "dagger",
"prompt": "a sharp iron dagger",
"seed": 0,
"attempt": 0,
"file": "dagger.png",
"gate": "accept"
},
{
"cls": "shield",
"prompt": "a round iron shield",
"seed": 0,
"attempt": 0,
"file": "shield.png",
"gate": "accept"
},
{
"cls": "staff",
"prompt": "a magic staff with a ruby orb",
"seed": 2027,
"attempt": 2,
"file": "staff.png",
"gate": "accept"
},
{
"cls": "potion_round",
"prompt": "a red potion in a round bottle",
"seed": 0,
"attempt": 0,
"file": "potion_round.png",
"gate": "accept"
},
{
"cls": "potion_vial",
"prompt": "a slim vial of green potion",
"seed": 0,
"attempt": 0,
"file": "potion_vial.png",
"gate": "accept"
},
{
"cls": "gem",
"prompt": "a shiny blue gem",
"seed": 0,
"attempt": 0,
"file": "gem.png",
"gate": "accept"
},
{
"cls": "coin",
"prompt": "a gold coin",
"seed": 0,
"attempt": 0,
"file": "coin.png",
"gate": "accept"
},
{
"cls": "chest",
"prompt": "a wooden chest with gold bands",
"seed": 0,
"attempt": 0,
"file": "chest.png",
"gate": "accept"
},
{
"cls": "key",
"prompt": "an upright golden key",
"seed": 0,
"attempt": 0,
"file": "key.png",
"gate": "accept"
},
{
"cls": "house",
"prompt": "a cottage with a red roof",
"seed": 0,
"attempt": 0,
"file": "house.png",
"gate": "accept"
},
{
"cls": "barrel",
"prompt": "a wooden barrel",
"seed": 0,
"attempt": 0,
"file": "barrel.png",
"gate": "accept"
},
{
"cls": "crate",
"prompt": "a wooden crate",
"seed": 0,
"attempt": 0,
"file": "crate.png",
"gate": "accept"
},
{
"cls": "apple",
"prompt": "a red apple",
"seed": 0,
"attempt": 0,
"file": "apple.png",
"gate": "accept"
},
{
"cls": "bread",
"prompt": "a loaf of bread",
"seed": 0,
"attempt": 0,
"file": "bread.png",
"gate": "accept"
},
{
"cls": "fireball",
"prompt": "a large fireball",
"seed": 0,
"attempt": 0,
"file": "fireball.png",
"gate": "accept"
},
{
"cls": "oak_tree",
"prompt": "a green oak tree",
"seed": 0,
"attempt": 0,
"file": "oak_tree.png",
"gate": "accept"
},
{
"cls": "pine_tree",
"prompt": "a dark pine tree",
"seed": 0,
"attempt": 0,
"file": "pine_tree.png",
"gate": "accept"
},
{
"cls": "bush",
"prompt": "a leafy bush",
"seed": 0,
"attempt": 0,
"file": "bush.png",
"gate": "accept"
},
{
"cls": "rock",
"prompt": "a gray rock",
"seed": 1013,
"attempt": 1,
"file": "rock.png",
"gate": "accept"
},
{
"cls": "chair",
"prompt": "a wooden chair",
"seed": 0,
"attempt": 0,
"file": "chair.png",
"gate": "accept"
},
{
"cls": "table",
"prompt": "a wooden table",
"seed": 0,
"attempt": 0,
"file": "table.png",
"gate": "accept"
},
{
"cls": "stool",
"prompt": "a wooden stool",
"seed": 0,
"attempt": 0,
"file": "stool.png",
"gate": "accept"
},
{
"cls": "grass_tile",
"prompt": "a grass texture tile",
"seed": 0,
"attempt": 0,
"file": "grass_tile.png",
"gate": "accept"
},
{
"cls": "stone_tile",
"prompt": "a stone texture tile",
"seed": 0,
"attempt": 0,
"file": "stone_tile.png",
"gate": "accept"
},
{
"cls": "water_tile",
"prompt": "a water texture tile",
"seed": 0,
"attempt": 0,
"file": "water_tile.png",
"gate": "accept"
}
],
"attributes": [
{
"slug": "sword_gold",
"prompt": "a golden sword",
"seed": 0,
"file": "attr_sword_gold.png",
"gate": "accept"
},
{
"slug": "sword_iron",
"prompt": "a sword made of pure iron",
"seed": 0,
"file": "attr_sword_iron.png",
"gate": "accept"
},
{
"slug": "potion_red",
"prompt": "liquid potion of red brew",
"seed": 0,
"file": "attr_potion_red.png",
"gate": "accept"
},
{
"slug": "potion_green_vial",
"prompt": "a slim vial of green potion",
"seed": 0,
"file": "attr_potion_green_vial.png",
"gate": "accept"
},
{
"slug": "house_red_roof",
"prompt": "a cottage with a red roof",
"seed": 0,
"file": "attr_house_red_roof.png",
"gate": "accept"
},
{
"slug": "house_slate_roof",
"prompt": "a cottage with a slate roof",
"seed": 4052,
"file": "attr_house_slate_roof.png",
"gate": "accept"
},
{
"slug": "gem_ruby_glow",
"prompt": "a ruby gem that glows",
"seed": 0,
"file": "attr_gem_ruby_glow.png",
"gate": "accept"
},
{
"slug": "gem_emerald",
"prompt": "an emerald gem",
"seed": 0,
"file": "attr_gem_emerald.png",
"gate": "accept"
},
{
"slug": "rock_mossy",
"prompt": "a mossy boulder",
"seed": 3039,
"file": "attr_rock_mossy.png",
"gate": "accept"
},
{
"slug": "oak_autumn",
"prompt": "old oak with autumn leaves",
"seed": 0,
"file": "attr_oak_autumn.png",
"gate": "accept"
},
{
"slug": "bush_berries",
"prompt": "a bush that has berries",
"seed": 0,
"file": "attr_bush_berries.png",
"gate": "accept"
},
{
"slug": "coin_side",
"prompt": "a gold coin seen from the side",
"seed": 0,
"file": "attr_coin_side.png",
"gate": "accept"
}
]
};
// hero strip: 12 picky sprites
const hero = ["wizard","knight","archer","deer","bat","cat","sword","gem","potion_vial","house","oak_tree","fireball"];
document.getElementById("heroStrip").innerHTML = hero.map(c =>
`<img class="px" src="assets/sprites/${c}@8.png" alt="${c}" title="${c}">`).join("");
// 38-class grid
const pretty = c => c.replace(/_/g," ");
document.getElementById("grid38").innerHTML = M.showcase.map(s =>
`<div class="card"><img class="px" src="assets/sprites/${s.file.replace(".png","")}@8.png"
alt="${s.cls}" title="${s.prompt}">
<div><div class="cls">${pretty(s.cls)}</div>
<div class="prompt">"${s.prompt}"</div>
<span class="verified">✓ verified · seed ${s.seed}</span></div></div>`).join("");
// attribute pairs
document.getElementById("pairGrid").innerHTML = M.attributes.map(a =>
`<div class="pair"><div class="prompt">"${a.prompt}"</div>
<img class="px" src="assets/sprites/${a.file.replace(".png","")}@8.png" alt="${a.slug}"></div>`).join("");
</script>
</body>
</html>