Make desktop login responsive

This commit is contained in:
Sreeram Sreedhar 2026-06-22 23:13:01 -07:00
parent 41c0a9333b
commit 8fd2b59014
2 changed files with 824 additions and 75 deletions

View file

@ -84,3 +84,509 @@ body {
user-select: none;
-webkit-user-select: none;
}
.desktop-login-page {
display: grid;
height: 100dvh;
min-height: 100dvh;
overflow: hidden;
grid-template-columns: 1fr;
}
@media (min-width: 1024px) {
.desktop-login-page {
grid-template-columns: minmax(0, 65fr) minmax(360px, 35fr);
}
}
.desktop-login-tools-panel {
position: relative;
display: none;
min-height: 0;
overflow: hidden;
border-right: 1px solid rgb(255 255 255 / 0.06);
}
@media (min-width: 1024px) {
.desktop-login-tools-panel {
display: block;
}
}
.desktop-login-panel-orb {
position: absolute;
inset: 0;
background:
radial-gradient(
ellipse 130% 100% at 50% 45%,
rgba(70, 155, 255, 0.24) 0%,
rgba(35, 100, 210, 0.14) 38%,
rgba(8, 22, 45, 0.55) 68%,
#030912 100%
),
radial-gradient(
ellipse 90% 70% at 50% 100%,
rgba(50, 130, 240, 0.16) 0%,
transparent 65%
);
}
.desktop-login-panel-glow {
position: absolute;
inset: -10%;
background:
radial-gradient(
circle at 50% 52%,
rgba(85, 150, 255, 0.24),
transparent 24%
),
radial-gradient(
circle at 48% 52%,
rgba(255, 255, 255, 0.18),
transparent 8%
),
radial-gradient(
ellipse 60% 38% at 52% 42%,
rgba(40, 96, 255, 0.22),
transparent 74%
);
opacity: 0.85;
}
.desktop-login-panel-vignette {
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgb(7 16 36 / 0.2),
transparent 42%,
rgb(3 9 18 / 0.36)
);
}
.desktop-login-tools-network {
position: relative;
z-index: 1;
height: 100%;
min-height: 360px;
}
.desktop-login-panel-caption {
position: absolute;
left: 50%;
bottom: 2rem;
z-index: 2;
margin: 0;
white-space: nowrap;
transform: translateX(-50%);
color: rgb(255 255 255 / 0.45);
font-size: 0.875rem;
text-align: center;
}
.desktop-login-tool-node-wrap {
position: absolute;
z-index: 2;
transform: translate(-50%, -50%);
}
.desktop-login-tool-node {
display: flex;
width: 2.875rem;
height: 2.875rem;
align-items: center;
justify-content: center;
border: 1px solid rgb(255 255 255 / 0.12);
border-radius: 0.8125rem;
background: rgb(12 22 40 / 0.82);
box-shadow:
0 1px 0 rgb(255 255 255 / 0.1) inset,
0 6px 20px rgb(0 0 0 / 0.22);
backdrop-filter: blur(12px);
transition:
border-color 0.35s ease,
background 0.35s ease,
box-shadow 0.35s ease,
transform 0.35s cubic-bezier(0.34, 1.2, 0.64, 1);
}
.desktop-login-tool-node-icon {
width: 1.375rem;
height: 1.375rem;
flex-shrink: 0;
font-weight: 700;
font-size: 0.625rem;
line-height: 1.375rem;
text-align: center;
opacity: 0.88;
transition: opacity 0.35s ease;
}
.desktop-login-tool-node-source {
border-color: rgb(100 180 255 / 0.55);
background: rgb(14 28 50 / 0.92);
box-shadow:
0 0 0 1px rgb(100 180 255 / 0.25),
0 0 24px rgb(75 160 250 / 0.28),
0 1px 0 rgb(255 255 255 / 0.1) inset;
transform: scale(1.05);
}
.desktop-login-tool-node-destination {
border-color: rgb(130 210 255 / 0.65);
background: rgb(16 32 58 / 0.95);
box-shadow:
0 0 0 1px rgb(130 210 255 / 0.3),
0 0 32px rgb(100 180 255 / 0.35),
0 1px 0 rgb(255 255 255 / 0.12) inset;
transform: scale(1.08);
}
.desktop-login-tool-node-source .desktop-login-tool-node-icon,
.desktop-login-tool-node-destination .desktop-login-tool-node-icon {
opacity: 1;
}
.desktop-login-tool-node-label {
position: absolute;
top: calc(100% + 0.4rem);
left: 50%;
max-width: 5.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transform: translateX(-50%);
color: rgb(200 225 255 / 0.95);
font-weight: 500;
font-size: 0.6875rem;
text-shadow: 0 1px 8px rgb(0 0 0 / 0.65);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease;
}
.desktop-login-tool-node-label-visible {
opacity: 1;
}
.desktop-login-active-path {
stroke-dasharray: 1;
stroke-dashoffset: 1;
animation: desktop-login-draw-path 1.1s cubic-bezier(0.33, 0, 0.2, 1) forwards;
}
.desktop-login-memory-chip-motion {
position: absolute;
z-index: 2;
transform: translate(-50%, -50%);
}
.desktop-login-chip-capture {
animation: desktop-login-chip-capture 1.1s cubic-bezier(0.35, 0, 0.15, 1)
forwards;
}
.desktop-login-chip-hold {
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(1.05);
}
.desktop-login-chip-recall {
animation: desktop-login-chip-recall 1.1s cubic-bezier(0.35, 0, 0.15, 1)
forwards;
}
.desktop-login-memory-chip {
display: flex;
width: 2.75rem;
height: 1.625rem;
align-items: center;
justify-content: center;
border: 1px solid rgb(180 220 255 / 0.35);
border-radius: 0.375rem;
background: rgb(12 24 44 / 0.95);
box-shadow:
0 0 16px rgb(100 180 255 / 0.4),
0 1px 0 rgb(255 255 255 / 0.12) inset;
}
.desktop-login-memory-chip-lines {
display: flex;
width: 1.5rem;
flex-direction: column;
gap: 0.2rem;
}
.desktop-login-memory-chip-lines span {
display: block;
height: 0.125rem;
border-radius: 9999px;
background: rgb(180 220 255 / 0.55);
}
.desktop-login-memory-chip-lines span:nth-child(2) {
width: 78%;
opacity: 0.75;
}
.desktop-login-memory-chip-lines span:nth-child(3) {
width: 52%;
opacity: 0.5;
}
.desktop-login-orb-wrap {
position: absolute;
top: 50%;
left: 50%;
z-index: 3;
transform: translate(-50%, -50%);
}
.desktop-login-nova-orb {
position: relative;
display: flex;
width: 9rem;
height: 9rem;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 9999px;
box-shadow:
0.3rem 0.6rem 1.2rem #0a0e14 inset,
0 0 54px rgb(65 135 255 / 0.58);
filter: blur(1px);
transition: transform 0.35s ease;
}
.desktop-login-nova-orb-active {
animation: desktop-login-orb-pulse 0.55s ease-out;
}
.desktop-login-nova-gradient {
position: absolute;
top: -32%;
left: -58%;
width: 16rem;
height: 16rem;
background:
radial-gradient(
circle at 28% 28%,
rgba(255, 255, 255, 0.95),
transparent 12%
),
conic-gradient(from 30deg, #7ce7ff, #295fff, #7c5cff, #22d3ee, #7ce7ff);
animation: desktop-login-orb-rotate 12s linear infinite;
}
.desktop-login-auth-pane {
display: flex;
min-height: 0;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
}
.desktop-login-auth-header {
height: 5.375rem;
padding-inline: 1.5rem;
}
.desktop-login-memory-api-button {
height: 2.75rem;
padding-inline: 1rem;
font-size: 1rem;
}
.desktop-login-auth-content {
display: flex;
min-height: 0;
flex: 1;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem 2.5rem;
}
.desktop-login-auth-stack {
width: 100%;
max-width: 24.375rem;
}
.desktop-login-headline {
margin-bottom: 2rem;
}
.desktop-login-headline h1 {
font-size: 1.75rem;
}
.desktop-login-subtitle {
margin-top: 1rem;
font-size: 0.875rem;
}
.desktop-login-card {
border-radius: 1.375rem;
padding: 2rem;
}
.desktop-login-form {
gap: 0.75rem;
}
.desktop-login-email-form {
gap: 1.5rem;
}
.desktop-login-email-input,
.desktop-login-primary-button {
height: 3.5rem;
}
.desktop-login-terms {
font-size: 0.875rem;
}
@media (max-width: 1023px) {
.desktop-login-page {
height: 100dvh;
overflow-y: auto;
}
.desktop-login-auth-pane {
min-height: 100dvh;
}
.desktop-login-auth-content {
align-items: center;
padding-inline: 1rem;
}
.desktop-login-auth-stack {
max-width: 25rem;
}
}
@media (max-width: 760px) {
.desktop-login-auth-header {
height: 4.5rem;
padding-inline: 1rem;
}
.desktop-login-memory-api-button {
height: 2.5rem;
padding-inline: 0.75rem;
font-size: 0.875rem;
}
.desktop-login-headline h1 {
font-size: 1.5rem;
}
.desktop-login-card {
padding: 1.5rem;
}
}
@media (max-height: 620px) {
.desktop-login-auth-header {
height: 3.75rem;
}
.desktop-login-auth-content {
align-items: flex-start;
padding-block: 0.5rem 1rem;
}
.desktop-login-headline {
margin-bottom: 0.75rem;
}
.desktop-login-headline h1 {
font-size: 1.375rem;
}
.desktop-login-subtitle {
display: none;
}
.desktop-login-card {
padding: 1.125rem 1.25rem;
border-radius: 1rem;
}
.desktop-login-form {
gap: 0.625rem;
}
.desktop-login-email-form {
gap: 0.75rem;
}
.desktop-login-email-input,
.desktop-login-primary-button {
height: 2.75rem;
}
.desktop-login-terms {
font-size: 0.6875rem;
}
.desktop-login-panel-caption {
bottom: 1rem;
}
}
@media (max-width: 1180px) and (min-width: 1024px) {
.desktop-login-page {
grid-template-columns: minmax(0, 58fr) minmax(360px, 42fr);
}
.desktop-login-panel-caption {
font-size: 0.75rem;
}
}
@keyframes desktop-login-draw-path {
to {
stroke-dashoffset: 0;
}
}
@keyframes desktop-login-chip-capture {
from {
left: var(--from-x);
top: var(--from-y);
}
to {
left: 50%;
top: 50%;
}
}
@keyframes desktop-login-chip-recall {
from {
left: 50%;
top: 50%;
}
to {
left: var(--to-x);
top: var(--to-y);
}
}
@keyframes desktop-login-orb-rotate {
to {
transform: rotate(360deg);
}
}
@keyframes desktop-login-orb-pulse {
0%,
100% {
transform: scale(1);
}
45% {
transform: scale(1.08);
}
}

View file

@ -15,7 +15,13 @@ import { Label } from "@ui/components/label"
import { TextSeparator } from "@ui/components/text-separator"
import { Loader2 } from "lucide-react"
import { useRouter } from "next/navigation"
import { type FormEvent, useEffect, useState } from "react"
import {
type CSSProperties,
type FormEvent,
type ReactNode,
useEffect,
useState,
} from "react"
import {
beginBrowserAuth,
desktopDevAuthEnabled,
@ -106,13 +112,13 @@ export default function LoginPage() {
const isAuthBusy = isBrowserAuthPending || isSubmitting
return (
<main className="grid h-screen min-h-screen overflow-hidden bg-[#030912] text-foreground lg:grid-cols-[65%_35%]">
<main className="desktop-login-page bg-[#030912] text-foreground">
<DesktopLoginToolsPanel />
<section className="relative z-10 flex min-h-0 min-w-0 flex-col">
<section className="desktop-login-auth-pane relative z-10 min-w-0">
<header
data-tauri-drag-region
className="flex h-[86px] shrink-0 items-center justify-between px-6"
className="desktop-login-auth-header flex shrink-0 items-center justify-between"
>
<div className="flex min-w-0 items-center">
<Logo className="h-7 shrink-0" />
@ -123,7 +129,7 @@ export default function LoginPage() {
<Button
asChild
variant="newDefault"
className="h-11 rounded-2xl px-4 text-base text-white"
className="desktop-login-memory-api-button rounded-2xl text-white"
>
<a
href="https://console.supermemory.ai"
@ -135,19 +141,19 @@ export default function LoginPage() {
</Button>
</header>
<div className="flex min-h-0 flex-1 flex-col items-center justify-center px-6 pb-10">
<div className="w-full max-w-[390px]">
<div className="mb-8 text-center">
<h1 className="text-balance font-medium text-[28px] leading-tight text-[#f7f9fc]">
<div className="desktop-login-auth-content">
<div className="desktop-login-auth-stack">
<div className="desktop-login-headline text-center">
<h1 className="text-balance font-medium leading-tight text-[#f7f9fc]">
Never forget anything, anywhere
<span className="block text-[#4BA0FA]">with supermemory</span>
</h1>
<p className="mt-4 text-muted-foreground/50 text-sm">
<p className="desktop-login-subtitle text-muted-foreground/50">
Save from Chrome, Notion, X - search it all in one place.
</p>
</div>
<div className="relative rounded-[22px] bg-linear-to-b from-[#06101F] to-[#030912] px-8 py-8 shadow-[1.5px_1.5px_20px_0_rgba(0,0,0,0.65),1px_1.5px_2px_0_rgba(128,189,255,0.07)_inset,-0.5px_-1.5px_4px_0_rgba(0,35,73,0.40)_inset]">
<div className="desktop-login-card relative bg-linear-to-b from-[#06101F] to-[#030912] shadow-[1.5px_1.5px_20px_0_rgba(0,0,0,0.65),1px_1.5px_2px_0_rgba(128,189,255,0.07)_inset,-0.5px_-1.5px_4px_0_rgba(0,35,73,0.40)_inset]">
<div className={isBrowserAuthPending ? "invisible" : undefined}>
<div className="mb-2 flex justify-end">
<Badge className="h-5 rounded-md px-2 text-[10px]">
@ -155,7 +161,7 @@ export default function LoginPage() {
</Badge>
</div>
<div className="flex flex-col gap-3">
<div className="desktop-login-form flex flex-col">
<ExternalAuthButton
authIcon={<GoogleIcon />}
authProvider="Google"
@ -177,7 +183,7 @@ export default function LoginPage() {
<TextSeparator text="OR" />
<form
className="flex flex-col gap-6"
className="desktop-login-email-form flex flex-col"
onSubmit={onBrowserAuthSubmit}
>
<Input
@ -186,12 +192,12 @@ export default function LoginPage() {
placeholder="your@email.com"
type="email"
autoComplete="email"
className="h-14 rounded-xl border-[#17202e] bg-[#040a14]/70 px-6 text-base text-foreground placeholder:text-muted-foreground/45"
className="desktop-login-email-input rounded-xl border-[#17202e] bg-[#040a14]/70 px-6 text-base text-foreground placeholder:text-muted-foreground/45"
/>
<Button
type="submit"
className="h-14 rounded-xl bg-linear-to-r from-[#2935ff] to-[#2f78ff] text-lg text-white hover:from-[#3440ff] hover:to-[#3b83ff]"
className="desktop-login-primary-button rounded-xl bg-linear-to-r from-[#2935ff] to-[#2f78ff] text-lg text-white hover:from-[#3440ff] hover:to-[#3b83ff]"
disabled={isAuthBusy}
>
<Logo className="size-5" />
@ -199,7 +205,7 @@ export default function LoginPage() {
</Button>
</form>
<p className="text-center text-muted-foreground/50 text-sm">
<p className="desktop-login-terms text-center text-muted-foreground/50">
By continuing, you agree to our{" "}
<a
className="underline underline-offset-4 hover:text-muted-foreground"
@ -276,84 +282,321 @@ export default function LoginPage() {
)
}
type ToolNode = {
id: string
name: string
x: number
y: number
icon: (props: { className?: string }) => ReactNode
}
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: ChromeLogo },
{ 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: RaycastMark },
{ id: "mcp", name: "MCP", x: 22, y: 84, icon: MCPIcon },
{
id: "claude-code",
name: "Claude Code",
x: 20,
y: 36,
icon: CodeMark,
},
{ id: "codex", name: "Codex", x: 92, y: 28, icon: CodexMark },
{ id: "opencode", name: "OpenCode", x: 58, y: 10, icon: OpenCodeMark },
{ id: "hermes", name: "Hermes", x: 36, y: 90, icon: HermesMark },
{ id: "openclaw", name: "OpenClaw", x: 68, y: 68, icon: OpenClawMark },
]
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 DesktopLoginToolsPanel() {
const [connection, setConnection] = useState<ContextConnection | null>(null)
const [pulseId, setPulseId] = useState(0)
const [phase, setPhase] = useState<ContextPhase>("idle")
useEffect(() => {
let cancelled = false
let pulseTimeout: ReturnType<typeof setTimeout>
function 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 destinationRole = (nodeId: string) =>
connection && connection.to.id === nodeId && phase === "recall"
? ("destination" as const)
: undefined
return (
<aside className="relative hidden min-h-0 overflow-hidden border-white/[0.06] border-r lg:block">
<div
className="absolute inset-0"
style={{
background:
"radial-gradient(ellipse 130% 100% at 50% 45%, rgba(70,155,255,0.26) 0%, rgba(35,100,210,0.15) 38%, rgba(8,22,45,0.58) 68%, #030912 100%), radial-gradient(ellipse 90% 70% at 50% 100%, rgba(50,130,240,0.18) 0%, transparent 65%)",
}}
/>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_52%,rgba(85,150,255,0.22),transparent_24%),radial-gradient(circle_at_48%_52%,rgba(255,255,255,0.18),transparent_8%)] opacity-80" />
<div className="absolute inset-0 bg-linear-to-b from-[#071024]/20 via-transparent to-[#030912]/35" />
<aside className="desktop-login-tools-panel" aria-hidden>
<div className="desktop-login-panel-orb" />
<div className="desktop-login-panel-glow" />
<div className="desktop-login-panel-vignette" />
<div className="relative z-10 h-full">
<FloatingToolNode x={14} y={22} label="Chrome">
<ChromeLogo className="size-5" />
</FloatingToolNode>
<FloatingToolNode x={84} y={18} label="Notion">
<Notion className="size-5" />
</FloatingToolNode>
<FloatingToolNode x={11} y={53} label="Google Drive">
<GoogleDrive className="size-5" />
</FloatingToolNode>
<FloatingToolNode x={88} y={45} label="Claude">
<ClaudeDesktopIcon className="size-5" />
</FloatingToolNode>
<FloatingToolNode x={22} y={84} label="MCP">
<MCPIcon className="size-5" />
</FloatingToolNode>
<FloatingToolNode x={68} y={70} label="Raycast">
<RaycastMark className="size-5" />
</FloatingToolNode>
<FloatingToolNode x={20} y={38} label="Code">
<span className="font-semibold text-[#ff9a63] text-lg"></span>
</FloatingToolNode>
<FloatingToolNode x={58} y={14} label="OpenCode">
<span className="font-semibold text-[#b8c4d8] text-sm"></span>
</FloatingToolNode>
<div className="desktop-login-tools-network">
{connection && phase !== "idle" ? (
<AnimatedContextFlow
key={pulseId}
connection={connection}
phase={phase}
/>
) : null}
<div className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2">
<div className="relative flex size-36 items-center justify-center rounded-full bg-[radial-gradient(circle_at_68%_34%,rgba(145,230,255,0.9),rgba(48,81,255,0.92)_44%,rgba(14,29,90,0.84)_72%,transparent_73%)] shadow-[0_0_54px_rgba(65,135,255,0.6),inset_12px_16px_24px_rgba(255,255,255,0.13)] blur-[1px]">
<div className="absolute inset-4 rounded-full bg-[#244cff]/50 blur-xl" />
<Logo className="relative z-10 size-8 opacity-85" />
{TOOL_NODES.map((node) => (
<FloatingToolNode
key={node.id}
node={node}
role={sourceRole(node.id) ?? destinationRole(node.id)}
/>
))}
<div className="desktop-login-orb-wrap">
<div
className={
phase === "hold"
? "desktop-login-nova-orb desktop-login-nova-orb-active"
: "desktop-login-nova-orb"
}
>
<div className="desktop-login-nova-gradient" />
<Logo className="relative z-10 size-8 opacity-80" />
</div>
</div>
<p className="-translate-x-1/2 absolute bottom-8 left-1/2 whitespace-nowrap text-center text-sm text-white/45">
One memory layer - context from any tool, everywhere you need it.
</p>
</div>
<p className="desktop-login-panel-caption">
One memory layer - context from any tool, everywhere you need it.
</p>
</aside>
)
}
function FloatingToolNode({
x,
y,
label,
children,
function AnimatedContextFlow({
connection,
phase,
}: {
x: number
y: number
label: string
children: React.ReactNode
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 chipPhase =
phase === "recall"
? "desktop-login-chip-recall"
: phase === "hold"
? "desktop-login-chip-hold"
: "desktop-login-chip-capture"
return (
<div
className="-translate-x-1/2 -translate-y-1/2 absolute z-10"
style={{ left: `${x}%`, top: `${y}%` }}
title={label}
>
<div className="flex size-12 items-center justify-center rounded-xl border border-white/12 bg-[#0c1628]/85 shadow-[inset_0_1px_0_rgba(255,255,255,0.1),0_6px_20px_rgba(0,0,0,0.22)] backdrop-blur-md">
{children}
<div className="pointer-events-none absolute inset-0 z-[1]">
<svg
className="absolute inset-0 h-full w-full"
viewBox="0 0 100 100"
preserveAspectRatio="none"
aria-hidden="true"
>
<path
d={dIn}
fill="none"
stroke="rgb(75 160 250 / 0.12)"
strokeWidth="2.5"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
<path
d={dOut}
fill="none"
stroke="rgb(75 160 250 / 0.12)"
strokeWidth="2.5"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
{phase === "capture" || phase === "hold" ? (
<path
d={dIn}
pathLength={1}
className="desktop-login-active-path"
fill="none"
stroke="rgb(140 205 255 / 0.75)"
strokeWidth="1.75"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
) : null}
{phase === "recall" ? (
<path
d={dOut}
pathLength={1}
className="desktop-login-active-path"
fill="none"
stroke="rgb(160 220 255 / 0.9)"
strokeWidth="1.75"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
) : null}
</svg>
<div
className={`desktop-login-memory-chip-motion ${chipPhase}`}
style={
{
"--from-x": `${from.x}%`,
"--from-y": `${from.y}%`,
"--to-x": `${to.x}%`,
"--to-y": `${to.y}%`,
} as CSSProperties
}
>
<MemoryChip />
</div>
</div>
)
}
function FloatingToolNode({
node,
role,
}: {
node: ToolNode
role?: "source" | "destination"
}) {
const Icon = node.icon
const roleClass =
role === "source"
? "desktop-login-tool-node-source"
: role === "destination"
? "desktop-login-tool-node-destination"
: ""
return (
<div
className="desktop-login-tool-node-wrap"
style={{ left: `${node.x}%`, top: `${node.y}%` }}
title={node.name}
>
<div className={`desktop-login-tool-node ${roleClass}`}>
<Icon className="desktop-login-tool-node-icon" />
</div>
<span
className={
role
? "desktop-login-tool-node-label desktop-login-tool-node-label-visible"
: "desktop-login-tool-node-label"
}
>
{node.name}
</span>
</div>
)
}
function MemoryChip() {
return (
<div className="desktop-login-memory-chip">
<div className="desktop-login-memory-chip-lines" aria-hidden>
<span />
<span />
<span />
</div>
</div>
)
}
function CodeMark({ className }: { className?: string }) {
return <span className={`${className ?? ""} text-[#ff9a63]`}>CC</span>
}
function CodexMark({ className }: { className?: string }) {
return <span className={`${className ?? ""} text-[#d7e6ff]`}>CX</span>
}
function OpenCodeMark({ className }: { className?: string }) {
return <span className={`${className ?? ""} text-[#b8c4d8]`}>OC</span>
}
function HermesMark({ className }: { className?: string }) {
return <span className={`${className ?? ""} text-[#8ccfff]`}>HM</span>
}
function OpenClawMark({ className }: { className?: string }) {
return <span className={`${className ?? ""} text-[#8af0d8]`}>OP</span>
}
function GoogleIcon() {
return (
<svg