diff --git a/apps/array/package.json b/apps/array/package.json index bc00d5de..013266a2 100644 --- a/apps/array/package.json +++ b/apps/array/package.json @@ -96,6 +96,7 @@ "@parcel/watcher": "^2.5.1", "@phosphor-icons/react": "^2.1.10", "@posthog/agent": "workspace:*", + "@posthog/electron-trpc": "workspace:*", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.1", @@ -125,6 +126,7 @@ "electron-log": "^5.4.3", "electron-store": "^11.0.0", "file-icon": "^6.0.0", + "framer-motion": "^12.26.2", "idb-keyval": "^6.2.2", "immer": "^11.0.1", "inversify": "^7.10.6", @@ -146,7 +148,6 @@ "remark-gfm": "^4.0.1", "sonner": "^2.0.7", "tippy.js": "^6.3.7", - "@posthog/electron-trpc": "workspace:*", "uuid": "^9.0.1", "vscode-icons-js": "^11.6.1", "zod": "^4.1.12", diff --git a/apps/array/src/renderer/App.tsx b/apps/array/src/renderer/App.tsx index f69e1c6a..18141560 100644 --- a/apps/array/src/renderer/App.tsx +++ b/apps/array/src/renderer/App.tsx @@ -1,3 +1,5 @@ +import { CursorGlow } from "@components/CursorGlow"; +import { LoginTransition } from "@components/LoginTransition"; import { MainLayout } from "@components/MainLayout"; import { AuthScreen } from "@features/auth/components/AuthScreen"; import { useAuthStore } from "@features/auth/stores/authStore"; @@ -6,11 +8,14 @@ import { initializePostHog } from "@renderer/lib/analytics"; import { initializeConnectivityStore } from "@renderer/stores/connectivityStore"; import { trpcVanilla } from "@renderer/trpc/client"; import { toast } from "@utils/toast"; -import { useEffect, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; +import { useEffect, useRef, useState } from "react"; function App() { const { isAuthenticated, initializeOAuth } = useAuthStore(); const [isLoading, setIsLoading] = useState(true); + const [showTransition, setShowTransition] = useState(false); + const wasAuthenticated = useRef(isAuthenticated); // Initialize PostHog analytics useEffect(() => { @@ -36,6 +41,19 @@ function App() { initializeOAuth().finally(() => setIsLoading(false)); }, [initializeOAuth]); + // Handle auth state change for transition + useEffect(() => { + if (!wasAuthenticated.current && isAuthenticated) { + // User just logged in - trigger transition + setShowTransition(true); + } + wasAuthenticated.current = isAuthenticated; + }, [isAuthenticated]); + + const handleTransitionComplete = () => { + setShowTransition(false); + }; + if (isLoading) { return ( @@ -47,7 +65,36 @@ function App() { ); } - return isAuthenticated ? : ; + return ( + <> + + + {!isAuthenticated ? ( + + + + ) : ( + + + + )} + + + + ); } export default App; diff --git a/apps/array/src/renderer/assets/fonts/Halfre.otf b/apps/array/src/renderer/assets/fonts/Halfre.otf new file mode 100644 index 00000000..5f5c8e62 Binary files /dev/null and b/apps/array/src/renderer/assets/fonts/Halfre.otf differ diff --git a/apps/array/src/renderer/assets/images/cave-hero.jpg b/apps/array/src/renderer/assets/images/cave-hero.jpg new file mode 100644 index 00000000..05e58d26 Binary files /dev/null and b/apps/array/src/renderer/assets/images/cave-hero.jpg differ diff --git a/apps/array/src/renderer/assets/images/twig-logo.svg b/apps/array/src/renderer/assets/images/twig-logo.svg new file mode 100644 index 00000000..f14796aa --- /dev/null +++ b/apps/array/src/renderer/assets/images/twig-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/array/src/renderer/components/CampfireToggle.tsx b/apps/array/src/renderer/components/CampfireToggle.tsx new file mode 100644 index 00000000..77f758a5 --- /dev/null +++ b/apps/array/src/renderer/components/CampfireToggle.tsx @@ -0,0 +1,22 @@ +import { Campfire } from "@phosphor-icons/react"; +import { IconButton, Tooltip } from "@radix-ui/themes"; +import { useThemeStore } from "@stores/themeStore"; + +export function CampfireToggle() { + const { isDarkMode, toggleDarkMode } = useThemeStore(); + + return ( + + + + + + ); +} diff --git a/apps/array/src/renderer/components/CursorGlow.tsx b/apps/array/src/renderer/components/CursorGlow.tsx new file mode 100644 index 00000000..9a125343 --- /dev/null +++ b/apps/array/src/renderer/components/CursorGlow.tsx @@ -0,0 +1,37 @@ +import { useThemeStore } from "@stores/themeStore"; +import { useEffect, useState } from "react"; + +export function CursorGlow() { + const isDarkMode = useThemeStore((state) => state.isDarkMode); + const [mousePos, setMousePos] = useState<{ x: number; y: number } | null>( + null, + ); + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + setMousePos({ x: e.clientX, y: e.clientY }); + }; + + window.addEventListener("mousemove", handleMouseMove); + return () => window.removeEventListener("mousemove", handleMouseMove); + }, []); + + // Only show in dark mode + if (!isDarkMode || !mousePos) return null; + + return ( +
+ ); +} diff --git a/apps/array/src/renderer/components/LoginTransition.tsx b/apps/array/src/renderer/components/LoginTransition.tsx new file mode 100644 index 00000000..b56cdaec --- /dev/null +++ b/apps/array/src/renderer/components/LoginTransition.tsx @@ -0,0 +1,28 @@ +import { motion } from "framer-motion"; + +interface LoginTransitionProps { + isAnimating: boolean; + onComplete: () => void; +} + +export function LoginTransition({ + isAnimating, + onComplete, +}: LoginTransitionProps) { + if (!isAnimating) return null; + + return ( + + ); +} diff --git a/apps/array/src/renderer/components/StatusBar.tsx b/apps/array/src/renderer/components/StatusBar.tsx index 172e6d6e..4286dd0c 100644 --- a/apps/array/src/renderer/components/StatusBar.tsx +++ b/apps/array/src/renderer/components/StatusBar.tsx @@ -1,3 +1,4 @@ +import { CampfireToggle } from "@components/CampfireToggle"; import { StatusBarMenu } from "@components/StatusBarMenu"; import { Badge, Box, Code, Flex, Kbd } from "@radix-ui/themes"; import { useStatusBarStore } from "@stores/statusBarStore"; @@ -43,6 +44,7 @@ export function StatusBar({ showKeyHints = true }: StatusBarProps) { )} + {IS_DEV && ( diff --git a/apps/array/src/renderer/features/auth/components/AuthScreen.tsx b/apps/array/src/renderer/features/auth/components/AuthScreen.tsx index 29959879..794bf1da 100644 --- a/apps/array/src/renderer/features/auth/components/AuthScreen.tsx +++ b/apps/array/src/renderer/features/auth/components/AuthScreen.tsx @@ -1,17 +1,7 @@ -import { AsciiArt } from "@components/AsciiArt"; import { useAuthStore } from "@features/auth/stores/authStore"; -import { - Box, - Button, - Callout, - Card, - Container, - Flex, - Heading, - Select, - Spinner, - Text, -} from "@radix-ui/themes"; +import { Box, Callout, Flex, Select, Spinner, Text } from "@radix-ui/themes"; +import caveHero from "@renderer/assets/images/cave-hero.jpg"; +import twigLogo from "@renderer/assets/images/twig-logo.svg"; import { trpcVanilla } from "@renderer/trpc/client"; import type { CloudRegion } from "@shared/types/oauth"; import { useMutation } from "@tanstack/react-query"; @@ -61,86 +51,114 @@ export function AuthScreen() { }; return ( - - {/* Left pane - Auth form */} - - - - - - - Welcome to Array - - Sign in with your PostHog account - - - - - - - PostHog region - - - - - US Cloud - EU Cloud - {IS_DEV && ( - Development - )} - - - - - {authMutation.isError && ( - - - {getErrorMessage(authMutation.error)} - - - )} - - {authMutation.isPending && ( - - - Waiting for authorization in your browser... - - - )} - - - - - + + {/* Full-screen cave painting background */} +
+ + {/* Left side - login form */} + + + + Twig + + the dawn of a new agentic era + - - - {/* Right pane - ASCII Art */} - - - + + + + PostHog region + + + + + US Cloud + EU Cloud + {IS_DEV && Development} + + + + + {authMutation.isError && ( + + + {getErrorMessage(authMutation.error)} + + + )} + + {authMutation.isPending && ( + + + Waiting for authorization in your browser... + + + )} + + + + + + + {/* Right side - empty, shows background */} + ); } diff --git a/apps/array/src/renderer/features/sessions/components/GeneratingIndicator.tsx b/apps/array/src/renderer/features/sessions/components/GeneratingIndicator.tsx index 956bc67e..375fe79c 100644 --- a/apps/array/src/renderer/features/sessions/components/GeneratingIndicator.tsx +++ b/apps/array/src/renderer/features/sessions/components/GeneratingIndicator.tsx @@ -1,7 +1,9 @@ -import { DotsCircleSpinner } from "@components/DotsCircleSpinner"; +import { Campfire } from "@phosphor-icons/react"; import { Flex, Text } from "@radix-ui/themes"; import { useEffect, useState } from "react"; +const ACTIVITIES = ["Foraging", "Hunting", "Building", "Gathering", "Crafting"]; + export function formatDuration(ms: number): string { const totalSeconds = Math.floor(ms / 1000); const mins = Math.floor(totalSeconds / 60); @@ -16,6 +18,7 @@ export function formatDuration(ms: number): string { export function GeneratingIndicator() { const [elapsed, setElapsed] = useState(0); + const [activityIndex, setActivityIndex] = useState(0); useEffect(() => { const startTime = Date.now(); @@ -26,6 +29,14 @@ export function GeneratingIndicator() { return () => clearInterval(interval); }, []); + useEffect(() => { + const interval = setInterval(() => { + setActivityIndex((i) => (i + 1) % ACTIVITIES.length); + }, 2000); + + return () => clearInterval(interval); + }, []); + return ( - - - Generating - - + + {ACTIVITIES[activityIndex]}... =6'} - bun-types@1.3.5: - resolution: {integrity: sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw==} + bun-types@1.3.6: + resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} @@ -4709,6 +4712,20 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + framer-motion@12.26.2: + resolution: {integrity: sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -5669,6 +5686,12 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + motion-dom@12.26.2: + resolution: {integrity: sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==} + + motion-utils@12.24.10: + resolution: {integrity: sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==} + mprocs@0.7.3: resolution: {integrity: sha512-I1Ptja/UnNVVSV5QEnBdiiwW70HMWJlmnMq6nvw4J7fwZ3s8UZGuf/FG+FTAUr6CLfat8kriHj+mWLx7xsLKSw==} engines: {node: '>=0.10.0'} @@ -10892,9 +10915,9 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@types/bun@1.3.5': + '@types/bun@1.3.6': dependencies: - bun-types: 1.3.5 + bun-types: 1.3.6 '@types/cacheable-request@6.0.3': dependencies: @@ -11537,7 +11560,7 @@ snapshots: builtin-modules@3.3.0: {} - bun-types@1.3.5: + bun-types@1.3.6: dependencies: '@types/node': 20.19.25 @@ -12387,6 +12410,15 @@ snapshots: fraction.js@5.3.4: {} + framer-motion@12.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + motion-dom: 12.26.2 + motion-utils: 12.24.10 + tslib: 2.8.1 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + fresh@2.0.0: {} fs-extra@10.1.0: @@ -13645,6 +13677,12 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + motion-dom@12.26.2: + dependencies: + motion-utils: 12.24.10 + + motion-utils@12.24.10: {} + mprocs@0.7.3: {} mri@1.2.0: {}