clay-server 2.7.2 → 2.8.0
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/bin/cli.js +2 -1
- package/lib/config.js +7 -4
- package/lib/project.js +343 -15
- package/lib/public/app.js +1043 -135
- 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/base.css +10 -0
- package/lib/public/css/filebrowser.css +1 -0
- package/lib/public/css/home-hub.css +455 -0
- package/lib/public/css/icon-strip.css +6 -5
- package/lib/public/css/loop.css +141 -23
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/mobile-nav.css +38 -12
- package/lib/public/css/overlays.css +205 -169
- package/lib/public/css/playbook.css +264 -0
- package/lib/public/css/profile.css +268 -0
- package/lib/public/css/scheduler-modal.css +1429 -0
- package/lib/public/css/scheduler.css +1305 -0
- package/lib/public/css/sidebar.css +305 -11
- package/lib/public/css/sticky-notes.css +23 -19
- package/lib/public/css/stt.css +155 -0
- package/lib/public/css/title-bar.css +14 -6
- package/lib/public/favicon-banded-32.png +0 -0
- package/lib/public/favicon-banded.png +0 -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/index.html +335 -42
- package/lib/public/modules/ascii-logo.js +389 -0
- package/lib/public/modules/filebrowser.js +2 -1
- package/lib/public/modules/markdown.js +118 -0
- package/lib/public/modules/notifications.js +50 -63
- package/lib/public/modules/playbook.js +578 -0
- package/lib/public/modules/profile.js +357 -0
- package/lib/public/modules/project-settings.js +4 -9
- package/lib/public/modules/scheduler.js +2826 -0
- package/lib/public/modules/server-settings.js +1 -1
- package/lib/public/modules/sidebar.js +378 -31
- package/lib/public/modules/sticky-notes.js +2 -0
- package/lib/public/modules/stt.js +272 -0
- package/lib/public/modules/terminal.js +32 -0
- package/lib/public/modules/theme.js +3 -10
- package/lib/public/modules/tools.js +2 -1
- package/lib/public/style.css +6 -0
- package/lib/public/sw.js +82 -3
- 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/scheduler.js +402 -0
- package/lib/sdk-bridge.js +3 -2
- package/lib/server.js +124 -3
- package/lib/sessions.js +35 -2
- package/package.json +1 -1
|
@@ -0,0 +1,389 @@
|
|
|
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
|
|
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 running = false;
|
|
46
|
+
|
|
47
|
+
function getGradientColor(row, totalRows) {
|
|
48
|
+
var t = totalRows > 1 ? row / (totalRows - 1) : 0;
|
|
49
|
+
var r, g, b;
|
|
50
|
+
if (t <= 0.5) {
|
|
51
|
+
var s = t * 2;
|
|
52
|
+
r = Math.round(GRADIENT_STOPS[0][0] + (GRADIENT_STOPS[1][0] - GRADIENT_STOPS[0][0]) * s);
|
|
53
|
+
g = Math.round(GRADIENT_STOPS[0][1] + (GRADIENT_STOPS[1][1] - GRADIENT_STOPS[0][1]) * s);
|
|
54
|
+
b = Math.round(GRADIENT_STOPS[0][2] + (GRADIENT_STOPS[1][2] - GRADIENT_STOPS[0][2]) * s);
|
|
55
|
+
} else {
|
|
56
|
+
var s = (t - 0.5) * 2;
|
|
57
|
+
r = Math.round(GRADIENT_STOPS[1][0] + (GRADIENT_STOPS[2][0] - GRADIENT_STOPS[1][0]) * s);
|
|
58
|
+
g = Math.round(GRADIENT_STOPS[1][1] + (GRADIENT_STOPS[2][1] - GRADIENT_STOPS[1][1]) * s);
|
|
59
|
+
b = Math.round(GRADIENT_STOPS[1][2] + (GRADIENT_STOPS[2][2] - GRADIENT_STOPS[1][2]) * s);
|
|
60
|
+
}
|
|
61
|
+
return [r, g, b];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function easeOutElastic(t) {
|
|
65
|
+
if (t === 0 || t === 1) return t;
|
|
66
|
+
return Math.pow(2, -10 * t) * Math.sin((t - 0.1) * 5 * Math.PI) + 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function easeOutCubic(t) {
|
|
70
|
+
var t1 = t - 1;
|
|
71
|
+
return t1 * t1 * t1 + 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function lerp(a, b, t) {
|
|
75
|
+
return a + (b - a) * t;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildParticles() {
|
|
79
|
+
particles = [];
|
|
80
|
+
maxCol = 0;
|
|
81
|
+
for (var row = 0; row < ASCII_LINES.length; row++) {
|
|
82
|
+
var line = ASCII_LINES[row];
|
|
83
|
+
for (var col = 0; col < line.length; col++) {
|
|
84
|
+
var ch = line[col];
|
|
85
|
+
if (ch === " " || ch === "_") continue;
|
|
86
|
+
if (col > maxCol) maxCol = col;
|
|
87
|
+
var gc = getGradientColor(row, ASCII_LINES.length);
|
|
88
|
+
particles.push({
|
|
89
|
+
char: ch,
|
|
90
|
+
row: row,
|
|
91
|
+
col: col,
|
|
92
|
+
targetX: 0,
|
|
93
|
+
targetY: 0,
|
|
94
|
+
x: 0,
|
|
95
|
+
y: 0,
|
|
96
|
+
vx: 0,
|
|
97
|
+
vy: 0,
|
|
98
|
+
opacity: 0,
|
|
99
|
+
rotation: 0,
|
|
100
|
+
vr: 0,
|
|
101
|
+
finalR: gc[0],
|
|
102
|
+
finalG: gc[1],
|
|
103
|
+
finalB: gc[2],
|
|
104
|
+
r: 160,
|
|
105
|
+
g: 160,
|
|
106
|
+
b: 160,
|
|
107
|
+
scatterX: 0,
|
|
108
|
+
scatterY: 0,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function computeLayout() {
|
|
115
|
+
if (!canvas) return;
|
|
116
|
+
var container = canvas.parentElement;
|
|
117
|
+
var dpr = window.devicePixelRatio || 1;
|
|
118
|
+
var w = container.clientWidth;
|
|
119
|
+
var h = container.clientHeight;
|
|
120
|
+
|
|
121
|
+
canvas.style.width = w + "px";
|
|
122
|
+
canvas.style.height = h + "px";
|
|
123
|
+
canvas.width = Math.round(w * dpr);
|
|
124
|
+
canvas.height = Math.round(h * dpr);
|
|
125
|
+
|
|
126
|
+
ctx = canvas.getContext("2d");
|
|
127
|
+
ctx.scale(dpr, dpr);
|
|
128
|
+
|
|
129
|
+
// Find longest line
|
|
130
|
+
var maxLen = 0;
|
|
131
|
+
for (var i = 0; i < ASCII_LINES.length; i++) {
|
|
132
|
+
if (ASCII_LINES[i].length > maxLen) maxLen = ASCII_LINES[i].length;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Responsive font size
|
|
136
|
+
fontSize = Math.floor(w / maxLen * 1.05);
|
|
137
|
+
fontSize = Math.max(6, Math.min(fontSize, 20));
|
|
138
|
+
|
|
139
|
+
ctx.font = fontSize + "px Menlo, Monaco, Consolas, 'Courier New', monospace";
|
|
140
|
+
charWidth = ctx.measureText("M").width;
|
|
141
|
+
lineHeight = fontSize * 1.5;
|
|
142
|
+
|
|
143
|
+
var totalW = maxLen * charWidth;
|
|
144
|
+
var totalH = ASCII_LINES.length * lineHeight;
|
|
145
|
+
offsetX = (w - totalW) / 2;
|
|
146
|
+
offsetY = (h - totalH) / 2;
|
|
147
|
+
centerX = w / 2;
|
|
148
|
+
centerY = h / 2;
|
|
149
|
+
|
|
150
|
+
// Recompute target positions
|
|
151
|
+
for (var i = 0; i < particles.length; i++) {
|
|
152
|
+
var p = particles[i];
|
|
153
|
+
p.targetX = offsetX + p.col * charWidth;
|
|
154
|
+
p.targetY = offsetY + p.row * lineHeight + lineHeight * 0.8;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function randomScatter(p) {
|
|
159
|
+
var canvasW = canvas.width / (window.devicePixelRatio || 1);
|
|
160
|
+
var canvasH = canvas.height / (window.devicePixelRatio || 1);
|
|
161
|
+
p.scatterX = (Math.random() - 0.5) * canvasW * 2;
|
|
162
|
+
p.scatterY = (Math.random() - 0.5) * canvasH * 2;
|
|
163
|
+
p.x = p.scatterX;
|
|
164
|
+
p.y = p.scatterY;
|
|
165
|
+
p.opacity = 0;
|
|
166
|
+
p.rotation = (Math.random() - 0.5) * Math.PI * 4;
|
|
167
|
+
p.vx = 0;
|
|
168
|
+
p.vy = 0;
|
|
169
|
+
p.vr = 0;
|
|
170
|
+
p.r = 160;
|
|
171
|
+
p.g = 160;
|
|
172
|
+
p.b = 160;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function setPhase(newPhase) {
|
|
176
|
+
phase = newPhase;
|
|
177
|
+
phaseTime = 0;
|
|
178
|
+
|
|
179
|
+
if (phase === "scatter-in") {
|
|
180
|
+
for (var i = 0; i < particles.length; i++) {
|
|
181
|
+
randomScatter(particles[i]);
|
|
182
|
+
}
|
|
183
|
+
} else if (phase === "shatter") {
|
|
184
|
+
for (var i = 0; i < particles.length; i++) {
|
|
185
|
+
var p = particles[i];
|
|
186
|
+
var angle = Math.atan2(p.targetY - centerY, p.targetX - centerX);
|
|
187
|
+
angle += (Math.random() - 0.5) * 0.6;
|
|
188
|
+
var speed = 250 + Math.random() * 450;
|
|
189
|
+
p.vx = Math.cos(angle) * speed;
|
|
190
|
+
p.vy = Math.sin(angle) * speed;
|
|
191
|
+
p.vr = (Math.random() - 0.5) * 8;
|
|
192
|
+
}
|
|
193
|
+
} else if (phase === "reassemble") {
|
|
194
|
+
// particles keep their current positions from shatter
|
|
195
|
+
for (var i = 0; i < particles.length; i++) {
|
|
196
|
+
var p = particles[i];
|
|
197
|
+
p.scatterX = p.x;
|
|
198
|
+
p.scatterY = p.y;
|
|
199
|
+
p.vx = 0;
|
|
200
|
+
p.vy = 0;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function updateParticles(dt) {
|
|
206
|
+
phaseTime += dt;
|
|
207
|
+
|
|
208
|
+
if (phase === "scatter-in") {
|
|
209
|
+
for (var i = 0; i < particles.length; i++) {
|
|
210
|
+
var p = particles[i];
|
|
211
|
+
// Stagger by column — delay proportional to column position
|
|
212
|
+
var delay = (p.col / (maxCol || 1)) * 0.6;
|
|
213
|
+
var localT = Math.max(0, phaseTime - delay) / (PHASE_SCATTER_IN - 0.6);
|
|
214
|
+
localT = Math.min(localT, 1);
|
|
215
|
+
var ease = easeOutElastic(localT);
|
|
216
|
+
|
|
217
|
+
p.x = lerp(p.scatterX, p.targetX, ease);
|
|
218
|
+
p.y = lerp(p.scatterY, p.targetY, ease);
|
|
219
|
+
p.opacity = Math.min(1, localT * 3);
|
|
220
|
+
p.rotation = lerp(p.rotation, 0, localT);
|
|
221
|
+
}
|
|
222
|
+
if (phaseTime >= PHASE_SCATTER_IN) {
|
|
223
|
+
// Snap to targets
|
|
224
|
+
for (var i = 0; i < particles.length; i++) {
|
|
225
|
+
var p = particles[i];
|
|
226
|
+
p.x = p.targetX;
|
|
227
|
+
p.y = p.targetY;
|
|
228
|
+
p.opacity = 1;
|
|
229
|
+
p.rotation = 0;
|
|
230
|
+
}
|
|
231
|
+
setPhase("color-sweep");
|
|
232
|
+
}
|
|
233
|
+
} else if (phase === "color-sweep") {
|
|
234
|
+
var sweepProgress = phaseTime / PHASE_COLOR_SWEEP;
|
|
235
|
+
for (var i = 0; i < particles.length; i++) {
|
|
236
|
+
var p = particles[i];
|
|
237
|
+
var colNorm = p.col / (maxCol || 1);
|
|
238
|
+
var localT = (sweepProgress - colNorm * 0.6) / 0.4;
|
|
239
|
+
localT = Math.max(0, Math.min(1, localT));
|
|
240
|
+
var ease = easeOutCubic(localT);
|
|
241
|
+
p.r = Math.round(lerp(160, p.finalR, ease));
|
|
242
|
+
p.g = Math.round(lerp(160, p.finalG, ease));
|
|
243
|
+
p.b = Math.round(lerp(160, p.finalB, ease));
|
|
244
|
+
}
|
|
245
|
+
if (phaseTime >= PHASE_COLOR_SWEEP) {
|
|
246
|
+
for (var i = 0; i < particles.length; i++) {
|
|
247
|
+
var p = particles[i];
|
|
248
|
+
p.r = p.finalR;
|
|
249
|
+
p.g = p.finalG;
|
|
250
|
+
p.b = p.finalB;
|
|
251
|
+
}
|
|
252
|
+
setPhase("hold");
|
|
253
|
+
}
|
|
254
|
+
} else if (phase === "hold") {
|
|
255
|
+
var breath = Math.sin(phaseTime * Math.PI * 0.8) * 0.08;
|
|
256
|
+
for (var i = 0; i < particles.length; i++) {
|
|
257
|
+
var p = particles[i];
|
|
258
|
+
p.opacity = 0.92 + breath;
|
|
259
|
+
// Subtle micro-jitter
|
|
260
|
+
p.x = p.targetX + Math.sin(phaseTime * 2.3 + i * 0.7) * 0.4;
|
|
261
|
+
p.y = p.targetY + Math.cos(phaseTime * 1.9 + i * 0.5) * 0.3;
|
|
262
|
+
}
|
|
263
|
+
if (phaseTime >= PHASE_HOLD) {
|
|
264
|
+
// Reset to exact positions before shatter
|
|
265
|
+
for (var i = 0; i < particles.length; i++) {
|
|
266
|
+
var p = particles[i];
|
|
267
|
+
p.x = p.targetX;
|
|
268
|
+
p.y = p.targetY;
|
|
269
|
+
p.opacity = 1;
|
|
270
|
+
}
|
|
271
|
+
setPhase("shatter");
|
|
272
|
+
}
|
|
273
|
+
} else if (phase === "shatter") {
|
|
274
|
+
var drag = 0.97;
|
|
275
|
+
for (var i = 0; i < particles.length; i++) {
|
|
276
|
+
var p = particles[i];
|
|
277
|
+
p.vx *= drag;
|
|
278
|
+
p.vy *= drag;
|
|
279
|
+
p.x += p.vx * dt;
|
|
280
|
+
p.y += p.vy * dt;
|
|
281
|
+
p.rotation += p.vr * dt;
|
|
282
|
+
p.vr *= drag;
|
|
283
|
+
|
|
284
|
+
// Fade out based on distance from center
|
|
285
|
+
var dx = p.x - centerX;
|
|
286
|
+
var dy = p.y - centerY;
|
|
287
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
288
|
+
var maxDist = Math.max(centerX, centerY) * 0.8;
|
|
289
|
+
p.opacity = Math.max(0, 1 - dist / maxDist);
|
|
290
|
+
}
|
|
291
|
+
if (phaseTime >= PHASE_SHATTER) {
|
|
292
|
+
setPhase("reassemble");
|
|
293
|
+
}
|
|
294
|
+
} else if (phase === "reassemble") {
|
|
295
|
+
for (var i = 0; i < particles.length; i++) {
|
|
296
|
+
var p = particles[i];
|
|
297
|
+
var delay = ((maxCol - p.col) / (maxCol || 1)) * 0.5;
|
|
298
|
+
var localT = Math.max(0, phaseTime - delay) / (PHASE_REASSEMBLE - 0.5);
|
|
299
|
+
localT = Math.min(localT, 1);
|
|
300
|
+
var ease = easeOutElastic(localT);
|
|
301
|
+
|
|
302
|
+
p.x = lerp(p.scatterX, p.targetX, ease);
|
|
303
|
+
p.y = lerp(p.scatterY, p.targetY, ease);
|
|
304
|
+
p.opacity = Math.min(1, localT * 2.5);
|
|
305
|
+
p.rotation = lerp(p.rotation, 0, localT * localT);
|
|
306
|
+
}
|
|
307
|
+
if (phaseTime >= PHASE_REASSEMBLE) {
|
|
308
|
+
for (var i = 0; i < particles.length; i++) {
|
|
309
|
+
var p = particles[i];
|
|
310
|
+
p.x = p.targetX;
|
|
311
|
+
p.y = p.targetY;
|
|
312
|
+
p.opacity = 1;
|
|
313
|
+
p.rotation = 0;
|
|
314
|
+
}
|
|
315
|
+
setPhase("hold");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function drawFrame() {
|
|
321
|
+
if (!ctx) return;
|
|
322
|
+
var w = canvas.width / (window.devicePixelRatio || 1);
|
|
323
|
+
var h = canvas.height / (window.devicePixelRatio || 1);
|
|
324
|
+
ctx.clearRect(0, 0, w, h);
|
|
325
|
+
ctx.font = fontSize + "px Menlo, Monaco, Consolas, 'Courier New', monospace";
|
|
326
|
+
ctx.textBaseline = "alphabetic";
|
|
327
|
+
|
|
328
|
+
for (var i = 0; i < particles.length; i++) {
|
|
329
|
+
var p = particles[i];
|
|
330
|
+
if (p.opacity <= 0.01) continue;
|
|
331
|
+
|
|
332
|
+
ctx.save();
|
|
333
|
+
ctx.globalAlpha = p.opacity;
|
|
334
|
+
ctx.fillStyle = "rgb(" + p.r + "," + p.g + "," + p.b + ")";
|
|
335
|
+
|
|
336
|
+
if (Math.abs(p.rotation) > 0.01) {
|
|
337
|
+
ctx.translate(p.x + charWidth / 2, p.y - fontSize / 3);
|
|
338
|
+
ctx.rotate(p.rotation);
|
|
339
|
+
ctx.fillText(p.char, -charWidth / 2, fontSize / 3);
|
|
340
|
+
} else {
|
|
341
|
+
ctx.fillText(p.char, p.x, p.y);
|
|
342
|
+
}
|
|
343
|
+
ctx.restore();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function tick(now) {
|
|
348
|
+
if (!running) return;
|
|
349
|
+
var dt = Math.min((now - lastTime) / 1000, 0.05);
|
|
350
|
+
lastTime = now;
|
|
351
|
+
updateParticles(dt);
|
|
352
|
+
drawFrame();
|
|
353
|
+
animId = requestAnimationFrame(tick);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
var resizeObserver = null;
|
|
357
|
+
|
|
358
|
+
export function initAsciiLogo(canvasEl) {
|
|
359
|
+
canvas = canvasEl;
|
|
360
|
+
buildParticles();
|
|
361
|
+
computeLayout();
|
|
362
|
+
|
|
363
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
364
|
+
resizeObserver = new ResizeObserver(function () {
|
|
365
|
+
computeLayout();
|
|
366
|
+
});
|
|
367
|
+
resizeObserver.observe(canvas.parentElement);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function startLogoAnimation() {
|
|
372
|
+
if (running) {
|
|
373
|
+
// Reset to beginning
|
|
374
|
+
cancelAnimationFrame(animId);
|
|
375
|
+
}
|
|
376
|
+
running = true;
|
|
377
|
+
computeLayout();
|
|
378
|
+
setPhase("scatter-in");
|
|
379
|
+
lastTime = performance.now();
|
|
380
|
+
animId = requestAnimationFrame(tick);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function stopLogoAnimation() {
|
|
384
|
+
running = false;
|
|
385
|
+
if (animId) {
|
|
386
|
+
cancelAnimationFrame(animId);
|
|
387
|
+
animId = null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
3
|
-
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf } from './markdown.js';
|
|
3
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf, parseEmojis } from './markdown.js';
|
|
4
4
|
import { closeSidebar } from './sidebar.js';
|
|
5
5
|
import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
|
|
6
6
|
import { initFileIcons, getFileIconSvg, getFolderIconSvg } from './fileicons.js';
|
|
@@ -198,6 +198,7 @@ function renderBody() {
|
|
|
198
198
|
}
|
|
199
199
|
highlightCodeBlocks(bodyEl);
|
|
200
200
|
renderMermaidBlocks(bodyEl);
|
|
201
|
+
parseEmojis(bodyEl);
|
|
201
202
|
renderBtn.classList.add("active");
|
|
202
203
|
renderBtn.title = "Show raw";
|
|
203
204
|
pdfBtn.classList.remove("hidden");
|
|
@@ -26,8 +26,126 @@ export function renderMarkdown(text) {
|
|
|
26
26
|
return DOMPurify.sanitize(marked.parse(text));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Parse all Unicode emojis inside an element into Twemoji SVGs.
|
|
31
|
+
* Safe to call on any DOM element — skips <pre>/<code> to avoid mangling.
|
|
32
|
+
* With the global MutationObserver active, manual calls are rarely needed.
|
|
33
|
+
*/
|
|
34
|
+
export function parseEmojis(el) {
|
|
35
|
+
if (typeof twemoji === "undefined" || !el) return;
|
|
36
|
+
twemoji.parse(el, { folder: "svg", ext: ".svg" });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Global Twemoji auto-parser.
|
|
41
|
+
* Observes all DOM mutations and automatically converts Unicode emojis to
|
|
42
|
+
* Twemoji SVGs. This eliminates the need to call parseEmojis() manually
|
|
43
|
+
* after every DOM update.
|
|
44
|
+
*/
|
|
45
|
+
var twemojiOpts = { folder: "svg", ext: ".svg" };
|
|
46
|
+
var SKIP_TAGS = { PRE: 1, CODE: 1, SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1 };
|
|
47
|
+
var emojiRegex = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{27BF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/u;
|
|
48
|
+
var pendingNodes = [];
|
|
49
|
+
var flushScheduled = false;
|
|
50
|
+
|
|
51
|
+
function shouldSkip(node) {
|
|
52
|
+
var el = node.nodeType === 3 ? node.parentElement : node;
|
|
53
|
+
while (el) {
|
|
54
|
+
if (SKIP_TAGS[el.tagName]) return true;
|
|
55
|
+
// Already parsed — img.emoji is what twemoji produces
|
|
56
|
+
if (el.tagName === "IMG" && el.classList && el.classList.contains("emoji")) return true;
|
|
57
|
+
el = el.parentElement;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function flushTwemoji() {
|
|
63
|
+
flushScheduled = false;
|
|
64
|
+
if (typeof twemoji === "undefined") return;
|
|
65
|
+
var nodes = pendingNodes;
|
|
66
|
+
pendingNodes = [];
|
|
67
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
68
|
+
var node = nodes[i];
|
|
69
|
+
if (!node.isConnected) continue;
|
|
70
|
+
if (shouldSkip(node)) continue;
|
|
71
|
+
// For text nodes, parse the parent element
|
|
72
|
+
var target = node.nodeType === 3 ? node.parentElement : node;
|
|
73
|
+
if (target) twemoji.parse(target, twemojiOpts);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function queueTwemoji(node) {
|
|
78
|
+
pendingNodes.push(node);
|
|
79
|
+
if (!flushScheduled) {
|
|
80
|
+
flushScheduled = true;
|
|
81
|
+
requestAnimationFrame(flushTwemoji);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Start observing once DOM is ready
|
|
86
|
+
(function initTwemojiObserver() {
|
|
87
|
+
if (typeof twemoji === "undefined") return;
|
|
88
|
+
if (typeof MutationObserver === "undefined") return;
|
|
89
|
+
|
|
90
|
+
var observer = new MutationObserver(function (mutations) {
|
|
91
|
+
for (var m = 0; m < mutations.length; m++) {
|
|
92
|
+
var mut = mutations[m];
|
|
93
|
+
// New nodes added
|
|
94
|
+
if (mut.type === "childList") {
|
|
95
|
+
for (var n = 0; n < mut.addedNodes.length; n++) {
|
|
96
|
+
var added = mut.addedNodes[n];
|
|
97
|
+
if (added.nodeType === 1) {
|
|
98
|
+
// Element — check if it contains emoji text
|
|
99
|
+
if (emojiRegex.test(added.textContent || "")) {
|
|
100
|
+
queueTwemoji(added);
|
|
101
|
+
}
|
|
102
|
+
} else if (added.nodeType === 3) {
|
|
103
|
+
// Text node
|
|
104
|
+
if (emojiRegex.test(added.data || "")) {
|
|
105
|
+
queueTwemoji(added);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Text content changed
|
|
111
|
+
if (mut.type === "characterData") {
|
|
112
|
+
if (emojiRegex.test(mut.target.data || "")) {
|
|
113
|
+
queueTwemoji(mut.target);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
function start() {
|
|
120
|
+
// Parse everything already in the page
|
|
121
|
+
twemoji.parse(document.body, twemojiOpts);
|
|
122
|
+
// Watch for future changes
|
|
123
|
+
observer.observe(document.body, {
|
|
124
|
+
childList: true,
|
|
125
|
+
subtree: true,
|
|
126
|
+
characterData: true,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (document.readyState === "loading") {
|
|
131
|
+
document.addEventListener("DOMContentLoaded", start);
|
|
132
|
+
} else {
|
|
133
|
+
start();
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
|
|
137
|
+
var langAliases = { jsonl: "json", dotenv: "bash" };
|
|
138
|
+
|
|
29
139
|
export function highlightCodeBlocks(el) {
|
|
30
140
|
el.querySelectorAll("pre code:not(.hljs):not(.language-mermaid)").forEach(function (block) {
|
|
141
|
+
var cls = Array.from(block.classList).find(function (c) { return c.startsWith("language-"); });
|
|
142
|
+
if (cls) {
|
|
143
|
+
var lang = cls.replace("language-", "");
|
|
144
|
+
if (langAliases[lang]) {
|
|
145
|
+
block.classList.remove(cls);
|
|
146
|
+
block.classList.add("language-" + langAliases[lang]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
31
149
|
hljs.highlightElement(block);
|
|
32
150
|
});
|
|
33
151
|
el.querySelectorAll("pre:not(.has-copy-btn):not([data-mermaid-processed])").forEach(function (pre) {
|