clay-server 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/bin/claude-relay.js +6 -0
- package/bin/cli.js +2602 -0
- package/lib/cli-sessions.js +265 -0
- package/lib/config.js +338 -0
- package/lib/daemon.js +802 -0
- package/lib/ipc.js +124 -0
- package/lib/notes.js +121 -0
- package/lib/pages.js +1308 -0
- package/lib/project.js +3172 -0
- package/lib/public/app.js +4795 -0
- package/lib/public/apple-touch-icon-dark.png +0 -0
- package/lib/public/apple-touch-icon.png +0 -0
- package/lib/public/clay-logo.png +0 -0
- package/lib/public/css/admin.css +576 -0
- package/lib/public/css/base.css +284 -0
- package/lib/public/css/diff.css +139 -0
- package/lib/public/css/filebrowser.css +1482 -0
- package/lib/public/css/highlight.css +144 -0
- package/lib/public/css/home-hub.css +455 -0
- package/lib/public/css/icon-strip.css +614 -0
- package/lib/public/css/input.css +654 -0
- package/lib/public/css/loop.css +898 -0
- package/lib/public/css/menus.css +823 -0
- package/lib/public/css/messages.css +1448 -0
- package/lib/public/css/mobile-nav.css +384 -0
- package/lib/public/css/overlays.css +893 -0
- package/lib/public/css/playbook.css +264 -0
- package/lib/public/css/profile.css +268 -0
- package/lib/public/css/rewind.css +528 -0
- package/lib/public/css/scheduler-modal.css +1429 -0
- package/lib/public/css/scheduler.css +1306 -0
- package/lib/public/css/server-settings.css +811 -0
- package/lib/public/css/sidebar.css +1189 -0
- package/lib/public/css/skills.css +789 -0
- package/lib/public/css/sticky-notes.css +848 -0
- package/lib/public/css/stt.css +155 -0
- package/lib/public/css/title-bar.css +517 -0
- package/lib/public/favicon-banded-32.png +0 -0
- package/lib/public/favicon-banded.png +0 -0
- package/lib/public/favicon-dark.svg +1 -0
- package/lib/public/favicon.svg +1 -0
- package/lib/public/icon-192-dark.png +0 -0
- package/lib/public/icon-192.png +0 -0
- package/lib/public/icon-512-dark.png +0 -0
- package/lib/public/icon-512.png +0 -0
- package/lib/public/icon-banded-76.png +0 -0
- package/lib/public/icon-banded-96.png +0 -0
- package/lib/public/icon-mono.svg +1 -0
- package/lib/public/index.html +1437 -0
- package/lib/public/manifest.json +27 -0
- package/lib/public/modules/admin.js +631 -0
- package/lib/public/modules/ascii-logo.js +442 -0
- package/lib/public/modules/diff.js +398 -0
- package/lib/public/modules/events.js +21 -0
- package/lib/public/modules/filebrowser.js +1535 -0
- package/lib/public/modules/fileicons.js +172 -0
- package/lib/public/modules/icons.js +54 -0
- package/lib/public/modules/input.js +661 -0
- package/lib/public/modules/markdown.js +378 -0
- package/lib/public/modules/notifications.js +548 -0
- package/lib/public/modules/playbook.js +578 -0
- package/lib/public/modules/profile.js +378 -0
- package/lib/public/modules/project-settings.js +901 -0
- package/lib/public/modules/qrcode.js +67 -0
- package/lib/public/modules/rewind.js +345 -0
- package/lib/public/modules/scheduler.js +2833 -0
- package/lib/public/modules/server-settings.js +928 -0
- package/lib/public/modules/sidebar.js +2264 -0
- package/lib/public/modules/skills.js +794 -0
- package/lib/public/modules/state.js +3 -0
- package/lib/public/modules/sticky-notes.js +1253 -0
- package/lib/public/modules/stt.js +272 -0
- package/lib/public/modules/terminal.js +736 -0
- package/lib/public/modules/theme.js +720 -0
- package/lib/public/modules/tools.js +1622 -0
- package/lib/public/modules/utils.js +56 -0
- package/lib/public/style.css +24 -0
- package/lib/public/sw.js +154 -0
- package/lib/public/wordmark-banded-20.png +0 -0
- package/lib/public/wordmark-banded-32.png +0 -0
- package/lib/public/wordmark-banded-64.png +0 -0
- package/lib/public/wordmark-banded-80.png +0 -0
- package/lib/push.js +130 -0
- package/lib/scheduler.js +402 -0
- package/lib/sdk-bridge.js +1035 -0
- package/lib/server.js +2055 -0
- package/lib/sessions.js +552 -0
- package/lib/smtp.js +221 -0
- package/lib/terminal-manager.js +187 -0
- package/lib/terminal.js +24 -0
- package/lib/themes/ayu-light.json +9 -0
- package/lib/themes/catppuccin-latte.json +9 -0
- package/lib/themes/catppuccin-mocha.json +9 -0
- package/lib/themes/clay-light.json +10 -0
- package/lib/themes/clay.json +10 -0
- package/lib/themes/dracula.json +9 -0
- package/lib/themes/everforest-light.json +9 -0
- package/lib/themes/everforest.json +9 -0
- package/lib/themes/github-light.json +9 -0
- package/lib/themes/gruvbox-dark.json +9 -0
- package/lib/themes/gruvbox-light.json +9 -0
- package/lib/themes/monokai.json +9 -0
- package/lib/themes/nord-light.json +9 -0
- package/lib/themes/nord.json +9 -0
- package/lib/themes/one-dark.json +9 -0
- package/lib/themes/one-light.json +9 -0
- package/lib/themes/rose-pine-dawn.json +9 -0
- package/lib/themes/rose-pine.json +9 -0
- package/lib/themes/solarized-dark.json +9 -0
- package/lib/themes/solarized-light.json +9 -0
- package/lib/themes/tokyo-night-light.json +9 -0
- package/lib/themes/tokyo-night.json +9 -0
- package/lib/updater.js +97 -0
- package/lib/users.js +459 -0
- package/lib/utils.js +64 -0
- package/package.json +56 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
// --- Animated ASCII Logo (Canvas particle system) ---
|
|
2
|
+
// Midjourney-style scatter-in / color-sweep / shatter / reassemble animation
|
|
3
|
+
|
|
4
|
+
var ASCII_LINES = [
|
|
5
|
+
"________/\\\\\\\\\\\\\\\\\\__/\\\\\\_________________/\\\\\\\\\\\\\\\\\\_____/\\\\\\________/\\\\\\",
|
|
6
|
+
" _____/\\\\\\////////__\\/\\\\\\_______________/\\\\\\\\\\\\\\\\\\\\\\\\\\__\\///\\\\\\____/\\\\\\/_",
|
|
7
|
+
" ___/\\\\\\/___________\\/\\\\\\______________/\\\\\\/////////\\\\\\___\\///\\\\\\/\\\\\\/___",
|
|
8
|
+
" __/\\\\\\_____________\\/\\\\\\_____________\\/\\\\\\_______\\/\\\\\\_____\\///\\\\\\/_____",
|
|
9
|
+
" _\\/\\\\\\_____________\\/\\\\\\_____________\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_______\\/\\\\\\______",
|
|
10
|
+
" _\\//\\\\\\____________\\/\\\\\\_____________\\/\\\\\\/////////\\\\\\_______\\/\\\\\\______",
|
|
11
|
+
" __\\///\\\\\\__________\\/\\\\\\_____________\\/\\\\\\_______\\/\\\\\\_______\\/\\\\\\______",
|
|
12
|
+
" ____\\////\\\\\\\\\\\\\\\\\\_\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_\\/\\\\\\_______\\/\\\\\\_______\\/\\\\\\______",
|
|
13
|
+
" _______\\/////////__\\///////////////__\\///________\\///________\\///_______",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// Tri-accent gradient stops: Green → Indigo → Terracotta (matches CLI)
|
|
17
|
+
var GRADIENT_STOPS = [
|
|
18
|
+
[9, 229, 163],
|
|
19
|
+
[88, 87, 252],
|
|
20
|
+
[254, 113, 80],
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Animation phase durations (seconds)
|
|
24
|
+
var PHASE_SCATTER_IN = 1.8;
|
|
25
|
+
var PHASE_COLOR_SWEEP = 0.9;
|
|
26
|
+
var PHASE_HOLD = 2.5;
|
|
27
|
+
var PHASE_SHATTER = 1.4;
|
|
28
|
+
var PHASE_REASSEMBLE = 1.8;
|
|
29
|
+
|
|
30
|
+
var canvas = null;
|
|
31
|
+
var ctx = null;
|
|
32
|
+
var particles = [];
|
|
33
|
+
var phase = "idle";
|
|
34
|
+
var phaseTime = 0;
|
|
35
|
+
var animId = null;
|
|
36
|
+
var lastTime = 0;
|
|
37
|
+
var charWidth = 0;
|
|
38
|
+
var lineHeight = 0;
|
|
39
|
+
var fontSize = 0;
|
|
40
|
+
var offsetX = 0;
|
|
41
|
+
var offsetY = 0;
|
|
42
|
+
var centerX = 0;
|
|
43
|
+
var centerY = 0;
|
|
44
|
+
var maxCol = 0;
|
|
45
|
+
var glyphCache = {}; // key: char → offscreen canvas (white glyph)
|
|
46
|
+
var running = false;
|
|
47
|
+
|
|
48
|
+
function getGradientColor(row, totalRows) {
|
|
49
|
+
var t = totalRows > 1 ? row / (totalRows - 1) : 0;
|
|
50
|
+
var r, g, b;
|
|
51
|
+
if (t <= 0.5) {
|
|
52
|
+
var s = t * 2;
|
|
53
|
+
r = Math.round(GRADIENT_STOPS[0][0] + (GRADIENT_STOPS[1][0] - GRADIENT_STOPS[0][0]) * s);
|
|
54
|
+
g = Math.round(GRADIENT_STOPS[0][1] + (GRADIENT_STOPS[1][1] - GRADIENT_STOPS[0][1]) * s);
|
|
55
|
+
b = Math.round(GRADIENT_STOPS[0][2] + (GRADIENT_STOPS[1][2] - GRADIENT_STOPS[0][2]) * s);
|
|
56
|
+
} else {
|
|
57
|
+
var s = (t - 0.5) * 2;
|
|
58
|
+
r = Math.round(GRADIENT_STOPS[1][0] + (GRADIENT_STOPS[2][0] - GRADIENT_STOPS[1][0]) * s);
|
|
59
|
+
g = Math.round(GRADIENT_STOPS[1][1] + (GRADIENT_STOPS[2][1] - GRADIENT_STOPS[1][1]) * s);
|
|
60
|
+
b = Math.round(GRADIENT_STOPS[1][2] + (GRADIENT_STOPS[2][2] - GRADIENT_STOPS[1][2]) * s);
|
|
61
|
+
}
|
|
62
|
+
return [r, g, b];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function easeOutBack(t) {
|
|
66
|
+
var s = 1.4;
|
|
67
|
+
var t1 = t - 1;
|
|
68
|
+
return t1 * t1 * ((s + 1) * t1 + s) + 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function easeOutCubic(t) {
|
|
72
|
+
var t1 = t - 1;
|
|
73
|
+
return t1 * t1 * t1 + 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function lerp(a, b, t) {
|
|
77
|
+
return a + (b - a) * t;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildParticles() {
|
|
81
|
+
particles = [];
|
|
82
|
+
maxCol = 0;
|
|
83
|
+
for (var row = 0; row < ASCII_LINES.length; row++) {
|
|
84
|
+
var line = ASCII_LINES[row];
|
|
85
|
+
for (var col = 0; col < line.length; col++) {
|
|
86
|
+
var ch = line[col];
|
|
87
|
+
if (ch === " ") continue;
|
|
88
|
+
if (col > maxCol) maxCol = col;
|
|
89
|
+
var gc = getGradientColor(row, ASCII_LINES.length);
|
|
90
|
+
particles.push({
|
|
91
|
+
char: ch,
|
|
92
|
+
row: row,
|
|
93
|
+
col: col,
|
|
94
|
+
targetX: 0,
|
|
95
|
+
targetY: 0,
|
|
96
|
+
x: 0,
|
|
97
|
+
y: 0,
|
|
98
|
+
vx: 0,
|
|
99
|
+
vy: 0,
|
|
100
|
+
opacity: 0,
|
|
101
|
+
rotation: 0,
|
|
102
|
+
vr: 0,
|
|
103
|
+
finalR: gc[0],
|
|
104
|
+
finalG: gc[1],
|
|
105
|
+
finalB: gc[2],
|
|
106
|
+
r: 220,
|
|
107
|
+
g: 220,
|
|
108
|
+
b: 220,
|
|
109
|
+
scatterX: 0,
|
|
110
|
+
scatterY: 0,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function computeLayout() {
|
|
117
|
+
if (!canvas) return;
|
|
118
|
+
var container = canvas.parentElement;
|
|
119
|
+
var dpr = window.devicePixelRatio || 1;
|
|
120
|
+
var w = container.clientWidth;
|
|
121
|
+
var h = container.clientHeight;
|
|
122
|
+
|
|
123
|
+
canvas.style.width = w + "px";
|
|
124
|
+
canvas.style.height = h + "px";
|
|
125
|
+
canvas.width = Math.round(w * dpr);
|
|
126
|
+
canvas.height = Math.round(h * dpr);
|
|
127
|
+
|
|
128
|
+
ctx = canvas.getContext("2d");
|
|
129
|
+
ctx.scale(dpr, dpr);
|
|
130
|
+
|
|
131
|
+
// Find longest line
|
|
132
|
+
var maxLen = 0;
|
|
133
|
+
for (var i = 0; i < ASCII_LINES.length; i++) {
|
|
134
|
+
if (ASCII_LINES[i].length > maxLen) maxLen = ASCII_LINES[i].length;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Responsive font size
|
|
138
|
+
fontSize = Math.floor(w / maxLen * 1.3);
|
|
139
|
+
fontSize = Math.max(8, Math.min(fontSize, 28));
|
|
140
|
+
|
|
141
|
+
ctx.font = "bold " + fontSize + "px 'Roboto Mono', Menlo, Monaco, Consolas, monospace";
|
|
142
|
+
charWidth = ctx.measureText("M").width;
|
|
143
|
+
lineHeight = fontSize * 1.2;
|
|
144
|
+
|
|
145
|
+
var totalW = maxLen * charWidth;
|
|
146
|
+
var totalH = ASCII_LINES.length * lineHeight;
|
|
147
|
+
offsetX = (w - totalW) / 2;
|
|
148
|
+
offsetY = (h - totalH) / 2;
|
|
149
|
+
centerX = w / 2;
|
|
150
|
+
centerY = h / 2;
|
|
151
|
+
|
|
152
|
+
// Recompute target positions
|
|
153
|
+
for (var i = 0; i < particles.length; i++) {
|
|
154
|
+
var p = particles[i];
|
|
155
|
+
p.targetX = offsetX + p.col * charWidth;
|
|
156
|
+
p.targetY = offsetY + p.row * lineHeight + lineHeight * 0.8;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Build glyph cache — pre-render each unique char as white on offscreen canvas
|
|
160
|
+
glyphCache = {};
|
|
161
|
+
var dpr2 = window.devicePixelRatio || 1;
|
|
162
|
+
var pad = Math.ceil(fontSize * 0.3);
|
|
163
|
+
var gw = Math.ceil(charWidth * dpr2) + pad * 2;
|
|
164
|
+
var gh = Math.ceil(fontSize * 1.6 * dpr2) + pad * 2;
|
|
165
|
+
var fontStr = "bold " + fontSize + "px 'Roboto Mono', Menlo, Monaco, Consolas, monospace";
|
|
166
|
+
var chars = {};
|
|
167
|
+
for (var i = 0; i < particles.length; i++) chars[particles[i].char] = 1;
|
|
168
|
+
var keys = Object.keys(chars);
|
|
169
|
+
for (var i = 0; i < keys.length; i++) {
|
|
170
|
+
var ch = keys[i];
|
|
171
|
+
var oc = document.createElement("canvas");
|
|
172
|
+
oc.width = gw;
|
|
173
|
+
oc.height = gh;
|
|
174
|
+
var ox = oc.getContext("2d");
|
|
175
|
+
ox.scale(dpr2, dpr2);
|
|
176
|
+
ox.font = fontStr;
|
|
177
|
+
ox.textBaseline = "alphabetic";
|
|
178
|
+
ox.fillStyle = "#fff";
|
|
179
|
+
ox.fillText(ch, pad / dpr2, (gh - pad) / dpr2);
|
|
180
|
+
glyphCache[ch] = oc;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function randomScatter(p) {
|
|
185
|
+
var canvasW = canvas.width / (window.devicePixelRatio || 1);
|
|
186
|
+
var canvasH = canvas.height / (window.devicePixelRatio || 1);
|
|
187
|
+
p.scatterX = (Math.random() - 0.5) * canvasW * 2;
|
|
188
|
+
p.scatterY = (Math.random() - 0.5) * canvasH * 2;
|
|
189
|
+
p.x = p.scatterX;
|
|
190
|
+
p.y = p.scatterY;
|
|
191
|
+
p.opacity = 0;
|
|
192
|
+
p.rotation = (Math.random() - 0.5) * Math.PI * 4;
|
|
193
|
+
p.vx = 0;
|
|
194
|
+
p.vy = 0;
|
|
195
|
+
p.vr = 0;
|
|
196
|
+
p.r = 220;
|
|
197
|
+
p.g = 220;
|
|
198
|
+
p.b = 220;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function setPhase(newPhase) {
|
|
202
|
+
phase = newPhase;
|
|
203
|
+
phaseTime = 0;
|
|
204
|
+
|
|
205
|
+
if (phase === "scatter-in") {
|
|
206
|
+
for (var i = 0; i < particles.length; i++) {
|
|
207
|
+
randomScatter(particles[i]);
|
|
208
|
+
}
|
|
209
|
+
} else if (phase === "shatter") {
|
|
210
|
+
for (var i = 0; i < particles.length; i++) {
|
|
211
|
+
var p = particles[i];
|
|
212
|
+
var angle = Math.atan2(p.targetY - centerY, p.targetX - centerX);
|
|
213
|
+
angle += (Math.random() - 0.5) * 0.6;
|
|
214
|
+
var speed = 250 + Math.random() * 450;
|
|
215
|
+
p.vx = Math.cos(angle) * speed;
|
|
216
|
+
p.vy = Math.sin(angle) * speed;
|
|
217
|
+
p.vr = (Math.random() - 0.5) * 8;
|
|
218
|
+
}
|
|
219
|
+
} else if (phase === "reassemble") {
|
|
220
|
+
// particles keep their current positions from shatter
|
|
221
|
+
for (var i = 0; i < particles.length; i++) {
|
|
222
|
+
var p = particles[i];
|
|
223
|
+
p.scatterX = p.x;
|
|
224
|
+
p.scatterY = p.y;
|
|
225
|
+
p.vx = 0;
|
|
226
|
+
p.vy = 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function updateParticles(dt) {
|
|
232
|
+
phaseTime += dt;
|
|
233
|
+
|
|
234
|
+
if (phase === "scatter-in") {
|
|
235
|
+
for (var i = 0; i < particles.length; i++) {
|
|
236
|
+
var p = particles[i];
|
|
237
|
+
// Stagger by column — delay proportional to column position
|
|
238
|
+
var delay = (p.col / (maxCol || 1)) * 0.6;
|
|
239
|
+
var localT = Math.max(0, phaseTime - delay) / (PHASE_SCATTER_IN - 0.6);
|
|
240
|
+
localT = Math.min(localT, 1);
|
|
241
|
+
var ease = easeOutBack(localT);
|
|
242
|
+
|
|
243
|
+
p.x = lerp(p.scatterX, p.targetX, ease);
|
|
244
|
+
p.y = lerp(p.scatterY, p.targetY, ease);
|
|
245
|
+
p.opacity = Math.min(1, localT * 3);
|
|
246
|
+
p.rotation = lerp(p.rotation, 0, localT);
|
|
247
|
+
}
|
|
248
|
+
if (phaseTime >= PHASE_SCATTER_IN) {
|
|
249
|
+
// Snap to targets
|
|
250
|
+
for (var i = 0; i < particles.length; i++) {
|
|
251
|
+
var p = particles[i];
|
|
252
|
+
p.x = p.targetX;
|
|
253
|
+
p.y = p.targetY;
|
|
254
|
+
p.opacity = 1;
|
|
255
|
+
p.rotation = 0;
|
|
256
|
+
}
|
|
257
|
+
setPhase("color-sweep");
|
|
258
|
+
}
|
|
259
|
+
} else if (phase === "color-sweep") {
|
|
260
|
+
var sweepProgress = phaseTime / PHASE_COLOR_SWEEP;
|
|
261
|
+
for (var i = 0; i < particles.length; i++) {
|
|
262
|
+
var p = particles[i];
|
|
263
|
+
var colNorm = p.col / (maxCol || 1);
|
|
264
|
+
var localT = (sweepProgress - colNorm * 0.6) / 0.4;
|
|
265
|
+
localT = Math.max(0, Math.min(1, localT));
|
|
266
|
+
var ease = easeOutCubic(localT);
|
|
267
|
+
p.r = Math.round(lerp(220, p.finalR, ease));
|
|
268
|
+
p.g = Math.round(lerp(220, p.finalG, ease));
|
|
269
|
+
p.b = Math.round(lerp(220, p.finalB, ease));
|
|
270
|
+
}
|
|
271
|
+
if (phaseTime >= PHASE_COLOR_SWEEP) {
|
|
272
|
+
for (var i = 0; i < particles.length; i++) {
|
|
273
|
+
var p = particles[i];
|
|
274
|
+
p.r = p.finalR;
|
|
275
|
+
p.g = p.finalG;
|
|
276
|
+
p.b = p.finalB;
|
|
277
|
+
}
|
|
278
|
+
setPhase("hold");
|
|
279
|
+
}
|
|
280
|
+
} else if (phase === "hold") {
|
|
281
|
+
var breath = Math.sin(phaseTime * Math.PI * 0.8) * 0.04;
|
|
282
|
+
for (var i = 0; i < particles.length; i++) {
|
|
283
|
+
var p = particles[i];
|
|
284
|
+
p.opacity = 0.96 + breath;
|
|
285
|
+
// Subtle micro-jitter
|
|
286
|
+
p.x = p.targetX + Math.sin(phaseTime * 2.3 + i * 0.7) * 0.4;
|
|
287
|
+
p.y = p.targetY + Math.cos(phaseTime * 1.9 + i * 0.5) * 0.3;
|
|
288
|
+
}
|
|
289
|
+
if (phaseTime >= PHASE_HOLD) {
|
|
290
|
+
// Reset to exact positions before shatter
|
|
291
|
+
for (var i = 0; i < particles.length; i++) {
|
|
292
|
+
var p = particles[i];
|
|
293
|
+
p.x = p.targetX;
|
|
294
|
+
p.y = p.targetY;
|
|
295
|
+
p.opacity = 1;
|
|
296
|
+
}
|
|
297
|
+
setPhase("shatter");
|
|
298
|
+
}
|
|
299
|
+
} else if (phase === "shatter") {
|
|
300
|
+
var drag = 0.97;
|
|
301
|
+
for (var i = 0; i < particles.length; i++) {
|
|
302
|
+
var p = particles[i];
|
|
303
|
+
p.vx *= drag;
|
|
304
|
+
p.vy *= drag;
|
|
305
|
+
p.x += p.vx * dt;
|
|
306
|
+
p.y += p.vy * dt;
|
|
307
|
+
p.rotation += p.vr * dt;
|
|
308
|
+
p.vr *= drag;
|
|
309
|
+
|
|
310
|
+
// Fade out based on distance from center
|
|
311
|
+
var dx = p.x - centerX;
|
|
312
|
+
var dy = p.y - centerY;
|
|
313
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
314
|
+
var maxDist = Math.max(centerX, centerY) * 0.8;
|
|
315
|
+
p.opacity = Math.max(0, 1 - dist / maxDist);
|
|
316
|
+
}
|
|
317
|
+
if (phaseTime >= PHASE_SHATTER) {
|
|
318
|
+
setPhase("reassemble");
|
|
319
|
+
}
|
|
320
|
+
} else if (phase === "reassemble") {
|
|
321
|
+
for (var i = 0; i < particles.length; i++) {
|
|
322
|
+
var p = particles[i];
|
|
323
|
+
var delay = ((maxCol - p.col) / (maxCol || 1)) * 0.5;
|
|
324
|
+
var localT = Math.max(0, phaseTime - delay) / (PHASE_REASSEMBLE - 0.5);
|
|
325
|
+
localT = Math.min(localT, 1);
|
|
326
|
+
var ease = easeOutBack(localT);
|
|
327
|
+
|
|
328
|
+
p.x = lerp(p.scatterX, p.targetX, ease);
|
|
329
|
+
p.y = lerp(p.scatterY, p.targetY, ease);
|
|
330
|
+
p.opacity = Math.min(1, localT * 2.5);
|
|
331
|
+
p.rotation = lerp(p.rotation, 0, localT * localT);
|
|
332
|
+
}
|
|
333
|
+
if (phaseTime >= PHASE_REASSEMBLE) {
|
|
334
|
+
for (var i = 0; i < particles.length; i++) {
|
|
335
|
+
var p = particles[i];
|
|
336
|
+
p.x = p.targetX;
|
|
337
|
+
p.y = p.targetY;
|
|
338
|
+
p.opacity = 1;
|
|
339
|
+
p.rotation = 0;
|
|
340
|
+
}
|
|
341
|
+
setPhase("hold");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function drawFrame() {
|
|
347
|
+
if (!ctx) return;
|
|
348
|
+
var dpr = window.devicePixelRatio || 1;
|
|
349
|
+
var w = canvas.width / dpr;
|
|
350
|
+
var h = canvas.height / dpr;
|
|
351
|
+
ctx.clearRect(0, 0, w, h);
|
|
352
|
+
|
|
353
|
+
var pad = Math.ceil(fontSize * 0.3);
|
|
354
|
+
var drawW = (glyphCache._w || (Math.ceil(charWidth * dpr) + pad * 2)) / dpr;
|
|
355
|
+
var drawH = (glyphCache._h || (Math.ceil(fontSize * 1.6 * dpr) + pad * 2)) / dpr;
|
|
356
|
+
var padCSS = pad / dpr;
|
|
357
|
+
|
|
358
|
+
// Use globalCompositeOperation to tint white glyphs
|
|
359
|
+
// 1) Draw glyphs as white (source-over)
|
|
360
|
+
// 2) Multiply with color overlay
|
|
361
|
+
// Instead, use simpler approach: drawImage with globalAlpha, then tint via composite
|
|
362
|
+
|
|
363
|
+
for (var i = 0; i < particles.length; i++) {
|
|
364
|
+
var p = particles[i];
|
|
365
|
+
if (p.opacity <= 0.01) continue;
|
|
366
|
+
|
|
367
|
+
var gc = glyphCache[p.char];
|
|
368
|
+
if (!gc) continue;
|
|
369
|
+
|
|
370
|
+
var dx = p.x - padCSS;
|
|
371
|
+
var dy = p.y - drawH + padCSS;
|
|
372
|
+
|
|
373
|
+
ctx.globalAlpha = p.opacity;
|
|
374
|
+
|
|
375
|
+
if (Math.abs(p.rotation) > 0.01) {
|
|
376
|
+
ctx.save();
|
|
377
|
+
ctx.translate(p.x + charWidth / 2, p.y - fontSize / 3);
|
|
378
|
+
ctx.rotate(p.rotation);
|
|
379
|
+
ctx.drawImage(gc, -drawW / 2, -drawH / 2, drawW, drawH);
|
|
380
|
+
ctx.restore();
|
|
381
|
+
} else {
|
|
382
|
+
ctx.drawImage(gc, dx, dy, drawW, drawH);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Tint pass — multiply white glyphs with particle colors
|
|
387
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
388
|
+
for (var i = 0; i < particles.length; i++) {
|
|
389
|
+
var p = particles[i];
|
|
390
|
+
if (p.opacity <= 0.01) continue;
|
|
391
|
+
ctx.globalAlpha = 1;
|
|
392
|
+
ctx.fillStyle = "rgb(" + p.r + "," + p.g + "," + p.b + ")";
|
|
393
|
+
var dx = p.x - padCSS;
|
|
394
|
+
var dy = p.y - drawH + padCSS;
|
|
395
|
+
ctx.fillRect(dx, dy, drawW, drawH);
|
|
396
|
+
}
|
|
397
|
+
ctx.globalCompositeOperation = "source-over";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function tick(now) {
|
|
401
|
+
if (!running) return;
|
|
402
|
+
var dt = Math.min((now - lastTime) / 1000, 0.05);
|
|
403
|
+
lastTime = now;
|
|
404
|
+
updateParticles(dt);
|
|
405
|
+
drawFrame();
|
|
406
|
+
animId = requestAnimationFrame(tick);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
var resizeObserver = null;
|
|
410
|
+
|
|
411
|
+
export function initAsciiLogo(canvasEl) {
|
|
412
|
+
canvas = canvasEl;
|
|
413
|
+
buildParticles();
|
|
414
|
+
computeLayout();
|
|
415
|
+
|
|
416
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
417
|
+
resizeObserver = new ResizeObserver(function () {
|
|
418
|
+
computeLayout();
|
|
419
|
+
});
|
|
420
|
+
resizeObserver.observe(canvas.parentElement);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function startLogoAnimation() {
|
|
425
|
+
if (running) {
|
|
426
|
+
// Reset to beginning
|
|
427
|
+
cancelAnimationFrame(animId);
|
|
428
|
+
}
|
|
429
|
+
running = true;
|
|
430
|
+
computeLayout();
|
|
431
|
+
setPhase("scatter-in");
|
|
432
|
+
lastTime = performance.now();
|
|
433
|
+
animId = requestAnimationFrame(tick);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function stopLogoAnimation() {
|
|
437
|
+
running = false;
|
|
438
|
+
if (animId) {
|
|
439
|
+
cancelAnimationFrame(animId);
|
|
440
|
+
animId = null;
|
|
441
|
+
}
|
|
442
|
+
}
|