Skip to content

Commit 0cbf9da

Browse files
committed
Imrpove connect form
1 parent 67aa26a commit 0cbf9da

2 files changed

Lines changed: 164 additions & 0 deletions

File tree

src/components/CodeRain.tsx

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
/**
4+
* Subtle falling code rain canvas — decorative background for the connect page.
5+
*
6+
* Renders columns of faint code-like characters that scroll downward at varying
7+
* speeds, giving the impression of live code being streamed. Designed to sit
8+
* behind the glass-morphism card without competing for attention.
9+
*/
10+
export function CodeRain() {
11+
const canvasRef = useRef<HTMLCanvasElement>(null);
12+
13+
useEffect(() => {
14+
const canvas = canvasRef.current;
15+
if (!canvas) return;
16+
17+
const ctx = canvas.getContext('2d');
18+
if (!ctx) return;
19+
20+
// Code-like snippets that feel realistic
21+
const snippets = [
22+
'const ', 'let ', 'fn ', 'def ', 'import ', 'export ', 'return ',
23+
'async ', 'await ', 'if ', 'else ', 'for ', 'while ', 'match ',
24+
'=> ', '-> ', ':: ', '() ', '[] ', '{}', '..', '// ',
25+
'true', 'false', 'null', 'nil', 'self', 'this',
26+
'pub ', 'use ', 'mod ', 'impl ', 'trait ', 'type ',
27+
'<T>', 'Ok()', 'Err', 'Some', 'None',
28+
'println!', 'console.', 'print(', 'log(',
29+
'= ', '!= ', '== ', '>= ', '<= ', '&& ', '|| ',
30+
'0x', '127', '443', '8080', '3000',
31+
'utf-8', 'json', 'ssh', 'tcp', 'http',
32+
'fn main', 'class ', 'struct ', 'enum ',
33+
'.map(', '.filter(', '.then(', '.catch(',
34+
'Result<', 'Vec<', 'Option<', 'Promise<',
35+
'|> ', ':ok', ':error', 'defmodule ',
36+
];
37+
38+
/** Pick a random snippet */
39+
const randomSnippet = () => snippets[Math.floor(Math.random() * snippets.length)];
40+
41+
// --- Column state ---
42+
43+
interface Column {
44+
x: number;
45+
y: number;
46+
speed: number; // px per frame
47+
opacity: number; // base opacity for this column
48+
chars: string; // current text being "typed"
49+
charIndex: number; // how many chars revealed so far
50+
fontSize: number;
51+
}
52+
53+
let columns: Column[] = [];
54+
let w = 0;
55+
let h = 0;
56+
let animId = 0;
57+
58+
const resize = () => {
59+
const dpr = window.devicePixelRatio || 1;
60+
const rect = canvas.getBoundingClientRect();
61+
w = rect.width;
62+
h = rect.height;
63+
canvas.width = w * dpr;
64+
canvas.height = h * dpr;
65+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
66+
initColumns();
67+
};
68+
69+
const initColumns = () => {
70+
const colGap = 26; // px between columns
71+
const count = Math.ceil(w / colGap);
72+
columns = Array.from({ length: count }, (_, i) => makeColumn(i * colGap, true));
73+
};
74+
75+
const makeColumn = (x: number, randomizeY: boolean): Column => ({
76+
x,
77+
y: randomizeY ? Math.random() * h : -20,
78+
speed: 0.2 + Math.random() * 0.45,
79+
opacity: 0.08 + Math.random() * 0.12,
80+
chars: randomSnippet() + randomSnippet() + ' ' + randomSnippet(),
81+
charIndex: 0,
82+
fontSize: 10 + Math.floor(Math.random() * 3),
83+
});
84+
85+
// --- Render loop ---
86+
87+
const draw = () => {
88+
ctx.clearRect(0, 0, w, h);
89+
90+
for (const col of columns) {
91+
ctx.font = `${col.fontSize}px "SF Mono", "Fira Code", "Cascadia Code", Menlo, Consolas, monospace`;
92+
93+
// Draw each character vertically
94+
const lineH = col.fontSize + 4;
95+
const visible = col.chars.slice(0, Math.floor(col.charIndex));
96+
97+
for (let j = 0; j < visible.length; j++) {
98+
// Fade trailing characters
99+
const distFromHead = visible.length - 1 - j;
100+
const fade = Math.max(0, 1 - distFromHead * 0.08);
101+
const alpha = col.opacity * fade;
102+
103+
// Head character gets a brighter teal tint
104+
if (j === visible.length - 1) {
105+
ctx.fillStyle = `rgba(0, 220, 240, ${Math.min(alpha * 3, 0.5)})`;
106+
} else {
107+
ctx.fillStyle = `rgba(180, 200, 220, ${alpha})`;
108+
}
109+
110+
ctx.fillText(visible[j], col.x, col.y + j * lineH);
111+
}
112+
113+
// Advance "typing"
114+
col.charIndex += col.speed * 0.5;
115+
116+
// Scroll downward
117+
col.y += col.speed;
118+
119+
// Reset when off-screen
120+
if (col.y > h + 40) {
121+
col.y = -(col.chars.length * (col.fontSize + 4));
122+
col.chars = randomSnippet() + randomSnippet() + ' ' + randomSnippet();
123+
col.charIndex = 0;
124+
col.speed = 0.2 + Math.random() * 0.45;
125+
col.opacity = 0.08 + Math.random() * 0.12;
126+
}
127+
128+
// When fully typed, get new text
129+
if (col.charIndex > col.chars.length + 6) {
130+
col.chars = randomSnippet() + randomSnippet() + ' ' + randomSnippet();
131+
col.charIndex = 0;
132+
}
133+
}
134+
135+
animId = requestAnimationFrame(draw);
136+
};
137+
138+
resize();
139+
animId = requestAnimationFrame(draw);
140+
141+
window.addEventListener('resize', resize);
142+
return () => {
143+
window.removeEventListener('resize', resize);
144+
cancelAnimationFrame(animId);
145+
};
146+
}, []);
147+
148+
return (
149+
<canvas
150+
ref={canvasRef}
151+
aria-hidden="true"
152+
style={{
153+
position: 'absolute',
154+
inset: 0,
155+
width: '100%',
156+
height: '100%',
157+
pointerEvents: 'none',
158+
zIndex: 0,
159+
}}
160+
/>
161+
);
162+
}

src/pages/ConnectForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useRef, useState } from 'react';
22
import type { Protocol } from '../bridge/utils';
3+
import { CodeRain } from '../components/CodeRain';
34
import './ConnectForm.css';
45

56
/** Discovery progress reported by the parent during auto-scan. */
@@ -60,6 +61,7 @@ export function ConnectForm({ onConnect, onDiscover, error, isConnecting, discov
6061

6162
return (
6263
<div className="connect-page">
64+
<CodeRain />
6365
<div className="connect-page-orb connect-page-orb--1" aria-hidden="true" />
6466
<div className="connect-page-orb connect-page-orb--2" aria-hidden="true" />
6567
<div className="connect-page-orb connect-page-orb--3" aria-hidden="true" />

0 commit comments

Comments
 (0)