- Split app.py into routes + ascii_engine.py (stdlib-only procedural generation: figlet banners, creatures, patterns, scenes, classics) - API: /api/random?category=..., /api/categories, richer JSON payload - New frontend: templates/index.html + static/ (vanilla JS/CSS) - 4 themes (graphite/paper/phosphor/amber), localStorage persist, no-flash boot, prefers-color-scheme default - Scramble-resolve art transition, reduced-motion support - Responsive: rail collapses to chips <760px, art auto-fits width - Fix grid blowout on mobile (min-width: 0 on stage children) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
105 lines
4.9 KiB
HTML
105 lines
4.9 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>asciigen — procedural ascii art</title>
|
||
<script>
|
||
/* Theme boot: runs before first paint, no flash. */
|
||
(function () {
|
||
var t;
|
||
try { t = localStorage.getItem("asciigen.theme"); } catch (e) {}
|
||
if (!t) {
|
||
t = (window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches)
|
||
? "paper" : "graphite";
|
||
}
|
||
document.documentElement.dataset.theme = t;
|
||
})();
|
||
</script>
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||
</head>
|
||
<body>
|
||
<div class="scan" aria-hidden="true"></div>
|
||
<div class="drift" aria-hidden="true">
|
||
<span style="--x:6%; --d:34s; --dl:-4s; --s:1.1">{ }</span>
|
||
<span style="--x:16%; --d:46s; --dl:-20s; --s:0.8">#</span>
|
||
<span style="--x:28%; --d:38s; --dl:-11s; --s:1.4">>_</span>
|
||
<span style="--x:41%; --d:52s; --dl:-30s; --s:0.9">▚</span>
|
||
<span style="--x:55%; --d:40s; --dl:-7s; --s:1.2">::</span>
|
||
<span style="--x:67%; --d:47s; --dl:-25s; --s:0.7">%</span>
|
||
<span style="--x:78%; --d:36s; --dl:-15s; --s:1.3">[]</span>
|
||
<span style="--x:90%; --d:50s; --dl:-2s; --s:1.0">\\</span>
|
||
</div>
|
||
|
||
<div class="app">
|
||
<header class="topbar">
|
||
<div class="brand">
|
||
<span class="brand-mark" aria-hidden="true">▚</span>
|
||
<span class="brand-name">asciigen</span>
|
||
<span class="brand-tag">/dev/random</span>
|
||
</div>
|
||
<div class="themes" role="group" aria-label="Theme">
|
||
<button class="theme-btn" data-set-theme="graphite" aria-label="Graphite theme (dark)" title="graphite"><span class="sw"></span></button>
|
||
<button class="theme-btn" data-set-theme="paper" aria-label="Paper theme (light)" title="paper"><span class="sw"></span></button>
|
||
<button class="theme-btn" data-set-theme="phosphor" aria-label="Phosphor theme (green terminal)" title="phosphor"><span class="sw"></span></button>
|
||
<button class="theme-btn" data-set-theme="amber" aria-label="Amber theme (CRT)" title="amber"><span class="sw"></span></button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="stage">
|
||
<aside class="rail">
|
||
<p class="rail-label">channel</p>
|
||
<nav class="cats" aria-label="Art category">
|
||
{% for cat in categories %}
|
||
<button class="cat{% if cat == 'any' %} is-active{% endif %}" data-cat="{{ cat }}">
|
||
<span class="cat-cursor" aria-hidden="true">▸</span>{{ cat }}
|
||
</button>
|
||
{% endfor %}
|
||
</nav>
|
||
<div class="hints" aria-hidden="true">
|
||
<p><kbd>space</kbd> generate</p>
|
||
<p><kbd>c</kbd> copy</p>
|
||
<p><kbd>t</kbd> theme</p>
|
||
</div>
|
||
</aside>
|
||
|
||
<section class="canvas">
|
||
<div class="frame" id="frame">
|
||
<span class="corner c-tl" aria-hidden="true"></span>
|
||
<span class="corner c-tr" aria-hidden="true"></span>
|
||
<span class="corner c-bl" aria-hidden="true"></span>
|
||
<span class="corner c-br" aria-hidden="true"></span>
|
||
<div class="art-scroll" id="artScroll" role="img" aria-label="Generated ASCII artwork">
|
||
<pre id="art">{{ piece.art }}</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status" aria-live="polite">
|
||
<span class="st-title" id="stTitle">{{ piece.title }}</span>
|
||
<span class="st-sep" aria-hidden="true">·</span>
|
||
<span class="st-dims" id="stDims">{{ piece.cols }}×{{ piece.rows }}</span>
|
||
<span class="st-msg" id="stMsg">ready</span>
|
||
</div>
|
||
|
||
<div class="actions">
|
||
<button class="btn btn-primary" id="genBtn">
|
||
<span class="btn-glyph" aria-hidden="true">▶</span> generate
|
||
</button>
|
||
<button class="btn" id="copyBtn">
|
||
<span class="btn-glyph" aria-hidden="true">⧉</span> <span id="copyLabel">copy</span>
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="foot">
|
||
<span>procedural · stdlib python · zero deps</span>
|
||
<span class="foot-api">GET /api/random?category=…</span>
|
||
</footer>
|
||
</div>
|
||
|
||
<script>window.__initial = {{ piece | tojson }};</script>
|
||
<script src="{{ url_for('static', filename='js/app.js') }}" defer></script>
|
||
</body>
|
||
</html>
|