From 43d2bed7096278df34b1aed2a93b6c49a2efd412 Mon Sep 17 00:00:00 2001 From: khatibqa Date: Sat, 29 Aug 2026 13:04:56 -0400 Subject: [PATCH 1/3] updated share modal on landing page --- apps/web/app/landing.css | 263 ++++++++++++++++++ .../landing-workspace-demo.test.tsx | 15 + .../web/components/landing-workspace-demo.tsx | 213 ++++++++++++-- apps/web/next-env.d.ts | 2 +- 4 files changed, 475 insertions(+), 18 deletions(-) diff --git a/apps/web/app/landing.css b/apps/web/app/landing.css index 16c5d7fbc..8f38872b2 100644 --- a/apps/web/app/landing.css +++ b/apps/web/app/landing.css @@ -572,6 +572,7 @@ } .lp-workspace { + position: relative; overflow: hidden; border: 1px solid rgba(237, 238, 240, 0.14); border-radius: 14px; @@ -579,6 +580,8 @@ } .lp-workspace-topbar { + position: relative; + z-index: 2; display: flex; height: 47px; padding: 0 12px; @@ -681,6 +684,266 @@ color: var(--lp-muted); } +.lp-workspace-share.is-open { + border-color: rgba(237, 238, 240, 0.3); + color: var(--lp-ink); +} + +/* A high-fidelity share interaction, intentionally shown only during Join. */ +.lp-share-overlay { + position: absolute; + z-index: 8; + inset: 0; + display: grid; + padding: 22px; + place-items: center; + background: rgba(10, 12, 14, 0.62); + backdrop-filter: blur(2px); + animation: lp-share-scrim-in 0.2s ease both; +} + +.lp-share-panel { + display: grid; + width: min(520px, calc(100% - 18px)); + padding: 22px; + gap: 18px; + border: 1px solid rgba(237, 238, 240, 0.2); + border-radius: 14px; + background: #f7f7f6; + box-shadow: 0 26px 70px -22px rgba(0, 0, 0, 0.78); + color: #202124; + text-align: left; + animation: lp-share-panel-in 0.26s ease both; +} + +.lp-share-heading, +.lp-share-heading > span, +.lp-share-person, +.lp-share-restricted, +.lp-share-actions, +.lp-share-actions > span { + display: flex; + align-items: center; +} + +.lp-share-heading { + justify-content: space-between; + gap: 16px; +} + +.lp-share-heading h3, +.lp-share-section h4, +.lp-share-person strong, +.lp-share-person small, +.lp-share-restricted strong, +.lp-share-restricted small { + margin: 0; +} + +.lp-share-heading h3 { + font-size: 21px; + font-weight: 500; + letter-spacing: -0.035em; +} + +.lp-share-heading > span { + gap: 15px; + color: #303238; +} + +.lp-share-add { + min-height: 52px; + padding: 10px 13px; + border: 2px solid #1a73e8; + border-radius: 5px; + color: #1967d2; + font-size: 13px; + line-height: 1.35; +} + +.lp-share-section { + display: grid; + gap: 13px; +} + +.lp-share-section h4 { + font-size: 15px; + font-weight: 600; +} + +.lp-share-person { + gap: 11px; +} + +.lp-share-person > i { + display: grid; + width: 32px; + height: 32px; + flex: 0 0 auto; + place-items: center; + border: 2px solid #202124; + border-radius: 50%; + background: #5b9bd5; + color: #fff; + font-size: 9px; + font-style: normal; + font-weight: 750; +} + +.lp-share-person > span, +.lp-share-restricted > span { + display: grid; + gap: 2px; + min-width: 0; +} + +.lp-share-person strong, +.lp-share-restricted strong { + color: #202124; + font-size: 13px; + font-weight: 500; +} + +.lp-share-person small, +.lp-share-restricted small { + color: #5f6368; + font-size: 11px; +} + +.lp-share-person em { + margin-left: auto; + color: #8d9095; + font-size: 12px; + font-style: normal; +} + +.lp-share-general { + gap: 12px; +} + +.lp-share-restricted { + gap: 11px; +} + +.lp-share-restricted > i { + display: grid; + width: 32px; + height: 32px; + flex: 0 0 auto; + place-items: center; + border-radius: 50%; + background: #e4e5e7; + color: #3c4043; +} + +.lp-share-restricted strong { + display: inline-flex; + align-items: center; + gap: 5px; +} + +.lp-share-actions { + justify-content: space-between; + margin-top: 10px; +} + +.lp-share-actions > span { + min-height: 39px; + padding: 0 16px; + gap: 8px; + border: 1px solid #7b7d80; + border-radius: 22px; + color: #1967d2; + font-size: 13px; + font-weight: 600; + transition: + background 0.15s ease, + color 0.15s ease; +} + +.lp-share-actions > span:last-child { + border-color: #1967d2; + background: #1967d2; + color: #fff; +} + +.lp-share-actions > span.is-copied { + border-color: #137333; + color: #137333; +} + +.lp-share-actions > span.is-done { + background: #1558b0; +} + +.lp-demo-cursor { + position: absolute; + z-index: 9; + top: 0; + left: 0; + width: 20px; + height: 20px; + margin: -2px 0 0 -3px; + pointer-events: none; + transform: translate(46px, 360px); + transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1); + will-change: transform; +} + +.lp-demo-cursor svg { + display: block; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6)); + transition: transform 0.12s ease; +} + +.lp-demo-cursor.is-tapping svg { + transform: scale(0.8); +} + +.lp-demo-cursor.is-tapping::after { + position: absolute; + top: 4px; + left: 3px; + width: 6px; + height: 6px; + border: 2px solid var(--lp-sky); + border-radius: 50%; + animation: lp-cursor-tap 0.45s ease-out; + content: ""; +} + +.lp-demo-cursor.is-leaving { + opacity: 0; + transition: + transform 0.5s ease, + opacity 0.45s ease; +} + +@keyframes lp-share-scrim-in { + from { + opacity: 0; + } +} + +@keyframes lp-share-panel-in { + from { + opacity: 0; + transform: translateY(-8px) scale(0.98); + } +} + +@keyframes lp-cursor-tap { + from { + opacity: 0.9; + transform: translate(-40%, -40%) scale(0.4); + } + + to { + opacity: 0; + transform: translate(-40%, -40%) scale(4); + } +} + .lp-workspace-body { display: grid; min-height: 520px; diff --git a/apps/web/components/landing-workspace-demo.test.tsx b/apps/web/components/landing-workspace-demo.test.tsx index 097b9f440..6a3f5d480 100644 --- a/apps/web/components/landing-workspace-demo.test.tsx +++ b/apps/web/components/landing-workspace-demo.test.tsx @@ -75,4 +75,19 @@ describe("LandingWorkspaceDemo", () => { expect(screen.getByRole("button", { name: "Play demo" })).toBeDisabled(); expect(screen.getByText(/ready for a person to review/i)).toBeVisible(); }); + + it("shows the detailed sharing panel during the Join sequence", () => { + render(); + + const sharePanel = screen.getByRole("note", { + name: "Share acme storefront workspace", + }); + expect(sharePanel).toHaveTextContent("Add people, groups, or teams"); + expect(sharePanel).toHaveTextContent("People with access"); + expect(sharePanel).toHaveTextContent("Alex Morgan (you)"); + expect(sharePanel).toHaveTextContent("General access"); + expect(sharePanel).toHaveTextContent("Restricted"); + expect(sharePanel).toHaveTextContent("Copy link"); + expect(sharePanel).toHaveTextContent("Done"); + }); }); diff --git a/apps/web/components/landing-workspace-demo.tsx b/apps/web/components/landing-workspace-demo.tsx index d3300804d..156912a57 100644 --- a/apps/web/components/landing-workspace-demo.tsx +++ b/apps/web/components/landing-workspace-demo.tsx @@ -1,8 +1,34 @@ "use client"; -import { Check, Pause, Play, RotateCcw, Share2, Users } from "lucide-react"; +import { + Check, + ChevronDown, + HelpCircle, + Link2, + LockKeyhole, + Pause, + Play, + RotateCcw, + Settings, + Share2, + Users, +} from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; +function GuideCursor() { + return ( + + ); +} + type DemoPhase = "join" | "write" | "verify" | "ready"; type AgentTone = "orange" | "green" | "purple"; @@ -22,7 +48,14 @@ type AgentTrack = { segments: TypingSegment[]; }; -const DEMO_DURATION = 15_000; +const DEMO_DURATION = 17_600; + +const SHARE_DEMO = { + opensAt: 850, + copyAt: 2_150, + doneAt: 3_250, + closesAt: 4_550, +} as const; const PHASES: { key: DemoPhase; @@ -33,20 +66,21 @@ const PHASES: { { key: "join", label: "Join", - endpoint: 2_150, - summary: "Three agents join the same checkout file with named cursors.", + endpoint: 4_750, + summary: + "Share one workspace link, choose its access, and bring the whole crew in.", }, { key: "write", label: "Write", - endpoint: 10_350, + endpoint: 12_950, summary: "Codex, Claude, and Review edit separate regions of the file together.", }, { key: "verify", label: "Verify", - endpoint: 13_150, + endpoint: 15_750, summary: "The agents run the checkout tests and review the combined change.", }, @@ -69,12 +103,12 @@ const AGENTS: AgentTrack[] = [ segments: [ { line: 7, - start: 2_450, + start: 5_050, text: " const cart = checkoutSchema.parse(input);", }, { line: 12, - start: 7_250, + start: 9_850, text: ' await audit.record("checkout.reserved", order.id);', }, ], @@ -89,12 +123,12 @@ const AGENTS: AgentTrack[] = [ segments: [ { line: 8, - start: 3_000, + start: 5_600, text: " const lock = await claim(`checkout:${cart.id}`);", }, { line: 11, - start: 6_750, + start: 9_350, text: " const order = await commit(cart, lock);", }, ], @@ -109,10 +143,10 @@ const AGENTS: AgentTrack[] = [ segments: [ { line: 9, - start: 3_600, + start: 6_200, text: " if (!lock) return retryLater(cart);", }, - { line: 13, start: 7_800, text: " return order;" }, + { line: 13, start: 10_400, text: " return order;" }, ], }, ]; @@ -220,12 +254,22 @@ function statusForAgent(agent: AgentTrack, elapsed: number) { return "Joining"; } -export function LandingWorkspaceDemo() { +export function LandingWorkspaceDemo({ + initialElapsed = 0, +}: { + /** Allows deterministic inspection of a point in the choreographed demo. */ + initialElapsed?: number; +}) { const reducedMotion = usePrefersReducedMotion(); const rootRef = useRef(null); - const elapsedRef = useRef(0); + const workspaceRef = useRef(null); + const shareRef = useRef(null); + const copyRef = useRef(null); + const doneRef = useRef(null); + const cursorRef = useRef(null); + const elapsedRef = useRef(initialElapsed); const startedRef = useRef(false); - const [elapsed, setElapsed] = useState(0); + const [elapsed, setElapsed] = useState(initialElapsed); const [playing, setPlaying] = useState(false); const [inView, setInView] = useState(false); const [documentVisible, setDocumentVisible] = useState(true); @@ -304,6 +348,60 @@ export function LandingWorkspaceDemo() { ), [], ); + const isJoinPhase = phase === "join"; + const sharePanelOpen = + renderedElapsed >= SHARE_DEMO.opensAt && + renderedElapsed < SHARE_DEMO.closesAt; + const linkCopied = renderedElapsed >= SHARE_DEMO.copyAt; + const shareComplete = renderedElapsed >= SHARE_DEMO.doneAt; + const cursorTarget: "share" | "copy" | "done" | "exit" = + renderedElapsed < SHARE_DEMO.copyAt - 600 + ? "share" + : renderedElapsed < SHARE_DEMO.doneAt - 600 + ? "copy" + : renderedElapsed < SHARE_DEMO.closesAt - 250 + ? "done" + : "exit"; + const showCursor = + !reducedMotion && + isJoinPhase && + renderedElapsed >= 250 && + renderedElapsed < SHARE_DEMO.closesAt + 200; + const cursorTapping = [ + SHARE_DEMO.opensAt, + SHARE_DEMO.copyAt, + SHARE_DEMO.doneAt, + ].some((at) => renderedElapsed >= at && renderedElapsed < at + 220); + + useEffect(() => { + if (!showCursor) return; + const workspace = workspaceRef.current; + const cursor = cursorRef.current; + if (!workspace || !cursor) return; + const frame = window.requestAnimationFrame(() => { + const workspaceBounds = workspace.getBoundingClientRect(); + const anchor = + cursorTarget === "share" + ? shareRef.current + : cursorTarget === "copy" + ? copyRef.current + : cursorTarget === "done" + ? doneRef.current + : null; + const x = anchor + ? anchor.getBoundingClientRect().left - + workspaceBounds.left + + anchor.getBoundingClientRect().width / 2 + : workspaceBounds.width * 0.17; + const y = anchor + ? anchor.getBoundingClientRect().top - + workspaceBounds.top + + anchor.getBoundingClientRect().height / 2 + : workspaceBounds.height * 0.82; + cursor.style.transform = `translate(${x}px, ${y}px)`; + }); + return () => window.cancelAnimationFrame(frame); + }, [cursorTarget, showCursor]); function setTimeline(next: number) { elapsedRef.current = next; @@ -314,6 +412,11 @@ export function LandingWorkspaceDemo() { const definition = PHASES.find((item) => item.key === nextPhase); if (!definition) return; startedRef.current = true; + if (nextPhase === "join") { + setTimeline(0); + setPlaying(!reducedMotion); + return; + } setPlaying(false); setTimeline( definition.key === "ready" @@ -373,7 +476,7 @@ export function LandingWorkspaceDemo() { -
+
- + Share
+ {sharePanelOpen ? ( +
+
+
+

Share ‘acme/storefront’

+ +
+
Add people, groups, or teams
+
+

People with access

+
+ AM + + Alex Morgan (you) + alex@acme.dev + + Owner +
+
+
+

General access

+
+ + + + + + Restricted + + + Only people with access can open with the link + + +
+
+
+ + {linkCopied ? : } + {linkCopied ? "Link copied" : "Copy link"} + + + Done + +
+
+
+ ) : null} + + {showCursor ? ( + + ) : null} +