jamdesk 1.1.174 → 1.1.175

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jamdesk",
3
- "version": "1.1.174",
3
+ "version": "1.1.175",
4
4
  "description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
5
5
  "keywords": [
6
6
  "jamdesk",
@@ -12,6 +12,7 @@ import {
12
12
  type ReactNode,
13
13
  } from 'react';
14
14
 
15
+ import { useBodyScrollLock } from '@/hooks/useBodyScrollLock';
15
16
  import { useOnClickOutside } from '@/hooks/useOnClickOutside';
16
17
  import { renderInlineMarkdown } from '@/lib/inline-markdown';
17
18
  import { decodePromptSource } from '@/lib/prompt-source-codec';
@@ -70,6 +71,188 @@ function ActionIcon({ action }: { action: ResolvedPromptAction['action'] }): Rea
70
71
  return null;
71
72
  }
72
73
 
74
+ interface PromptCopyButtonProps {
75
+ copyState: CopyState;
76
+ className: string;
77
+ onCopy: () => void;
78
+ }
79
+
80
+ function PromptCopyButton({
81
+ copyState,
82
+ className,
83
+ onCopy,
84
+ }: PromptCopyButtonProps): ReactElement {
85
+ const label = copyState === 'copied'
86
+ ? 'Copied'
87
+ : copyState === 'error'
88
+ ? 'Copy failed'
89
+ : 'Copy prompt';
90
+ return (
91
+ <button
92
+ type="button"
93
+ aria-label="Copy prompt"
94
+ title={label}
95
+ className={className}
96
+ data-copy-state={copyState}
97
+ onClick={onCopy}
98
+ >
99
+ <i
100
+ className={copyState === 'copied'
101
+ ? 'fa-solid fa-check text-[var(--color-success-text)]'
102
+ : copyState === 'error'
103
+ ? 'fa-solid fa-triangle-exclamation text-[var(--color-error-text)]'
104
+ : 'fa-regular fa-copy'}
105
+ aria-hidden="true"
106
+ />
107
+ </button>
108
+ );
109
+ }
110
+
111
+ interface PromptDialogProps {
112
+ isOpen: boolean;
113
+ prompt: string;
114
+ title?: string;
115
+ copyState: CopyState;
116
+ onCopy: () => void;
117
+ onClose: () => void;
118
+ }
119
+
120
+ const contrastSafeFocusClassName = [
121
+ 'focus-visible:outline-none focus-visible:ring-2',
122
+ 'focus-visible:ring-[var(--color-text-primary)] focus-visible:ring-offset-2',
123
+ 'focus-visible:ring-offset-[var(--color-bg-primary)]',
124
+ ].join(' ');
125
+
126
+ const promptUtilityClassName = [
127
+ 'inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-[var(--radius-lg)]',
128
+ 'text-[var(--color-text-tertiary)] transition-colors',
129
+ 'hover:bg-[var(--color-bg-hover)]',
130
+ 'hover:text-[var(--color-primary)]',
131
+ contrastSafeFocusClassName,
132
+ ].join(' ');
133
+
134
+ const primaryActionClassName = [
135
+ 'inline-flex min-h-10 min-w-0 items-center gap-2 px-4 py-2 text-sm font-semibold',
136
+ 'prompt-primary-action bg-[var(--color-text-primary)] text-[var(--color-bg-primary)] transition-opacity cursor-pointer',
137
+ 'hover:opacity-90',
138
+ contrastSafeFocusClassName,
139
+ ].join(' ');
140
+
141
+ function PromptDialog({
142
+ isOpen,
143
+ prompt,
144
+ title,
145
+ copyState,
146
+ onCopy,
147
+ onClose,
148
+ }: PromptDialogProps): ReactElement {
149
+ const dialogRef = useRef<HTMLDialogElement>(null);
150
+ const focusFrameRef = useRef<number | null>(null);
151
+ useBodyScrollLock(isOpen);
152
+
153
+ useEffect(() => {
154
+ const dialog = dialogRef.current;
155
+ if (!dialog) return;
156
+ if (isOpen && !dialog.open) {
157
+ dialog.showModal();
158
+ focusFrameRef.current = requestAnimationFrame(() => {
159
+ dialog.querySelector<HTMLButtonElement>('[aria-label="Close prompt"]')?.focus();
160
+ });
161
+ } else if (!isOpen && dialog.open) {
162
+ dialog.close();
163
+ }
164
+ return () => {
165
+ if (focusFrameRef.current !== null) cancelAnimationFrame(focusFrameRef.current);
166
+ };
167
+ }, [isOpen]);
168
+
169
+ useEffect(() => {
170
+ if (!isOpen) return;
171
+ const handleKeyDown = (event: KeyboardEvent): void => {
172
+ if (event.key === 'Escape') {
173
+ event.preventDefault();
174
+ event.stopPropagation();
175
+ onClose();
176
+ return;
177
+ }
178
+ if (event.key !== 'Tab' || !dialogRef.current) return;
179
+ const focusable = Array.from(dialogRef.current.querySelectorAll<HTMLElement>(
180
+ 'button, [href], [tabindex]:not([tabindex="-1"])',
181
+ )).filter((element) => !element.hasAttribute('disabled'));
182
+ if (focusable.length === 0) return;
183
+ const first = focusable[0];
184
+ const last = focusable[focusable.length - 1];
185
+ if (event.shiftKey && document.activeElement === first) {
186
+ event.preventDefault();
187
+ last.focus();
188
+ } else if (!event.shiftKey && document.activeElement === last) {
189
+ event.preventDefault();
190
+ first.focus();
191
+ }
192
+ };
193
+ window.addEventListener('keydown', handleKeyDown, true);
194
+ return () => window.removeEventListener('keydown', handleKeyDown, true);
195
+ }, [isOpen, onClose]);
196
+
197
+ return (
198
+ <dialog
199
+ ref={dialogRef}
200
+ aria-modal="true"
201
+ aria-label={title ?? 'Prompt'}
202
+ className="prompt-dialog m-auto max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-4xl overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-bg-primary)] p-0 text-[var(--color-text-primary)] shadow-[var(--shadow-lg)]"
203
+ onCancel={(event) => {
204
+ event.preventDefault();
205
+ onClose();
206
+ }}
207
+ onMouseDown={(event) => {
208
+ if (event.target === event.currentTarget) onClose();
209
+ }}
210
+ >
211
+ <div className="flex max-h-[calc(100dvh-2rem)] min-h-0 flex-col">
212
+ <div className="flex shrink-0 items-center justify-between gap-3 border-b border-[var(--color-border)] px-4 py-3">
213
+ <h2 className="m-0 min-w-0 truncate text-sm font-semibold text-[var(--color-text-primary)]">
214
+ {title ?? 'Prompt'}
215
+ </h2>
216
+ <button
217
+ type="button"
218
+ aria-label="Close prompt"
219
+ className={promptUtilityClassName}
220
+ onClick={onClose}
221
+ >
222
+ <i className="fa-solid fa-xmark" aria-hidden="true" />
223
+ </button>
224
+ </div>
225
+ <div className="flex min-h-0 flex-1 gap-3 overflow-hidden p-4">
226
+ <pre
227
+ data-testid="prompt-dialog-source"
228
+ role="region"
229
+ aria-label="Prompt source"
230
+ tabIndex={0}
231
+ className={`m-0 min-h-0 min-w-0 flex-1 overflow-auto overscroll-contain whitespace-pre-wrap break-words font-mono text-sm leading-6 text-[var(--color-text-primary)] ${contrastSafeFocusClassName}`}
232
+ >
233
+ {prompt}
234
+ </pre>
235
+ <PromptCopyButton
236
+ copyState={copyState}
237
+ className={promptUtilityClassName}
238
+ onCopy={onCopy}
239
+ />
240
+ </div>
241
+ <span role="status" aria-live="polite" className="sr-only">
242
+ {copyState === 'copied' ? 'Copied' : copyState === 'error' ? 'Copy failed' : ''}
243
+ </span>
244
+ </div>
245
+ <style jsx>{`
246
+ .prompt-dialog::backdrop {
247
+ background: var(--color-text-primary);
248
+ opacity: 0.6;
249
+ backdrop-filter: blur(4px);
250
+ }
251
+ `}</style>
252
+ </dialog>
253
+ );
254
+ }
255
+
73
256
  export function Prompt({
74
257
  children,
75
258
  title,
@@ -84,9 +267,12 @@ export function Prompt({
84
267
  }: PromptProps): ReactElement {
85
268
  const [copyState, setCopyState] = useState<CopyState>('idle');
86
269
  const [menuOpen, setMenuOpen] = useState(false);
270
+ const [dialogOpen, setDialogOpen] = useState(false);
87
271
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
88
272
  const copyAttemptRef = useRef(0);
89
273
  const mountedRef = useRef(false);
274
+ const expandTriggerRef = useRef<HTMLButtonElement>(null);
275
+ const returnFocusFrameRef = useRef<number | null>(null);
90
276
  const menuBoundaryRef = useRef<HTMLDivElement>(null);
91
277
  const triggerRef = useRef<HTMLButtonElement>(null);
92
278
  const itemRefs = useRef<Array<HTMLAnchorElement | null>>([]);
@@ -105,6 +291,8 @@ export function Prompt({
105
291
  const hasTitle = Boolean(title?.trim());
106
292
  const shouldHidePrompt = hasTitle && (hidePrompt === true || hidePrompt === 'true');
107
293
  const authorIcon = resolveAuthorIcon(icon, iconType);
294
+ const hasDescription = description !== undefined;
295
+ const hasHeader = hasTitle || resolvedActions.length > 0;
108
296
 
109
297
  const closeMenu = useCallback(() => {
110
298
  setMenuOpen(false);
@@ -115,6 +303,13 @@ export function Prompt({
115
303
  triggerRef.current?.focus();
116
304
  }, []);
117
305
 
306
+ const closeDialog = useCallback(() => {
307
+ setDialogOpen(false);
308
+ returnFocusFrameRef.current = requestAnimationFrame(() => {
309
+ expandTriggerRef.current?.focus();
310
+ });
311
+ }, []);
312
+
118
313
  useOnClickOutside(menuBoundaryRef, closeMenu, menuOpen);
119
314
 
120
315
  useEffect(() => {
@@ -126,6 +321,12 @@ export function Prompt({
126
321
  };
127
322
  }, []);
128
323
 
324
+ useEffect(() => () => {
325
+ if (returnFocusFrameRef.current !== null) {
326
+ cancelAnimationFrame(returnFocusFrameRef.current);
327
+ }
328
+ }, []);
329
+
129
330
  useEffect(() => {
130
331
  if (menuOpen) itemRefs.current[0]?.focus();
131
332
  }, [menuOpen]);
@@ -193,157 +394,183 @@ export function Prompt({
193
394
  }, [closeMenuAndFocusTrigger, secondary.length]);
194
395
 
195
396
  const rootClassName = [
196
- 'not-prose my-4 min-w-0 max-w-full overflow-visible',
197
- 'rounded-[var(--radius-lg)] border border-[var(--color-border)]',
198
- 'bg-[var(--color-bg-secondary)] text-[var(--color-text-primary)]',
199
- 'shadow-[var(--shadow-sm)]',
397
+ 'prompt-card-fern not-prose my-4 min-w-0 max-w-full overflow-visible',
398
+ 'rounded-[calc(var(--radius-lg)+0.25rem)] border text-[var(--color-text-primary)]',
200
399
  className,
201
400
  ].filter(Boolean).join(' ');
202
401
 
203
- const actionClassName = [
204
- 'inline-flex min-w-0 max-w-full items-center gap-2 rounded-[var(--radius-lg)]',
205
- 'border border-[var(--color-border)] px-3 py-2 text-sm font-medium',
206
- 'text-[var(--color-text-primary)] transition-colors cursor-pointer',
207
- 'hover:border-[var(--color-primary)] hover:text-[var(--color-primary)]',
208
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)]',
209
- 'disabled:cursor-not-allowed disabled:opacity-50',
210
- ].join(' ');
211
-
212
402
  return (
213
403
  <section data-testid="prompt-card" className={rootClassName}>
214
- <div
215
- data-testid={!hasTitle ? 'prompt-body-row' : undefined}
216
- className={hasTitle
217
- ? 'contents'
218
- : 'flex min-w-0 max-w-full flex-wrap items-start gap-3 px-4 py-4'}
219
- >
404
+ {hasHeader ? (
220
405
  <div
221
- data-testid={hasTitle ? 'prompt-header' : undefined}
222
- className={hasTitle
223
- ? 'flex min-w-0 max-w-full items-center gap-3 px-4 pt-4'
224
- : 'contents'}
406
+ data-testid="prompt-header"
407
+ className="flex min-w-0 flex-col items-stretch gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-5"
225
408
  >
226
- <span
227
- data-testid="prompt-icon"
228
- className="shrink-0 text-[var(--color-primary)]"
229
- aria-hidden="true"
230
- >
231
- <Icon icon={authorIcon} size={18} />
232
- </span>
233
409
  {hasTitle ? (
234
- <h3 className="m-0 min-w-0 max-w-full break-words [overflow-wrap:anywhere] text-base font-semibold text-[var(--color-text-primary)]">
410
+ <h3 className="prompt-title m-0 w-full min-w-0 break-words [overflow-wrap:anywhere] text-base font-semibold text-[var(--color-text-primary)] sm:w-auto sm:flex-1">
235
411
  {title}
236
412
  </h3>
237
413
  ) : null}
238
- </div>
239
-
240
- {!shouldHidePrompt ? (
241
414
  <div
242
- data-testid="prompt-preview"
243
- className={`prompt-preview min-w-0 max-w-full text-sm leading-6 text-[var(--color-text-tertiary)]${hasTitle ? ' mx-4 mt-3' : ' flex-1'}${isSingleLine ? ' prompt-preview-single-line' : ''}`}
415
+ data-testid="prompt-actions"
416
+ className="flex min-w-0 max-w-full items-stretch justify-end self-end sm:ml-auto sm:self-auto"
244
417
  >
245
- {description !== undefined ? renderInlineMarkdown(description) : children}
418
+ {primary ? (
419
+ <a
420
+ href={primary.href}
421
+ target="_blank"
422
+ rel="noopener noreferrer"
423
+ aria-label={primary.label}
424
+ className={`${primaryActionClassName} ${secondary.length > 0 ? 'rounded-l-[var(--radius-lg)]' : 'rounded-[var(--radius-lg)]'}`}
425
+ >
426
+ <span aria-hidden="true"><ActionIcon action={primary.action} /></span>
427
+ <span className="min-w-0 truncate">{primary.label}</span>
428
+ </a>
429
+ ) : null}
430
+ {secondary.length > 0 ? (
431
+ <div ref={menuBoundaryRef} className="relative flex shrink-0">
432
+ <button
433
+ ref={triggerRef}
434
+ type="button"
435
+ aria-label="More prompt actions"
436
+ aria-haspopup="menu"
437
+ aria-expanded={menuOpen}
438
+ aria-controls={menuId}
439
+ className={`${primaryActionClassName} min-w-10 rounded-r-[var(--radius-lg)] border-l border-[var(--color-bg-primary)] px-3`}
440
+ onMouseDown={(event) => {
441
+ if (menuOpen) event.preventDefault();
442
+ }}
443
+ onClick={() => {
444
+ setMenuOpen((open) => !open);
445
+ if (menuOpen) triggerRef.current?.focus();
446
+ }}
447
+ >
448
+ <i className="fa-solid fa-chevron-down text-xs" aria-hidden="true" />
449
+ </button>
450
+ {menuOpen ? (
451
+ <div
452
+ id={menuId}
453
+ role="menu"
454
+ aria-label="More prompt actions"
455
+ className="absolute right-0 top-full z-20 mt-2 w-max max-w-[min(20rem,calc(100vw-2rem))] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-bg-primary)] p-1 shadow-[var(--shadow-sm)]"
456
+ onKeyDown={handleMenuKeyDown}
457
+ onBlur={(event) => {
458
+ if (!event.currentTarget.contains(event.relatedTarget)) closeMenu();
459
+ }}
460
+ >
461
+ {secondary.map((action, index) => (
462
+ <a
463
+ key={`${action.label}-${action.href}-${index}`}
464
+ ref={(node) => { itemRefs.current[index] = node; }}
465
+ role="menuitem"
466
+ tabIndex={-1}
467
+ href={action.href}
468
+ target="_blank"
469
+ rel="noopener noreferrer"
470
+ aria-label={action.label}
471
+ className={`flex min-h-10 min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-[var(--radius-lg)] px-3 py-2 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-border)] ${contrastSafeFocusClassName}`}
472
+ onClick={closeMenuAndFocusTrigger}
473
+ >
474
+ <span aria-hidden="true"><ActionIcon action={action.action} /></span>
475
+ <span className="min-w-0 truncate">{action.label}</span>
476
+ </a>
477
+ ))}
478
+ </div>
479
+ ) : null}
480
+ </div>
481
+ ) : null}
482
+ {shouldHidePrompt && hasPrompt ? (
483
+ <PromptCopyButton
484
+ copyState={copyState}
485
+ className={`${promptUtilityClassName} ml-2`}
486
+ onCopy={copyPrompt}
487
+ />
488
+ ) : null}
246
489
  </div>
247
- ) : null}
490
+ </div>
491
+ ) : null}
248
492
 
493
+ {!shouldHidePrompt ? (
249
494
  <div
250
- data-testid="prompt-actions"
251
- className={`flex min-w-0 max-w-full flex-wrap items-center gap-2${hasTitle ? ' px-4 pb-4 pt-3' : ' basis-full sm:ml-auto sm:basis-auto'}`}
495
+ data-testid="prompt-well"
496
+ className={`prompt-well mx-3 mb-3 flex min-w-0 items-start gap-3 rounded-[var(--radius-lg)] border bg-[var(--color-bg-primary)] px-3 py-3 sm:mx-4 sm:mb-4 sm:px-4 ${hasHeader ? '' : 'mt-3 sm:mt-4'}`}
252
497
  >
253
- <button
254
- type="button"
255
- aria-label="Copy prompt"
256
- className={actionClassName}
257
- data-copy-state={copyState}
258
- disabled={!hasPrompt}
259
- onClick={copyPrompt}
498
+ <span
499
+ data-testid="prompt-icon"
500
+ className="mt-1 shrink-0 text-[var(--color-primary)]"
501
+ aria-hidden="true"
260
502
  >
261
- <i className="fa-regular fa-copy shrink-0" aria-hidden="true" />
262
- <span>{copyState === 'copied' ? 'Copied' : copyState === 'error' ? 'Copy failed' : 'Copy'}</span>
263
- </button>
264
-
265
- <span role="status" aria-live="polite" className="sr-only">
266
- {copyState === 'copied' ? 'Copied' : copyState === 'error' ? 'Copy failed' : ''}
503
+ <Icon icon={authorIcon} size={18} />
267
504
  </span>
268
-
269
- {primary ? (
270
- <a
271
- href={primary.href}
272
- target="_blank"
273
- rel="noopener noreferrer"
274
- className={actionClassName}
275
- aria-label={primary.label}
276
- >
277
- <span aria-hidden="true"><ActionIcon action={primary.action} /></span>
278
- <span className="min-w-0 truncate">{primary.label}</span>
279
- </a>
280
- ) : null}
281
-
282
- {secondary.length > 0 ? (
283
- <div
284
- ref={menuBoundaryRef}
285
- className="relative flex min-w-0 max-w-full basis-full w-full shrink-0 justify-end sm:ml-auto sm:basis-auto sm:w-auto sm:shrink"
286
- >
505
+ <div
506
+ data-testid="prompt-preview"
507
+ className={`prompt-preview min-w-0 flex-1 text-sm leading-6 text-[var(--color-text-primary)]${hasPrompt && !hasDescription ? ' font-mono whitespace-pre-wrap' : ''}${isSingleLine ? ' prompt-preview-single-line' : ''}`}
508
+ >
509
+ {hasDescription ? renderInlineMarkdown(description) : prompt || children}
510
+ </div>
511
+ {hasPrompt ? (
512
+ <div data-testid="prompt-utilities" className="flex shrink-0 items-center gap-1">
287
513
  <button
288
- ref={triggerRef}
514
+ ref={expandTriggerRef}
289
515
  type="button"
290
- aria-label="More prompt actions"
291
- aria-haspopup="menu"
292
- aria-expanded={menuOpen}
293
- aria-controls={menuId}
294
- className={actionClassName}
295
- onMouseDown={(event) => {
296
- if (menuOpen) event.preventDefault();
297
- }}
298
- onClick={() => {
299
- setMenuOpen((open) => !open);
300
- if (menuOpen) triggerRef.current?.focus();
301
- }}
516
+ aria-label="Expand prompt"
517
+ title="Expand prompt"
518
+ className={promptUtilityClassName}
519
+ onClick={() => setDialogOpen(true)}
302
520
  >
303
- <span>More</span>
304
- <i className="fa-solid fa-chevron-down text-xs" aria-hidden="true" />
521
+ <i className="fa-solid fa-expand" aria-hidden="true" />
305
522
  </button>
306
-
307
- {menuOpen ? (
308
- <div
309
- id={menuId}
310
- role="menu"
311
- aria-label="More prompt actions"
312
- className="absolute top-full right-0 z-20 mt-2 w-max max-w-[min(20rem,calc(100vw-2rem))] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-bg-secondary)] p-1 shadow-[var(--shadow-sm)]"
313
- onKeyDown={handleMenuKeyDown}
314
- onBlur={(event) => {
315
- if (!event.currentTarget.contains(event.relatedTarget)) {
316
- closeMenu();
317
- }
318
- }}
319
- >
320
- {secondary.map((action, index) => (
321
- <a
322
- key={`${action.label}-${action.href}-${index}`}
323
- ref={(node) => { itemRefs.current[index] = node; }}
324
- role="menuitem"
325
- tabIndex={-1}
326
- href={action.href}
327
- target="_blank"
328
- rel="noopener noreferrer"
329
- aria-label={action.label}
330
- className="flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-[var(--radius-lg)] px-3 py-2 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-border)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)]"
331
- onClick={closeMenuAndFocusTrigger}
332
- >
333
- <span aria-hidden="true"><ActionIcon action={action.action} /></span>
334
- <span className="min-w-0 truncate">{action.label}</span>
335
- </a>
336
- ))}
337
- </div>
338
- ) : null}
523
+ <PromptCopyButton
524
+ copyState={copyState}
525
+ className={promptUtilityClassName}
526
+ onCopy={copyPrompt}
527
+ />
339
528
  </div>
340
529
  ) : null}
341
530
  </div>
342
- </div>
531
+ ) : null}
532
+ {!dialogOpen ? (
533
+ <span role="status" aria-live="polite" className="sr-only">
534
+ {copyState === 'copied' ? 'Copied' : copyState === 'error' ? 'Copy failed' : ''}
535
+ </span>
536
+ ) : null}
537
+
538
+ <PromptDialog
539
+ isOpen={dialogOpen}
540
+ prompt={prompt}
541
+ title={hasTitle ? title!.trim() : undefined}
542
+ copyState={copyState}
543
+ onCopy={copyPrompt}
544
+ onClose={closeDialog}
545
+ />
343
546
 
344
547
  <style jsx>{`
548
+ .prompt-card-fern {
549
+ border-color: var(--color-primary);
550
+ background: var(--color-bg-secondary);
551
+ }
552
+ .prompt-well {
553
+ border-color: var(--color-border);
554
+ }
555
+ .prompt-primary-action {
556
+ background: var(--color-text-primary);
557
+ color: var(--color-bg-primary);
558
+ }
559
+ .prompt-title {
560
+ color: var(--color-text-primary);
561
+ }
562
+ @supports (color: color-mix(in srgb, black, white)) {
563
+ .prompt-card-fern {
564
+ border-color: color-mix(in srgb, var(--color-primary) 48%, var(--color-border));
565
+ background: color-mix(in srgb, var(--color-primary) 7%, var(--color-bg-primary));
566
+ }
567
+ .prompt-well {
568
+ border-color: color-mix(in srgb, var(--color-border) 88%, var(--color-primary));
569
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 3%, transparent);
570
+ }
571
+ }
345
572
  .prompt-preview {
346
- max-height: 7.5rem;
573
+ max-height: 10rem;
347
574
  overflow-y: auto;
348
575
  overflow-wrap: anywhere;
349
576
  }
@@ -30,9 +30,10 @@ interface CaptureResult {
30
30
  error?: string;
31
31
  }
32
32
 
33
- // Headroom for the retry path: two 15s page.goto budgets + 3s settle wait
34
- // already total 33s before the screenshot is even taken. Launch retries
35
- // (closed-target only) fail fast (<~2s) so they stay inside this 45s race.
33
+ // Headroom for the retry path: two 15s page.goto('load') budgets + 3s retry
34
+ // wait + 2s best-effort networkidle + 1s settle total ~36s worst case before
35
+ // the screenshot is taken. Launch retries (closed-target only) fail fast
36
+ // (<~2s) so they stay inside this 45s race.
36
37
  const SCREENSHOT_TIMEOUT_MS = 45000;
37
38
 
38
39
  // Playwright launch flakes intermittently in the container — the Chromium
@@ -201,22 +202,31 @@ export async function captureHomepageScreenshot(options: CaptureOptions): Promis
201
202
  // Cold render after a project-wide revalidation can transiently 404 —
202
203
  // retry once on 4xx/5xx, then capture whatever the page shows so the
203
204
  // dashboard preview reflects the real state of the site.
205
+ //
206
+ // Wait for 'load' (document + images/CSS/fonts), NOT 'networkidle'. A
207
+ // custom-domain-only subdomain 404s the client's RSC nav prefetches
208
+ // (`/docs/<page>?_rsc=…`) — they're built from the route and carry no
209
+ // ?jd_proxy=1 marker — so Next keeps retrying them and a large docs site
210
+ // (jamdesk-docs) NEVER goes network-idle. 'networkidle' timed out at 15s
211
+ // and every capture/Retry failed. 'load' fires before the prefetch storm.
212
+ const gotoOpts = { waitUntil: 'load', timeout: 15000 } as const;
204
213
  console.log(`Capturing screenshot from: ${homepageUrl}`);
205
- const firstResponse = await page.goto(homepageUrl, {
206
- waitUntil: 'networkidle',
207
- timeout: 15000,
208
- });
214
+ const firstResponse = await page.goto(homepageUrl, gotoOpts);
209
215
  const firstStatus = firstResponse?.status();
210
216
 
211
217
  if (firstStatus !== undefined && firstStatus >= 400) {
212
218
  console.log(`Screenshot got ${firstStatus} on first attempt, retrying once after 3s`);
213
219
  await page.waitForTimeout(3000);
214
- await page.goto(homepageUrl, {
215
- waitUntil: 'networkidle',
216
- timeout: 15000,
217
- });
220
+ await page.goto(homepageUrl, gotoOpts);
218
221
  }
219
222
 
223
+ // Short best-effort settle for late images/fonts — the page above already
224
+ // reached 'load', and a never-idle page (see the prefetch note above)
225
+ // would otherwise burn the full timeout for nothing. Never fail the
226
+ // capture on it. Mirrors the pdf-service networkidle fallback
227
+ // (worker-pool.ts).
228
+ await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
229
+
220
230
  // Additional wait for any late-loading content
221
231
  await page.waitForTimeout(1000);
222
232
 
@@ -53,6 +53,25 @@
53
53
  --color-accent-light: #FBF0DE;
54
54
  --color-accent-text: #8A5E23;
55
55
 
56
+ /* Primary — the platform's "brand" token, read by the TOC scroll thumb,
57
+ active TOC links, active step chips, Card/Tile icons and hover borders,
58
+ li::marker, code-line highlights, the update badge, and more. It is ONLY
59
+ ever emitted by generatePrimaryColorVariables (layout-helpers.tsx) when a
60
+ project sets colors.primary — there is NO global default — so without
61
+ this line every one of those consumers computes to transparent/inherit
62
+ and silently vanishes (the invisible TOC thumb bug). Halo's primary IS
63
+ its accent, the same primary==accent equivalence
64
+ generatePrimaryColorVariables itself enforces. var() is late-resolved,
65
+ so this single :root declaration tracks the .dark accent swap — no .dark
66
+ restatement needed. A project's colors.primary still wins: its style
67
+ block (jd-colors-<slug>) is injected AFTER this file (jd-theme-halo) at
68
+ the same precedence, so its --color-primary out-cascades this on source
69
+ order. --color-primary-dark rides along because PlaygroundModal reads the
70
+ PAIR as a gradient — defining only one would blend caramel into the
71
+ other's indigo fallback. */
72
+ --color-primary: var(--color-accent);
73
+ --color-primary-dark: var(--color-accent-hover);
74
+
56
75
  /* Code block colors — light. Tracks --color-bg-secondary by hand (a literal,
57
76
  not a var(), to match how every other theme writes this block): a code
58
77
  panel sits one step below the card, same as the ground. Left behind when
@@ -368,11 +387,40 @@ body[data-theme="halo"] .sidebar-scroll ul li a.nav-active {
368
387
  font-weight: 600;
369
388
  }
370
389
 
371
- /* Tab + TOC links match the sidebar pill. */
390
+ /* Tab + TOC links match the sidebar pill (var(--radius-lg), same as the
391
+ .sidebar-scroll ::before highlight) — --radius-md left them a step tighter. */
372
392
  body[data-theme="halo"] .nav-tab-link,
373
393
  body[data-theme="halo"] .toc-link,
374
394
  body[data-theme="halo"] .toc-link-bg {
375
- border-radius: var(--radius-md) !important;
395
+ border-radius: var(--radius-lg) !important;
396
+ }
397
+
398
+ /* TOC step-number circles ("On this page" → <Steps> headings). TableOfContents
399
+ sets the INACTIVE number inline to var(--color-text-muted). LIGHT mode is the
400
+ broken case: on halo's sand card that muted token is #A99D89 on ~#FFFDF9 =
401
+ 2.6:1, so the numbers read as invisible until a step goes active. Secondary
402
+ (#5C5346 = ~7.4:1) clears the 4.5:1 floor with margin; tertiary (4.26:1)
403
+ would sit just under it. (Earlier "still faint" field reports were a stale
404
+ open tab, not a cascade loss — this CSS is a runtime fs read inlined as a
405
+ React-hoisted <style>, so Turbopack never hot-reloads it; verify edits with
406
+ a full page reload.) Scoped to LIGHT only (html:not(.dark)): in dark, muted on
407
+ the near-black ground already reads fine, so dark keeps the shipped default.
408
+ Inline colour ⇒ !important; inactive-only via data-active so the active
409
+ white-on-caramel chip is untouched. */
410
+ html:not(.dark) body[data-theme="halo"] a[data-step][data-active="false"] > span[aria-hidden="true"] {
411
+ color: var(--color-text-secondary) !important;
412
+ }
413
+
414
+ /* ACTIVE step number, dark only. The component fills the circle inline with
415
+ var(--color-primary) and a #fff digit. In light that now resolves (via the
416
+ :root default above) to caramel #C0873F — white-on-caramel at ~3.1:1, the
417
+ intended active look, so light needs no rule here. In dark the same fill
418
+ would be #E0A867, putting the white digit at ~2.1:1 — LESS legible than the
419
+ shipped-and-approved dark rendering, which is the bare white digit on the
420
+ near-black card (~17:1). Pin the dark chip transparent so defining
421
+ --color-primary cannot regress the approved dark look. */
422
+ html.dark body[data-theme="halo"] a[data-step][data-active="true"] > span[aria-hidden="true"] {
423
+ background-color: transparent !important;
376
424
  }
377
425
 
378
426
  /* ===== HALO SIGNATURE: THE CONTENT CARD ===== */
@@ -467,6 +515,27 @@ body[data-theme="halo"] {
467
515
  }
468
516
  }
469
517
 
518
+ /* Sidebar scroll container height cap — REQUIRED for a tall nav to scroll.
519
+ The shared markup gives .sidebar-scroll `overflow-y-auto` but no bounded
520
+ height, and its <aside> is `lg:self-start` (height = content), so without a
521
+ cap the nav grows unbounded and never scrolls once it's taller than the
522
+ viewport. jam and nebula both carry this exact rule; halo's full-bleed rewrite
523
+ dropped it. Header is 57px tall; a top tab strip adds another 57px (114px). */
524
+ @media (min-width: 1024px) {
525
+ body[data-theme="halo"] .sidebar-scroll {
526
+ position: sticky !important;
527
+ flex: 0 0 auto !important;
528
+ }
529
+ body[data-theme="halo"] .sidebar-scroll[data-has-tabs="true"] {
530
+ top: 114px !important;
531
+ max-height: calc(100vh - 114px) !important;
532
+ }
533
+ body[data-theme="halo"] .sidebar-scroll[data-has-tabs="false"] {
534
+ top: 57px !important;
535
+ max-height: calc(100vh - 57px) !important;
536
+ }
537
+ }
538
+
470
539
  /* 6. Vertical alignment of the three columns.
471
540
  The article and the TOC wrapper both use `py-6 sm:py-10`, so breadcrumb
472
541
  and "On this page" already share a baseline at 2.5rem. The sidebar's first
@@ -512,3 +581,4 @@ body[data-theme="halo"] .sidebar-scroll .sidebar-nav-groups > div.ml-6 {
512
581
  min-height: 36px;
513
582
  }
514
583
  }
584
+
@@ -1275,47 +1275,47 @@
1275
1275
  }
1276
1276
  },
1277
1277
  "node_modules/@tailwindcss/node": {
1278
- "version": "4.3.2",
1279
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz",
1280
- "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==",
1278
+ "version": "4.3.3",
1279
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
1280
+ "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
1281
1281
  "license": "MIT",
1282
1282
  "dependencies": {
1283
1283
  "@jridgewell/remapping": "^2.3.5",
1284
- "enhanced-resolve": "5.21.6",
1284
+ "enhanced-resolve": "^5.24.1",
1285
1285
  "jiti": "^2.7.0",
1286
1286
  "lightningcss": "1.32.0",
1287
1287
  "magic-string": "^0.30.21",
1288
1288
  "source-map-js": "^1.2.1",
1289
- "tailwindcss": "4.3.2"
1289
+ "tailwindcss": "4.3.3"
1290
1290
  }
1291
1291
  },
1292
1292
  "node_modules/@tailwindcss/oxide": {
1293
- "version": "4.3.2",
1294
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz",
1295
- "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==",
1293
+ "version": "4.3.3",
1294
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
1295
+ "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
1296
1296
  "license": "MIT",
1297
1297
  "engines": {
1298
1298
  "node": ">= 20"
1299
1299
  },
1300
1300
  "optionalDependencies": {
1301
- "@tailwindcss/oxide-android-arm64": "4.3.2",
1302
- "@tailwindcss/oxide-darwin-arm64": "4.3.2",
1303
- "@tailwindcss/oxide-darwin-x64": "4.3.2",
1304
- "@tailwindcss/oxide-freebsd-x64": "4.3.2",
1305
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2",
1306
- "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2",
1307
- "@tailwindcss/oxide-linux-arm64-musl": "4.3.2",
1308
- "@tailwindcss/oxide-linux-x64-gnu": "4.3.2",
1309
- "@tailwindcss/oxide-linux-x64-musl": "4.3.2",
1310
- "@tailwindcss/oxide-wasm32-wasi": "4.3.2",
1311
- "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2",
1312
- "@tailwindcss/oxide-win32-x64-msvc": "4.3.2"
1301
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
1302
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
1303
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
1304
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
1305
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
1306
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
1307
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
1308
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
1309
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
1310
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
1311
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
1312
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
1313
1313
  }
1314
1314
  },
1315
1315
  "node_modules/@tailwindcss/oxide-android-arm64": {
1316
- "version": "4.3.2",
1317
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz",
1318
- "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==",
1316
+ "version": "4.3.3",
1317
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
1318
+ "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
1319
1319
  "cpu": [
1320
1320
  "arm64"
1321
1321
  ],
@@ -1329,9 +1329,9 @@
1329
1329
  }
1330
1330
  },
1331
1331
  "node_modules/@tailwindcss/oxide-darwin-arm64": {
1332
- "version": "4.3.2",
1333
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz",
1334
- "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==",
1332
+ "version": "4.3.3",
1333
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
1334
+ "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
1335
1335
  "cpu": [
1336
1336
  "arm64"
1337
1337
  ],
@@ -1345,9 +1345,9 @@
1345
1345
  }
1346
1346
  },
1347
1347
  "node_modules/@tailwindcss/oxide-darwin-x64": {
1348
- "version": "4.3.2",
1349
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz",
1350
- "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==",
1348
+ "version": "4.3.3",
1349
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
1350
+ "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
1351
1351
  "cpu": [
1352
1352
  "x64"
1353
1353
  ],
@@ -1361,9 +1361,9 @@
1361
1361
  }
1362
1362
  },
1363
1363
  "node_modules/@tailwindcss/oxide-freebsd-x64": {
1364
- "version": "4.3.2",
1365
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz",
1366
- "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==",
1364
+ "version": "4.3.3",
1365
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
1366
+ "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
1367
1367
  "cpu": [
1368
1368
  "x64"
1369
1369
  ],
@@ -1377,9 +1377,9 @@
1377
1377
  }
1378
1378
  },
1379
1379
  "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
1380
- "version": "4.3.2",
1381
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz",
1382
- "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==",
1380
+ "version": "4.3.3",
1381
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
1382
+ "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
1383
1383
  "cpu": [
1384
1384
  "arm"
1385
1385
  ],
@@ -1393,9 +1393,9 @@
1393
1393
  }
1394
1394
  },
1395
1395
  "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
1396
- "version": "4.3.2",
1397
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz",
1398
- "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==",
1396
+ "version": "4.3.3",
1397
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
1398
+ "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
1399
1399
  "cpu": [
1400
1400
  "arm64"
1401
1401
  ],
@@ -1412,9 +1412,9 @@
1412
1412
  }
1413
1413
  },
1414
1414
  "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
1415
- "version": "4.3.2",
1416
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz",
1417
- "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==",
1415
+ "version": "4.3.3",
1416
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
1417
+ "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
1418
1418
  "cpu": [
1419
1419
  "arm64"
1420
1420
  ],
@@ -1431,9 +1431,9 @@
1431
1431
  }
1432
1432
  },
1433
1433
  "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
1434
- "version": "4.3.2",
1435
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz",
1436
- "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==",
1434
+ "version": "4.3.3",
1435
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
1436
+ "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
1437
1437
  "cpu": [
1438
1438
  "x64"
1439
1439
  ],
@@ -1450,9 +1450,9 @@
1450
1450
  }
1451
1451
  },
1452
1452
  "node_modules/@tailwindcss/oxide-linux-x64-musl": {
1453
- "version": "4.3.2",
1454
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz",
1455
- "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==",
1453
+ "version": "4.3.3",
1454
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
1455
+ "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
1456
1456
  "cpu": [
1457
1457
  "x64"
1458
1458
  ],
@@ -1469,9 +1469,9 @@
1469
1469
  }
1470
1470
  },
1471
1471
  "node_modules/@tailwindcss/oxide-wasm32-wasi": {
1472
- "version": "4.3.2",
1473
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz",
1474
- "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==",
1472
+ "version": "4.3.3",
1473
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
1474
+ "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
1475
1475
  "bundleDependencies": [
1476
1476
  "@napi-rs/wasm-runtime",
1477
1477
  "@emnapi/core",
@@ -1558,9 +1558,9 @@
1558
1558
  "optional": true
1559
1559
  },
1560
1560
  "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
1561
- "version": "4.3.2",
1562
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz",
1563
- "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==",
1561
+ "version": "4.3.3",
1562
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
1563
+ "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
1564
1564
  "cpu": [
1565
1565
  "arm64"
1566
1566
  ],
@@ -1574,9 +1574,9 @@
1574
1574
  }
1575
1575
  },
1576
1576
  "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
1577
- "version": "4.3.2",
1578
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz",
1579
- "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==",
1577
+ "version": "4.3.3",
1578
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
1579
+ "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
1580
1580
  "cpu": [
1581
1581
  "x64"
1582
1582
  ],
@@ -1590,16 +1590,16 @@
1590
1590
  }
1591
1591
  },
1592
1592
  "node_modules/@tailwindcss/postcss": {
1593
- "version": "4.3.2",
1594
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.2.tgz",
1595
- "integrity": "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g==",
1593
+ "version": "4.3.3",
1594
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz",
1595
+ "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==",
1596
1596
  "license": "MIT",
1597
1597
  "dependencies": {
1598
1598
  "@alloc/quick-lru": "^5.2.0",
1599
- "@tailwindcss/node": "4.3.2",
1600
- "@tailwindcss/oxide": "4.3.2",
1601
- "postcss": "^8.5.15",
1602
- "tailwindcss": "4.3.2"
1599
+ "@tailwindcss/node": "4.3.3",
1600
+ "@tailwindcss/oxide": "4.3.3",
1601
+ "postcss": "^8.5.16",
1602
+ "tailwindcss": "4.3.3"
1603
1603
  }
1604
1604
  },
1605
1605
  "node_modules/@tailwindcss/typography": {
@@ -2113,9 +2113,9 @@
2113
2113
  }
2114
2114
  },
2115
2115
  "node_modules/autoprefixer": {
2116
- "version": "10.5.3",
2117
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.3.tgz",
2118
- "integrity": "sha512-bJRzflk8GgE4JX+iZNEwz9f9p460NCHnU7bd+CZ9vIjIlZuTkt6F3WSl2oNO8StZBFx17nLEsiQ6H2wcZiY7nA==",
2116
+ "version": "10.5.4",
2117
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz",
2118
+ "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==",
2119
2119
  "funding": [
2120
2120
  {
2121
2121
  "type": "opencollective",
@@ -2133,7 +2133,7 @@
2133
2133
  "license": "MIT",
2134
2134
  "dependencies": {
2135
2135
  "browserslist": "^4.28.6",
2136
- "caniuse-lite": "^1.0.30001805",
2136
+ "caniuse-lite": "^1.0.30001806",
2137
2137
  "fraction.js": "^5.3.4",
2138
2138
  "picocolors": "^1.1.1",
2139
2139
  "postcss-value-parser": "^4.2.0"
@@ -2231,9 +2231,9 @@
2231
2231
  "license": "MIT"
2232
2232
  },
2233
2233
  "node_modules/caniuse-lite": {
2234
- "version": "1.0.30001805",
2235
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz",
2236
- "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==",
2234
+ "version": "1.0.30001806",
2235
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
2236
+ "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
2237
2237
  "funding": [
2238
2238
  {
2239
2239
  "type": "opencollective",
@@ -2968,9 +2968,9 @@
2968
2968
  "license": "ISC"
2969
2969
  },
2970
2970
  "node_modules/enhanced-resolve": {
2971
- "version": "5.21.6",
2972
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
2973
- "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
2971
+ "version": "5.24.2",
2972
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz",
2973
+ "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==",
2974
2974
  "license": "MIT",
2975
2975
  "dependencies": {
2976
2976
  "graceful-fs": "^4.2.4",
@@ -6292,9 +6292,9 @@
6292
6292
  "license": "MIT"
6293
6293
  },
6294
6294
  "node_modules/tailwindcss": {
6295
- "version": "4.3.2",
6296
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz",
6297
- "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==",
6295
+ "version": "4.3.3",
6296
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
6297
+ "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
6298
6298
  "license": "MIT"
6299
6299
  },
6300
6300
  "node_modules/tapable": {