Skip to content

Commit 1920619

Browse files
authored
feat: new flame cursor glow (#526)
### TL;DR Replaced the `CursorGlow` component with a new `TorchGlow` component that creates a torch-like effect when hovering over the task input area in dark mode. ### What changed? - Removed the global `CursorGlow` component from the main App component - Added a new `TorchGlow` component that creates a torch-like effect with multiple layers and a grainy texture - Integrated the `TorchGlow` component into the `TaskInput` component with a container reference - Added CSS animations for the torch glow pulse effect in globals.css - Restructured the TaskInput component to properly contain the torch effect ### How to test? 1. Switch to dark mode in the application 2. Navigate to the task input area 3. Move your cursor over the input area to see the torch-like glow effect following your cursor 4. Verify the effect only appears in dark mode and has a warm, flickering appearance ### Why make this change? This change enhances the user experience in dark mode by providing a more engaging and visually interesting cursor interaction. The torch-like effect adds a subtle but immersive element to the interface, making the dark mode experience more distinctive and polished while maintaining functionality.
1 parent b5825b4 commit 1920619

4 files changed

Lines changed: 233 additions & 80 deletions

File tree

apps/array/src/renderer/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { CursorGlow } from "@components/CursorGlow";
21
import { LoginTransition } from "@components/LoginTransition";
32
import { MainLayout } from "@components/MainLayout";
43
import { AuthScreen } from "@features/auth/components/AuthScreen";
@@ -67,7 +66,6 @@ function App() {
6766

6867
return (
6968
<>
70-
<CursorGlow />
7169
<AnimatePresence mode="wait">
7270
{!isAuthenticated ? (
7371
<motion.div
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { useThemeStore } from "@stores/themeStore";
2+
import { useEffect, useState } from "react";
3+
4+
interface TorchGlowProps {
5+
containerRef: React.RefObject<HTMLElement>;
6+
}
7+
8+
export function TorchGlow({ containerRef }: TorchGlowProps) {
9+
const isDarkMode = useThemeStore((state) => state.isDarkMode);
10+
const [mousePos, setMousePos] = useState<{ x: number; y: number } | null>(
11+
null,
12+
);
13+
const [isHovering, setIsHovering] = useState(false);
14+
15+
useEffect(() => {
16+
const container = containerRef.current;
17+
if (!container) return;
18+
19+
const handleMouseMove = (e: MouseEvent) => {
20+
const rect = container.getBoundingClientRect();
21+
setMousePos({
22+
x: e.clientX - rect.left,
23+
y: e.clientY - rect.top,
24+
});
25+
};
26+
27+
const handleMouseEnter = () => setIsHovering(true);
28+
const handleMouseLeave = () => {
29+
setIsHovering(false);
30+
setMousePos(null);
31+
};
32+
33+
container.addEventListener("mousemove", handleMouseMove);
34+
container.addEventListener("mouseenter", handleMouseEnter);
35+
container.addEventListener("mouseleave", handleMouseLeave);
36+
37+
return () => {
38+
container.removeEventListener("mousemove", handleMouseMove);
39+
container.removeEventListener("mouseenter", handleMouseEnter);
40+
container.removeEventListener("mouseleave", handleMouseLeave);
41+
};
42+
}, [containerRef]);
43+
44+
// Only show in dark mode when hovering
45+
if (!isDarkMode || !isHovering || !mousePos) return null;
46+
47+
return (
48+
<>
49+
{/* SVG filter for grainy torch light texture */}
50+
<svg
51+
aria-hidden="true"
52+
style={{
53+
position: "absolute",
54+
width: 0,
55+
height: 0,
56+
overflow: "hidden",
57+
}}
58+
>
59+
<defs>
60+
<filter id="torch-grain" x="-50%" y="-50%" width="200%" height="200%">
61+
<feTurbulence
62+
type="fractalNoise"
63+
baseFrequency="2.5"
64+
numOctaves="4"
65+
result="noise"
66+
/>
67+
<feColorMatrix
68+
type="matrix"
69+
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.6 0"
70+
result="alphaNoise"
71+
/>
72+
<feComposite in="SourceGraphic" in2="alphaNoise" operator="in" />
73+
</filter>
74+
</defs>
75+
</svg>
76+
77+
{/* Base layer - outer glow */}
78+
<div
79+
style={{
80+
position: "absolute",
81+
left: mousePos.x - 75,
82+
top: mousePos.y - 80,
83+
width: 150,
84+
height: 160,
85+
pointerEvents: "none",
86+
background:
87+
"radial-gradient(ellipse 60% 70% at 50% 55%, rgba(255,120,50,0.58) 0%, transparent 70%)",
88+
filter: "url(#torch-grain)",
89+
zIndex: 1,
90+
}}
91+
/>
92+
93+
{/* Middle layer - offset for irregular shape */}
94+
<div
95+
style={{
96+
position: "absolute",
97+
left: mousePos.x - 65,
98+
top: mousePos.y - 70,
99+
width: 140,
100+
height: 130,
101+
pointerEvents: "none",
102+
background:
103+
"radial-gradient(ellipse 70% 55% at 45% 50%, rgba(255,100,40,0.42) 0%, transparent 65%)",
104+
filter: "url(#torch-grain)",
105+
zIndex: 1,
106+
}}
107+
/>
108+
109+
{/* Inner layer - pulsing flame core */}
110+
<div
111+
className="torch-glow-pulse"
112+
style={{
113+
position: "absolute",
114+
left: mousePos.x - 45,
115+
top: mousePos.y - 55,
116+
width: 90,
117+
height: 100,
118+
pointerEvents: "none",
119+
background:
120+
"radial-gradient(ellipse 50% 60% at 50% 45%, rgba(255,180,80,0.48) 0%, transparent 70%)",
121+
filter: "url(#torch-grain)",
122+
zIndex: 1,
123+
}}
124+
/>
125+
</>
126+
);
127+
}
Lines changed: 91 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TorchGlow } from "@components/TorchGlow";
12
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
23
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
34
import type { ExecutionMode } from "@features/sessions/stores/sessionStore";
@@ -35,6 +36,7 @@ export function TaskInput() {
3536
const { lastUsedLocalWorkspaceMode } = useSettingsStore();
3637

3738
const editorRef = useRef<MessageEditorHandle>(null);
39+
const containerRef = useRef<HTMLDivElement>(null);
3840

3941
const [selectedDirectory, setSelectedDirectory] = useState(
4042
lastUsedDirectory || "",
@@ -81,89 +83,100 @@ export function TaskInput() {
8183
});
8284

8385
return (
84-
<Flex
85-
align="center"
86-
justify="center"
87-
height="100%"
88-
style={{ position: "relative" }}
86+
<div
87+
ref={containerRef}
88+
style={{
89+
position: "relative",
90+
height: "100%",
91+
width: "100%",
92+
overflow: "hidden",
93+
}}
8994
>
90-
<svg
91-
aria-hidden="true"
92-
style={{
93-
position: "absolute",
94-
bottom: 0,
95-
left: 0,
96-
width: "100%",
97-
height: "100.333%",
98-
pointerEvents: "none",
99-
opacity: 0.4,
100-
maskImage: "linear-gradient(to top, black 0%, transparent 100%)",
101-
WebkitMaskImage:
102-
"linear-gradient(to top, black 0%, transparent 100%)",
103-
}}
104-
>
105-
<defs>
106-
<pattern
107-
id="dot-pattern"
108-
patternUnits="userSpaceOnUse"
109-
width="8"
110-
height="8"
111-
>
112-
<circle cx="0" cy="0" r="1" fill={DOT_FILL} />
113-
<circle cx="0" cy="8" r="1" fill={DOT_FILL} />
114-
<circle cx="8" cy="8" r="1" fill={DOT_FILL} />
115-
<circle cx="8" cy="0" r="1" fill={DOT_FILL} />
116-
<circle cx="4" cy="4" r="1" fill={DOT_FILL} />
117-
</pattern>
118-
</defs>
119-
<rect width="100%" height="100%" fill="url(#dot-pattern)" />
120-
</svg>
95+
<TorchGlow containerRef={containerRef} />
12196
<Flex
122-
direction="column"
123-
gap="4"
124-
style={{
125-
fontFamily: "monospace",
126-
width: "100%",
127-
maxWidth: "600px",
128-
position: "relative",
129-
zIndex: 1,
130-
}}
97+
align="center"
98+
justify="center"
99+
height="100%"
100+
style={{ position: "relative" }}
131101
>
132-
<Flex gap="2" align="center">
133-
<FolderPicker
134-
value={selectedDirectory}
135-
onChange={handleDirectoryChange}
136-
placeholder="Select working directory..."
137-
size="1"
138-
/>
139-
{selectedDirectory && (
140-
<BranchSelect
141-
value={selectedBranch}
142-
onChange={setSelectedBranch}
143-
directoryPath={selectedDirectory}
144-
runMode={runMode}
102+
<svg
103+
aria-hidden="true"
104+
style={{
105+
position: "absolute",
106+
bottom: 0,
107+
left: 0,
108+
width: "100%",
109+
height: "100.333%",
110+
pointerEvents: "none",
111+
opacity: 0.4,
112+
maskImage: "linear-gradient(to top, black 0%, transparent 100%)",
113+
WebkitMaskImage:
114+
"linear-gradient(to top, black 0%, transparent 100%)",
115+
}}
116+
>
117+
<defs>
118+
<pattern
119+
id="dot-pattern"
120+
patternUnits="userSpaceOnUse"
121+
width="8"
122+
height="8"
123+
>
124+
<circle cx="0" cy="0" r="1" fill={DOT_FILL} />
125+
<circle cx="0" cy="8" r="1" fill={DOT_FILL} />
126+
<circle cx="8" cy="8" r="1" fill={DOT_FILL} />
127+
<circle cx="8" cy="0" r="1" fill={DOT_FILL} />
128+
<circle cx="4" cy="4" r="1" fill={DOT_FILL} />
129+
</pattern>
130+
</defs>
131+
<rect width="100%" height="100%" fill="url(#dot-pattern)" />
132+
</svg>
133+
<Flex
134+
direction="column"
135+
gap="4"
136+
style={{
137+
fontFamily: "monospace",
138+
width: "100%",
139+
maxWidth: "600px",
140+
position: "relative",
141+
zIndex: 1,
142+
}}
143+
>
144+
<Flex gap="2" align="center">
145+
<FolderPicker
146+
value={selectedDirectory}
147+
onChange={handleDirectoryChange}
148+
placeholder="Select working directory..."
149+
size="1"
145150
/>
146-
)}
147-
</Flex>
151+
{selectedDirectory && (
152+
<BranchSelect
153+
value={selectedBranch}
154+
onChange={setSelectedBranch}
155+
directoryPath={selectedDirectory}
156+
runMode={runMode}
157+
/>
158+
)}
159+
</Flex>
160+
161+
<TaskInputEditor
162+
ref={editorRef}
163+
sessionId="task-input"
164+
repoPath={selectedDirectory}
165+
isCreatingTask={isCreatingTask}
166+
runMode={runMode}
167+
localWorkspaceMode={localWorkspaceMode}
168+
onLocalWorkspaceModeChange={setLocalWorkspaceMode}
169+
canSubmit={canSubmit}
170+
onSubmit={handleSubmit}
171+
hasDirectory={!!selectedDirectory}
172+
onEmptyChange={setEditorIsEmpty}
173+
executionMode={executionMode}
174+
onModeChange={handleModeChange}
175+
/>
148176

149-
<TaskInputEditor
150-
ref={editorRef}
151-
sessionId="task-input"
152-
repoPath={selectedDirectory}
153-
isCreatingTask={isCreatingTask}
154-
runMode={runMode}
155-
localWorkspaceMode={localWorkspaceMode}
156-
onLocalWorkspaceModeChange={setLocalWorkspaceMode}
157-
canSubmit={canSubmit}
158-
onSubmit={handleSubmit}
159-
hasDirectory={!!selectedDirectory}
160-
onEmptyChange={setEditorIsEmpty}
161-
executionMode={executionMode}
162-
onModeChange={handleModeChange}
163-
/>
164-
165-
<SuggestedTasks editorRef={editorRef} />
177+
<SuggestedTasks editorRef={editorRef} />
178+
</Flex>
166179
</Flex>
167-
</Flex>
180+
</div>
168181
);
169182
}

apps/array/src/renderer/styles/globals.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@
113113
animation: campfire-pulse 1s ease-in-out infinite;
114114
}
115115

116+
/* Torch glow pulse animation for cursor flame effect */
117+
@keyframes torch-pulse {
118+
0%,
119+
100% {
120+
opacity: 1;
121+
}
122+
50% {
123+
opacity: 0.3;
124+
}
125+
}
126+
127+
.torch-glow-pulse {
128+
animation: torch-pulse 1.5s ease-in-out infinite;
129+
}
130+
116131
.radix-themes {
117132
/* Font families */
118133
--default-font-family:

0 commit comments

Comments
 (0)