import { For, Show, createSignal, type JSX } from "solid-js";
import {
ModuleCard,
FakeCursor,
IslandPill,
AgentDotGrid,
STATUS,
ACCENT,
CALM,
CRITICAL,
CARD_STROKE
} from "./IslandMock";
import { useDemoLoop } from "./useDemoLoop";
import { Glyph, WingCapsule, type GlyphName } from "./ModuleMocks";
/**
* Live demos for the breakdowns added with the 0.6–0.10 feature set:
* terminal replies, the volume mixer, Capture (module + Wings), and
* floating actions. Same ink chrome and status palette as the originals.
*/
/* ── Reply lands in the agent's own terminal ──────────────────────── */
const REPLY = "yes — and run them";
export function TerminalReplyDemo() {
const [typed, setTyped] = createSignal(0);
const [phase, setPhase] = createSignal<
"asking" | "typing" | "sent" | "working" | "jump"
>("asking");
useDemoLoop(10000, [
[0, () => (setTyped(0), setPhase("asking"))],
[900, () => setPhase("typing")],
...REPLY.split("").map(
(_, i) => [1000 + i * 70, () => setTyped(i + 1)] as [number, () => void]
),
[2900, () => setPhase("sent")],
[3300, () => setPhase("working")],
[6600, () => setPhase("jump")]
]);
const delivered = () => phase() === "working" || phase() === "jump";
const cursorPhase = () =>
phase() === "sent" || phase() === "jump"
? ("press" as const)
: phase() === "typing" || phase() === "asking"
? ("idle" as const)
: ("live" as const);
const cursorAt = () =>
phase() === "jump" ? { x: 88, y: 14 } : { x: 88, y: 80 };
return (
the-nook
main
Jump ↗
{delivered()
? "Running the test suite…"
: "Done. Want me to update the tests too?"}
{delivered() ? "" : REPLY.slice(0, typed())}
▍
Reply…
Send
{/* the terminal the session actually runs in */}
tmux · the-nook:2
focused
⏺ Updated 3 files
>{" "}
{REPLY}
⏺ Bash(swift test)
);
}
/* ── Volume mixer: every app its own level and output ─────────────── */
interface MixerApp {
name: string;
color: string;
glyph: string;
}
const MIXER_APPS: MixerApp[] = [
{ name: "Music", color: "#fa5c7c", glyph: "♫" },
{ name: "Browser", color: "#6EA7FF", glyph: "◎" },
{ name: "Meeting", color: "#6FB982", glyph: "●" },
{ name: "podcast-cli", color: "#E7A762", glyph: ">_" }
];
export function MixerDemo() {
const [step, setStep] = createSignal(0);
useDemoLoop(9600, [
[0, () => setStep(0)],
[1800, () => setStep(1)],
[3800, () => setStep(2)],
[5800, () => setStep(3)]
]);
const level = (i: number) => {
if (i === 0) return step() >= 3 ? 0.38 : 0.72;
if (i === 1) return 0.55;
if (i === 2) return 0.86;
return 0.46;
};
const muted = (i: number) => i === 1 && step() >= 1;
const route = (i: number) =>
i === 0 && step() >= 2 ? "Headphones" : i === 2 ? "Display" : "Speakers";
return (
Volume mixer
out · Speakers
{(app, i) => (
{app.glyph}
{app.name}
{route(i())}
{/* live signal riding inside the level */}
{muted(i()) ? "✕" : "♪"}
)}
{
[
"every app on its own fader",
"browser muted — the rest keep playing",
"music routed to headphones, meeting stays on the display",
"music down, nothing else moved"
][step()]
}
);
}
/* ── Capture: the module, and the Wings while a recording runs ────── */
/** CapturePalette.recording and the NookPalette paper/ink pair. */
const RECORDING_RED = "rgb(242,69,69)";
const PAPER = "#f1ead9";
const INK = "#0d0d0f";
function CaptureChip(props: {
glyph: GlyphName;
label: string;
menu?: boolean;
pressed?: boolean;
}) {
return (
{props.label}
);
}
/** CaptureAudioToggles: paper capsule when on, faint when off. */
function AudioToggles(props: { system: boolean; mic: boolean }) {
const toggle = (on: boolean, glyph: GlyphName, label: string) => (
{label}
);
return (
{toggle(props.system, props.system ? "speaker" : "speakerOff", "System audio")}
{toggle(props.mic, props.mic ? "mic" : "micOff", "Microphone")}
);
}
function RowLabel(props: { glyph: GlyphName; label: string }) {
return (
{props.label}
);
}
export function CaptureDemo() {
const [phase, setPhase] = createSignal<"idle" | "press" | "rec" | "paused" | "saved">("idle");
const [secs, setSecs] = createSignal(0);
const [mic, setMic] = createSignal(true);
const [wingPress, setWingPress] = createSignal<"" | "mic" | "pause" | "stop">("");
useDemoLoop(12400, [
[0, () => (setPhase("idle"), setSecs(0), setMic(true), setWingPress(""))],
[1500, () => setPhase("press")],
[1800, () => setPhase("rec")],
...[1, 2, 3, 4, 5].map((s) => [1800 + s * 1000, () => setSecs(s)] as [number, () => void]),
[4300, () => setWingPress("mic")],
[4550, () => (setMic(false), setWingPress(""))],
[7100, () => setWingPress("pause")],
[7350, () => (setPhase("paused"), setWingPress(""))],
[9000, () => setWingPress("stop")],
[9250, () => (setPhase("saved"), setWingPress(""))]
]);
const recording = () => phase() === "rec" || phase() === "paused";
const paused = () => phase() === "paused";
/* Island row is drawn at half size; Wing controls are as tall as it. */
const wingWidth = 2 * 60 + 12;
const cursor = () => {
switch (wingPress()) {
case "mic":
return { phase: "press" as const, at: { x: 21.8, y: 7 } };
case "pause":
return { phase: "press" as const, at: { x: 78.2, y: 7 } };
case "stop":
return { phase: "press" as const, at: { x: 86.5, y: 7 } };
}
if (phase() === "idle" || phase() === "press")
return { phase: phase() === "press" ? ("press" as const) : ("idle" as const), at: { x: 50, y: 58 } };
if (phase() === "saved") return { phase: "live" as const, at: { x: 86.5, y: 7 } };
return { phase: "idle" as const, at: { x: 21.8, y: 30 } };
};
return (
{/* the Compact island, flanked by the Wings while recording */}
{/* the Capture module */}
Screen Recording 2026-10-13 at 9.41.12.mov
Show in Finder
}
>
0:0{secs()}
{paused() ? "Paused" : "Recording"}
{/* cursor plotted on a fixed 360 px frame centered over the demo */}
);
}
/* ── Floating actions bloom around the open island ────────────────── */
/* Tiny monochrome glyphs for the action kinds the app offers. */
const GLYPH: Record JSX.Element> = {
leaf: () => (
),
bolt: () => ,
timer: () => (
),
speaker: () => (
),
shell: () => (
),
stack: () => (
),
link: () => (
)
};
interface FloatAction {
glyph: keyof typeof GLYPH;
label: string;
x: number;
y: number;
}
/* Positions mirror the app's slots: Left ·1/·2, Right ·1/·2, Bottom ·1–3. */
const ACTIONS: FloatAction[] = [
{ glyph: "leaf", label: "Low Power Mode on", x: -128, y: -18 },
{ glyph: "bolt", label: "Top-up to 100%", x: -128, y: 26 },
{ glyph: "speaker", label: "Output → Headphones", x: 128, y: -18 },
{ glyph: "stack", label: "Shortcut: Focus Time", x: 128, y: 26 },
{ glyph: "timer", label: "Timer · 25 min", x: -50, y: 78 },
{ glyph: "shell", label: "$ make deploy", x: 0, y: 78 },
{ glyph: "link", label: "Opened dashboard", x: 50, y: 78 }
];
export function FloatingActionsDemo() {
const [open, setOpen] = createSignal(false);
const [pressed, setPressed] = createSignal(-1);
const [lit, setLit] = createSignal([]);
useDemoLoop(9600, [
[0, () => (setOpen(false), setPressed(-1), setLit([]))],
[400, () => setOpen(true)],
[2200, () => setPressed(0)],
[2500, () => setLit([0])],
[4000, () => setPressed(4)],
[4300, () => setLit([0, 4])],
[5800, () => setPressed(2)],
[6100, () => setLit([0, 4, 2])],
[8800, () => (setOpen(false), setPressed(-1))]
]);
const lastLit = () => lit()[lit().length - 1];
return (
{/* the expanded island */}
{(a, i) => (
{GLYPH[a.glyph]()}
)}
{/* what the last press did */}
{lastLit() !== undefined ? ACTIONS[lastLit()].label : " "}
);
}