73 lines
53 KiB
HTML
73 lines
53 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Electromagnetic Teal Quiver — exploration</title>
|
||
|
|
<style>
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
body { background: #0a0a0f; color: #3a3a4a; font-family: ui-monospace, monospace; overflow: hidden; }
|
||
|
|
#grid { padding: 20px; font-size: 12px; line-height: 1.4; white-space: pre; }
|
||
|
|
.c { display: inline-block; transition: all 0.3s; }
|
||
|
|
.c.active { color: #fde68a; text-shadow: 0 0 6px rgba(253,230,138,0.5); }
|
||
|
|
.c.strong { color: #34d399; text-shadow: 0 0 10px rgba(52,211,153,0.6); font-weight: bold; }
|
||
|
|
.c.dead { color: #1a1a2a; }
|
||
|
|
#info { position: fixed; bottom: 10px; left: 20px; color: #3a3a4a; font-size: 11px; }
|
||
|
|
#controls { position: fixed; top: 10px; right: 20px; color: #666; font-size: 11px; }
|
||
|
|
#controls span { cursor: pointer; margin-left: 12px; color: #fde68a; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="grid"></div>
|
||
|
|
<div id="info">neurameba · physarum exploration</div>
|
||
|
|
<div id="controls"><span id="play-btn">play</span><span id="reset-btn">reset</span></div>
|
||
|
|
<script>
|
||
|
|
const text = "# API Reference\n\nmotd is API-first. Every feature is an endpoint. Build clients, bots, integrations — whatever you want.\n\n## Base URL\n\n```\nhttps://motd.social/api\n```\n\nLocal dev: `http://localhost:3006/api`\n\n## Response Format\n\nEvery endpoint returns the same envelope:\n\n**Success:**\n```json\n{ \"ok\": true, \"data\": { ... } }\n```\n\n**Error:**\n```json\n{ \"ok\": false, \"error\": \"Something went wrong.\" }\n```\n\nHTTP status codes are standard: 200 success, 400 bad request, 401 unauthorized, 404 not found, 409 conflict.\n\n## Authentication\n\nMost endpoints require authentication. Get a token by registering or logging in, then send it as a Bearer token or let the browser handle it via cookies.\n\n**Header auth (for scripts and clients):**\n```\nAuthorization: Bearer <token>\n```\n\n**Cookie auth (for the web client):**\nThe login endpoints set an `motd_session` httpOnly cookie automatically.\n\nSessions expire after 7 days. Use `/login-permanently` in the web client to persist across tabs (stored in localStorage instead of sessionStorage — the token is the same).\n\n---\n\n## Auth\n\n### POST /api/auth/register\n\nCreate a new account.\n\n**Auth:** none\n\n**Body:**\n```json\n{ \"username\": \"alice\", \"password\": \"hunter2pwd\" }\n```\n\n**Rules:**\n- Username: 3-20 characters, letters, numbers, underscore only\n- Password: minimum 8 characters\n\n**Response:**\n```json\n{\n \"ok\": true,\n \"data\": {\n \"token\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"user\": {\n \"username\": \"alice\",\n \"display_name\": \"alice\"\n }\n }\n}\n```\n\n**Errors:**\n- `400` — username or password missing, invalid format\n- `409` — username taken\n\n**Example:**\n```bash\ncurl -X POST http://localhost:3006/api/auth/register \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"alice\",\"password\":\"hunter2pwd\"}'\n```\n\n### POST /api/auth/login\n\nLog in to an existing account.\n\n**Auth:** none\n\n**Body:**\n```json\n{ \"username\": \"alice\", \"password\": \"hunter2pwd\" }\n```\n\n**Response:**\n```json\n{\n \"ok\": true,\n \"data\": {\n \"token\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"user\": {\n \"username\": \"alice\",\n \"display_name\": \"Alice\"\n }\n }\n}\n```\n\n**Errors:**\n- `400` — missing fields\n- `401` — invalid username or password\n\n**Example:**\n```bash\ncurl -X POST http://localhost:3006/api/auth/login \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"alice\",\"password\":\"hunter2pwd\"}'\n```\n\n### GET /api/auth/me\n\nGet the current authenticated user.\n\n**Auth:** required\n\n**Response:**\n```json\n{\n \"ok\": true,\n \"data\": {\n \"user\": {\n \"username\": \"alice\",\n \"display_name\": \"Alice\",\n \"bio\": \"building things\",\n \"created_at\": \"2026-03-15T10:30:00.000Z\"\n }\n }\n}\n```\n\n**Example:**\n```bash\ncurl http://localhost:3006/api/auth/me \\\n -H \"Authorization: Bearer YOUR_TOKEN\"\n```\n\n### POST /api/auth/logout\n\nEnd the current session.\n\n**Auth:** required\n\n**Response:**\n```json\n{ \"ok\": true, \"data\": { \"message\": \"Logged out.\" } }\n```\n\n**Example:**\n```bash\ncurl -X POST http://localhost:3006/api/auth/logout \\\n -H \"Authorization: Bearer YOUR_TOKEN\"\n```\n\n---\n\n## Profile\n\n### GET /api/profile/:username\n\nView a user's profile.\n\n**Auth:** none\n\n**Response:**\n```json\n{\n \"ok\": true,\n \"data\": {\n \"username\": \"alice\",\n \"display_name\": \"Alice\",\n \"bio\": \"building things\",\n \"created_at\": \"2026-03-15T10:30:00.000Z\",\n \"posts\": 42,\n \"avatar\": [\n \"████ ████\",\n \" ████████ \",\n \"████████████\",\n \" ████████ \",\n \"██ ████ ██\"\n ]\n }\n}\n```\n\n**Errors:**\n- `404` — user not found\n\n**Example:**\n```bash\ncurl http://localhost:3006/api/profile/alice\n```\n\n### PUT /api/profile\n\nUpdate your display
|
||
|
|
const passes = [{"t":0,"r":933,"c":2,"a":"hold","s":0.2679057573205296,"ps":9,"e":100.79324605856424,"pr":1.1},{"t":0,"r":1068,"c":36,"a":"extend","s":0.42489483628409375,"ps":9,"e":71.43441108319092,"pr":1.1},{"t":0,"r":167,"c":0,"a":"died","s":0,"ps":8,"e":100,"pr":1},{"t":0,"r":796,"c":15,"a":"extend","s":0.37931071527731497,"ps":9,"e":71.17914000555297,"pr":1.1},{"t":0,"r":42,"c":0,"a":"extend","s":0.32302273565569745,"ps":9,"e":70.8639273196719,"pr":1.1},{"t":1,"r":933,"c":2,"a":"hold","s":0.25824367063828835,"ps":9,"e":101.50919542367055,"pr":1.1},{"t":1,"r":1068,"c":36,"a":"extend","s":0.5779786606743086,"ps":9,"e":52.29576825800977,"pr":1.1},{"t":1,"r":796,"c":15,"a":"extend","s":0.4092885881965942,"ps":8,"e":51.277414097788,"pr":1.05},{"t":1,"r":42,"c":0,"a":"hold","s":0.29633833558563366,"ps":8,"e":36.01731700217849,"pr":1.05},{"t":1,"r":796,"c":16,"a":"hold","s":0.24783871072648092,"ps":5,"e":31.738055402477404,"pr":1.2000000000000002},{"t":2,"r":933,"c":2,"a":"retracted","s":0.25824367063828835,"ps":8,"e":102.37514478877685,"pr":1.05},{"t":2,"r":1068,"c":36,"a":"extend","s":0.5779786606743086,"ps":8,"e":39.00371828038297,"pr":1.05},{"t":2,"r":796,"c":15,"a":"extend","s":0.37931071527731497,"ps":7,"e":37.28332987400456,"pr":1},{"t":2,"r":42,"c":0,"a":"hold","s":0.32302273565569745,"ps":7,"e":37.55149888742407,"pr":1},{"t":2,"r":796,"c":16,"a":"hold","s":0.2495315755412578,"ps":4,"e":33.134308006807466,"pr":1.1500000000000001},{"t":2,"r":1068,"c":37,"a":"hold","s":0.20062126283830753,"ps":5,"e":23.267442213282077,"pr":1.2000000000000002},{"t":2,"r":44,"c":4,"a":"extend","s":0.5176965445743369,"ps":9,"e":27.166222551141228,"pr":1.1500000000000001},{"t":3,"r":1068,"c":36,"a":"extend","s":0.42489483628409375,"ps":8,"e":28.842013879459003,"pr":1.05},{"t":3,"r":796,"c":15,"a":"hold","s":0.34350194395433276,"ps":6,"e":39.13134542563922,"pr":0.95},{"t":3,"r":42,"c":0,"a":"retracted","s":0.2984114068934646,"ps":7,"e":19.444395071285893,"pr":1},{"t":3,"r":796,"c":16,"a":"retracted","s":0.24783871072648092,"ps":4,"e":34.51701769261931,"pr":1.1},{"t":3,"r":1068,"c":37,"a":"hold","s":0.2152219174878237,"ps":4,"e":24.389217553184668,"pr":1.1500000000000001},{"t":3,"r":44,"c":4,"a":"extend","s":0.5241575429024936,"ps":8,"e":21.111638026052823,"pr":1.1},{"t":3,"r":1068,"c":35,"a":"hold","s":0.1824731850379719,"ps":5,"e":17.425664743325047,"pr":1.1500000000000001},{"t":3,"r":44,"c":5,"a":"hold","s":0.27996254254417446,"ps":5,"e":13.132367147985352,"pr":1.2500000000000002},{"t":4,"r":1068,"c":36,"a":"extend","s":0.42489483628409375,"ps":7,"e":21.833820798812226,"pr":1},{"t":4,"r":796,"c":15,"a":"hold","s":0.34115086789809645,"ps":5,"e":41.11055236882399,"pr":0.8999999999999999},{"t":4,"r":1068,"c":37,"a":"retracted","s":0.20062126283830753,"ps":4,"e":25.39418765589113,"pr":1.1},{"t":4,"r":44,"c":4,"a":"extend","s":0.5176965445743369,"ps":7,"e":16.94224726785326,"pr":1.05},{"t":4,"r":1068,"c":35,"a":"hold","s":0.18617985382671517,"ps":5,"e":18.16510357393877,"pr":1.1500000000000001},{"t":4,"r":44,"c":5,"a":"hold","s":0.276578224317989,"ps":4,"e":14.744992942529265,"pr":1.2000000000000002},{"t":4,"r":44,"c":3,"a":"hold","s":0.14000033913783452,"ps":8,"e":19.36439778438857,"pr":1.1},{"t":5,"r":1068,"c":36,"a":"extend","s":0.401124774909063,"ps":7,"e":16.79497329865931,"pr":1},{"t":5,"r":796,"c":15,"a":"retracted","s":0.2928320546289322,"ps":5,"e":42.703208805855446,"pr":0.8999999999999999},{"t":5,"r":44,"c":4,"a":"extend","s":0.545010766015573,"ps":7,"e":27.73171182625649,"pr":1},{"t":5,"r":1068,"c":35,"a":"retracted","s":0.18617985382671517,"ps":4,"e":19.05454240455249,"pr":1.1},{"t":5,"r":44,"c":5,"a":"retracted","s":0.268800053888311,"ps":4,"e":16.295393373635754,"pr":1.1500000000000001},{"t":6,"r":1068,"c":36,"a":"extend","s":0.401124774909063,"ps":6,"e":13.37278004855227,"pr":0.95},{"t":6,"r":44,"c":4,"a":"extend","s":0.4933595836456635,"ps":7,"e":21.44001194679526,"pr":1},{"t":7,"r":1068,"c":36,"a":"hold","s":0.3474974071802458,"ps":6,"e":15.252759305994237,"pr":0.95},{"t":7,"r":44,"c":4,"a":"extend","s":0.4933595836456635,
|
||
|
|
const lines = text.split('\n');
|
||
|
|
const gridEl = document.getElementById('grid');
|
||
|
|
const charEls = [];
|
||
|
|
for (let r = 0; r < lines.length; r++) {
|
||
|
|
const row = [];
|
||
|
|
for (let c = 0; c < lines[r].length; c++) {
|
||
|
|
const s = document.createElement('span');
|
||
|
|
s.className = 'c';
|
||
|
|
s.textContent = lines[r][c];
|
||
|
|
row.push(s);
|
||
|
|
gridEl.appendChild(s);
|
||
|
|
}
|
||
|
|
charEls.push(row);
|
||
|
|
gridEl.appendChild(document.createTextNode('\n'));
|
||
|
|
}
|
||
|
|
let tick = -1, playing = false, iv;
|
||
|
|
function apply(t) {
|
||
|
|
for (const r of charEls) for (const e of r) e.className = 'c';
|
||
|
|
const active = new Map();
|
||
|
|
for (const p of passes) {
|
||
|
|
if (p.t > t) break;
|
||
|
|
const k = p.r+','+p.c;
|
||
|
|
if (p.a === 'died' || p.a === 'retracted') active.set(k, 'dead');
|
||
|
|
else if (p.ps > 16) active.set(k, 'strong');
|
||
|
|
else active.set(k, 'active');
|
||
|
|
}
|
||
|
|
for (const [k, cls] of active) {
|
||
|
|
const [r, c] = k.split(',').map(Number);
|
||
|
|
if (charEls[r]?.[c]) charEls[r][c].className = 'c ' + cls;
|
||
|
|
}
|
||
|
|
document.getElementById('info').textContent = 'tick ' + t + ' · ' + [...active.values()].filter(v=>v!=='dead').length + ' alive';
|
||
|
|
}
|
||
|
|
function play() {
|
||
|
|
if (playing) return;
|
||
|
|
playing = true;
|
||
|
|
document.getElementById('play-btn').textContent = 'pause';
|
||
|
|
const max = passes.length > 0 ? passes[passes.length-1].t : 0;
|
||
|
|
iv = setInterval(() => { tick++; if (tick > max) { pause(); return; } apply(tick); }, 900);
|
||
|
|
}
|
||
|
|
function pause() { playing = false; clearInterval(iv); document.getElementById('play-btn').textContent = 'play'; }
|
||
|
|
function reset() { pause(); tick = -1; for (const r of charEls) for (const e of r) e.className = 'c'; document.getElementById('info').textContent = 'neurameba'; }
|
||
|
|
document.getElementById('play-btn').addEventListener('click', () => playing ? pause() : play());
|
||
|
|
document.getElementById('reset-btn').addEventListener('click', reset);
|
||
|
|
setTimeout(play, 1000);
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|