Skip to content

Commit b6e0947

Browse files
authored
feat: light twig styling (#508)
### TL;DR Added a new cave-themed UI with animated transitions, cursor glow effects, and dark mode toggle. ### What changed? - Redesigned the login screen with a cave painting background and new typography - Added a smooth transition animation between login and main app using Framer Motion - Implemented a cursor glow effect that follows the mouse in dark mode - Created a campfire toggle button for switching between light and dark modes - Updated the generating indicator to show themed "primitive" activities with a pulsing campfire icon - Added new fonts, colors, and styling variables for the cave theme - Added SVG logo and background image assets ### Why make this change? This update enhances the visual appeal and user experience of the application with a cohesive cave/primitive theme. The animations and visual effects create a more engaging and polished interface, while the dark mode toggle improves accessibility. The smooth transitions between screens provide better continuity in the user journey, making the app feel more professional and refined.
1 parent 489b41f commit b6e0947

File tree

14 files changed

+353
-113
lines changed

14 files changed

+353
-113
lines changed

apps/array/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"@parcel/watcher": "^2.5.1",
9797
"@phosphor-icons/react": "^2.1.10",
9898
"@posthog/agent": "workspace:*",
99+
"@posthog/electron-trpc": "workspace:*",
99100
"@radix-ui/react-collapsible": "^1.1.12",
100101
"@radix-ui/react-icons": "^1.3.2",
101102
"@radix-ui/themes": "^3.2.1",
@@ -125,6 +126,7 @@
125126
"electron-log": "^5.4.3",
126127
"electron-store": "^11.0.0",
127128
"file-icon": "^6.0.0",
129+
"framer-motion": "^12.26.2",
128130
"idb-keyval": "^6.2.2",
129131
"immer": "^11.0.1",
130132
"inversify": "^7.10.6",
@@ -146,7 +148,6 @@
146148
"remark-gfm": "^4.0.1",
147149
"sonner": "^2.0.7",
148150
"tippy.js": "^6.3.7",
149-
"@posthog/electron-trpc": "workspace:*",
150151
"uuid": "^9.0.1",
151152
"vscode-icons-js": "^11.6.1",
152153
"zod": "^4.1.12",

apps/array/src/renderer/App.tsx

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CursorGlow } from "@components/CursorGlow";
2+
import { LoginTransition } from "@components/LoginTransition";
13
import { MainLayout } from "@components/MainLayout";
24
import { AuthScreen } from "@features/auth/components/AuthScreen";
35
import { useAuthStore } from "@features/auth/stores/authStore";
@@ -6,11 +8,14 @@ import { initializePostHog } from "@renderer/lib/analytics";
68
import { initializeConnectivityStore } from "@renderer/stores/connectivityStore";
79
import { trpcVanilla } from "@renderer/trpc/client";
810
import { toast } from "@utils/toast";
9-
import { useEffect, useState } from "react";
11+
import { AnimatePresence, motion } from "framer-motion";
12+
import { useEffect, useRef, useState } from "react";
1013

1114
function App() {
1215
const { isAuthenticated, initializeOAuth } = useAuthStore();
1316
const [isLoading, setIsLoading] = useState(true);
17+
const [showTransition, setShowTransition] = useState(false);
18+
const wasAuthenticated = useRef(isAuthenticated);
1419

1520
// Initialize PostHog analytics
1621
useEffect(() => {
@@ -36,6 +41,19 @@ function App() {
3641
initializeOAuth().finally(() => setIsLoading(false));
3742
}, [initializeOAuth]);
3843

44+
// Handle auth state change for transition
45+
useEffect(() => {
46+
if (!wasAuthenticated.current && isAuthenticated) {
47+
// User just logged in - trigger transition
48+
setShowTransition(true);
49+
}
50+
wasAuthenticated.current = isAuthenticated;
51+
}, [isAuthenticated]);
52+
53+
const handleTransitionComplete = () => {
54+
setShowTransition(false);
55+
};
56+
3957
if (isLoading) {
4058
return (
4159
<Flex align="center" justify="center" minHeight="100vh">
@@ -47,7 +65,36 @@ function App() {
4765
);
4866
}
4967

50-
return isAuthenticated ? <MainLayout /> : <AuthScreen />;
68+
return (
69+
<>
70+
<CursorGlow />
71+
<AnimatePresence mode="wait">
72+
{!isAuthenticated ? (
73+
<motion.div
74+
key="auth"
75+
initial={{ opacity: 1 }}
76+
exit={{ opacity: 0 }}
77+
transition={{ duration: 0.5 }}
78+
>
79+
<AuthScreen />
80+
</motion.div>
81+
) : (
82+
<motion.div
83+
key="main"
84+
initial={{ opacity: 0 }}
85+
animate={{ opacity: 1 }}
86+
transition={{ duration: 0.5, delay: showTransition ? 1.5 : 0 }}
87+
>
88+
<MainLayout />
89+
</motion.div>
90+
)}
91+
</AnimatePresence>
92+
<LoginTransition
93+
isAnimating={showTransition}
94+
onComplete={handleTransitionComplete}
95+
/>
96+
</>
97+
);
5198
}
5299

53100
export default App;
19.7 KB
Binary file not shown.
1.13 MB
Loading

apps/array/src/renderer/assets/images/twig-logo.svg

Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Campfire } from "@phosphor-icons/react";
2+
import { IconButton, Tooltip } from "@radix-ui/themes";
3+
import { useThemeStore } from "@stores/themeStore";
4+
5+
export function CampfireToggle() {
6+
const { isDarkMode, toggleDarkMode } = useThemeStore();
7+
8+
return (
9+
<Tooltip content={isDarkMode ? "Light mode" : "Dark mode"}>
10+
<IconButton
11+
size="1"
12+
variant="ghost"
13+
onClick={toggleDarkMode}
14+
style={{
15+
color: isDarkMode ? "var(--orange-9)" : "var(--gray-9)",
16+
}}
17+
>
18+
<Campfire size={16} weight={isDarkMode ? "fill" : "regular"} />
19+
</IconButton>
20+
</Tooltip>
21+
);
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useThemeStore } from "@stores/themeStore";
2+
import { useEffect, useState } from "react";
3+
4+
export function CursorGlow() {
5+
const isDarkMode = useThemeStore((state) => state.isDarkMode);
6+
const [mousePos, setMousePos] = useState<{ x: number; y: number } | null>(
7+
null,
8+
);
9+
10+
useEffect(() => {
11+
const handleMouseMove = (e: MouseEvent) => {
12+
setMousePos({ x: e.clientX, y: e.clientY });
13+
};
14+
15+
window.addEventListener("mousemove", handleMouseMove);
16+
return () => window.removeEventListener("mousemove", handleMouseMove);
17+
}, []);
18+
19+
// Only show in dark mode
20+
if (!isDarkMode || !mousePos) return null;
21+
22+
return (
23+
<div
24+
style={{
25+
position: "fixed",
26+
left: mousePos.x - 100,
27+
top: mousePos.y - 100,
28+
width: 200,
29+
height: 200,
30+
pointerEvents: "none",
31+
background:
32+
"radial-gradient(circle at center, var(--fire-glow) 0%, transparent 70%)",
33+
zIndex: 9999,
34+
}}
35+
/>
36+
);
37+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { motion } from "framer-motion";
2+
3+
interface LoginTransitionProps {
4+
isAnimating: boolean;
5+
onComplete: () => void;
6+
}
7+
8+
export function LoginTransition({
9+
isAnimating,
10+
onComplete,
11+
}: LoginTransitionProps) {
12+
if (!isAnimating) return null;
13+
14+
return (
15+
<motion.div
16+
style={{
17+
position: "fixed",
18+
inset: 0,
19+
zIndex: 10000,
20+
background: "var(--cave-charcoal)",
21+
}}
22+
initial={{ opacity: 0 }}
23+
animate={{ opacity: 1 }}
24+
transition={{ duration: 0.5 }}
25+
onAnimationComplete={onComplete}
26+
/>
27+
);
28+
}

apps/array/src/renderer/components/StatusBar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CampfireToggle } from "@components/CampfireToggle";
12
import { StatusBarMenu } from "@components/StatusBarMenu";
23
import { Badge, Box, Code, Flex, Kbd } from "@radix-ui/themes";
34
import { useStatusBarStore } from "@stores/statusBarStore";
@@ -43,6 +44,7 @@ export function StatusBar({ showKeyHints = true }: StatusBarProps) {
4344
)}
4445

4546
<Flex align="center" gap="2">
47+
<CampfireToggle />
4648
{IS_DEV && (
4749
<Badge size="1">
4850
<Code size="1" variant="ghost">

0 commit comments

Comments
 (0)