"use client" /** * OrbitMemory — Supermemory "Connect Your Tools" onboarding visual (desktop, left column). * Zero-dependency. A curated constellation of what Supermemory connects to — AI models & agents * (Claude, ChatGPT/Codex, Gemini, Copilot, Perplexity), the MCP protocol, and apps/tools — orbits * a central Supermemory mark on layered dotted tracks (continuous, gentle per-ring parallax). * A single requestAnimationFrame loop periodically connects to ONE node: a STATIC dashed neutral- * gray track fades in and staggered blue comet streaks glide along it while the node glows cobalt, * then it disconnects and moves to the next. Honors prefers-reduced-motion. */ import type React from "react" import { useEffect, useRef } from "react" const ICONS: Record = { googlegemini: '', openai: '', raycast: '\n\n', n8n: '', googledrive: '\n \n \n \n \n \n \n', claude: '', dropbox: '', figma: '\n\n\n\n\n\n\n\n\n\n\n\n\n', linear: '', slack: '\n \n \n \n \n \n \n', gmail: '\n \n \n \n \n \n \n \n \n \n', notion: '', perplexity: '\n\n\n\n', obsidian: '', githubcopilot: '', mcp: '', } const INTEG: { key?: string label?: string sub?: string ring: number ang: number size: number dim: boolean _x?: number _y?: number }[] = [ { ring: 0, ang: 0.0, size: 80, dim: false, key: "claude" }, { ring: 0, ang: 60.0, size: 80, dim: false, key: "openai" }, { ring: 0, ang: 120.0, size: 80, dim: false, key: "googlegemini" }, { ring: 0, ang: 180.0, size: 80, dim: false, key: "githubcopilot" }, { ring: 0, ang: 240.0, size: 80, dim: false, key: "perplexity" }, { ring: 0, ang: 300.0, size: 80, dim: false, key: "mcp", sub: "MCP" }, { ring: 1, ang: 18.0, size: 58, dim: true, key: "notion" }, { ring: 1, ang: 54.0, size: 58, dim: true, key: "slack" }, { ring: 1, ang: 90.0, size: 58, dim: true, key: "linear" }, { ring: 1, ang: 126.0, size: 58, dim: true, key: "figma" }, { ring: 1, ang: 162.0, size: 58, dim: true, key: "googledrive" }, { ring: 1, ang: 198.0, size: 58, dim: true, key: "gmail" }, { ring: 1, ang: 234.0, size: 58, dim: true, key: "obsidian" }, { ring: 1, ang: 270.0, size: 58, dim: true, key: "dropbox" }, { ring: 1, ang: 306.0, size: 58, dim: true, key: "n8n" }, { ring: 1, ang: 342.0, size: 58, dim: true, key: "raycast" }, ] const DW = 780 const DH = 1024 const CX = 390 const CY = 512 const RR = [200, 335] const SPEED = [1.0, 0.78] const DRAW: [number, number, number, string][] = [ [125, 0.28, 1.7, "#74a8f6"], [200, 0.18, 1.9, "#4389ff"], [335, 0.14, 1.8, "#4389ff"], [378, 0.05, 1.7, "#74a8f6"], ] const ACCENT: Record = { claude: "217,119,87", openai: "190,210,245", googlegemini: "80,130,255", githubcopilot: "190,210,245", perplexity: "32,184,205", mcp: "190,210,245", notion: "205,213,235", slack: "54,197,240", linear: "110,120,230", figma: "162,89,255", googledrive: "0,180,90", gmail: "234,67,53", obsidian: "138,99,246", dropbox: "40,120,255", n8n: "234,75,113", raycast: "255,99,99", } const NC = 2 const HL = 22 const P1 = "M86.9333 31.8278H56.1203V5.39087H46.1648V34.0754C46.1648 37.122 47.3832 40.0479 49.5488 42.2037L74.7085 67.2494L81.7477 60.2421L63.1652 41.7439H86.9389V31.8336L86.9333 31.8278Z" const P2 = "M10.4478 17.1572L28.5503 35.6573H5.39062V45.5685H35.4075V72.0078H45.1057V43.3207C45.1057 40.2736 43.9188 37.3474 41.8091 35.1917L17.3051 10.1492L10.4478 17.1572Z" const SPARK_HTML = `` export default function OrbitMemory({ className = "", style, grain = true, }: { className?: string style?: React.CSSProperties grain?: boolean }) { const stageRef = useRef(null) const tileRefs = useRef([]) const trackRef = useRef(null) const cmRefs = useRef([]) const cmgRefs = useRef([]) const gradRefs = useRef([]) const logoRef = useRef(null) const ringRefs = useRef([]) const dtrackRef = useRef(null) const omRefs = useRef([]) const omgRefs = useRef([]) const ogRefs = useRef([]) useEffect(() => { const stage = stageRef.current if (!stage) return const nodes = tileRefs.current const track = trackRef.current! const cms = cmRefs.current const cmgs = cmgRefs.current const grads = gradRefs.current const dtrack = dtrackRef.current! const oms = omRefs.current const omgs = omgRefs.current const ogrs = ogRefs.current let scale = 1 const measure = () => { scale = stage.clientWidth / DW } measure() const ro = new ResizeObserver(measure) ro.observe(stage) const setLine = ( el: Element, ax: number, ay: number, bx: number, by: number, ) => { el.setAttribute("x1", ax.toFixed(1)) el.setAttribute("y1", ay.toFixed(1)) el.setAttribute("x2", bx.toFixed(1)) el.setAttribute("y2", by.toFixed(1)) } const hideConn = () => { track.setAttribute("stroke-opacity", "0") dtrack.setAttribute("stroke-opacity", "0") for (let k = 0; k < NC; k++) { cms[k].setAttribute("opacity", "0") cmgs[k].setAttribute("opacity", "0") oms[k].setAttribute("opacity", "0") omgs[k].setAttribute("opacity", "0") } } let angle = 0 const place = () => { for (let i = 0; i < INTEG.length; i++) { const it = INTEG[i] const a = ((it.ang + angle * SPEED[it.ring]) * Math.PI) / 180 const R = RR[it.ring] const ux = CX + R * Math.cos(a) const uy = CY + R * Math.sin(a) it._x = ux it._y = uy const n = nodes[i] if (n) n.style.transform = `translate(-50%,-50%) translate(${((ux - CX) * scale).toFixed(2)}px,${((uy - CY) * scale).toFixed(2)}px)` } } if (matchMedia("(prefers-reduced-motion: reduce)").matches) { place() hideConn() return () => ro.disconnect() } const ORBIT = 2200 const RECV = 2000 const SEND = 2000 const DISC = 700 const INN: number[] = [] const OUT: number[] = [] for (let i = 0; i < INTEG.length; i++) { ;(INTEG[i].ring === 0 ? INN : OUT).push(i) } let phase = "orbit" let pT0 = 0 let ci = 0 let src = -1 let dst = -1 let last = 0 let raf = 0 let lastPing = 0 let ri = 0 const rings = ringRefs.current const ping = () => { const el = rings[ri++ % rings.length] if (!el) return el.getAnimations().forEach((a) => { a.cancel() }) el.animate( [ { transform: "scale(1)", opacity: 0, offset: 0 }, { transform: "scale(1.55)", opacity: 0.7, offset: 0.22 }, { transform: "scale(2)", opacity: 0.55, offset: 0.55 }, { transform: "scale(2.45)", opacity: 0, offset: 1 }, ], { duration: 2400, easing: "linear" }, ) } const BASE = 360 / 90 / 1000 const eo = (t: number) => 1 - (1 - t) ** 3 const ei = (t: number) => t * t * t const seg = ( now: number, trk: SVGLineElement, nx: number, ny: number, P: number, gr: SVGLinearGradientElement[], cr: SVGLineElement[], gl: SVGLineElement[], dir: number, off: number, ) => { setLine(trk, CX, CY, nx, ny) trk.setAttribute("stroke-opacity", (0.62 * P).toFixed(2)) const dx = nx - CX const dy = ny - CY const len = Math.hypot(dx, dy) || 1 const ux = dx / len const uy = dy / len for (let k = 0; k < NC; k++) { const f = (now / 1500 + (k + off) / NC) % 1 const t = dir < 0 ? 1 - f : f const pxc = CX + dx * t const pyc = CY + dy * t const ax = pxc - ux * HL const ay = pyc - uy * HL const bx = pxc + ux * HL const by = pyc + uy * HL setLine(gr[k], ax, ay, bx, by) setLine(cr[k], ax, ay, bx, by) setLine(gl[k], ax, ay, bx, by) const o = Math.sin(f * Math.PI) * P cr[k].setAttribute("opacity", o.toFixed(2)) gl[k].setAttribute("opacity", (0.6 * o).toFixed(2)) } } const frame = (now: number) => { if (!last) last = now const dt = Math.min(now - last, 50) last = now if (!pT0) pT0 = now const pt = now - pT0 if (phase === "orbit" && pt > ORBIT) { phase = "receive" pT0 = now src = INN[(ci * 5) % INN.length] dst = OUT[(ci * 7) % OUT.length] ci++ nodes[src]?.classList.add("sm-active") stage.style.setProperty( "--cm", `rgb(${ACCENT[INTEG[src].key!] || "91,157,255"})`, ) } else if (phase === "receive" && pt > RECV) { phase = "send" pT0 = now nodes[dst]?.classList.add("sm-active") } else if (phase === "send" && pt > SEND) { phase = "disconnect" pT0 = now nodes[src]?.classList.remove("sm-active") nodes[dst]?.classList.remove("sm-active") } else if (phase === "disconnect" && pt > DISC) { phase = "orbit" pT0 = now src = -1 dst = -1 } angle += BASE * dt place() if (phase === "send" && now - lastPing > 820) { ping() lastPing = now } if (src < 0) { hideConn() } else { const cpt = now - pT0 let ps = 1 if (phase === "receive") ps = eo(Math.min(cpt / RECV, 1)) else if (phase === "disconnect") ps = 1 - ei(Math.min(cpt / DISC, 1)) let pd = 0 if (phase === "send") pd = eo(Math.min(cpt / SEND, 1)) else if (phase === "disconnect") pd = 1 - ei(Math.min(cpt / DISC, 1)) seg( now, track, INTEG[src]._x!, INTEG[src]._y!, ps, grads, cms, cmgs, -1, 0, ) seg( now, dtrack, INTEG[dst]._x!, INTEG[dst]._y!, pd, ogrs, oms, omgs, 1, 0.5, ) } raf = requestAnimationFrame(frame) } raf = requestAnimationFrame(frame) return () => { cancelAnimationFrame(raf) ro.disconnect() } }, []) return (
{INTEG.map((it, i) => (
{ if (el) tileRefs.current[i] = el }} className={`sm-tile${it.dim ? " sm-dim" : ""}`} style={ { width: `${((it.size / DW) * 100).toFixed(3)}cqmin`, height: `${((it.size / DW) * 100).toFixed(3)}cqmin`, ["--s" as unknown as string]: `${((it.size / DW) * 100).toFixed(3)}cqmin`, ["--ac" as unknown as string]: ACCENT[it.key!] || "47,123,255", } as React.CSSProperties } > {it.sub ? ( {it.sub} ) : it.label ? ( {it.label} ) : ( )}
))}
{(["ring-0", "ring-1", "ring-2", "ring-3"] as const).map((rk, k) => ( { if (el) ringRefs.current[k] = el }} /> ))}
{grain &&
}
) } const CSS = ` .sm-orbit{position:relative;width:100%;height:100%;container-type:size;overflow:hidden;isolation:isolate;background:#05060b;--ease-soft:cubic-bezier(.45,0,.55,1)} .sm-glow{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);border-radius:50%;pointer-events:none} .sm-glow-1{width:150cqmin;height:150cqmin;background:radial-gradient(circle, rgba(8,100,240,.40) 0%, rgba(6,98,239,.20) 28%, rgba(6,98,239,.08) 50%, transparent 70%);opacity:.68} .sm-glow-2{width:92cqmin;height:92cqmin;background:radial-gradient(circle, rgba(50,126,255,.46) 0%, rgba(47,123,255,.18) 38%, transparent 66%);opacity:.66} .sm-glow-3{width:54cqmin;height:54cqmin;background:radial-gradient(circle, rgba(98,158,255,.70) 0%, rgba(30,100,240,.28) 42%, transparent 68%);opacity:.8} .sm-lines{position:absolute;inset:0;width:100%;height:100%;z-index:2;overflow:visible} .sm-tiles{position:absolute;inset:0;z-index:6} .sm-tile{--ac:47,123,255;position:absolute;left:50%;top:50%;display:grid;place-items:center;border-radius:28%;background:rgba(15,19,31,.94);border:0.3px solid rgba(245,247,252,.12);box-shadow:0 calc(var(--s)*0.1111) calc(var(--s)*0.2778) calc(var(--s)*-0.0694) rgba(0,0,0,.5),inset 0 calc(var(--s)*0.0139) calc(var(--s)*0.0097) rgba(255,255,255,.12),inset 0 calc(var(--s)*0.1804) calc(var(--s)*0.1804) rgba(0,0,0,.25),inset 0 calc(var(--s)*-0.1353) calc(var(--s)*0.1804) rgba(0,0,0,.25),inset 0 calc(var(--s)*-0.0451) calc(var(--s)*0.1804) rgba(186,209,255,.25),0 calc(var(--s)*0.1227) calc(var(--s)*0.2863) rgba(3,4,28,.39),0 calc(var(--s)*0.5317) calc(var(--s)*0.5317) rgba(3,4,28,.34),0 calc(var(--s)*1.1452) calc(var(--s)*0.6953) rgba(3,4,28,.2),0 calc(var(--s)*2.045) calc(var(--s)*0.818) rgba(3,4,28,.06),0 calc(var(--s)*-0.1636) calc(var(--s)*4.09) rgba(41,130,255,.5);transition:box-shadow .5s var(--ease-soft),border-color .5s var(--ease-soft),border-width .5s var(--ease-soft);will-change:transform} .sm-tile.sm-active{border-color:rgba(var(--ac),.6);border-width:0.3px;box-shadow:0 calc(var(--s)*0.1111) calc(var(--s)*0.2778) calc(var(--s)*-0.0694) rgba(0,0,0,.5),inset 0 calc(var(--s)*0.0139) calc(var(--s)*0.0097) rgba(255,255,255,.12),inset 0 calc(var(--s)*0.1804) calc(var(--s)*0.1804) rgba(var(--ac),.3),inset 0 calc(var(--s)*-0.1353) calc(var(--s)*0.1804) rgba(0,0,0,.25),inset 0 calc(var(--s)*-0.0451) calc(var(--s)*0.1804) rgba(186,209,255,.25),0 calc(var(--s)*0.1227) calc(var(--s)*0.2863) rgba(3,4,28,.39),0 calc(var(--s)*0.5317) calc(var(--s)*0.5317) rgba(3,4,28,.34),0 calc(var(--s)*1.1452) calc(var(--s)*0.6953) rgba(3,4,28,.2),0 calc(var(--s)*2.045) calc(var(--s)*0.818) rgba(3,4,28,.06),0 calc(var(--s)*-0.1636) calc(var(--s)*4.09) rgba(var(--ac),.62)} .sm-glyph{width:46%;height:46%;color:#eef3ff;opacity:.92;display:grid;place-items:center} .sm-glyph svg{width:100%;height:100%;display:block} .sm-tile.sm-dim .sm-glyph{opacity:.72} .sm-tile.sm-active .sm-glyph{color:#fff;opacity:1} .sm-glabel{font:600 2.55cqmin/1 -apple-system,system-ui,sans-serif;letter-spacing:.08em;color:#eef3ff;opacity:.92} .sm-iconsub{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5cqmin;width:100%;height:100%} .sm-iconsub .sm-glyph{width:42%;height:42%} .sm-gsub{font:600 1.4cqmin/1 -apple-system,system-ui,sans-serif;letter-spacing:.01em;color:#cfe0ff;opacity:.8} .sm-tile.sm-active .sm-gsub{color:#e6f0ff;opacity:1} .sm-tile.sm-active .sm-glabel{color:#fff;opacity:1} .sm-grain{position:absolute;inset:0;z-index:7;pointer-events:none;opacity:.05;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27200%27%20height%3D%27200%27%3E%3Cfilter%20id%3D%27n%27%3E%3CfeTurbulence%20type%3D%27fractalNoise%27%20baseFrequency%3D%270.9%27%20numOctaves%3D%272%27%20stitchTiles%3D%27stitch%27%2F%3E%3CfeColorMatrix%20type%3D%27saturate%27%20values%3D%270%27%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20filter%3D%27url%28%23n%29%27%2F%3E%3C%2Fsvg%3E");background-size:200px 200px} .sm-core{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:5;width:0;height:0;display:grid;place-items:center} .sm-core>*{position:absolute} .sm-cglow{width:43.6cqmin;height:43.6cqmin;border-radius:50%;background:rgba(47,123,255,.63);filter:blur(7.7cqmin);pointer-events:none} .sm-clogo{width:0;height:0;display:grid;place-items:center;will-change:transform} .sm-clogo>*{position:absolute} .sm-ring{position:absolute;width:17cqmin;height:17cqmin;border-radius:50%;background:radial-gradient(circle, transparent 56%, rgba(130,185,255,.6) 74%, rgba(175,210,255,.22) 84%, transparent 100%);mix-blend-mode:screen;opacity:0;will-change:transform,opacity;pointer-events:none} .sm-tile2{width:17.7cqmin;height:17.7cqmin;border-radius:29%;background:linear-gradient(180deg,#293047 0%,#384369 19%,#21273f 51%,#141824 100%);box-shadow:inset 0 .5cqmin .55cqmin rgba(0,0,0,.28),inset 0 -.4cqmin .55cqmin rgba(0,0,0,.28),inset 0 -.14cqmin .55cqmin rgba(186,209,255,.32),0 .35cqmin .85cqmin rgba(3,4,28,.4),0 1.55cqmin 1.55cqmin rgba(3,4,28,.34),0 3.3cqmin 2cqmin rgba(3,4,28,.2),0 -.5cqmin 9cqmin rgba(41,130,255,.5)} .sm-spark{width:12cqmin;height:9.8cqmin;display:grid;place-items:center} .sm-spark svg{width:100%;height:100%;display:block;overflow:visible} `