From 2fb79debf5786b7488f38fc68da05ec8003de74f Mon Sep 17 00:00:00 2001 From: cynfria Date: Thu, 27 Aug 2026 17:02:36 -0700 Subject: [PATCH] feat(desktop): add detachable Bestie chat Signed-off-by: cynfria --- .../app/buzz-project-prototype.tsx | 101 +++++++++++++++++- prototypes/project-cube-orbit/app/globals.css | 57 ++++++++++ prototypes/project-cube-orbit/public/snek.png | Bin 0 -> 17200 bytes 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 prototypes/project-cube-orbit/public/snek.png diff --git a/prototypes/project-cube-orbit/app/buzz-project-prototype.tsx b/prototypes/project-cube-orbit/app/buzz-project-prototype.tsx index 8547a5d96c..9b845227a6 100644 --- a/prototypes/project-cube-orbit/app/buzz-project-prototype.tsx +++ b/prototypes/project-cube-orbit/app/buzz-project-prototype.tsx @@ -1009,6 +1009,102 @@ function MessageComposer({ compact = false }: { compact?: boolean }) { ); } +function BestieChat({ onClose }: { onClose: () => void }) { + const [detached, setDetached] = useState(false); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const panelRef = useRef(null); + const dragRef = useRef({ + pointerId: -1, + startX: 0, + startY: 0, + originX: 0, + originY: 0, + x: 0, + y: 0, + beganDetached: false, + }); + + const beginDrag = (event: ReactPointerEvent) => { + if ((event.target as Element).closest("button, input")) return; + const panel = panelRef.current; + if (!panel) return; + const rect = panel.getBoundingClientRect(); + event.currentTarget.setPointerCapture(event.pointerId); + dragRef.current = { + pointerId: event.pointerId, + startX: event.clientX, + startY: event.clientY, + originX: rect.left, + originY: rect.top, + x: rect.left, + y: rect.top, + beganDetached: detached, + }; + panel.style.setProperty("--bestie-x", `${rect.left}px`); + panel.style.setProperty("--bestie-y", `${rect.top}px`); + panel.classList.add("is-dragging"); + }; + + const updateDrag = (event: ReactPointerEvent) => { + const drag = dragRef.current; + const panel = panelRef.current; + if (!panel || drag.pointerId !== event.pointerId) return; + const pullDistance = Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY); + drag.x = Math.max(16, Math.min(window.innerWidth - panel.offsetWidth - 16, drag.originX + event.clientX - drag.startX)); + drag.y = Math.max(74, Math.min(window.innerHeight - panel.offsetHeight - 16, drag.originY + event.clientY - drag.startY)); + panel.style.setProperty("--bestie-x", `${drag.x}px`); + panel.style.setProperty("--bestie-y", `${drag.y}px`); + panel.classList.toggle("is-tearing", !drag.beganDetached && pullDistance > 48); + }; + + const endDrag = (event: ReactPointerEvent) => { + const drag = dragRef.current; + const panel = panelRef.current; + if (!panel || drag.pointerId !== event.pointerId) return; + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId); + } + const shouldDetach = drag.beganDetached || Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 84; + panel.classList.remove("is-dragging", "is-tearing"); + if (shouldDetach) { + panel.classList.add("is-detached"); + setDetached(true); + setPosition({ x: drag.x, y: drag.y }); + } else { + panel.style.removeProperty("--bestie-x"); + panel.style.removeProperty("--bestie-y"); + } + drag.pointerId = -1; + }; + + return ( + + ); +} + function MessagesView() { const [layouts, setLayouts] = useState(INITIAL_MESSAGE_LAYOUT); const [connections, setConnections] = useState(INITIAL_MESSAGE_CONNECTIONS); @@ -1304,6 +1400,7 @@ function MessagesView() { export function BuzzProjectPrototype() { const [activeTab, setActiveTab] = useState<"me" | "messages" | "projects">("me"); const [selectedTask, setSelectedTask] = useState(null); + const [bestieOpen, setBestieOpen] = useState(false); const appShellRef = useRef(null); const projectWorldRef = useRef(null); const canvasPanRef = useRef({ @@ -1389,9 +1486,11 @@ export function BuzzProjectPrototype() { -
C
+
C
+ {bestieOpen ? setBestieOpen(false)} /> : null} + {activeTab === "projects" ? ( ) : activeTab === "messages" ? ( diff --git a/prototypes/project-cube-orbit/app/globals.css b/prototypes/project-cube-orbit/app/globals.css index fdffbdbfe0..6c7c8e7a7d 100644 --- a/prototypes/project-cube-orbit/app/globals.css +++ b/prototypes/project-cube-orbit/app/globals.css @@ -47,10 +47,65 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif .segmented .add-button { width: 36px; padding: 0; display: grid; place-items: center; } .utilities { justify-self: end; display: flex; align-items: center; gap: 12px; } .round-button { width: var(--header-control-size); height: var(--header-control-size); border-radius: 50%; display: grid; place-items: center; border: 1px solid rgba(255,255,255,.9); background: rgba(244,249,251,.52); cursor: pointer; backdrop-filter: blur(16px); } +.snake-button img { display: block; width: 30px; height: 30px; object-fit: contain; } +.snake-button.is-active { background: rgba(255,255,255,.78); box-shadow: 0 8px 22px rgba(54,82,94,.12); } .search-icon { display: block; width: 18px; height: 18px; } .avatar { width: var(--header-control-size); height: var(--header-control-size); border-radius: 50%; display: grid; place-items: center; color: rgba(255,255,255,.95); background: radial-gradient(circle at 53% 29%, #d8af86 0 13%, transparent 14%), radial-gradient(ellipse at 50% 68%, #3f2923 0 25%, transparent 26%), linear-gradient(145deg, #99a65d, #49352d 65%, #c1a16e); border: 2px solid rgba(255,255,255,.95); box-shadow: 0 0 0 1px rgba(44,52,45,.08); font-size: 11px; font-weight: 650; } .avatar span { transform: translateY(1px); text-shadow: 0 1px 3px rgba(0,0,0,.38); } +.bestie-chat { + --bestie-x: 0px; + --bestie-y: 0px; + position: fixed; + z-index: 45; + top: 74px; + right: 20px; + width: min(360px, calc(100vw - 32px)); + height: 430px; + overflow: hidden; + border: 1px solid rgba(255,255,255,.92); + border-radius: 28px; + background: rgba(255,255,255,.82); + box-shadow: 0 18px 50px rgba(53,76,86,.16), inset 0 1px 0 rgba(255,255,255,.8); + backdrop-filter: blur(24px) saturate(118%); + animation: bestie-popover-enter 180ms cubic-bezier(.215,.61,.355,1); + transform-origin: calc(100% - 21px) -28px; +} +.bestie-chat.is-dragging, +.bestie-chat.is-detached { + top: 0; + right: auto; + left: 0; + transform: translate3d(var(--bestie-x), var(--bestie-y), 0) scale(1); + animation: none; + will-change: transform; +} +.bestie-chat.is-detached { z-index: 44; background: rgba(255,255,255,.9); transition: transform 240ms cubic-bezier(.455,.03,.515,.955), box-shadow 180ms ease; } +.bestie-chat.is-dragging { transition: none; } +.bestie-chat.is-tearing { transform: translate3d(var(--bestie-x), var(--bestie-y), 0) scale(1.018); box-shadow: 0 24px 64px rgba(53,76,86,.22), inset 0 1px 0 rgba(255,255,255,.9); } +.bestie-chat-header { display: flex; height: 58px; align-items: center; justify-content: space-between; padding: 0 14px 0 16px; border-bottom: 1px solid rgba(38,38,38,.08); cursor: grab; touch-action: none; user-select: none; } +.bestie-chat.is-dragging .bestie-chat-header { cursor: grabbing; } +.bestie-chat-header > div { display: flex; align-items: center; gap: 9px; } +.bestie-chat-header img { width: 28px; height: 28px; object-fit: contain; } +.bestie-chat-header strong { font-size: 14px; font-weight: 650; } +.bestie-chat-header button { display: grid; width: 34px; height: 34px; place-items: center; border: 0; border-radius: 50%; background: transparent; cursor: pointer; } +.bestie-chat-header button:hover { background: rgba(38,38,38,.055); } +.bestie-chat-content { height: calc(100% - 144px); overflow: auto; padding: 20px 16px; } +.bestie-message { display: flex; align-items: flex-end; gap: 8px; margin-bottom: 14px; } +.bestie-message img { width: 28px; height: 28px; flex: 0 0 auto; object-fit: contain; } +.bestie-message p { max-width: 82%; margin: 0; padding: 10px 13px; border-radius: 17px; background: rgba(238,238,238,.86); font-size: 14px; line-height: 1.35; } +.bestie-message.is-user { justify-content: flex-end; } +.bestie-message.is-user p { color: #fff; background: #1688cf; } +.bestie-composer { position: absolute; right: 14px; bottom: 14px; left: 14px; display: flex; height: 58px; align-items: center; gap: 10px; padding: 0 10px 0 16px; border: 1px solid rgba(38,38,38,.12); border-radius: 18px; background: rgba(255,255,255,.82); box-shadow: 0 7px 22px rgba(57,78,87,.05); } +.bestie-composer input { min-width: 0; flex: 1; border: 0; outline: 0; color: #262626; background: transparent; font-size: 14px; } +.bestie-composer input::placeholder { color: rgba(38,38,38,.4); } +.bestie-composer button { display: grid; width: 34px; height: 34px; flex: 0 0 auto; place-items: center; border: 0; border-radius: 50%; background: #f3e7ff; cursor: pointer; } + +@keyframes bestie-popover-enter { + from { opacity: 0; transform: translateY(-6px) scale(.96); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + .project-stage { position: relative; min-height: 100svh; overflow: hidden; touch-action: none; user-select: none; cursor: grab; } .project-stage.is-panning { cursor: grabbing; } .project-world { position: absolute; inset: 0; transform: translate3d(0,0,0); transition: transform 520ms cubic-bezier(.22,1,.36,1); will-change: transform; } @@ -464,6 +519,8 @@ body { background: #e6f2f5; color: var(--ink); font-family: Inter, ui-sans-serif .orbit-system, .task-detail-panel { transition-duration: 1ms; animation-duration: 1ms; } .message-panel { transition: none; } .merge-edge-indicator { transition: none; } + .bestie-chat { animation: none; } + .bestie-chat.is-detached { transition: none; } .response-composer { transition: none; } .response-composer textarea { animation: none; } .project-name-pill { transition: none; } diff --git a/prototypes/project-cube-orbit/public/snek.png b/prototypes/project-cube-orbit/public/snek.png new file mode 100644 index 0000000000000000000000000000000000000000..20c71db96ba462ff76c043504a7bad0274b40900 GIT binary patch literal 17200 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