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.
Files changed (58) hide show
  1. package/bin/cli.js +2 -1
  2. package/lib/config.js +7 -4
  3. package/lib/project.js +343 -15
  4. package/lib/public/app.js +1043 -135
  5. package/lib/public/apple-touch-icon-dark.png +0 -0
  6. package/lib/public/apple-touch-icon.png +0 -0
  7. package/lib/public/clay-logo.png +0 -0
  8. package/lib/public/css/base.css +10 -0
  9. package/lib/public/css/filebrowser.css +1 -0
  10. package/lib/public/css/home-hub.css +455 -0
  11. package/lib/public/css/icon-strip.css +6 -5
  12. package/lib/public/css/loop.css +141 -23
  13. package/lib/public/css/messages.css +2 -0
  14. package/lib/public/css/mobile-nav.css +38 -12
  15. package/lib/public/css/overlays.css +205 -169
  16. package/lib/public/css/playbook.css +264 -0
  17. package/lib/public/css/profile.css +268 -0
  18. package/lib/public/css/scheduler-modal.css +1429 -0
  19. package/lib/public/css/scheduler.css +1305 -0
  20. package/lib/public/css/sidebar.css +305 -11
  21. package/lib/public/css/sticky-notes.css +23 -19
  22. package/lib/public/css/stt.css +155 -0
  23. package/lib/public/css/title-bar.css +14 -6
  24. package/lib/public/favicon-banded-32.png +0 -0
  25. package/lib/public/favicon-banded.png +0 -0
  26. package/lib/public/icon-192-dark.png +0 -0
  27. package/lib/public/icon-192.png +0 -0
  28. package/lib/public/icon-512-dark.png +0 -0
  29. package/lib/public/icon-512.png +0 -0
  30. package/lib/public/icon-banded-76.png +0 -0
  31. package/lib/public/icon-banded-96.png +0 -0
  32. package/lib/public/index.html +335 -42
  33. package/lib/public/modules/ascii-logo.js +389 -0
  34. package/lib/public/modules/filebrowser.js +2 -1
  35. package/lib/public/modules/markdown.js +118 -0
  36. package/lib/public/modules/notifications.js +50 -63
  37. package/lib/public/modules/playbook.js +578 -0
  38. package/lib/public/modules/profile.js +357 -0
  39. package/lib/public/modules/project-settings.js +4 -9
  40. package/lib/public/modules/scheduler.js +2826 -0
  41. package/lib/public/modules/server-settings.js +1 -1
  42. package/lib/public/modules/sidebar.js +378 -31
  43. package/lib/public/modules/sticky-notes.js +2 -0
  44. package/lib/public/modules/stt.js +272 -0
  45. package/lib/public/modules/terminal.js +32 -0
  46. package/lib/public/modules/theme.js +3 -10
  47. package/lib/public/modules/tools.js +2 -1
  48. package/lib/public/style.css +6 -0
  49. package/lib/public/sw.js +82 -3
  50. package/lib/public/wordmark-banded-20.png +0 -0
  51. package/lib/public/wordmark-banded-32.png +0 -0
  52. package/lib/public/wordmark-banded-64.png +0 -0
  53. package/lib/public/wordmark-banded-80.png +0 -0
  54. package/lib/scheduler.js +402 -0
  55. package/lib/sdk-bridge.js +3 -2
  56. package/lib/server.js +124 -3
  57. package/lib/sessions.js +35 -2
  58. 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) {