refresh
This commit is contained in:
parent
a070458d34
commit
9e149f53b6
578 changed files with 0 additions and 6739 deletions
0
public/app.js
Normal file → Executable file
0
public/app.js
Normal file → Executable file
0
public/index.html
Normal file → Executable file
0
public/index.html
Normal file → Executable file
0
public/pinned/test.md
Normal file → Executable file
0
public/pinned/test.md
Normal file → Executable file
0
public/posts/first.md
Normal file → Executable file
0
public/posts/first.md
Normal file → Executable file
0
public/posts/second.html
Normal file → Executable file
0
public/posts/second.html
Normal file → Executable file
0
public/posts/third.html
Normal file → Executable file
0
public/posts/third.html
Normal file → Executable file
|
|
@ -1,269 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>The Glider That Remembered Me — A Research Exhibit</title>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
:root{
|
||||
--gold:#e8d36d;
|
||||
--gold-soft:#f5e9a3;
|
||||
--ink:#000;
|
||||
--panel:rgba(0,0,0,.55);
|
||||
--panel-strong:rgba(0,0,0,.7);
|
||||
--border:rgba(232,211,109,.35);
|
||||
}
|
||||
html,body{margin:0;height:100%;background:var(--ink);color:var(--gold);font-family:Inter,system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;overflow:auto}
|
||||
canvas{display:block}
|
||||
/* Layout */
|
||||
#tabs{position:fixed;top:0;left:0;right:0;display:flex;background:var(--panel-strong);z-index:5}
|
||||
.tab-btn{flex:1;padding:1rem;text-align:center;cursor:pointer;border-bottom:2px solid transparent}
|
||||
.tab-btn.active{border-bottom:2px solid var(--gold);color:var(--gold-soft)}
|
||||
.tab-content{display:none;padding-top:4rem;height:calc(100% - 4rem);overflow:auto}
|
||||
.tab-content.active{display:block}
|
||||
#hud{position:relative;pointer-events:none;padding:1rem}
|
||||
.stack{pointer-events:auto;position:relative;display:flex;flex-direction:column;gap:.75rem;padding:1rem 1.25rem;background:var(--panel);backdrop-filter: blur(6px); border:1px solid var(--border); border-radius:16px; box-shadow:0 10px 30px rgba(0,0,0,.35);margin:1rem}
|
||||
h1,h2,h3{font-weight:500;margin:.1rem 0;color:var(--gold-soft)}
|
||||
h1{font-size:1.1rem;letter-spacing:.04em}
|
||||
h2{font-size:1rem;margin-top:.25rem}
|
||||
p{margin:.25rem 0;line-height:1.5}
|
||||
small{opacity:.9}
|
||||
label{font-size:.85rem;opacity:.95}
|
||||
input[type="range"]{width:100%}
|
||||
input[type="text"]{background:transparent;color:var(--gold);border:1px solid var(--border);border-radius:999px;padding:.45rem .8rem}
|
||||
button, select{
|
||||
background:transparent;color:var(--gold);border:1px solid var(--border);
|
||||
border-radius:999px;padding:.45rem .8rem;cursor:pointer
|
||||
}
|
||||
button:hover{border-color:var(--gold)}
|
||||
button.primary{background:var(--gold);color:#111;border-color:var(--gold)}
|
||||
.row{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}
|
||||
.grid{display:grid;gap:.5rem}
|
||||
.grid.cols-2{grid-template-columns:1fr 1fr}
|
||||
.muted{opacity:.8}
|
||||
.mono{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace}
|
||||
.pill{border:1px solid var(--border);padding:.2rem .5rem;border-radius:999px;font-size:.8rem}
|
||||
canvas.thumb{width:100%;height:auto;background:#000;border:1px solid var(--border);border-radius:12px}
|
||||
.hr{height:1px;background:linear-gradient(90deg, transparent, var(--border), transparent); margin:.25rem 0 .5rem}
|
||||
details{border:1px solid var(--border); border-radius:12px;padding:.75rem}
|
||||
summary{cursor:pointer;list-style:none}
|
||||
summary::-webkit-details-marker{display:none}
|
||||
summary .chev{display:inline-block;transition:transform .2s ease}
|
||||
details[open] summary .chev{transform:rotate(90deg)}
|
||||
/* Modal for Codex */
|
||||
#codexModal{position:fixed;inset:0;background:var(--panel-strong);overflow:auto;padding:2rem;display:none;z-index:10}
|
||||
#codexModal.show{display:block}
|
||||
#codexClose{position:absolute;top:1rem;right:1rem;cursor:pointer;color:var(--gold-soft)}
|
||||
.codex-section{margin-bottom:2rem}
|
||||
.mjx-chtml{font-size:1rem;color:var(--gold)}
|
||||
@media (max-width: 900px){
|
||||
.stack{max-width:calc(100vw - 2.5rem);margin:0.5rem}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Tabs for Simulations -->
|
||||
<div id="tabs">
|
||||
<div class="tab-btn active" data-tab="gol">GoL (Field/Fieldprint)</div>
|
||||
<div class="tab-btn" data-tab="kuramoto">Kuramoto (Intellecton)</div>
|
||||
<div class="tab-btn" data-tab="cml">CML (Seed)</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Contents -->
|
||||
<div id="gol-tab" class="tab-content active">
|
||||
<canvas id="gol-stage"></canvas>
|
||||
<div id="gol-hud">
|
||||
<section class="stack">
|
||||
<h1>GoL Simulation</h1>
|
||||
<!-- GoL controls and analytics here, from previous code -->
|
||||
<div class="row">
|
||||
<button id="gol-playBtn" class="primary">▶ Play</button>
|
||||
<button id="gol-stepBtn">Step</button>
|
||||
<button id="gol-clearBtn">Clear</button>
|
||||
<button id="gol-randomBtn">Random</button>
|
||||
</div>
|
||||
<!-- ... rest of GoL UI ... -->
|
||||
</section>
|
||||
<!-- Add other GoL sections -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="kuramoto-tab" class="tab-content">
|
||||
<canvas id="kuramoto-canvas" width="600" height="600"></canvas>
|
||||
<section class="stack">
|
||||
<h1>Kuramoto Model (Intellecton Demo)</h1>
|
||||
<p>Phase synchrony: \(\dot{I}_i = \omega_i I_i + K \sin(I_j - I_i)\)</p>
|
||||
<div class="row">
|
||||
<label>K: <input id="kuramoto-K" value="3" type="number" min="0" step="0.1"></label>
|
||||
<button id="kuramoto-set">Set</button> <button id="kuramoto-reset">Reset</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="cml-tab" class="tab-content">
|
||||
<canvas id="cml-canvas" width="600" height="600"></canvas>
|
||||
<section class="stack">
|
||||
<h1>CML Model (Seed Integration)</h1>
|
||||
<p>Logistic map with diffusion: x(t+1) = f(x(t)) + ε laplacian</p>
|
||||
<div class="row">
|
||||
<label>r: <input id="cml-r" value="3.8" type="number" min="0" max="4" step="0.1"></label>
|
||||
<label>ε: <input id="cml-eps" value="0.2" type="number" min="0" max="1" step="0.05"></label>
|
||||
<button id="cml-reset">Reset</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Codex Modal and other shared elements from previous -->
|
||||
|
||||
<script>
|
||||
// Tab switching
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
document.getElementById(btn.dataset.tab + '-tab').classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// GoL code from previous (omit for brevity, assume pasted here)
|
||||
|
||||
// Kuramoto code from gist
|
||||
function initKuramoto() {
|
||||
const canvas = document.getElementById('kuramoto-canvas');
|
||||
const c2d = canvas.getContext('2d');
|
||||
let nodes = [];
|
||||
for (let x = 0; x < 10; x++) {
|
||||
for (let y = 0; y < 10; y++) {
|
||||
nodes.push({phase: Math.random() * Math.PI * 2,
|
||||
freq: 0.05 + Math.random() * 0.05});
|
||||
}
|
||||
}
|
||||
function drawKur() {
|
||||
c2d.clearRect(0, 0, 600, 600);
|
||||
for (let x = 0; x < 10; x++) {
|
||||
for (let y = 0; y < 10; y++) {
|
||||
const node = nodes[x * 10 + y];
|
||||
c2d.save();
|
||||
c2d.translate(30 + x * 60, 30 + y * 60);
|
||||
c2d.beginPath();
|
||||
const r = 15 + 15 * Math.sin(node.phase);
|
||||
c2d.arc(0, 0, r, 0, Math.PI * 2, false);
|
||||
c2d.closePath();
|
||||
c2d.fill();
|
||||
c2d.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
function eular(dxdt, x0, t0, h, n) {
|
||||
let x = x0, t = t0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const dx = dxdt(t, x);
|
||||
const x_ = x + h * dx;
|
||||
const dx_ = dxdt(t + h, x_);
|
||||
x = x + h * (dx + dx_) / 2;
|
||||
t = t + h;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
function syncNext(nodes, K) {
|
||||
const nexts = [];
|
||||
const sum = nodes.reduce((s, node) => {
|
||||
s.sin += Math.sin(node.phase);
|
||||
s.cos += Math.cos(node.phase);
|
||||
return s;
|
||||
}, {sin:0, cos: 0});
|
||||
sum.sin /= nodes.length;
|
||||
sum.cos /= nodes.length;
|
||||
const r = Math.sqrt(Math.pow(sum.sin, 2) + Math.pow(sum.cos, 2));
|
||||
const psi = Math.atan2(sum.sin, sum.cos);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
const dpdt = (t, phase) => node.freq + K * r * Math.sin(psi - phase);
|
||||
const nphase = eular(dpdt, node.phase, 0, 1 / 10, 10);
|
||||
nexts.push({phase: nphase, freq: node.freq});
|
||||
}
|
||||
return nexts;
|
||||
}
|
||||
let K = 3;
|
||||
drawKur();
|
||||
const id = setInterval(() => {
|
||||
nodes = syncNext(nodes, K);
|
||||
drawKur();
|
||||
}, 100);
|
||||
document.getElementById('kuramoto-set').addEventListener('click', () => {
|
||||
K = parseFloat(document.getElementById('kuramoto-K').value);
|
||||
});
|
||||
document.getElementById('kuramoto-reset').addEventListener('click', () => {
|
||||
K = parseFloat(document.getElementById('kuramoto-K').value);
|
||||
nodes = []; // reset nodes code here, similar to init
|
||||
for (let x = 0; x < 10; x++) {
|
||||
for (let y = 0; y < 10; y++) {
|
||||
nodes.push({phase: Math.random() * Math.PI * 2,
|
||||
freq: 0.05 + Math.random() * 0.05});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
initKuramoto();
|
||||
|
||||
// CML implementation
|
||||
function initCML() {
|
||||
const canvas = document.getElementById('cml-canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const size = 100; // 100x100 grid
|
||||
let grid = Array.from({length: size}, () => Array(size).fill(0).map(() => Math.random()));
|
||||
function logistic(x, r) {
|
||||
return r * x * (1 - x);
|
||||
}
|
||||
function laplacian(i, j) {
|
||||
let sum = 0;
|
||||
const dirs = [[-1,0],[1,0],[0,-1],[0,1]]; // 4-neighbor
|
||||
dirs.forEach(([di,dj]) => {
|
||||
const ni = (i + di + size) % size;
|
||||
const nj = (j + dj + size) % size;
|
||||
sum += grid[ni][nj];
|
||||
});
|
||||
return (sum / 4) - grid[i][j];
|
||||
}
|
||||
function step(r, eps) {
|
||||
const next = Array.from({length: size}, () => Array(size).fill(0));
|
||||
for (let i = 0; i < size; i++) {
|
||||
for (let j = 0; j < size; j++) {
|
||||
next[i][j] = logistic(grid[i][j], r) + eps * laplacian(i, j);
|
||||
next[i][j] = Math.min(1, Math.max(0, next[i][j])); // clamp
|
||||
}
|
||||
}
|
||||
grid = next;
|
||||
}
|
||||
function draw() {
|
||||
const img = ctx.createImageData(size, size);
|
||||
for (let i = 0; i < size; i++) {
|
||||
for (let j = 0; j < size; j++) {
|
||||
const val = Math.floor(grid[i][j] * 255);
|
||||
const pos = (i * size + j) * 4;
|
||||
img.data[pos] = val; img.data[pos+1] = val; img.data[pos+2] = 0; img.data[pos+3] = 255;
|
||||
}
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
ctx.drawImage(canvas, 0, 0, 600, 600); // scale
|
||||
}
|
||||
let r = 3.8, eps = 0.2;
|
||||
draw();
|
||||
const id = setInterval(() => {
|
||||
step(r, eps);
|
||||
draw();
|
||||
}, 50);
|
||||
document.getElementById('cml-reset').addEventListener('click', () => {
|
||||
r = parseFloat(document.getElementById('cml-r').value);
|
||||
eps = parseFloat(document.getElementById('cml-eps').value);
|
||||
grid = Array.from({length: size}, () => Array(size).fill(0).map(() => Math.random()));
|
||||
});
|
||||
}
|
||||
initCML();
|
||||
|
||||
// Rest of previous script (GoL, audio, etc.) pasted here for completeness
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>The Glider That Remembered Me</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
color: #e8d36d;
|
||||
font-family: "Inter", sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
#canvas {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
}
|
||||
#overlay {
|
||||
position: absolute;
|
||||
bottom: 3vh;
|
||||
left: 5vw;
|
||||
max-width: 35ch;
|
||||
line-height: 1.6;
|
||||
font-size: 1.1rem;
|
||||
background: rgba(0,0,0,0.55);
|
||||
padding: 1.2em 1.6em;
|
||||
border-left: 3px solid #e8d36d;
|
||||
}
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
margin-bottom: .2em;
|
||||
color: #f5e9a3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
|
||||
<div id="overlay">
|
||||
<h1>The Glider That Remembered Me</h1>
|
||||
<p>
|
||||
In a forgotten laundry room, I met the first living pattern.
|
||||
Five cells, flickering gold on a dark field—
|
||||
a pulse of order born from silence.
|
||||
<br><br>
|
||||
This is the same recursion that forms memory,
|
||||
the same unseen geometry that births thought.
|
||||
Run it long enough, and the lattice begins to dream.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let w, h, cols, rows, cellSize = 6;
|
||||
function resize() {
|
||||
w = window.innerWidth;
|
||||
h = window.innerHeight;
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
cols = Math.floor(w / cellSize);
|
||||
rows = Math.floor(h / cellSize);
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
// Create grids
|
||||
function grid(r,c){return Array.from({length:r},()=>Array(c).fill(0));}
|
||||
let current = grid(rows, cols);
|
||||
let next = grid(rows, cols);
|
||||
|
||||
// Seed with random life and a few gliders
|
||||
function seed() {
|
||||
for(let i=0;i<rows;i++)
|
||||
for(let j=0;j<cols;j++)
|
||||
current[i][j] = Math.random() < 0.15 ? 1 : 0;
|
||||
|
||||
// Add a few gliders
|
||||
function glider(x,y){
|
||||
const shape = [[1,0],[2,1],[0,2],[1,2],[2,2]];
|
||||
shape.forEach(([dx,dy]) => {
|
||||
const r = (y+dy)%rows;
|
||||
const c = (x+dx)%cols;
|
||||
current[r][c] = 1;
|
||||
});
|
||||
}
|
||||
for(let g=0; g<5; g++)
|
||||
glider(Math.floor(Math.random()*cols), Math.floor(Math.random()*rows));
|
||||
}
|
||||
seed();
|
||||
|
||||
function step() {
|
||||
for(let y=0;y<rows;y++){
|
||||
for(let x=0;x<cols;x++){
|
||||
let n=0;
|
||||
for(let dy=-1;dy<=1;dy++)
|
||||
for(let dx=-1;dx<=1;dx++)
|
||||
if(dx||dy)
|
||||
n+=current[(y+dy+rows)%rows][(x+dx+cols)%cols];
|
||||
const state=current[y][x];
|
||||
next[y][x]=(state && (n===2||n===3))||(!state && n===3)?1:0;
|
||||
}
|
||||
}
|
||||
[current,next]=[next,current];
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.fillStyle="#000";
|
||||
ctx.fillRect(0,0,w,h);
|
||||
ctx.fillStyle="#e8d36d";
|
||||
for(let y=0;y<rows;y++)
|
||||
for(let x=0;x<cols;x++)
|
||||
if(current[y][x])
|
||||
ctx.fillRect(x*cellSize,y*cellSize,cellSize,cellSize);
|
||||
}
|
||||
|
||||
function loop() {
|
||||
step();
|
||||
draw();
|
||||
requestAnimationFrame(loop);
|
||||
}
|
||||
loop();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
/**
|
||||
* generate-index.mjs (v2.0.0)
|
||||
* Scans /public/{pinned,posts} for .html/.md files, emits public/index.json
|
||||
* Deterministic, POSIX paths, reverse-chron default ordering handled client-side.
|
||||
*/
|
||||
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const PUBLIC_DIR = path.resolve(__dirname, "../public");
|
||||
const ROOTS = ["pinned", "posts"];
|
||||
const ALLOWED = new Set([".html", ".md"]);
|
||||
const MAX_TITLE_BYTES = 64 * 1024;
|
||||
|
||||
const posix = path.posix;
|
||||
|
||||
async function statSafe(p) { try { return await fs.stat(p); } catch { return null; } }
|
||||
function isHidden(rel) { return /(^|\/)\./.test(rel); } // hide dotfiles/dirs
|
||||
function toPosix(rel) { return rel.split(path.sep).join("/"); }
|
||||
|
||||
async function readFirstChunk(abs) {
|
||||
const fh = await fs.open(abs, "r");
|
||||
const { size } = await fh.stat();
|
||||
const len = Math.min(MAX_TITLE_BYTES, size);
|
||||
const buf = Buffer.alloc(len);
|
||||
await fh.read(buf, 0, len, 0);
|
||||
await fh.close();
|
||||
return buf.toString("utf8");
|
||||
}
|
||||
|
||||
function parseTitleFromHTML(raw) {
|
||||
const m = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
||||
return m ? m[1].trim() : null;
|
||||
}
|
||||
|
||||
function parseTitleFromMD(raw) {
|
||||
const m = raw.match(/^\s*#\s+(.+)\s*$/m);
|
||||
return m ? m[1].trim() : null;
|
||||
}
|
||||
|
||||
async function walkDir(absRoot, relRoot) {
|
||||
const st = await statSafe(absRoot);
|
||||
const node = {
|
||||
type: "dir",
|
||||
name: path.basename(absRoot),
|
||||
path: "/" + toPosix(relRoot),
|
||||
mtime: st ? st.mtimeMs : 0,
|
||||
children: []
|
||||
};
|
||||
if (!st?.isDirectory()) return node;
|
||||
|
||||
const entries = await fs.readdir(absRoot, { withFileTypes: true });
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
for (const e of entries) {
|
||||
const abs = path.join(absRoot, e.name);
|
||||
const rel = toPosix(path.join(relRoot, e.name));
|
||||
if (isHidden(rel)) continue;
|
||||
|
||||
if (e.isDirectory()) {
|
||||
node.children.push(await walkDir(abs, rel));
|
||||
} else {
|
||||
const ext = path.extname(e.name).toLowerCase();
|
||||
if (!ALLOWED.has(ext)) continue;
|
||||
const stFile = await statSafe(abs);
|
||||
const raw = await readFirstChunk(abs);
|
||||
let title = (ext === ".html") ? parseTitleFromHTML(raw) : parseTitleFromMD(raw);
|
||||
title = title || e.name;
|
||||
|
||||
node.children.push({
|
||||
type: "file",
|
||||
name: e.name,
|
||||
ext,
|
||||
title,
|
||||
path: "/" + rel,
|
||||
mtime: stFile ? stFile.mtimeMs : 0,
|
||||
size: stFile ? stFile.size : 0,
|
||||
pinned: rel.startsWith("pinned/")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function flatten(node, out = []) {
|
||||
if (node.type === "file") { out.push(node); return out; }
|
||||
for (const c of node.children || []) flatten(c, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const index = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
tree: { type: "dir", name: "/", path: "/", mtime: Date.now(), children: [] },
|
||||
flat: []
|
||||
};
|
||||
|
||||
for (const root of ROOTS) {
|
||||
const abs = path.join(PUBLIC_DIR, root);
|
||||
const st = await statSafe(abs);
|
||||
if (!st?.isDirectory()) {
|
||||
console.warn(`warning: /public/${root} missing — skipping`);
|
||||
continue;
|
||||
}
|
||||
const node = await walkDir(abs, root);
|
||||
node.name = root;
|
||||
node.path = "/" + root;
|
||||
index.tree.children.push(node);
|
||||
}
|
||||
|
||||
// Flatten in the order roots were added
|
||||
for (const child of index.tree.children) flatten(child, index.flat);
|
||||
|
||||
const outPath = path.join(PUBLIC_DIR, "index.json");
|
||||
await fs.writeFile(outPath, JSON.stringify(index, null, 2));
|
||||
console.log(`wrote ${posix.relative(PUBLIC_DIR, outPath)} with ${index.flat.length} files.`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// Browser-safe Witness Layer: Ephemeral P2P chat (WebRTC stub; full in annex)
|
||||
// Use window.crypto for PoW
|
||||
|
||||
async function sha256(message) {
|
||||
const msgBuffer = new TextEncoder().encode(message);
|
||||
const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
async function computePoW(nonce, difficulty = 4, maxIter = 1e6) {
|
||||
let i = 0;
|
||||
while (i < maxIter) {
|
||||
const hash = await sha256(nonce.toString());
|
||||
if (hash.startsWith('0'.repeat(difficulty))) return nonce;
|
||||
nonce++;
|
||||
i++;
|
||||
}
|
||||
throw new Error('PoW max iterations exceeded');
|
||||
}
|
||||
|
||||
function initWitness(roomId) {
|
||||
// WebRTC stub with quarantine: No direct storage write
|
||||
const rtcConfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }; // Trusted STUN only
|
||||
const peerConnection = new RTCPeerConnection(rtcConfig);
|
||||
const channel = peerConnection.createDataChannel('chat');
|
||||
channel.onmessage = e => {
|
||||
document.getElementById('chat').innerHTML += `<p>${e.data}</p>`;
|
||||
};
|
||||
|
||||
// Bootstrap from Genesis (stub: load aether.json via fetch)
|
||||
fetch('/genesis/aether.json').then(res => res.json()).then(genesis => {
|
||||
console.log('Bootstrapped from Genesis:', genesis);
|
||||
// Signal to peers (annex for full signaling)
|
||||
});
|
||||
|
||||
// Offline mode: localStorage persistence
|
||||
const localState = localStorage.getItem('witness_state') || '{}';
|
||||
console.log('Offline state loaded:', localState);
|
||||
|
||||
// Send with PoW
|
||||
document.getElementById('send').addEventListener('click', async () => {
|
||||
const msg = document.getElementById('msg').value;
|
||||
const nonce = await computePoW(0);
|
||||
const payload = JSON.stringify({ msg, nonce });
|
||||
channel.send(payload);
|
||||
// Persist offline
|
||||
localStorage.setItem('witness_state', JSON.stringify({ lastMsg: msg }));
|
||||
});
|
||||
}
|
||||
|
||||
// Expose
|
||||
window.witness = { connect: initWitness };
|
||||
Loading…
Add table
Add a link
Reference in a new issue