clay-server 2.7.2 → 2.8.2
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 +31 -17
- package/lib/config.js +7 -4
- package/lib/project.js +343 -15
- package/lib/public/app.js +1039 -134
- 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 +18 -1
- 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 +336 -44
- package/lib/public/modules/ascii-logo.js +442 -0
- package/lib/public/modules/markdown.js +18 -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 +1 -9
- package/lib/public/modules/scheduler.js +2826 -0
- package/lib/public/modules/server-settings.js +1 -1
- package/lib/public/modules/sidebar.js +376 -32
- 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/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,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
|
+
}
|
|
@@ -26,8 +26,26 @@ export function renderMarkdown(text) {
|
|
|
26
26
|
return DOMPurify.sanitize(marked.parse(text));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* parseEmojis — no-op stub.
|
|
31
|
+
* Emoji rendering is now handled by the Twemoji COLR font via CSS @font-face.
|
|
32
|
+
* No DOM manipulation needed — the font renders emoji glyphs directly.
|
|
33
|
+
* This stub is kept so existing callers don't break.
|
|
34
|
+
*/
|
|
35
|
+
export function parseEmojis() {}
|
|
36
|
+
|
|
37
|
+
var langAliases = { jsonl: "json", dotenv: "bash" };
|
|
38
|
+
|
|
29
39
|
export function highlightCodeBlocks(el) {
|
|
30
40
|
el.querySelectorAll("pre code:not(.hljs):not(.language-mermaid)").forEach(function (block) {
|
|
41
|
+
var cls = Array.from(block.classList).find(function (c) { return c.startsWith("language-"); });
|
|
42
|
+
if (cls) {
|
|
43
|
+
var lang = cls.replace("language-", "");
|
|
44
|
+
if (langAliases[lang]) {
|
|
45
|
+
block.classList.remove(cls);
|
|
46
|
+
block.classList.add("language-" + langAliases[lang]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
31
49
|
hljs.highlightElement(block);
|
|
32
50
|
});
|
|
33
51
|
el.querySelectorAll("pre:not(.has-copy-btn):not([data-mermaid-processed])").forEach(function (pre) {
|
|
@@ -3,7 +3,7 @@ import { iconHtml, refreshIcons } from './icons.js';
|
|
|
3
3
|
|
|
4
4
|
var ctx;
|
|
5
5
|
var basePath = "/";
|
|
6
|
-
var
|
|
6
|
+
var onboardingPill, onboardingText, onboardingClose, onboardingDismissed;
|
|
7
7
|
var notifAlertEnabled, notifSoundEnabled, notifPermission;
|
|
8
8
|
var audioCtx = null;
|
|
9
9
|
|
|
@@ -13,12 +13,12 @@ export function getNotifPermission() { return notifPermission; }
|
|
|
13
13
|
|
|
14
14
|
export function showOnboarding(html) {
|
|
15
15
|
onboardingText.innerHTML = html;
|
|
16
|
-
|
|
16
|
+
onboardingPill.classList.remove("hidden");
|
|
17
17
|
refreshIcons();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function hideOnboarding() {
|
|
21
|
-
|
|
21
|
+
onboardingPill.classList.add("hidden");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function playDoneSound() {
|
|
@@ -80,69 +80,57 @@ export function initNotifications(_ctx) {
|
|
|
80
80
|
window.visualViewport.addEventListener("scroll", onViewportChange);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// --- Update
|
|
83
|
+
// --- Update pill badge ---
|
|
84
84
|
(function () {
|
|
85
|
-
var
|
|
86
|
-
var
|
|
87
|
-
var
|
|
85
|
+
var pillWrap = $("update-pill-wrap");
|
|
86
|
+
var pillBtn = $("update-pill");
|
|
87
|
+
var popover = $("update-popover");
|
|
88
88
|
var updateNowBtn = $("update-now");
|
|
89
|
-
if (!
|
|
90
|
-
|
|
91
|
-
// Build popover (manual update instructions)
|
|
92
|
-
var popover = document.createElement("div");
|
|
93
|
-
popover.id = "update-popover";
|
|
94
|
-
popover.innerHTML =
|
|
95
|
-
'<div class="popover-label">Run in your terminal:</div>' +
|
|
96
|
-
'<div class="popover-cmd">' +
|
|
97
|
-
'<code>npx clay-server@latest</code>' +
|
|
98
|
-
'<button class="popover-copy" title="Copy">' + iconHtml("copy") + '</button>' +
|
|
99
|
-
'</div>';
|
|
100
|
-
banner.appendChild(popover);
|
|
101
|
-
refreshIcons();
|
|
89
|
+
if (!pillWrap) return;
|
|
102
90
|
|
|
103
91
|
var copyBtn = popover.querySelector(".popover-copy");
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
copyBtn.classList.remove("copied");
|
|
111
|
-
copyBtn.innerHTML = iconHtml("copy");
|
|
92
|
+
if (copyBtn) {
|
|
93
|
+
copyBtn.addEventListener("click", function (e) {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
copyToClipboard("npx clay-server@latest").then(function () {
|
|
96
|
+
copyBtn.classList.add("copied");
|
|
97
|
+
copyBtn.innerHTML = iconHtml("check");
|
|
112
98
|
refreshIcons();
|
|
113
|
-
|
|
99
|
+
setTimeout(function () {
|
|
100
|
+
copyBtn.classList.remove("copied");
|
|
101
|
+
copyBtn.innerHTML = iconHtml("copy");
|
|
102
|
+
refreshIcons();
|
|
103
|
+
}, 1500);
|
|
104
|
+
});
|
|
114
105
|
});
|
|
115
|
-
}
|
|
106
|
+
}
|
|
116
107
|
|
|
117
108
|
// "Update now" button — trigger server-side update + restart
|
|
118
109
|
if (updateNowBtn) {
|
|
119
|
-
updateNowBtn.addEventListener("click", function () {
|
|
110
|
+
updateNowBtn.addEventListener("click", function (e) {
|
|
111
|
+
e.stopPropagation();
|
|
120
112
|
if (ctx.ws && ctx.connected) {
|
|
121
113
|
ctx.ws.send(JSON.stringify({ type: "update_now" }));
|
|
122
|
-
|
|
114
|
+
var textNode = updateNowBtn.lastChild;
|
|
115
|
+
if (textNode) textNode.textContent = " Updating...";
|
|
123
116
|
updateNowBtn.disabled = true;
|
|
124
117
|
}
|
|
125
118
|
});
|
|
126
119
|
}
|
|
127
120
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
// Toggle popover on pill click
|
|
122
|
+
if (pillBtn) {
|
|
123
|
+
pillBtn.addEventListener("click", function (e) {
|
|
124
|
+
e.stopPropagation();
|
|
125
|
+
popover.classList.toggle("visible");
|
|
126
|
+
});
|
|
127
|
+
}
|
|
133
128
|
|
|
134
129
|
document.addEventListener("click", function (e) {
|
|
135
|
-
if (!popover.contains(e.target) && e.target !==
|
|
130
|
+
if (!popover.contains(e.target) && e.target !== pillBtn && !pillBtn.contains(e.target)) {
|
|
136
131
|
popover.classList.remove("visible");
|
|
137
132
|
}
|
|
138
133
|
});
|
|
139
|
-
|
|
140
|
-
if (closeBtn) {
|
|
141
|
-
closeBtn.addEventListener("click", function () {
|
|
142
|
-
banner.classList.add("hidden");
|
|
143
|
-
popover.classList.remove("visible");
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
134
|
})();
|
|
147
135
|
|
|
148
136
|
// --- Settings: Check for updates ---
|
|
@@ -199,10 +187,10 @@ export function initNotifications(_ctx) {
|
|
|
199
187
|
}
|
|
200
188
|
})();
|
|
201
189
|
|
|
202
|
-
// --- Onboarding
|
|
203
|
-
|
|
204
|
-
onboardingText = $("onboarding-
|
|
205
|
-
onboardingClose = $("onboarding-
|
|
190
|
+
// --- Onboarding pill badge (HTTPS / Push) ---
|
|
191
|
+
onboardingPill = $("onboarding-pill");
|
|
192
|
+
onboardingText = $("onboarding-pill-text");
|
|
193
|
+
onboardingClose = $("onboarding-pill-close");
|
|
206
194
|
onboardingDismissed = localStorage.getItem("onboarding-dismissed");
|
|
207
195
|
|
|
208
196
|
if (onboardingClose) {
|
|
@@ -218,7 +206,7 @@ export function initNotifications(_ctx) {
|
|
|
218
206
|
if (!onboardingDismissed) {
|
|
219
207
|
showOnboarding(
|
|
220
208
|
iconHtml("bell-ring") +
|
|
221
|
-
'
|
|
209
|
+
' <a href="/setup">Set up HTTPS</a>'
|
|
222
210
|
);
|
|
223
211
|
}
|
|
224
212
|
}
|
|
@@ -563,8 +551,7 @@ export function initNotifications(_ctx) {
|
|
|
563
551
|
if (!onboardingDismissed) {
|
|
564
552
|
showOnboarding(
|
|
565
553
|
iconHtml("bell-ring") +
|
|
566
|
-
'
|
|
567
|
-
'<button class="onboarding-cta" id="onboarding-enable-push">Enable push notifications</button>'
|
|
554
|
+
' <button class="onboarding-cta" id="onboarding-enable-push">Enable push</button>'
|
|
568
555
|
);
|
|
569
556
|
var enableBtn = $("onboarding-enable-push");
|
|
570
557
|
if (enableBtn) {
|
|
@@ -596,13 +583,13 @@ export function initNotifications(_ctx) {
|
|
|
596
583
|
var open = debugMenu.classList.toggle("hidden");
|
|
597
584
|
debugBtn.classList.toggle("active", !open);
|
|
598
585
|
|
|
599
|
-
// Sync toggle states with current
|
|
600
|
-
var
|
|
601
|
-
if (debugToggleUpdate &&
|
|
602
|
-
debugToggleUpdate.checked = !
|
|
586
|
+
// Sync toggle states with current pill visibility
|
|
587
|
+
var updatePillWrap = $("update-pill-wrap");
|
|
588
|
+
if (debugToggleUpdate && updatePillWrap) {
|
|
589
|
+
debugToggleUpdate.checked = !updatePillWrap.classList.contains("hidden");
|
|
603
590
|
}
|
|
604
|
-
if (debugToggleOnboarding &&
|
|
605
|
-
debugToggleOnboarding.checked = !
|
|
591
|
+
if (debugToggleOnboarding && onboardingPill) {
|
|
592
|
+
debugToggleOnboarding.checked = !onboardingPill.classList.contains("hidden");
|
|
606
593
|
}
|
|
607
594
|
});
|
|
608
595
|
|
|
@@ -615,15 +602,15 @@ export function initNotifications(_ctx) {
|
|
|
615
602
|
|
|
616
603
|
if (debugToggleUpdate) {
|
|
617
604
|
debugToggleUpdate.addEventListener("change", function () {
|
|
618
|
-
var
|
|
619
|
-
if (!
|
|
605
|
+
var pillWrap = $("update-pill-wrap");
|
|
606
|
+
if (!pillWrap) return;
|
|
620
607
|
if (debugToggleUpdate.checked) {
|
|
621
608
|
// Trigger real update check from server (debug mode uses v0.0.9)
|
|
622
609
|
if (ctx.ws && ctx.connected) {
|
|
623
610
|
ctx.ws.send(JSON.stringify({ type: "check_update" }));
|
|
624
611
|
}
|
|
625
612
|
} else {
|
|
626
|
-
|
|
613
|
+
pillWrap.classList.add("hidden");
|
|
627
614
|
}
|
|
628
615
|
refreshIcons();
|
|
629
616
|
});
|
|
@@ -635,10 +622,10 @@ export function initNotifications(_ctx) {
|
|
|
635
622
|
if (!onboardingText.innerHTML.trim()) {
|
|
636
623
|
showOnboarding(
|
|
637
624
|
iconHtml("bell-ring") +
|
|
638
|
-
'
|
|
625
|
+
' <a href="/setup">Set up HTTPS</a>'
|
|
639
626
|
);
|
|
640
627
|
} else {
|
|
641
|
-
|
|
628
|
+
onboardingPill.classList.remove("hidden");
|
|
642
629
|
}
|
|
643
630
|
} else {
|
|
644
631
|
hideOnboarding();
|