Skip to content

Commit 2b6c500

Browse files
committed
feat: futuristic fx suite (3D grid, node map, vault viz)
1 parent 566d621 commit 2b6c500

8 files changed

Lines changed: 1075 additions & 82 deletions

File tree

desktop/src/renderer/App.tsx

Lines changed: 270 additions & 4 deletions
Large diffs are not rendered by default.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import React, { useEffect, useRef } from "react";
2+
import * as THREE from "three";
3+
4+
type BackgroundCanvasProps = {
5+
active: boolean;
6+
};
7+
8+
const FRAME_INTERVAL = 1000 / 30; // cap to ~30fps
9+
10+
const BackgroundCanvas: React.FC<BackgroundCanvasProps> = ({ active }) => {
11+
const mountRef = useRef<HTMLDivElement>(null);
12+
const canvasRef = useRef<HTMLCanvasElement>(null);
13+
14+
useEffect(() => {
15+
if (!active) return;
16+
const mount = mountRef.current;
17+
const canvas = canvasRef.current;
18+
if (!mount || !canvas) return;
19+
20+
let renderer: THREE.WebGLRenderer | null = null;
21+
let scene: THREE.Scene | null = null;
22+
let camera: THREE.PerspectiveCamera | null = null;
23+
let grid: THREE.LineSegments | null = null;
24+
let aurora: THREE.Mesh | null = null;
25+
let auroraMaterial: THREE.ShaderMaterial | null = null;
26+
let raf = 0;
27+
let disposed = false;
28+
let lastFrame = 0;
29+
30+
try {
31+
renderer = new THREE.WebGLRenderer({
32+
canvas,
33+
alpha: true,
34+
antialias: false,
35+
powerPreference: "low-power",
36+
preserveDrawingBuffer: false,
37+
});
38+
} catch {
39+
return;
40+
}
41+
42+
const css = getComputedStyle(document.documentElement);
43+
const accent = css.getPropertyValue("--accent").trim() || "#2ff5ff";
44+
const accentStrong = css.getPropertyValue("--accent-strong").trim() || "#ff45e6";
45+
const bg = css.getPropertyValue("--bg").trim() || "#050910";
46+
47+
const setSize = () => {
48+
const { clientWidth, clientHeight } = mount;
49+
renderer!.setSize(clientWidth, clientHeight, false);
50+
renderer!.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));
51+
if (camera) {
52+
camera.aspect = clientWidth / clientHeight;
53+
camera.updateProjectionMatrix();
54+
}
55+
};
56+
57+
setSize();
58+
59+
scene = new THREE.Scene();
60+
scene.fog = new THREE.FogExp2(new THREE.Color(bg), 0.035);
61+
62+
camera = new THREE.PerspectiveCamera(52, 1, 0.1, 200);
63+
camera.position.set(0, 10, 28);
64+
camera.lookAt(0, 0, 0);
65+
66+
// Grid geometry (wireframe plane)
67+
const gridSize = 64;
68+
const step = 1.6;
69+
const points: number[] = [];
70+
for (let i = -gridSize; i <= gridSize; i += step) {
71+
points.push(-gridSize, 0, i, gridSize, 0, i); // lines parallel X
72+
points.push(i, 0, -gridSize, i, 0, gridSize); // lines parallel Z
73+
}
74+
const gridGeometry = new THREE.BufferGeometry();
75+
gridGeometry.setAttribute("position", new THREE.Float32BufferAttribute(points, 3));
76+
const gridMaterial = new THREE.LineBasicMaterial({
77+
color: new THREE.Color(accent),
78+
transparent: true,
79+
opacity: 0.22,
80+
depthWrite: false,
81+
blending: THREE.AdditiveBlending,
82+
});
83+
grid = new THREE.LineSegments(gridGeometry, gridMaterial);
84+
grid.rotation.x = -0.4;
85+
grid.position.y = -6;
86+
scene.add(grid);
87+
88+
// Aurora sheet
89+
const auroraGeometry = new THREE.PlaneGeometry(80, 60, 1, 1);
90+
auroraMaterial = new THREE.ShaderMaterial({
91+
transparent: true,
92+
depthWrite: false,
93+
uniforms: {
94+
time: { value: 0 },
95+
color1: { value: new THREE.Color(accent) },
96+
color2: { value: new THREE.Color(accentStrong) },
97+
},
98+
vertexShader: `
99+
varying vec2 vUv;
100+
void main() {
101+
vUv = uv;
102+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
103+
}
104+
`,
105+
fragmentShader: `
106+
uniform float time;
107+
uniform vec3 color1;
108+
uniform vec3 color2;
109+
varying vec2 vUv;
110+
float noise(vec2 p){
111+
return fract(sin(dot(p, vec2(12.9898,78.233))) * 43758.5453);
112+
}
113+
void main() {
114+
float n = noise(vUv * 40.0 + time * 0.3);
115+
float wave = sin((vUv.y + time * 0.08) * 12.0) * 0.5 + 0.5;
116+
float alpha = smoothstep(0.05, 0.35, wave) * 0.35;
117+
vec3 tint = mix(color1, color2, wave * 0.6 + n * 0.25);
118+
gl_FragColor = vec4(tint, alpha);
119+
}
120+
`,
121+
blending: THREE.AdditiveBlending,
122+
});
123+
aurora = new THREE.Mesh(auroraGeometry, auroraMaterial);
124+
aurora.position.set(0, 6, -18);
125+
aurora.rotation.x = -0.3;
126+
scene.add(aurora);
127+
128+
const resizeObserver = new ResizeObserver(setSize);
129+
resizeObserver.observe(mount);
130+
131+
const renderFrame = (now: number) => {
132+
if (disposed || !renderer || !scene || !camera) return;
133+
if (now - lastFrame < FRAME_INTERVAL) {
134+
raf = requestAnimationFrame(renderFrame);
135+
return;
136+
}
137+
lastFrame = now;
138+
const t = now * 0.001;
139+
if (grid) {
140+
grid.position.z = (t * 4) % step;
141+
}
142+
if (auroraMaterial) {
143+
auroraMaterial.uniforms.time.value = t;
144+
}
145+
renderer.render(scene, camera);
146+
raf = requestAnimationFrame(renderFrame);
147+
};
148+
149+
raf = requestAnimationFrame(renderFrame);
150+
151+
return () => {
152+
disposed = true;
153+
cancelAnimationFrame(raf);
154+
resizeObserver.disconnect();
155+
renderer?.dispose();
156+
grid?.geometry.dispose();
157+
if (Array.isArray(grid?.material)) {
158+
grid?.material.forEach((m) => m.dispose());
159+
} else {
160+
grid?.material.dispose();
161+
}
162+
auroraGeometry.dispose();
163+
auroraMaterial?.dispose();
164+
scene?.clear();
165+
};
166+
}, [active]);
167+
168+
if (!active) return null;
169+
170+
return (
171+
<div className="background-canvas" ref={mountRef} aria-hidden>
172+
<canvas ref={canvasRef} />
173+
</div>
174+
);
175+
};
176+
177+
export default BackgroundCanvas;

desktop/src/renderer/components/CommandPalette.tsx

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef } from "react";
1+
import React, { useEffect, useRef, useState } from "react";
22

33
import useFocusTrap from "../hooks/useFocusTrap";
44
import { useI18n } from "../locales";
@@ -33,6 +33,16 @@ interface CommandPaletteProps {
3333
onClose: () => void;
3434
}
3535

36+
const FX_STORAGE_KEY = "darkmesh-palette-fx";
37+
38+
const getInitialFxSetting = (): boolean => {
39+
if (typeof window === "undefined") return true;
40+
const stored = window.localStorage.getItem(FX_STORAGE_KEY);
41+
if (stored === "off") return false;
42+
if (stored === "on") return true;
43+
return true;
44+
};
45+
3646
const CommandPalette: React.FC<CommandPaletteProps> = ({
3747
open,
3848
query,
@@ -48,11 +58,18 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
4858
const { messages } = useI18n();
4959
const paletteText = messages.paletteUi;
5060
const dialogRef = useRef<HTMLDivElement>(null);
61+
const [fxEnabled, setFxEnabled] = useState<boolean>(() => getInitialFxSetting());
62+
5163
useEffect(() => {
5264
if (!open) return;
5365
window.setTimeout(() => inputRef.current?.focus(), 0);
5466
}, [inputRef, open]);
5567

68+
useEffect(() => {
69+
if (typeof window === "undefined") return;
70+
window.localStorage.setItem(FX_STORAGE_KEY, fxEnabled ? "on" : "off");
71+
}, [fxEnabled]);
72+
5673
useFocusTrap(dialogRef, { active: open, initialFocus: inputRef.current, onEscape: onClose });
5774

5875
if (!open) return null;
@@ -61,10 +78,10 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
6178
const descriptionId = "command-palette-hint";
6279

6380
return (
64-
<div className="command-palette-backdrop" role="presentation" onMouseDown={onClose}>
81+
<div className={`command-palette-backdrop ${fxEnabled ? "fx-on" : "fx-off"}`} role="presentation" onMouseDown={onClose}>
6582
<section
6683
ref={dialogRef}
67-
className="command-palette"
84+
className={`command-palette ${fxEnabled ? "fx-on" : "fx-off"}`}
6885
role="dialog"
6986
aria-modal="true"
7087
aria-labelledby={titleId}
@@ -76,9 +93,20 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
7693
<p className="eyebrow">{paletteText.eyebrow}</p>
7794
<h3 id={titleId}>{paletteText.title}</h3>
7895
</div>
79-
<button className="ghost small" type="button" onClick={onClose}>
80-
{paletteText.close}
81-
</button>
96+
<div className="command-palette-actions">
97+
<button
98+
className="ghost small"
99+
type="button"
100+
onClick={() => setFxEnabled((prev) => !prev)}
101+
aria-pressed={fxEnabled}
102+
title={fxEnabled ? "Turn off palette visuals" : "Turn on palette visuals"}
103+
>
104+
{fxEnabled ? "FX on" : "FX off"}
105+
</button>
106+
<button className="ghost small" type="button" onClick={onClose}>
107+
{paletteText.close}
108+
</button>
109+
</div>
82110
</div>
83111

84112
<label className="command-palette-input">

0 commit comments

Comments
 (0)