From f1e2d3b41b5b7937213fd3d001e83a08cad2eaf8 Mon Sep 17 00:00:00 2001 From: Parthiv Date: Sat, 27 Jun 2026 23:25:37 +0530 Subject: [PATCH] Show the orbit animation on the login panel (#1168) Co-authored-by: Claude Opus 4.8 --- apps/web/components/login-tools-panel.tsx | 382 +------------- apps/web/components/orbit-memory.tsx | 582 ++++++++++++++++++++++ 2 files changed, 602 insertions(+), 362 deletions(-) create mode 100644 apps/web/components/orbit-memory.tsx diff --git a/apps/web/components/login-tools-panel.tsx b/apps/web/components/login-tools-panel.tsx index 8b6f1da1..40c3b6d5 100644 --- a/apps/web/components/login-tools-panel.tsx +++ b/apps/web/components/login-tools-panel.tsx @@ -1,367 +1,8 @@ "use client" -import Image from "next/image" -import { useEffect, useState } from "react" -import { motion } from "motion/react" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" -import { ChromeIcon, RaycastIcon } from "@/components/integration-icons" -import { - ClaudeDesktopIcon, - GoogleDrive, - MCPIcon, - Notion, -} from "@ui/assets/icons" -import { Logo } from "@ui/assets/Logo" -import NovaOrb from "@/components/nova/nova-orb" - -type ToolNode = { - id: string - name: string - x: number - y: number - icon?: React.ComponentType<{ className?: string }> - iconSrc?: string -} - -type ContextConnection = { - from: ToolNode - to: ToolNode -} - -type ContextPhase = "idle" | "capture" | "hold" | "recall" - -const CENTER = { x: 50, y: 50 } -const IN_MS = 1100 -const HOLD_MS = 700 -const OUT_MS = 1100 -const TOTAL_MS = IN_MS + HOLD_MS + OUT_MS - -const TOOL_NODES: ToolNode[] = [ - { id: "chrome", name: "Chrome", x: 14, y: 20, icon: ChromeIcon }, - { id: "notion", name: "Notion", x: 84, y: 16, icon: Notion }, - { id: "drive", name: "Google Drive", x: 10, y: 52, icon: GoogleDrive }, - { id: "claude", name: "Claude", x: 90, y: 44, icon: ClaudeDesktopIcon }, - { id: "raycast", name: "Raycast", x: 76, y: 76, icon: RaycastIcon }, - { id: "mcp", name: "MCP", x: 22, y: 84, icon: MCPIcon }, - { - id: "claude-code", - name: "Claude Code", - x: 20, - y: 36, - iconSrc: "/images/plugins/claude-code.svg", - }, - { - id: "codex", - name: "Codex", - x: 92, - y: 28, - iconSrc: "/images/plugins/codex.png", - }, - { - id: "opencode", - name: "OpenCode", - x: 58, - y: 10, - iconSrc: "/images/plugins/opencode.svg", - }, - { - id: "hermes", - name: "Hermes", - x: 36, - y: 90, - iconSrc: "/images/plugins/hermes.svg", - }, - { - id: "openclaw", - name: "OpenClaw", - x: 68, - y: 68, - iconSrc: "/images/plugins/openclaw.svg", - }, -] - -const CONTEXT_FLOWS: [string, string][] = [ - ["chrome", "claude"], - ["notion", "raycast"], - ["drive", "claude-code"], - ["opencode", "codex"], - ["claude-code", "mcp"], - ["hermes", "openclaw"], - ["claude", "mcp"], - ["notion", "claude"], -] - -function nodeById(id: string) { - return TOOL_NODES.find((node) => node.id === id) -} - -function pickContextFlow(): ContextConnection | null { - const flow = CONTEXT_FLOWS[Math.floor(Math.random() * CONTEXT_FLOWS.length)] - if (!flow) return null - const [fromId, toId] = flow - const from = nodeById(fromId) - const to = nodeById(toId) - if (!from || !to) return null - return { from, to } -} - -function ToolNodeIcon({ - node, - role, -}: { - node: ToolNode - role?: "source" | "destination" -}) { - const Icon = node.icon - - return ( -
-
- {Icon ? ( - - ) : node.iconSrc ? ( - - ) : null} -
- - {node.name} - -
- ) -} - -function MemoryChip() { - return ( -
-
- - - -
-
- ) -} - -function AnimatedContextFlow({ - connection, - phase, -}: { - connection: ContextConnection - phase: ContextPhase -}) { - const { from, to } = connection - const dIn = `M ${from.x} ${from.y} L ${CENTER.x} ${CENTER.y}` - const dOut = `M ${CENTER.x} ${CENTER.y} L ${to.x} ${to.y}` - - const chipLeft = - phase === "recall" - ? [`${CENTER.x}%`, `${to.x}%`] - : phase === "hold" - ? `${CENTER.x}%` - : [`${from.x}%`, `${CENTER.x}%`] - - const chipTop = - phase === "recall" - ? [`${CENTER.y}%`, `${to.y}%`] - : phase === "hold" - ? `${CENTER.y}%` - : [`${from.y}%`, `${CENTER.y}%`] - - return ( -
- - - {phase !== "idle" && ( - - - - )} -
- ) -} - -function ToolsContextNetwork() { - const [connection, setConnection] = useState(null) - const [pulseId, setPulseId] = useState(0) - const [phase, setPhase] = useState("idle") - - useEffect(() => { - let cancelled = false - let pulseTimeout: ReturnType - - const runPulse = () => { - if (cancelled) return - const next = pickContextFlow() - if (!next) return - setConnection(next) - setPulseId((id) => id + 1) - pulseTimeout = setTimeout(runPulse, TOTAL_MS + 900 + Math.random() * 500) - } - - runPulse() - return () => { - cancelled = true - clearTimeout(pulseTimeout) - } - }, []) - - useEffect(() => { - if (!connection) return - - setPhase("capture") - const holdTimer = setTimeout(() => setPhase("hold"), IN_MS) - const recallTimer = setTimeout(() => setPhase("recall"), IN_MS + HOLD_MS) - const idleTimer = setTimeout(() => setPhase("idle"), TOTAL_MS) - - return () => { - clearTimeout(holdTimer) - clearTimeout(recallTimer) - clearTimeout(idleTimer) - } - }, [connection]) - - const sourceRole = (nodeId: string) => - connection && - connection.from.id === nodeId && - (phase === "capture" || phase === "hold") - ? ("source" as const) - : undefined - - const destRole = (nodeId: string) => - connection && connection.to.id === nodeId && phase === "recall" - ? ("destination" as const) - : undefined - - return ( -
- {connection && phase !== "idle" ? ( - - ) : null} - - {TOOL_NODES.map((node) => ( - - ))} - -
- - -
- -
-
-
-
- ) -} +import OrbitMemory from "@/components/orbit-memory" function LoginPanelBackground() { return ( @@ -373,6 +14,15 @@ function LoginPanelBackground() {
+
) } @@ -382,8 +32,16 @@ export function LoginToolsPanel() {