better feature design

This commit is contained in:
2026-09-05 16:06:27 -04:00
parent 8492821f75
commit 23f86681af
5 changed files with 666 additions and 108 deletions

1
.gitignore vendored
View File

@@ -34,3 +34,4 @@ Thumbs.db
# pygienium run-state and check artifacts # pygienium run-state and check artifacts
.pygienium/ .pygienium/
scripts/ scripts/
.cache

Binary file not shown.

View File

@@ -749,3 +749,9 @@ a.hover-underline-animation:hover::after {
10% { opacity: 1; } 10% { opacity: 1; }
100% { transform: translate(var(--sx, 20px), var(--sy, 30px)) scale(0.4); opacity: 0; } 100% { transform: translate(var(--sx, 20px), var(--sy, 30px)) scale(0.4); opacity: 0; }
} }
/* Demo pulse: battery bolt */
@keyframes nook-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}

View File

@@ -1,10 +1,17 @@
import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js"; import {
For,
createEffect,
createSignal,
onCleanup,
onMount,
type JSX
} from "solid-js";
import { import {
IslandPill, IslandPill,
IslandExpanded,
AgentDotGrid, AgentDotGrid,
STATUS, STATUS,
CALM, CALM,
WARM,
ACCENT, ACCENT,
CRITICAL, CRITICAL,
ModuleCard ModuleCard
@@ -13,9 +20,9 @@ import { Campfire, type CampfireState } from "./Campfire";
/** /**
* Detailed feature breakdowns: alternating copy + live-demo sections. * Detailed feature breakdowns: alternating copy + live-demo sections.
* Each demo animates when scrolled into view (IntersectionObserver + * Each demo stages the real interaction loop: attention flares, approval
* motion's animate), staging the real interaction loop: attention * resolves, fans throttle, the charge ceiling holds, meetings surface,
* flares, approval resolves, fans throttle, sessions stream. * the camera wakes, the hook installs.
*/ */
/* Scroll-triggered reveal: adds `in` once the element crosses 25%. */ /* Scroll-triggered reveal: adds `in` once the element crosses 25%. */
@@ -131,9 +138,15 @@ function FleetDemo() {
<div <div
class="rounded-xl px-4 py-3 text-center shadow-lg transition-all duration-500" class="rounded-xl px-4 py-3 text-center shadow-lg transition-all duration-500"
style={{ style={{
background: phase() === "flare" ? "rgba(231,167,98,0.12)" : "rgba(255,255,255,0.055)", background:
phase() === "flare"
? "rgba(231,167,98,0.12)"
: "rgba(255,255,255,0.055)",
"box-shadow": "inset 0 0 0 1px rgba(255,255,255,0.07)", "box-shadow": "inset 0 0 0 1px rgba(255,255,255,0.07)",
transform: phase() === "flare" ? "translateY(0) scale(1)" : "translateY(6px) scale(0.98)", transform:
phase() === "flare"
? "translateY(0) scale(1)"
: "translateY(6px) scale(0.98)",
opacity: phase() === "watch" ? 0.55 : 1 opacity: phase() === "watch" ? 0.55 : 1
}} }}
> >
@@ -191,7 +204,9 @@ function ApprovalDemo() {
<span <span
class="ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-semibold text-white" class="ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-semibold text-white"
style={{ style={{
background: allowed() ? "rgba(150,220,150,0.25)" : "rgba(234,179,8,0.25)" background: allowed()
? "rgba(150,220,150,0.25)"
: "rgba(234,179,8,0.25)"
}} }}
> >
{allowed() ? "allowed" : "approval"} {allowed() ? "allowed" : "approval"}
@@ -318,49 +333,49 @@ function FansDemo() {
"--fan-speed": `${(0.4 - shakeIntensity() * 0.28).toFixed(3)}s` "--fan-speed": `${(0.4 - shakeIntensity() * 0.28).toFixed(3)}s`
}} }}
> >
<div class="space-y-3 text-left"> <div class="space-y-3 text-left">
<For each={fans()}> <For each={fans()}>
{(f, i) => ( {(f, i) => (
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-[10px] font-medium text-white/50"> <span class="text-[10px] font-medium text-white/50">
Fan {i() + 1} Fan {i() + 1}
</span> </span>
<div
class="h-1 flex-1 overflow-hidden rounded-full"
style={{ background: "rgba(255,255,255,0.08)" }}
>
<div <div
class="h-full rounded-full" class="h-1 flex-1 overflow-hidden rounded-full"
style={{ style={{ background: "rgba(255,255,255,0.08)" }}
width: `${Math.min(f.frac, 1) * 100}%`, >
background: maxed() ? CRITICAL : CALM, <div
transition: "width 0.2s linear, background 0.4s" class="h-full rounded-full"
}} style={{
/> width: `${Math.min(f.frac, 1) * 100}%`,
background: maxed() ? CRITICAL : CALM,
transition: "width 0.2s linear, background 0.4s"
}}
/>
</div>
<span
class="font-mono text-white"
style={{ "font-size": "13px", "font-weight": 500 }}
>
{Math.round(f.rpm)}
</span>
<span class="text-[9px] font-semibold text-white/50">RPM</span>
</div> </div>
<span )}
class="font-mono text-white" </For>
style={{ "font-size": "13px", "font-weight": 500 }} <div class="h-px" style={{ background: "rgba(255,255,255,0.06)" }} />
> <div class="flex items-center gap-2">
{Math.round(f.rpm)} <span
</span> class="rounded-md px-2 py-0.5 text-[11px] font-bold transition-colors"
<span class="text-[9px] font-semibold text-white/50">RPM</span> style={{ color: maxed() ? CRITICAL : "rgba(255,255,255,0.7)" }}
</div> >
)} {maxed() ? "MAX" : "Auto"}
</For> </span>
<div class="h-px" style={{ background: "rgba(255,255,255,0.06)" }} /> <span class="text-[10px] text-white/40">
<div class="flex items-center gap-2"> {maxed() ? "cooling session active" : "curve in control"}
<span </span>
class="rounded-md px-2 py-0.5 text-[11px] font-bold transition-colors" </div>
style={{ color: maxed() ? CRITICAL : "rgba(255,255,255,0.7)" }}
>
{maxed() ? "MAX" : "Auto"}
</span>
<span class="text-[10px] text-white/40">
{maxed() ? "cooling session active" : "curve in control"}
</span>
</div> </div>
</div>
</ModuleCard> </ModuleCard>
<div <div
class="pointer-events-none absolute z-0" class="pointer-events-none absolute z-0"
@@ -391,77 +406,565 @@ function FansDemo() {
); );
} }
/* ── Demo 4: remote sessions streaming into the island ────────────── */ /** Fake cursor dot: rests on a target, presses, exits. */
const REMOTE_TICKS: { function FakeCursor(props: {
name: string; phase: "idle" | "press" | "live";
via: string; at: { x: number; y: number };
state: keyof typeof STATUS; exit?: { x: number; y: number };
line: string; }) {
}[] = [ const p = () =>
{ name: "build-box", via: "ssh", state: "running", line: "You: fix flaky test" }, props.phase === "press"
{ name: "gpu-rig", via: "tailscale", state: "running", line: "You: benchmark fp16" } ? { ...props.at, scale: 0.62, o: 1 }
: props.phase === "idle"
? { ...props.at, scale: 1, o: 1 }
: { ...(props.exit ?? { x: 106, y: 8 }), scale: 1, o: 0 };
return (
<span
class="pointer-events-none absolute z-10 rounded-full bg-white"
style={{
width: "9px",
height: "9px",
"box-shadow":
"0 1px 4px rgba(0,0,0,0.6), 0 0 0 4px rgba(255,255,255,0.18)",
left: `${p().x}%`,
top: `${p().y}%`,
opacity: p().o,
transform: `translate(-50%, -50%) scale(${p().scale})`,
transition:
"left 0.8s cubic-bezier(0.35,0,0.25,1), top 0.8s cubic-bezier(0.35,0,0.25,1), opacity 0.4s, transform 0.3s"
}}
/>
);
}
/* ── Demo 4: the charge ceiling holds, top-up overrules ───── */
function BatteryDemo() {
const [level, setLevel] = createSignal(62);
const [phase, setPhase] = createSignal<"charge" | "held" | "topup">("charge");
onMount(() => {
let frame = 0;
const timers: ReturnType<typeof setTimeout>[] = [];
const ramp = (target: number, durationMs: number) => {
cancelAnimationFrame(frame);
const start = level();
const startTime = performance.now();
const tick = (now: number) => {
const t = Math.min((now - startTime) / durationMs, 1);
setLevel(start + (target - start) * t);
if (t < 1) frame = requestAnimationFrame(tick);
};
frame = requestAnimationFrame(tick);
};
const loop = () => {
setLevel(62);
setPhase("charge");
ramp(80, 2600);
timers.push(setTimeout(() => setPhase("held"), 2800));
timers.push(
setTimeout(() => {
setPhase("topup");
ramp(100, 2600);
}, 5200)
);
};
loop();
const interval = setInterval(loop, 9600);
onCleanup(() => {
clearInterval(interval);
timers.forEach(clearTimeout);
cancelAnimationFrame(frame);
});
});
const pct = () => Math.round(level());
const watts = () => (phase() === "held" ? null : pct() >= 100 ? 9 : 38);
const subline = () =>
phase() === "held"
? "charging paused — at the limit"
: pct() >= 100
? "topped up — re-arms on unplug"
: phase() === "topup"
? `${watts()} W · top-up past the limit`
: `${watts()} W · ${Math.max(1, Math.round((100 - pct()) * 1.5))} min to full`;
return (
<ModuleCard class="mx-auto w-full max-w-[320px]">
<div class="space-y-3 text-left">
<div class="flex items-center gap-2">
<span class="text-[10px] font-semibold tracking-wide text-white/40 uppercase">
Battery
</span>
<span
class="ml-auto rounded-md px-2 py-0.5 font-mono text-[10px] font-semibold transition-transform duration-300"
style={{
color: "rgba(255,255,255,0.75)",
background: "rgba(255,255,255,0.08)",
transform: phase() === "held" ? "scale(1.12)" : "scale(1)"
}}
>
Limit 80%
</span>
</div>
<div class="flex items-baseline gap-1.5">
<span class="font-mono text-[22px] font-semibold text-white">
{pct()}%
</span>
<span class="text-[11px] text-white/50">{subline()}</span>
</div>
<div
class="relative h-[6px] overflow-hidden rounded-full"
style={{ background: "rgba(255,255,255,0.08)" }}
>
<div
class="h-full rounded-full"
style={{
width: `${pct()}%`,
background: pct() > 80 ? WARM : CALM,
transition: "background 0.4s"
}}
/>
<span
class="absolute top-0 h-full w-px"
style={{ left: "80%", background: "rgba(255,255,255,0.5)" }}
/>
</div>
<div
class="flex items-center gap-2 rounded-lg px-2 py-1.5 transition-colors duration-500"
style={{
background:
phase() === "topup" ? "rgba(110,167,255,0.10)" : "transparent"
}}
>
<div class="flex-1">
<p class="text-[11px] font-semibold text-white/85">Top-up mode</p>
<p class="text-[9px] text-white/40">
charge past the limit while plugged in
</p>
</div>
<span
class="relative inline-flex h-[16px] w-[30px] items-center rounded-full transition-colors duration-300"
style={{
background:
phase() === "topup" ? ACCENT : "rgba(255,255,255,0.15)"
}}
>
<span
class="absolute h-[12px] w-[12px] rounded-full bg-white transition-all duration-300"
style={{ left: phase() === "topup" ? "16px" : "2px" }}
/>
</span>
</div>
<div class="h-px" style={{ background: "rgba(255,255,255,0.06)" }} />
<div class="flex items-center gap-2">
<svg
width="10"
height="13"
viewBox="0 0 10 13"
style={{
animation: watts()
? "nook-pulse 1.1s ease-in-out infinite"
: "none",
opacity: watts() ? 1 : 0.3
}}
>
<path
d="M6.2 0 0.8 7h2.6l-1 5.2L7.8 5.4H5l1.2-5.4z"
fill="#FFD23F"
/>
</svg>
<span class="text-[10px] text-white/50">
{phase() === "held"
? "holding — the pack stays young longer"
: phase() === "topup"
? "topping up — re-arms when you unplug"
: "on the way to the ceiling"}
</span>
</div>
</div>
</ModuleCard>
);
}
/* ── Demo 5: the slot counts down, then opens the day ──────── */
const WEEK_LETTERS = ["S", "M", "T", "W", "T", "F", "S"];
const DAY_EVENTS = [
{ time: "09:3009:45", title: "Standup", loc: "Daily", meeting: true },
{ time: "15:3016:00", title: "Design review", loc: "4th floor", meeting: false }
]; ];
function RemoteDemo() { function CalendarDemo() {
const [tick, setTick] = createSignal(0); const [mins, setMins] = createSignal(14);
const [today, setToday] = createSignal(new Date());
onMount(() => { onMount(() => {
setToday(new Date());
const interval = setInterval( const interval = setInterval(
() => setTick((t) => (t + 1) % REMOTE_TICKS.length), () => setMins((m) => (m <= 0 ? 14 : m - 1)),
2600 900
); );
onCleanup(() => clearInterval(interval)); onCleanup(() => clearInterval(interval));
}); });
const imminent = () => mins() <= 5;
const dialDays = () => {
const base = today();
return [-4, -3, -2, -1, 0, 1, 2, 3, 4].map((off) => {
const d = new Date(
base.getFullYear(),
base.getMonth(),
base.getDate() + off
);
return {
letter: WEEK_LETTERS[d.getDay()],
num: d.getDate(),
today: off === 0,
past: off < 0,
dots: (d.getDate() * 7 + d.getDay() * 3) % 4
};
});
};
return ( return (
<div class="flex flex-col items-center gap-4"> <div class="flex flex-col items-center gap-4">
<IslandExpanded <IslandPill
activeCount={2} campfire="running"
tabs={[ right={
{ icon: "🔥", label: "Agents", active: true }, <div class="flex items-center gap-1">
{ icon: "♪", label: "Music" }, <svg width="10" height="8" viewBox="0 0 12 9" class="shrink-0">
{ icon: "◌", label: "System" } <path
]} d="M0 2A1.4 1.4 0 0 1 1.4 0.6h5.6A1.4 1.4 0 0 1 8.4 2v5A1.4 1.4 0 0 1 7 8.4H1.4A1.4 1.4 0 0 1 0 7z M12 2.6 9 4.5l3 1.9z"
fill="rgba(255,255,255,0.75)"
/>
</svg>
<span
class="whitespace-nowrap font-mono text-[9.5px] font-semibold text-white/90"
style={{ color: imminent() ? "#ff9e42" : "rgba(255,255,255,0.9)" }}
>
{mins() === 0 ? "now" : `${mins()}m`}
</span>
</div>
}
/>
{/* the island opening onto the day panel */}
<div
class="w-full max-w-[380px] overflow-hidden rounded-[14px] bg-black transition-all duration-700"
style={{
"max-height": imminent() ? "240px" : "0px",
opacity: imminent() ? 1 : 0,
transform: imminent() ? "translateY(0)" : "translateY(-8px)"
}}
> >
<div class="space-y-2 text-left"> <div class="px-4 pt-3 pb-3 text-left">
<For each={REMOTE_TICKS}> <div class="mb-2 flex items-center">
{(host, i) => ( <span class="text-[11px] font-semibold text-white/50">
<div Calendar
class="rounded-[10px] p-2.5 transition-all duration-500" </span>
style={{ <span class="ml-auto flex h-4 w-4 items-center justify-center text-[12px] font-semibold text-white/50">
background: "rgba(255,255,255,0.055)", +
"box-shadow": "inset 0 0 0 1px rgba(255,255,255,0.07)", </span>
opacity: i() === tick() ? 1 : 0.55, </div>
transform: i() === tick() ? "scale(1.01)" : "scale(1)" <div class="mb-2 flex gap-1">
}} <For each={dialDays()}>
> {(d) => (
<div class="flex items-center gap-2"> <div
<span class="flex flex-1 flex-col items-center gap-[3px]"
class="inline-block h-[7px] w-[7px] rounded-full" style={{ opacity: d.today ? 1 : d.past ? 0.55 : 0.8 }}
style={{ background: STATUS[host.state] }} >
/> <span class="text-[8px] font-semibold text-white/45">
<span class="text-[13px] font-semibold text-white"> {d.letter}
{host.name}
</span> </span>
<span <span
class="ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-semibold text-white/75" class="flex h-[20px] w-[20px] items-center justify-center rounded-full text-[10px]"
style={{ background: "rgba(255,255,255,0.08)" }} style={{
background: d.today
? "rgb(255,158,66)"
: "rgba(255,255,255,0.06)",
color: "rgba(255,255,255,0.95)",
"font-weight": d.today ? 700 : 500
}}
> >
{host.via} {d.num}
</span>
<span class="flex h-[4px] gap-[2px]">
<For each={Array.from({ length: Math.min(d.dots, 3) })}>
{() => (
<span
class="rounded-full"
style={{
width: "3.5px",
height: "3.5px",
background: d.today
? "rgb(255,158,66)"
: "rgba(255,255,255,0.55)"
}}
/>
)}
</For>
</span> </span>
</div> </div>
<p class="mt-0.5 text-[11px] text-white/50">{host.line}</p> )}
</For>
</div>
<div
class="mb-1 h-px"
style={{ background: "rgba(255,255,255,0.06)" }}
/>
<For each={DAY_EVENTS}>
{(ev) => (
<div
class="flex items-center gap-2 rounded-md px-1.5 py-1.5 transition-colors duration-500"
style={{
background:
ev.meeting && imminent()
? "rgba(72,151,178,0.16)"
: "transparent"
}}
>
<div>
<p class="font-mono text-[10px] text-white/45">{ev.time}</p>
<p class="text-[11px] font-semibold text-white">{ev.title}</p>
{ev.loc && <p class="text-[10px] text-white/35">{ev.loc}</p>}
</div>
{ev.meeting && (
<span
class="ml-auto rounded-md px-2 py-0.5 text-[10px] font-semibold text-white transition-colors duration-500"
style={{
background: imminent() ? ACCENT : "rgba(255,255,255,0.10)"
}}
>
Open
</span>
)}
</div> </div>
)} )}
</For> </For>
<p class="pt-1 text-center text-[10px] text-white/40">
same island, same glance agents two networks away
</p>
</div> </div>
</IslandExpanded> </div>
</div> </div>
); );
} }
/* ── Demo 5: campfire states ───────────────────────────────────────── */ /* ── Demo 6: the lens only wakes for a click ──────────────── */
function CameraDemo() {
const [phase, setPhase] = createSignal<"idle" | "press" | "live">("idle");
let videoRef: HTMLVideoElement | undefined;
onMount(() => {
const timers: ReturnType<typeof setTimeout>[] = [];
const loop = () => {
setPhase("idle");
timers.push(setTimeout(() => setPhase("press"), 2600));
timers.push(setTimeout(() => setPhase("live"), 3000));
};
loop();
const interval = setInterval(loop, 8800);
onCleanup(() => {
clearInterval(interval);
timers.forEach(clearTimeout);
});
});
createEffect(() => {
if (phase() === "live") videoRef?.play();
else videoRef?.pause();
});
return (
<ModuleCard class="relative mx-auto w-full max-w-[300px]">
<div class="space-y-2.5 text-left">
<div class="flex items-center">
<span class="text-[11px] font-semibold text-white/50">Camera</span>
<span
class="ml-auto rounded-md px-2 py-0.5 text-[10px] text-white/60 transition-opacity duration-300"
style={{
background: "rgba(255,255,255,0.06)",
opacity: phase() === "live" ? 1 : 0
}}
>
FaceTime HD Camera
</span>
</div>
<div class="relative h-[120px] overflow-hidden rounded-lg">
{/* inert dashed start button */}
<div
class="absolute inset-0 flex flex-col items-center justify-center gap-2 transition-all duration-500"
style={{
border: "1px dashed rgba(255,255,255,0.14)",
"border-radius": "8px",
opacity: phase() === "live" ? 0 : 1,
transform: phase() === "press" ? "scale(0.96)" : "scale(1)"
}}
>
<svg width="18" height="14" viewBox="0 0 18 14">
<path
d="M2 2.8A1.8 1.8 0 0 1 3.8 1h2.2l1 1.4h4.2A1.8 1.8 0 0 1 13 4.2v6.6a1.8 1.8 0 0 1-1.8 1.8H3.8A1.8 1.8 0 0 1 2 10.8z"
fill="rgba(255,255,255,0.5)"
/>
<circle cx="7.5" cy="7.4" r="2.6" fill="rgba(0,0,0,0.35)" />
</svg>
<span class="text-[12px] font-semibold text-white/80">
Start camera
</span>
</div>
{/* live preview */}
<video
ref={videoRef}
src="/nook/cam-recording.mp4"
muted
loop
playsinline
preload="metadata"
class="absolute inset-0 h-full w-full object-cover transition-opacity duration-700"
style={{ opacity: phase() === "live" ? 1 : 0 }}
/>
<FakeCursor phase={phase()} at={{ x: 50, y: 50 }} />
</div>
<p class="text-[10px] text-white/40">
{phase() === "live"
? "live — stops the moment you leave the panel"
: "inert — the lens stays dark until you click"}
</p>
</div>
</ModuleCard>
);
}
/* ── Demo 7: one click copies, sets up, probes — pong ─────── */
const SSH_LINES = [
{ at: 1, text: "scp nook-hook gpu-rig:~/.nook/bin/" },
{ at: 2, text: "setup: bake NOOK_HOST, write ~/.nook/endpoint" },
{ at: 3, text: "nook-hook --probe → pong" }
];
function SshInstallDemo() {
const [step, setStep] = createSignal(0);
onMount(() => {
const timers: ReturnType<typeof setTimeout>[] = [];
const loop = () => {
setStep(0);
timers.push(setTimeout(() => setStep(1), 1600));
timers.push(setTimeout(() => setStep(2), 3000));
timers.push(setTimeout(() => setStep(3), 4200));
timers.push(setTimeout(() => setStep(4), 5400));
};
loop();
const interval = setInterval(loop, 11000);
onCleanup(() => {
clearInterval(interval);
timers.forEach(clearTimeout);
});
});
const cursorPhase = () =>
step() === 0
? ("idle" as const)
: step() === 1
? ("press" as const)
: ("live" as const);
return (
<ModuleCard class="relative mx-auto w-full max-w-[330px]">
<div class="space-y-2.5 text-left">
<div class="flex items-center gap-2">
<div class="min-w-0 flex-1">
<p class="text-[13px] font-semibold text-white">gpu-rig</p>
<p class="truncate font-mono text-[10px] text-white/40">
mike@192.168.64.3 · linux
</p>
</div>
<span
class="rounded-md px-2.5 py-1 text-[11px] font-semibold text-white transition-all duration-300"
style={{
background: step() === 0 ? ACCENT : "rgba(255,255,255,0.08)",
opacity: step() >= 4 ? 0 : 1,
transform: step() === 1 ? "scale(0.94)" : "scale(1)"
}}
>
{step() === 0 ? "Install hook" : "installing…"}
</span>
</div>
<div class="h-px" style={{ background: "rgba(255,255,255,0.06)" }} />
<div class="relative" style={{ "min-height": "86px" }}>
<div
class="space-y-1.5 transition-opacity duration-500"
style={{ opacity: step() >= 4 ? 0 : 1 }}
>
{step() === 0 && (
<p class="pt-2 text-center text-[10px] text-white/35">
one click: copy setup probe
</p>
)}
<For each={SSH_LINES}>
{(l) => (
<div
class="flex items-center gap-1.5 transition-all duration-500"
style={{
opacity: step() >= l.at ? 1 : 0,
transform:
step() >= l.at ? "translateY(0)" : "translateY(4px)"
}}
>
<span
class="font-mono text-[10px]"
style={{
color:
step() > l.at
? "rgba(150,220,150,0.9)"
: "rgba(255,255,255,0.45)"
}}
>
{step() > l.at ? "✓" : "…"}
</span>
<span class="truncate font-mono text-[10px] text-white/70">
{l.text}
</span>
</div>
)}
</For>
</div>
<div
class="absolute inset-0 flex flex-col justify-center transition-all duration-500"
style={{
opacity: step() >= 4 ? 1 : 0,
transform: step() >= 4 ? "translateY(0)" : "translateY(6px)"
}}
>
<div
class="rounded-[10px] p-2.5"
style={{
background: "rgba(255,255,255,0.055)",
"box-shadow": "inset 0 0 0 1px rgba(255,255,255,0.07)"
}}
>
<div class="flex items-center gap-2">
<span
class="inline-block h-[7px] w-[7px] rounded-full"
style={{ background: STATUS.running }}
/>
<span class="text-[13px] font-semibold text-white">
gpu-rig
</span>
<span
class="ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-semibold"
style={{
background: "rgba(110,167,255,0.22)",
color: "#9cc3ff"
}}
>
remote
</span>
</div>
<p class="mt-0.5 text-[11px] text-white/50">
You: benchmark fp16
</p>
</div>
<p class="pt-1.5 text-center text-[10px] text-white/40">
agents join the same dot grid
</p>
</div>
</div>
</div>
<FakeCursor phase={cursorPhase()} at={{ x: 90, y: 14 }} />
</ModuleCard>
);
}
/* ── Demo 8: campfire states ───────────────────────────────────────── */
function CampfireDemo() { function CampfireDemo() {
const states: CampfireState[] = [ const states: CampfireState[] = [
@@ -489,7 +992,9 @@ function CampfireDemo() {
> >
<Campfire state={state} pixel={3} /> <Campfire state={state} pixel={3} />
</div> </div>
<p class="text-center text-[11px] font-medium text-white/60">{labels[i()]}</p> <p class="text-center text-[11px] font-medium text-white/60">
{labels[i()]}
</p>
</div> </div>
)} )}
</For> </For>
@@ -507,7 +1012,8 @@ export default function FeatureBreakdowns() {
bullets={[ bullets={[
"12 agents: Claude Code, Codex, Gemini CLI, OpenCode, Pi, and more", "12 agents: Claude Code, Codex, Gemini CLI, OpenCode, Pi, and more",
"The campfire dances while work runs, simmers when all is calm", "The campfire dances while work runs, simmers when all is calm",
"Slot rankings — you choose which data earns the notch" "Slot rankings — you choose which data earns the notch",
"You don't need a notch — external displays get the same island, floating top-center"
]} ]}
> >
<FleetDemo /> <FleetDemo />
@@ -530,8 +1036,9 @@ export default function FeatureBreakdowns() {
<BreakdownSection <BreakdownSection
kicker="Cooling" kicker="Cooling"
title="Fans that answer to you, not the scheduler" title="Fans that answer to you, not the scheduler"
body="Live RPM for every fan, a privileged helper for the curve. MAX before a compile, aggressive when the render queue spikes, auto when you walk away." body="Live RPM for every fan, and the curve is yours. Apple's stock curve waits until you're already cooking — swap to Aggressive and it climbs earlier and harder, or slam MAX before the compile."
bullets={[ bullets={[
"Stock, Aggressive, and MAX — swap curves in one click",
"MAX and COOL sessions surface right in the pill", "MAX and COOL sessions surface right in the pill",
"CPU/GPU die temps tier-color before things get loud", "CPU/GPU die temps tier-color before things get loud",
"All local: your hardware data never leaves the device" "All local: your hardware data never leaves the device"
@@ -541,17 +1048,61 @@ export default function FeatureBreakdowns() {
</BreakdownSection> </BreakdownSection>
<BreakdownSection <BreakdownSection
kicker="Remote" kicker="Power"
title="SSH boxes join the same island" title="Wall power, on your terms"
body="Push the nook-hook to any Linux host — Tailscale or plain SSH with a reverse tunnel — and its agents appear beside your local ones, indistinguishable." body="Live watts from the adapter to the battery to the chassis — and a charge ceiling you set yourself. On recent macOS The Nook drives the system's own limiter; below that it enforces the limit itself. One slider, 50100%, any version."
bullets={[ bullets={[
"One-line install bakes the bridge endpoint for you", "One slider, 50100%, on any macOS version",
"Works for Claude Code, Codex, Gemini CLI and friends", "Hooks the system limiter where it exists, enforces elsewhere",
"Sessions from both machines share one dot grid" "Top-up mode charges past, then re-arms on unplug",
"Thermal protection pauses charging when the pack runs hot",
"Energy flow figure: adapter → battery → system, live watts"
]} ]}
reversed reversed
> >
<RemoteDemo /> <BatteryDemo />
</BreakdownSection>
<BreakdownSection
kicker="Your day"
title="Meetings come to the notch"
body="Today sits in a dial — days behind, days ahead, dots where things happen. The slot counts your next call down, and when it gets close the island opens the day on its own."
bullets={[
"Next meeting counts down right in the slot",
"Near a call, the island opens the day itself",
"Meeting links get an Open button in the row",
"Real calendars and reminders — no sync service"
]}
>
<CalendarDemo />
</BreakdownSection>
<BreakdownSection
kicker="Camera"
title="A lens that stays asleep"
body="A live camera preview in the island for the seconds before a call. It never wakes on its own: permission-gated, inert until you click Start, and dead the moment you leave the panel."
bullets={[
"Nothing watches until Start camera gets a click",
"Live only while the panel sits open",
"A device picker for every connected camera",
"Opt-in module — in no default panel"
]}
reversed
>
<CameraDemo />
</BreakdownSection>
<BreakdownSection
kicker="Remote"
title="Any SSH box, one click away"
body="Point at any SSH-reachable box and click once — the nook-hook installs itself and its agents appear beside your local ones, indistinguishable."
bullets={[
"One click: copy the hook, run setup, probe answers pong",
"Remote agents wear a blue badge in the same dot grid",
"Works for Claude, Codex, Pi and many more"
]}
>
<SshInstallDemo />
</BreakdownSection> </BreakdownSection>
<BreakdownSection <BreakdownSection

View File

@@ -184,7 +184,7 @@ function RemoteMock() {
class="ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-semibold text-white/75" class="ml-auto rounded-full px-1.5 py-0.5 text-[10px] font-semibold text-white/75"
style={{ background: "rgba(255,255,255,0.08)" }} style={{ background: "rgba(255,255,255,0.08)" }}
> >
tailscale ssh
</span> </span>
</div> </div>
<p class="mt-0.5 text-[11px] text-white/50">Ready</p> <p class="mt-0.5 text-[11px] text-white/50">Ready</p>
@@ -330,7 +330,7 @@ export default function FeatureCollage() {
<CollageCell <CollageCell
kicker="Remote" kicker="Remote"
title="Agents on other machines" title="Agents on other machines"
body="Run agents on Linux boxes and SSH servers — a one-line install pipes their sessions back to the same island, over Tailscale or a reverse tunnel." body="Run agents on Linux boxes and SSH servers — one click installs the nook-hook, and their sessions join the same island."
> >
<InkStage class="w-full"> <InkStage class="w-full">
<div class="w-full max-w-[280px] text-left"> <div class="w-full max-w-[280px] text-left">