feat: redesign UI with themes, animations, procedural ASCII engine
- 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>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user