import { For, createMemo, onMount, onCleanup, type JSX } from "solid-js"; import { Campfire, type CampfireState } from "./Campfire"; /** * Faithful mock of the island's collapsed pill and expanded panel. * Geometry follows the app's IslandSurfaceShape: the top edge spans the * full width, the notch housing sits centered, and each corner is a * concave scoop (quadratic curve with the control ON the top edge). */ /* The app's island indicator palette (AgentsModuleView.statusColor). */ export const STATUS = { running: "#6EA7FF", ready: "#6FB982", attention: "#E7A762", error: "#E5484D", idle: "rgba(255,255,255,0.35)" }; /** MetricPalette.calm — the desaturated sky of every metric bar. */ export const CALM = "#6EA7FF"; export const WARM = "#E7A762"; export const CRITICAL = "rgba(229,72,77,0.9)"; /* NookPalette.accent — brand + live interactive state, never status. */ export const ACCENT = "#4897b2"; /** The expanded panel's card chrome: white 5.5% fill, white 7% stroke. */ export const CARD_FILL = "rgba(255,255,255,0.055)"; export const CARD_STROKE = "rgba(255,255,255,0.07)"; /** * One module-card surface shared by every mock panel — the app's * cardChrome(): rounded 12, hairline stroke, white-on-black fill. */ export function ModuleCard(props: { children: JSX.Element; class?: string; style?: Record; }) { return (
{props.children}
); } /** * Island surface, faithful to the app's IslandSurfaceShape: the top edge * spans the full outer width while the body is inset, each corner joining * them with a concave quad flare (control point ON the top edge), the * bottom a convex 14px round. Rendered as an inline SVG with * foreignObject-clip via CSS `clip-path: path(...)`. */ const FLARE = 22; // the app's topRadius — how far the wings taper inward const BOTTOM_R = 14; function islandPath(w: number, h: number): string { const tr = Math.min(FLARE, w / 2); const br = Math.min(BOTTOM_R, tr); // Mirrors IslandSurfaceShape.path exactly (same node order, same controls). return ( `M0,0 L${w},0 ` + `Q${w - tr},0 ${w - tr},${tr} ` + `L${w - tr},${h - br} ` + `Q${w - tr},${h} ${w - tr - br},${h} ` + `L${tr + br},${h} ` + `Q${tr},${h} ${tr},${h - br} ` + `L${tr},${tr} ` + `Q${tr},0 0,0 Z` ); } function IslandSurface(props: { children: JSX.Element; class?: string }) { let el: HTMLDivElement | undefined; // The path must track the element's real size; a fixed viewBox would // stretch the flare. Measure on mount + resize. onMount(() => { const node = el; if (!node) return; const apply = () => { const r = node.getBoundingClientRect(); node.style.clipPath = `path("${islandPath(r.width, r.height)}")`; }; apply(); const observer = new ResizeObserver(apply); observer.observe(node); onCleanup(() => observer.disconnect()); }); return (
{props.children}
); } /** ISLAND … */ const NOTCH_NAME = "The Nook"; /* ── Collapsed pill ─────────────────────────────────────────────────── */ /** * The collapsed pill: campfire slot left, camera housing center, live * data slot right. Slot squares are native (no menu-bar stub — the wings * hang from nothing, like a floating notch). */ export function IslandPill(props: { campfire?: CampfireState; right?: JSX.Element; }) { return (
{/* camera housing: lens dot centered like the real notch */}
{props.right}
); } /** The agents dot-grid slot indicator: balanced rows of status squares. */ export function AgentDotGrid(props: { states: (keyof typeof STATUS)[] }) { // Balanced rows: 3+3+2 for 8, 2x2 for 4, 3 for 3 … app: 1,2,3,4=2x2, // 5=[3,2], 6=[3,3], 7=[4,3], 8=[4,4], 9=[3,3,3] const rows = (n: number): number[] => { switch (n) { case 1: return [1]; case 2: return [2]; case 3: return [3]; case 4: return [2, 2]; case 5: return [3, 2]; case 6: return [3, 3]; case 7: return [4, 3]; case 8: return [4, 4]; default: return [4, 4]; } }; const sizes = rows(props.states.length); const groups = createMemo(() => { let cursor = 0; return sizes.map((count) => props.states.slice(cursor, (cursor += count))); }); return (
{(group) => (
{(s) => ( )}
)}
); } /* ── Expanded panel ─────────────────────────────────────────────────── */ /** * The expanded island: header ("The Nook" + "N active" + controls), * tab strip, divider, then one panel page. Width ~ the app's expanded * footprint on a 14" display. */ export function IslandExpanded(props: { activeCount: number; tabs: { icon: string; label: string; active?: boolean }[]; children: JSX.Element; }) { return (
{/* header */}
{NOTCH_NAME}
{props.activeCount} active
{/* tab strip: one icon button per panel; active wears accent 22% */}
{(tab) => ( {tab.icon} )}
{props.children}
); }