73 lines
483 KiB
HTML
73 lines
483 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Teal murmurs in monochrome — 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 = "# Plugin Development\n\nmotd is API-first. Every feature is an API endpoint. That means you can build anything on top of it — custom clients, bots, bridges, tools.\n\nPlugins run on your machine, not on the server. There's no plugin registry, no sandboxing, no approval process. You call the API. That's it.\n\n## Authentication\n\n1. Register or log in via the API\n2. Store the returned token\n3. Send it with every request as `Authorization: Bearer <token>`\n\n```bash\n# Get a token\nTOKEN=$(curl -s -X POST http://localhost:3006/api/auth/login \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"alice\",\"password\":\"hunter2pwd\"}' | jq -r '.data.token')\n\necho $TOKEN\n```\n\nTokens expire after 7 days. Re-authenticate when needed.\n\n## Example: Fetch Your Feed\n\n```bash\n#!/bin/bash\nTOKEN=\"your-token-here\"\n\ncurl -s http://localhost:3006/api/posts/feed \\\n -H \"Authorization: Bearer $TOKEN\" | jq '.data.posts[] | {user: .username, text: .content, id: .id}'\n```\n\nOutput:\n```json\n{ \"user\": \"alice\", \"text\": \"just shipped the wasm compiler [rust] [wasm]\", \"id\": \"a3kf9x\" }\n{ \"user\": \"bob\", \"text\": \"working on a new side project [coding]\", \"id\": \"c9xk4m\" }\n```\n\n## Example: Post From the Command Line\n\n```bash\n#!/bin/bash\nTOKEN=\"your-token-here\"\n\ncurl -s -X POST http://localhost:3006/api/posts \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"content\\\":\\\"$1\\\"}\" | jq '.data.id'\n```\n\nUsage:\n```bash\n./motd-post.sh \"automated post from my build pipeline [ci]\"\n```\n\n## Example: Upload and Post an Image\n\n```bash\n#!/bin/bash\nTOKEN=\"your-token-here\"\nIMAGE=\"$1\"\nCAPTION=\"$2\"\n\n# Upload the image\nMEDIA_ID=$(curl -s -X POST http://localhost:3006/api/media/upload \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -F \"file=@$IMAGE\" | jq -r '.data.media_id')\n\n# Post with attachment\ncurl -s -X POST http://localhost:3006/api/posts \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"content\\\":\\\"$CAPTION /attach $MEDIA_ID\\\"}\" | jq '.'\n```\n\nUsage:\n```bash\n./motd-upload.sh screenshot.png \"new feature landed [showcase]\"\n```\n\n## Example: GitHub Bridge (Concept)\n\nSync your GitHub activity to motd. Watch for push events, post summaries.\n\n```bash\n#!/bin/bash\nTOKEN=\"your-token-here\"\n\n# Poll GitHub events (simplified)\nLATEST=$(curl -s https://api.github.com/users/alice/events?per_page=1 | jq -r '.[0]')\nTYPE=$(echo $LATEST | jq -r '.type')\nREPO=$(echo $LATEST | jq -r '.repo.name')\n\nif [ \"$TYPE\" = \"PushEvent\" ]; then\n curl -s -X POST http://localhost:3006/api/posts \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"content\\\":\\\"pushed to $REPO [coding] [github]\\\"}\"\nfi\n```\n\n## Example: Bookmark Monitor\n\nWatch your bookmarked tags for new posts.\n\n```bash\n#!/bin/bash\nTOKEN=\"your-token-here\"\n\n# Get bookmarked tags\nTAGS=$(curl -s \"http://localhost:3006/api/bookmarks?type=tag\" \\\n -H \"Authorization: Bearer $TOKEN\" | jq -r '.data.bookmarks[].id')\n\nfor TAG in $TAGS; do\n echo \"--- [$TAG] ---\"\n curl -s \"http://localhost:3006/api/posts/search?q=$TAG\" \\\n -H \"Authorization: Bearer $TOKEN\" | jq '.data.posts[:3][] | \"\\(.username): \\(.content)\"'\ndone\n```\n\n## Media Converters\n\nmotd accepts PNG, MP3, and MP4 only. Build converter plugins for other formats:\n\n```bash\n# JPG to PNG before upload\nconvert photo.jpg photo.png && ./motd-upload.sh photo.png \"converted from jpg\"\n\n# WAV to MP3 before upload\nffmpeg -i recording.wav -q:a 2 recording.mp3\n```\n\n## Ideas\n\nBuilding something? Post about it with the [ideas] tag. Found a bug? Use [bugs]. The community is on motd itself.\n\nSee `/read api` for the complete endpoint reference.\n";
|
||
|
|
const passes = [{"t":0,"r":99,"c":0,"a":"extend","s":0.46708386175494293,"ps":9,"e":71.67066962582767,"pr":1.1},{"t":0,"r":124,"c":0,"a":"died","s":0,"ps":8,"e":100,"pr":1},{"t":0,"r":64,"c":12,"a":"extend","s":0.6989909167483066,"ps":9,"e":72.96934913379052,"pr":1.1},{"t":1,"r":99,"c":0,"a":"extend","s":0.44753299090375614,"ps":9,"e":51.73065348714041,"pr":1.1},{"t":1,"r":64,"c":12,"a":"extend","s":0.6728232555694473,"ps":8,"e":54.00635462484226,"pr":1.05},{"t":1,"r":98,"c":0,"a":"hold","s":0.24396839506523121,"ps":5,"e":31.917748428733713,"pr":1.2000000000000002},{"t":1,"r":64,"c":11,"a":"extend","s":0.4459791458330713,"ps":5,"e":23.863287956802353,"pr":1.2000000000000002},{"t":1,"r":65,"c":12,"a":"extend","s":0.34198772999584487,"ps":5,"e":23.280936028113885,"pr":1.2000000000000002},{"t":2,"r":99,"c":0,"a":"extend","s":0.43516935287929204,"ps":8,"e":37.80840581712232,"pr":1.05},{"t":2,"r":64,"c":12,"a":"extend","s":0.6414577779933043,"ps":7,"e":40.66161179415209,"pr":1},{"t":2,"r":98,"c":0,"a":"hold","s":0.25705155288971443,"ps":4,"e":33.37416085185143,"pr":1.1500000000000001},{"t":2,"r":64,"c":11,"a":"extend","s":0.44251448004202787,"ps":5,"e":18.657382657997,"pr":1.2000000000000002},{"t":2,"r":65,"c":12,"a":"extend","s":0.5168665703422747,"ps":5,"e":18.666108013596457,"pr":1.2000000000000002},{"t":2,"r":100,"c":0,"a":"extend","s":0.37477892031487486,"ps":5,"e":17.092957999905423,"pr":1.2000000000000002},{"t":2,"r":64,"c":13,"a":"hold","s":0.22614000465531672,"ps":5,"e":24.20470059074636,"pr":1.1500000000000001},{"t":2,"r":65,"c":11,"a":"extend","s":0.3970897474723184,"ps":5,"e":8.85768897288569,"pr":1.3000000000000003},{"t":2,"r":66,"c":12,"a":"extend","s":0.4272573307722448,"ps":5,"e":8.851921860758736,"pr":1.3000000000000003},{"t":2,"r":65,"c":11,"a":"hold","s":0.24706054767106753,"ps":5,"e":11.204028393417348,"pr":1.3000000000000003},{"t":3,"r":99,"c":0,"a":"extend","s":0.42925606054936877,"ps":8,"e":28.029718011062084,"pr":1.05},{"t":3,"r":64,"c":12,"a":"extend","s":0.6199518765886067,"ps":7,"e":31.199858764802656,"pr":1},{"t":3,"r":98,"c":0,"a":"retracted","s":0.23663441310933722,"ps":4,"e":34.667236156726126,"pr":1.1},{"t":3,"r":64,"c":11,"a":"extend","s":0.43044229969854,"ps":4,"e":15.050644738909723,"pr":1.1500000000000001},{"t":3,"r":65,"c":12,"a":"extend","s":0.5005017124131381,"ps":4,"e":15.44908519903109,"pr":1.1500000000000001},{"t":3,"r":100,"c":0,"a":"extend","s":0.45546404002094054,"ps":5,"e":13.99066922405106,"pr":1.2000000000000002},{"t":3,"r":64,"c":13,"a":"hold","s":0.22243516117473117,"ps":4,"e":25.384181880144208,"pr":1.1},{"t":3,"r":65,"c":11,"a":"extend","s":0.4428339219829226,"ps":5,"e":8.15525224412435,"pr":1.3000000000000003},{"t":3,"r":66,"c":12,"a":"hold","s":0.36903816747556767,"ps":5,"e":11.054227200563277,"pr":1.3000000000000003},{"t":3,"r":65,"c":11,"a":"hold","s":0.23958496026209022,"ps":4,"e":12.520708075514069,"pr":1.2500000000000002},{"t":3,"r":64,"c":10,"a":"extend","s":0.6243870266803977,"ps":5,"e":8.568782146809328,"pr":1.3000000000000003},{"t":3,"r":65,"c":13,"a":"hold","s":0.28916749652977086,"ps":5,"e":9.563100549493791,"pr":1.3000000000000003},{"t":3,"r":100,"c":1,"a":"hold","s":0.08950317595386906,"ps":5,"e":7.291578836161848,"pr":1.3000000000000003},{"t":3,"r":66,"c":11,"a":"extend","s":0.4428541576825956,"ps":5,"e":4.612289974888242,"pr":1.4000000000000004},{"t":3,"r":65,"c":10,"a":"extend","s":0.5464646117117743,"ps":5,"e":5.1925085174516425,"pr":1.4000000000000004},{"t":3,"r":67,"c":12,"a":"extend","s":0.39809939485879714,"ps":5,"e":4.359933169436885,"pr":1.4000000000000004},{"t":3,"r":66,"c":13,"a":"hold","s":0.3104050499571068,"ps":5,"e":5.526921197124885,"pr":1.4000000000000004},{"t":4,"r":99,"c":0,"a":"extend","s":0.417226145842782,"ps":7,"e":21.22226902446304,"pr":1},{"t":4,"r":64,"c":12,"a":"extend","s":0.5937231195855112,"ps":6,"e":24.53475060504072,"pr":0.95},{"t":4,"r":64,"c":11,"a":"extend","s":0.4106860707833183,"ps":4,"e":12.415293313623389,"pr":1.1500000000000001},{"t":4,"r":65,"c":12,"a":"hold","s":0.31409474537242454,"ps":4,"e":17.361843162010487,"pr":
|
||
|
|
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>
|