Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions www/assets/home-islands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Home page interactivity — vanilla JS, no framework.
// Drives (1) the OperationTree halt↓/teardown↑ cascade and (2) the install
// command copy button. Both no-op gracefully when their DOM isn't present.

const NAVY = "#14315d";
const GREEN = "#16a34a";
const AMBER = "#b45309";
const IDLE_EDGE = "#cbd5e1";

function initOperationTree() {
let root = document.getElementById("operation-tree");
let haltBtn = document.getElementById("operation-tree-halt");
let resetBtn = document.getElementById("operation-tree-reset");
if (!root || !haltBtn || !resetBtn) return;

let nodes = Array.from(root.querySelectorAll("[data-node]"));
let edges = Array.from(root.querySelectorAll("[data-edge]"));
let state = {}; // id -> "halting" | "done"
let timers = [];
let running = false;

function nodeFill(st) {
if (st === "done") return "#fffbeb";
if (st === "halting") return "#f0fdf4";
return "#fff";
}
function nodeStroke(st) {
if (st === "done") return AMBER;
if (st === "halting") return GREEN;
return NAVY;
}
function edgeColor(st) {
if (st === "done") return AMBER;
if (st === "halting") return GREEN;
return IDLE_EDGE;
}

function render() {
for (let g of nodes) {
let st = state[g.getAttribute("data-node")];
let rect = g.querySelector("[data-rect]");
let label = g.querySelector("[data-label]");
let done = g.querySelector("[data-done-label]");
if (rect) {
rect.setAttribute("fill", nodeFill(st));
rect.setAttribute("stroke", nodeStroke(st));
}
if (label) label.setAttribute("fill", st === "done" ? AMBER : NAVY);
if (done) done.setAttribute("opacity", st === "done" ? "1" : "0");
g.setAttribute("opacity", st === "done" ? "0.7" : "1");
}
for (let line of edges) {
line.setAttribute(
"stroke",
edgeColor(state[line.getAttribute("data-child")]),
);
}
}

function reset() {
timers.forEach(clearTimeout);
timers = [];
state = {};
running = false;
haltBtn.disabled = false;
render();
}

function halt() {
if (running) return;
running = true;
haltBtn.disabled = true;

// 1) halt signal propagates DOWN the tree, fast, reaching every operation
let haltAt = {
main: 450,
server: 850,
watch: 850,
sock: 1250,
db: 1250,
fs: 1250,
};
// 2) teardown COMPLETES bottom-up, independently per branch — a parent
// only completes once ALL of its own children have
let doneAt = {
sock: 2150,
db: 2650,
fs: 2350,
server: 3500,
watch: 3150,
main: 4300,
};

for (let [id, t] of Object.entries(haltAt)) {
timers.push(setTimeout(() => {
if (state[id] !== "done") state[id] = "halting";
render();
}, t));
}
for (let [id, t] of Object.entries(doneAt)) {
timers.push(setTimeout(() => {
state[id] = "done";
render();
}, t));
}
timers.push(setTimeout(() => {
running = false;
haltBtn.disabled = false;
}, 4700));
}

haltBtn.addEventListener("click", halt);
resetBtn.addEventListener("click", reset);
}

function initCopyButtons() {
for (let btn of document.querySelectorAll("[data-copy]")) {
btn.addEventListener("click", async () => {
let text = btn.getAttribute("data-copy");
try {
await navigator.clipboard.writeText(text);
} catch (_) { /* clipboard unavailable */ }
let label = btn.querySelector("[data-copy-label]") || btn;
let prev = label.textContent;
label.textContent = "copied";
btn.setAttribute("data-copied", "1");
setTimeout(() => {
label.textContent = prev;
btn.removeAttribute("data-copied");
}, 1400);
});
}
}

initOperationTree();
initCopyButtons();
56 changes: 56 additions & 0 deletions www/components/code-window.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { refractor } from "refractor/all";
import type { JSXElement } from "revolution/jsx-runtime";

const LANGS: Record<string, string> = {
ts: "typescript",
tsx: "tsx",
js: "javascript",
jsx: "jsx",
};

export interface CodeWindowProps {
code: string;
filename?: string;
language?: string;
}

/**
* CodeWindow — a "macOS window" code block: a chrome bar with three
* traffic-light dots and an optional filename, above a syntax-highlighted
* `<pre>`. Highlighting is produced at render time with `refractor` (the same
* tokenizer `rehype-prism-plus` uses across the site), so colours come from
* `/assets/prism-atom-one-dark.css` and follow the OS light/dark preference.
*/
export function CodeWindow(
{ code, filename, language = "ts" }: CodeWindowProps,
): JSXElement {
let grammar = LANGS[language] ?? "typescript";
// refractor returns a hast tree of <span class="token …"> nodes, which is the
// same shape revolution's JSX runtime renders — embed its children directly.
let tokens = refractor.highlight(code.replace(/\n$/, ""), grammar)
.children as unknown as JSXElement[];

return (
<div class="overflow-hidden rounded-lg border border-gray-200 shadow-sm dark:border-gray-700">
<div class="flex items-center gap-2 border-b border-gray-200 bg-gray-100 px-4 py-2.5 dark:border-gray-700 dark:bg-gray-800">
<span class="flex gap-1.5">
<span class="h-3 w-3 rounded-full bg-[#ff5f57]"></span>
<span class="h-3 w-3 rounded-full bg-[#febc2e]"></span>
<span class="h-3 w-3 rounded-full bg-[#28c840]"></span>
</span>
{filename
? (
<span class="ml-1 font-mono text-xs text-gray-500 dark:text-gray-400">
{filename}
</span>
)
: <></>}
</div>
<pre
class={`language-${language} !m-0 !rounded-none overflow-x-auto p-4 text-[13.5px] leading-relaxed`}
>
<code class={`language-${language} code-highlight`}>{tokens}</code>
</pre>
</div>
);
}
31 changes: 31 additions & 0 deletions www/components/install-copy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// @ts-nocheck hastx's button `type` union omits "button"
import type { JSXElement } from "revolution/jsx-runtime";

export interface InstallCopyProps {
command?: string;
}

/**
* InstallCopy — a terminal-style install pill with a copy-to-clipboard button.
* The button carries the command in `data-copy`; `/assets/home-islands.js`
* wires the clipboard write and the "copied" feedback. Kept dark in both
* themes since it reads as a terminal.
*/
export function InstallCopy(
{ command = "npm install effection" }: InstallCopyProps,
): JSXElement {
return (
<div class="inline-flex items-center gap-3.5 rounded-lg border border-white/10 bg-[#0f1b2d] px-4 py-3 pl-[18px] font-mono text-[15px]">
<span class="select-none text-[#6b7c93]">$</span>
<span class="text-[#e6edf6]">{command}</span>
<button
type="button"
data-copy={command}
aria-label="Copy install command"
class="ml-1 inline-flex items-center gap-1.5 rounded border border-white/10 bg-white/[0.06] px-2.5 py-1 font-mono text-[12.5px] font-semibold text-[#8aa0b8] transition-colors data-[copied]:text-[#28c840]"
>
<span data-copy-label>copy</span>
</button>
</div>
);
}
152 changes: 152 additions & 0 deletions www/components/operation-tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// @ts-nocheck hastx does not typecheck raw <svg> children
import type { JSXElement } from "revolution/jsx-runtime";

interface Node {
id: string;
x: number;
y: number;
label: string;
}

const NODES: Node[] = [
{ id: "main", x: 300, y: 34, label: "entrypoint()" },
{ id: "server", x: 150, y: 138, label: "startServer()" },
{ id: "watch", x: 450, y: 138, label: "watchFiles()" },
{ id: "sock", x: 70, y: 246, label: "socket" },
{ id: "db", x: 230, y: 246, label: "db pool" },
{ id: "fs", x: 450, y: 246, label: "fs handle" },
];

const EDGES: [string, string][] = [
["main", "server"],
["main", "watch"],
["server", "sock"],
["server", "db"],
["watch", "fs"],
];

const NAVY = "#14315d";
const IDLE_EDGE = "#cbd5e1";
const byId = Object.fromEntries(NODES.map((n) => [n.id, n]));

/**
* OperationTree — the interactive proof that halting forces control back and
* teardown always runs. Rendered as static SVG server-side with stable
* `data-*` hooks; `/assets/operation-tree.js` drives the halt↓ / teardown↑
* cascade on click (no client framework).
*/
export function OperationTree(): JSXElement {
return (
<div id="operation-tree" class="mx-auto max-w-[620px]">
<div class="rounded-lg border border-gray-200 bg-white px-2 pt-2 shadow-sm dark:border-gray-700">
<svg
viewBox="0 0 600 300"
class="block h-auto w-full"
font-family='"Fira Code", "Fira Mono", Menlo, Consolas, monospace'
>
<>
{EDGES.map(([a, b]) => {
let na = byId[a];
let nb = byId[b];
return (
<line
data-edge
data-child={b}
x1={na.x}
y1={na.y + 16}
x2={nb.x}
y2={nb.y - 16}
stroke={IDLE_EDGE}
stroke-width={2}
style="transition: stroke .3s"
/>
);
})}
</>
<>
{NODES.map((n) => {
let w = Math.round(n.label.length * 8.2 + 34);
return (
<g
data-node={n.id}
style="transition: opacity .3s"
>
<rect
data-rect
x={n.x - w / 2}
y={n.y - 16}
width={w}
height={32}
rx={6}
fill="#fff"
stroke={NAVY}
stroke-width={2}
style="transition: fill .3s, stroke .3s"
/>
<text
data-label
x={n.x}
y={n.y + 5}
text-anchor="middle"
font-size={13.5}
fill={NAVY}
style="transition: fill .3s"
>
{n.label}
</text>
<text
data-done-label
x={n.x + w / 2 + 6}
y={n.y + 4}
font-size={11}
fill="#b45309"
opacity={0}
>
✓ torn down
</text>
</g>
);
})}
</>
</svg>
</div>

<div class="mt-3.5 flex items-center justify-center gap-5 text-xs text-gray-500 dark:text-gray-400">
<span class="inline-flex items-center gap-2">
<span class="h-2.5 w-2.5 rounded-[3px] border-2 border-[#16a34a] bg-[#f0fdf4]">
</span>
halt signal ↓
</span>
<span class="inline-flex items-center gap-2">
<span class="h-2.5 w-2.5 rounded-[3px] border-2 border-[#b45309] bg-[#fffbeb]">
</span>
teardown ↑
</span>
</div>

<div class="mt-4 flex items-center justify-center gap-3">
<button
id="operation-tree-halt"
type="button"
class="rounded font-mono text-[13px] font-semibold text-white bg-blue-900 px-4 py-2.5 transition-colors hover:bg-blue-800 disabled:cursor-default disabled:bg-gray-400"
>
entrypoint.halt()
</button>
<button
id="operation-tree-reset"
type="button"
class="rounded border-2 border-[#14315D] font-mono text-[13px] font-semibold text-[#14315D] px-3.5 py-1.5 transition-colors hover:bg-blue-50 dark:border-[#26ABE8] dark:text-[#26ABE8]"
>
reset
</button>
</div>

<p class="mt-3 text-center text-[13px] text-gray-500 dark:text-gray-400">
The halt signal travels down the tree (green); teardown then completes
bottom-up, rolling up each branch independently (amber) — a parent
finishes only once all its children have, each <code>ensure</code>{" "}
running as its operation exits.
</p>
</div>
);
}
Loading
Loading