better-svelte-email 1.0.0-beta.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,13 +2,55 @@
2
2
  import type { PreviewData } from './index.js';
3
3
  import type { ActionResult } from '@sveltejs/kit';
4
4
  import { deserialize } from '$app/forms';
5
+ import { goto } from '$app/navigation';
5
6
  import { codeToHtml } from 'shiki';
7
+ import './theme.css';
8
+ import EmailTreeNode from './EmailTreeNode.svelte';
9
+ import { buildEmailTree } from './email-tree.js';
10
+ import { onMount } from 'svelte';
11
+ import Favicon from './Favicon.svelte';
12
+
13
+ type Page = {
14
+ params: {
15
+ email?: string | null;
16
+ };
17
+ data: {
18
+ emails?: PreviewData;
19
+ };
20
+ url: URL;
21
+ };
22
+
23
+ type ViewMode = 'render' | 'code' | 'source';
24
+
25
+ function parseViewMode(url: URL): ViewMode {
26
+ const viewParam = url.searchParams.get('view');
27
+ if (viewParam === 'code' || viewParam === 'source') {
28
+ return viewParam;
29
+ }
30
+ return 'render';
31
+ }
6
32
 
7
- let { emailList }: { emailList: PreviewData } = $props();
33
+ let { page }: { page: Page } = $props();
34
+ let emailList = $derived(page.data.emails ?? { files: null, path: null });
35
+ let emailTree = $derived.by(() => buildEmailTree(emailList.files ?? []));
36
+
37
+ // Derive selected email from URL params
38
+ let selectedEmail = $derived(page.params.email || null);
39
+
40
+ // Derive the base path for navigation by removing the email param from current path
41
+ let basePath = $derived.by(() => {
42
+ const pathname = page.url.pathname;
43
+ if (selectedEmail) {
44
+ // Remove the selected email from the path
45
+ return pathname.replace(`/${selectedEmail}`, '').replace(/\/$/, '');
46
+ }
47
+ // If no email selected, remove trailing slash if any
48
+ return pathname.replace(/\/$/, '');
49
+ });
8
50
 
9
- let selectedEmail = $state<string | null>(null);
10
51
  let renderedHtml = $state<string>('');
11
52
  let highlightedHtml = $state<string>('');
53
+ let highlightedSource = $state<string>('');
12
54
  let iframeContent = $state<string>('');
13
55
  let loading = $state(false);
14
56
  let error = $state<string | null>(null);
@@ -17,6 +59,7 @@
17
59
  let sendSuccess = $state(false);
18
60
  let sendError = $state<string | null>(null);
19
61
  let recipientEmail = $state('');
62
+ let viewMode = $state<ViewMode>(parseViewMode(page.url));
20
63
 
21
64
  const FONT_SANS_STYLE = `<style>
22
65
  body {
@@ -62,11 +105,20 @@
62
105
  return `${FONT_SANS_STYLE}${html}`;
63
106
  }
64
107
 
65
- async function previewEmail(fileName: string) {
66
- selectedEmail = fileName;
108
+ // Navigate to the email's URL instead of updating state
109
+ function selectEmail(fileName: string) {
110
+ const search = page.url.search;
111
+ const hash = page.url.hash;
112
+ goto(`${basePath}/${fileName}${search}${hash}`);
113
+ }
114
+
115
+ // Load the email content when selectedEmail changes
116
+ async function loadEmailContent(fileName: string) {
67
117
  loading = true;
68
118
  error = null;
69
119
  renderedHtml = '';
120
+ highlightedHtml = '';
121
+ highlightedSource = '';
70
122
  iframeContent = '';
71
123
 
72
124
  try {
@@ -79,15 +131,24 @@
79
131
  })
80
132
  });
81
133
 
82
- const result: ActionResult<{ body: string }> = deserialize(await response.text());
134
+ const result: ActionResult<{ body: string; source?: string | null }> = deserialize(
135
+ await response.text()
136
+ );
83
137
 
84
138
  if (result.type === 'success' && result.data?.body) {
85
139
  renderedHtml = result.data.body;
86
140
  iframeContent = withFontSans(result.data.body);
87
141
  highlightedHtml = await codeToHtml(result.data.body, {
88
142
  lang: 'html',
89
- theme: 'github-dark-default'
143
+ theme: 'vesper'
90
144
  });
145
+ const source = result.data.source ?? '';
146
+ highlightedSource = source
147
+ ? await codeToHtml(source, {
148
+ lang: 'svelte',
149
+ theme: 'vesper'
150
+ })
151
+ : '';
91
152
  } else if (result.type === 'error') {
92
153
  error = result.error?.message || 'Failed to render email';
93
154
  }
@@ -98,6 +159,35 @@
98
159
  }
99
160
  }
100
161
 
162
+ // Effect to load email content when the URL changes
163
+ $effect(() => {
164
+ if (selectedEmail) {
165
+ loadEmailContent(selectedEmail);
166
+ }
167
+ });
168
+
169
+ $effect(() => {
170
+ const parsed = parseViewMode(page.url);
171
+ if (parsed !== viewMode) {
172
+ viewMode = parsed;
173
+ }
174
+ });
175
+
176
+ function updateViewMode(mode: ViewMode) {
177
+ if (viewMode === mode) return;
178
+
179
+ viewMode = mode;
180
+
181
+ const url = new URL(page.url);
182
+ url.searchParams.set('view', mode);
183
+
184
+ void goto(`${url.pathname}${url.search}${url.hash}`, {
185
+ replaceState: true,
186
+ noScroll: true,
187
+ keepFocus: true
188
+ });
189
+ }
190
+
101
191
  function copyHtml() {
102
192
  if (renderedHtml) {
103
193
  navigator.clipboard.writeText(renderedHtml);
@@ -158,20 +248,110 @@
158
248
  sendLoading = false;
159
249
  }
160
250
  }
251
+
252
+ onMount(() => {
253
+ const html = document.querySelector('html');
254
+ if (window.localStorage.getItem('bse_preview_mode') === 'dark') {
255
+ if (html && !html.classList.contains('dark')) {
256
+ setTimeout(() => {
257
+ html.classList.toggle('dark');
258
+ }, 100);
259
+ }
260
+ } else {
261
+ if (html && html.classList.contains('dark')) {
262
+ setTimeout(() => {
263
+ html.classList.remove('dark');
264
+ }, 100);
265
+ }
266
+ }
267
+ });
268
+
269
+ function toggleMode() {
270
+ const html = document.querySelector('html');
271
+ html?.classList.toggle('dark');
272
+ window.localStorage.setItem(
273
+ 'bse_preview_mode',
274
+ html?.classList.contains('dark') ? 'dark' : 'light'
275
+ );
276
+ }
161
277
  </script>
162
278
 
163
279
  <div class="container">
164
280
  <div class="sidebar">
165
281
  <div class="sidebar-header">
166
- <h2 class="sidebar-title">Email Templates</h2>
167
- {#if emailList.files}
168
- <span class="badge">
169
- {emailList.files.length}
170
- </span>
171
- {/if}
282
+ <div class="sidebar-brand">
283
+ <Favicon class="brand-logo" />
284
+
285
+ <h2 class="sidebar-title">Email Preview</h2>
286
+ </div>
287
+ <div class="sidebar-header-right">
288
+ <a
289
+ class="sidebar-header-docs-link"
290
+ href="https://better-svelte-email.konixy.fr/docs"
291
+ title="Documentation"
292
+ target="_blank"
293
+ rel="noopener noreferrer"
294
+ >
295
+ <svg
296
+ class="sidebar-header-docs-link-icon"
297
+ xmlns="http://www.w3.org/2000/svg"
298
+ width="24"
299
+ height="24"
300
+ viewBox="0 0 24 24"
301
+ fill="none"
302
+ stroke="currentColor"
303
+ stroke-width="2"
304
+ stroke-linecap="round"
305
+ stroke-linejoin="round"
306
+ ><path d="M12 7v14" /><path
307
+ d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"
308
+ /></svg
309
+ >
310
+ </a>
311
+ <button class="theme-toggle-btn" onclick={toggleMode} aria-label="Toggle theme">
312
+ <svg
313
+ class="theme-icon theme-icon-dark"
314
+ xmlns="http://www.w3.org/2000/svg"
315
+ width="24"
316
+ height="24"
317
+ viewBox="0 0 24 24"
318
+ fill="none"
319
+ stroke="currentColor"
320
+ stroke-width="2"
321
+ stroke-linecap="round"
322
+ stroke-linejoin="round"
323
+ >
324
+ <path
325
+ d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"
326
+ />
327
+ </svg>
328
+ <svg
329
+ class="theme-icon theme-icon-light"
330
+ xmlns="http://www.w3.org/2000/svg"
331
+ width="24"
332
+ height="24"
333
+ viewBox="0 0 24 24"
334
+ fill="none"
335
+ stroke="currentColor"
336
+ stroke-width="2"
337
+ stroke-linecap="round"
338
+ stroke-linejoin="round"
339
+ >
340
+ <circle cx="12" cy="12" r="4" />
341
+ <path d="M12 2v2" />
342
+ <path d="M12 20v2" />
343
+ <path d="m4.93 4.93 1.41 1.41" />
344
+ <path d="m17.66 17.66 1.41 1.41" />
345
+ <path d="M2 12h2" />
346
+ <path d="M20 12h2" />
347
+ <path d="m6.34 17.66-1.41 1.41" />
348
+ <path d="m19.07 4.93-1.41 1.41" />
349
+ </svg>
350
+ </button>
351
+ </div>
172
352
  </div>
173
353
 
174
- {#if !emailList.files || emailList.files.length === 0}
354
+ {#if emailTree.length === 0}
175
355
  <div class="empty-state">
176
356
  <p>No email templates found</p>
177
357
  <p>
@@ -179,18 +359,9 @@
179
359
  </p>
180
360
  </div>
181
361
  {:else}
182
- <ul class="email-list">
183
- {#each emailList.files as file}
184
- <li>
185
- <button
186
- class="email-button"
187
- class:active={selectedEmail === file}
188
- onclick={() => previewEmail(file)}
189
- >
190
- <span class="email-icon">📧</span>
191
- <span class="email-name">{file}</span>
192
- </button>
193
- </li>
362
+ <ul class="email-list email-tree">
363
+ {#each emailTree as node (node.path)}
364
+ <EmailTreeNode {node} {selectedEmail} onSelect={selectEmail} />
194
365
  {/each}
195
366
  </ul>
196
367
  {/if}
@@ -218,39 +389,117 @@
218
389
  <div class="emoji-xlarge">⚠️</div>
219
390
  <h3 class="heading">Error Rendering Email</h3>
220
391
  <p class="text-gray">{error}</p>
221
- <button class="btn" onclick={() => selectedEmail && previewEmail(selectedEmail)}>
392
+ <button class="btn" onclick={() => selectedEmail && loadEmailContent(selectedEmail)}>
222
393
  Try Again
223
394
  </button>
224
395
  </div>
225
396
  </div>
226
397
  {:else if renderedHtml}
227
398
  <div class="preview-header">
228
- <h3 class="preview-title">{selectedEmail}</h3>
229
- <div class="button-group">
230
- <button class="btn-secondary" onclick={copyHtml} title="Copy HTML">
231
- <span class="btn-icon">📋</span> Copy HTML
232
- </button>
233
- <button class="btn-primary" onclick={openSendModal} title="Send Test Email">
234
- <span class="btn-icon">📨</span> Send Test
399
+ <h3 class="preview-title">
400
+ <svg
401
+ class="preview-title-icon"
402
+ xmlns="http://www.w3.org/2000/svg"
403
+ width="24"
404
+ height="24"
405
+ viewBox="0 0 24 24"
406
+ fill="none"
407
+ stroke="currentColor"
408
+ stroke-width="2"
409
+ stroke-linecap="round"
410
+ stroke-linejoin="round"
411
+ ><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect
412
+ x="2"
413
+ y="4"
414
+ width="20"
415
+ height="16"
416
+ rx="2"
417
+ /></svg
418
+ >
419
+ {selectedEmail}
420
+ </h3>
421
+ <div class="preview-actions">
422
+ <div class="view-toggle" role="group" aria-label="Preview view selection">
423
+ <button
424
+ class="view-toggle-btn"
425
+ class:active={viewMode === 'render'}
426
+ onclick={() => updateViewMode('render')}
427
+ aria-pressed={viewMode === 'render'}
428
+ type="button"
429
+ >
430
+ Render
431
+ </button>
432
+ <button
433
+ class="view-toggle-btn"
434
+ class:active={viewMode === 'code'}
435
+ onclick={() => updateViewMode('code')}
436
+ aria-pressed={viewMode === 'code'}
437
+ type="button"
438
+ >
439
+ Code
440
+ </button>
441
+ <button
442
+ class="view-toggle-btn"
443
+ class:active={viewMode === 'source'}
444
+ onclick={() => updateViewMode('source')}
445
+ aria-pressed={viewMode === 'source'}
446
+ type="button"
447
+ >
448
+ Source
449
+ </button>
450
+ </div>
451
+
452
+ <button class="btn-primary send-btn" onclick={openSendModal} title="Send Test Email">
453
+ <svg
454
+ class="send-btn-icon"
455
+ xmlns="http://www.w3.org/2000/svg"
456
+ width="24"
457
+ height="24"
458
+ viewBox="0 0 24 24"
459
+ fill="none"
460
+ stroke="currentColor"
461
+ stroke-width="2"
462
+ stroke-linecap="round"
463
+ stroke-linejoin="round"
464
+ ><path
465
+ d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"
466
+ /><path d="m21.854 2.147-10.94 10.939" /></svg
467
+ >
468
+ Send Test Email
235
469
  </button>
236
470
  </div>
237
471
  </div>
238
472
 
239
- <div class="preview-container">
240
- <iframe
241
- title="Email Preview"
242
- srcdoc={iframeContent}
243
- class="preview-iframe"
244
- sandbox="allow-same-origin allow-scripts"
245
- ></iframe>
246
- </div>
247
-
248
- <details class="code-section">
249
- <summary class="code-summary"> View HTML Source </summary>
250
- <div class="code-content">
251
- {@html highlightedHtml}
473
+ {#if viewMode === 'render'}
474
+ <div class="preview-container">
475
+ <iframe
476
+ title="Email Preview"
477
+ srcdoc={iframeContent}
478
+ class="preview-iframe"
479
+ sandbox="allow-same-origin allow-scripts"
480
+ ></iframe>
481
+ </div>
482
+ {:else if viewMode === 'code'}
483
+ <div class="code-view">
484
+ <div class="code-view-content">
485
+ <div class="code-content">
486
+ {@html highlightedHtml}
487
+ </div>
488
+ </div>
489
+ </div>
490
+ {:else}
491
+ <div class="code-view">
492
+ <div class="code-view-content">
493
+ {#if highlightedSource}
494
+ <div class="code-content">
495
+ {@html highlightedSource}
496
+ </div>
497
+ {:else}
498
+ <div class="code-empty">Source could not be loaded.</div>
499
+ {/if}
500
+ </div>
252
501
  </div>
253
- </details>
502
+ {/if}
254
503
  {/if}
255
504
  </div>
256
505
  </div>
@@ -321,7 +570,7 @@
321
570
  height: 100vh;
322
571
  width: 100vw;
323
572
  max-width: none;
324
- background-color: #fafaf9;
573
+ background-color: var(--background);
325
574
  font-family:
326
575
  ui-sans-serif,
327
576
  system-ui,
@@ -345,15 +594,15 @@
345
594
  display: flex;
346
595
  flex-direction: column;
347
596
  overflow: hidden;
348
- border-right: 1px solid #e7e5e4;
349
- background-color: white;
597
+ border-right: 1px solid var(--border);
598
+ background-color: var(--card);
350
599
  }
351
600
 
352
601
  @media (max-width: 768px) {
353
602
  .sidebar {
354
603
  max-height: 40vh;
355
604
  border-right: 0;
356
- border-bottom: 1px solid #e7e5e4;
605
+ border-bottom: 1px solid var(--border);
357
606
  }
358
607
  }
359
608
 
@@ -362,51 +611,131 @@
362
611
  align-items: center;
363
612
  justify-content: space-between;
364
613
  gap: 0.5rem;
365
- border-bottom: 1px solid #e7e5e4;
366
- padding: 1.5rem;
367
- padding-bottom: 1rem;
614
+ padding-inline: 1rem;
615
+ height: 4rem;
616
+ border-bottom: 1px solid var(--border);
617
+ }
618
+
619
+ .sidebar-brand {
620
+ display: flex;
621
+ align-items: center;
622
+ gap: 0.75rem;
623
+ }
624
+
625
+ .sidebar-brand > :global(.brand-logo) {
626
+ width: 1.5rem;
627
+ height: 1.5rem;
368
628
  }
369
629
 
370
630
  .sidebar-title {
371
631
  margin: 0;
372
- font-size: 1.125rem;
632
+ font-size: 0.875rem;
373
633
  font-weight: 600;
374
- color: #1c1917;
634
+ letter-spacing: 0.025em;
635
+ color: var(--card-foreground);
636
+ }
637
+ .sidebar-header-right {
638
+ display: flex;
639
+ align-items: center;
640
+ gap: 0.2rem;
375
641
  }
376
642
 
377
- .badge {
378
- min-width: 1.5rem;
379
- border-radius: 9999px;
380
- background-color: #ea580c;
381
- padding: 0.125rem 0.5rem;
382
- text-align: center;
383
- font-size: 0.75rem;
384
- font-weight: 600;
385
- color: white;
643
+ .sidebar-header-docs-link {
644
+ display: flex;
645
+ align-items: center;
646
+ gap: 0.5rem;
647
+ cursor: pointer;
648
+ border-radius: 0.75rem;
649
+ border: 0;
650
+ background-color: transparent;
651
+ padding: 0.5rem;
652
+ font-size: 0.875rem;
653
+ color: var(--muted-foreground);
654
+ transition: all 0.15s;
655
+ display: flex;
656
+ align-items: center;
657
+ justify-content: center;
658
+ }
659
+ .sidebar-header-docs-link:hover {
660
+ background-color: var(--muted);
661
+ color: var(--foreground);
662
+ }
663
+
664
+ .sidebar-header-docs-link-icon {
665
+ width: 1rem;
666
+ height: 1rem;
667
+ }
668
+
669
+ .theme-toggle-btn {
670
+ cursor: pointer;
671
+ border-radius: 0.75rem;
672
+ border: 0;
673
+ background-color: transparent;
674
+ padding: 0.5rem;
675
+ font-size: 0.875rem;
676
+ color: var(--muted-foreground);
677
+ transition: all 0.15s;
678
+ display: flex;
679
+ align-items: center;
680
+ justify-content: center;
681
+ }
682
+
683
+ .theme-toggle-btn:hover {
684
+ background-color: var(--muted);
685
+ color: var(--foreground);
686
+ }
687
+
688
+ .theme-icon {
689
+ width: 1rem;
690
+ height: 1rem;
691
+ }
692
+
693
+ :global(html:not(.dark)) .theme-icon-dark {
694
+ display: none;
695
+ }
696
+
697
+ :global(html.dark) .theme-icon-light {
698
+ display: none;
699
+ }
700
+
701
+ :global(html.dark) .theme-icon-dark {
702
+ display: block;
703
+ }
704
+
705
+ :global(html:not(.dark)) .theme-icon-light {
706
+ display: block;
386
707
  }
387
708
 
388
709
  .empty-state {
389
- padding: 2rem 1rem;
710
+ padding: 2rem 1.5rem;
390
711
  text-align: center;
391
- color: #78716c;
712
+ color: var(--muted-foreground);
392
713
  }
393
714
 
394
715
  .empty-state p {
395
716
  margin: 0.5rem 0;
396
717
  font-size: 0.875rem;
718
+ line-height: 1.5;
719
+ }
720
+
721
+ .empty-state p:first-child {
722
+ font-weight: 500;
723
+ color: var(--foreground);
397
724
  }
398
725
 
399
726
  .empty-state p:last-child {
400
- font-size: 0.75rem;
401
- color: #a8a29e;
727
+ font-size: 0.8125rem;
728
+ color: var(--muted-foreground);
729
+ opacity: 0.8;
402
730
  }
403
731
 
404
732
  .empty-state code {
405
733
  border-radius: 0.375rem;
406
- background-color: #f5f5f4;
734
+ background-color: var(--muted);
407
735
  padding: 0.125rem 0.375rem;
408
736
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
409
737
  font-size: 0.75rem;
738
+ font-weight: 500;
410
739
  }
411
740
 
412
741
  .email-list {
@@ -417,53 +746,17 @@
417
746
  overflow-y: auto;
418
747
  }
419
748
 
420
- .email-list li {
421
- list-style: none;
422
- }
423
-
424
- .email-button {
749
+ .email-tree {
425
750
  display: flex;
426
- width: 100%;
427
- cursor: pointer;
428
- align-items: center;
429
- gap: 0.75rem;
430
- border-radius: 0.75rem;
431
- border: 0;
432
- background-color: transparent;
433
- padding: 0.75rem;
434
- text-align: left;
435
- font-size: 0.875rem;
436
- color: #57534e;
437
- transition: all 0.15s;
438
- }
439
-
440
- .email-button:hover {
441
- background-color: #f5f5f4;
442
- }
443
-
444
- .email-button.active {
445
- background-color: #fff7ed;
446
- color: #c2410c;
447
- font-weight: 500;
448
- }
449
-
450
- .email-icon {
451
- flex-shrink: 0;
452
- font-size: 1.25rem;
453
- }
454
-
455
- .email-name {
456
- flex: 1;
457
- overflow: hidden;
458
- text-overflow: ellipsis;
459
- white-space: nowrap;
751
+ flex-direction: column;
752
+ gap: 0.125rem;
460
753
  }
461
754
 
462
755
  .main-content {
463
756
  display: flex;
464
757
  flex-direction: column;
465
758
  overflow: hidden;
466
- background-color: white;
759
+ background-color: var(--card);
467
760
  width: 100%;
468
761
  min-width: 0;
469
762
  }
@@ -473,7 +766,7 @@
473
766
  flex: 1;
474
767
  align-items: center;
475
768
  justify-content: center;
476
- background-color: #fafaf9;
769
+ background-color: var(--background);
477
770
  }
478
771
 
479
772
  .centered-content {
@@ -497,8 +790,8 @@
497
790
  height: 3rem;
498
791
  width: 3rem;
499
792
  border-radius: 9999px;
500
- border: 4px solid #e7e5e4;
501
- border-top-color: #ea580c;
793
+ border: 4px solid var(--border);
794
+ border-top-color: var(--svelte);
502
795
  animation: spin 1s linear infinite;
503
796
  }
504
797
 
@@ -510,14 +803,16 @@
510
803
 
511
804
  .heading {
512
805
  margin-bottom: 0.5rem;
806
+ margin-top: 0;
513
807
  font-size: 1.5rem;
514
808
  font-weight: 600;
515
- color: #1c1917;
809
+ line-height: 1.2;
810
+ color: var(--foreground);
516
811
  }
517
812
 
518
813
  .text-gray {
519
814
  margin: 0;
520
- color: #78716c;
815
+ color: var(--muted-foreground);
521
816
  }
522
817
 
523
818
  .btn {
@@ -525,85 +820,113 @@
525
820
  cursor: pointer;
526
821
  border-radius: 0.75rem;
527
822
  border: 0;
528
- background-color: #ea580c;
823
+ background-color: color-mix(in srgb, var(--svelte) 90%, transparent);
529
824
  padding: 0.5rem 1rem;
530
- font-weight: 500;
531
- color: white;
532
- transition: background-color 0.15s;
825
+ font-weight: 600;
826
+ color: var(--primary-foreground);
827
+ transition: all 0.15s;
828
+ box-shadow: 0 1px 3px 0 color-mix(in srgb, var(--svelte) 50%, transparent);
533
829
  }
534
830
 
535
831
  .btn:hover {
536
- background-color: #c2410c;
832
+ background-color: var(--svelte);
833
+ box-shadow: 0 4px 6px -1px color-mix(in srgb, var(--svelte) 50%, transparent);
537
834
  }
538
835
 
539
836
  .preview-header {
540
837
  display: flex;
541
838
  align-items: center;
542
839
  justify-content: space-between;
543
- border-bottom: 1px solid #e7e5e4;
544
- background-color: white;
545
- padding: 1rem 1.5rem;
840
+ border-bottom: 1px solid var(--border);
841
+ background-color: var(--card);
842
+ padding-inline: 1rem;
843
+ height: 4rem;
546
844
  }
547
845
 
548
846
  .preview-title {
549
847
  margin: 0;
550
- font-size: 1.125rem;
848
+ font-size: 1rem;
551
849
  font-weight: 600;
552
- color: #1c1917;
553
- }
554
-
555
- .button-group {
850
+ letter-spacing: -0.01em;
851
+ color: var(--card-foreground);
556
852
  display: flex;
853
+ align-items: center;
557
854
  gap: 0.5rem;
855
+ padding-left: 0.5rem;
856
+ }
857
+
858
+ .preview-title-icon {
859
+ color: var(--muted-foreground);
860
+ width: 1.15rem;
861
+ height: 1.15rem;
558
862
  }
559
863
 
560
- .btn-secondary {
864
+ .preview-actions {
561
865
  display: flex;
562
- cursor: pointer;
563
866
  align-items: center;
564
- gap: 0.375rem;
867
+ gap: 0.75rem;
868
+ }
869
+
870
+ .view-toggle {
871
+ display: inline-flex;
872
+ border: 1px solid var(--border);
565
873
  border-radius: 0.75rem;
566
- border: 1px solid #d6d3d1;
567
- background-color: white;
568
- padding: 0.5rem 0.875rem;
569
- font-size: 0.875rem;
570
- font-weight: 500;
571
- color: #57534e;
874
+ background-color: var(--muted);
875
+ padding: 0.25rem;
876
+ }
877
+
878
+ .view-toggle-btn {
879
+ cursor: pointer;
880
+ border: 0;
881
+ background-color: transparent;
882
+ padding: 0.375rem 0.75rem;
883
+ font-size: 0.8125rem;
884
+ font-weight: 600;
885
+ color: var(--secondary-foreground);
886
+ border-radius: 0.6rem;
572
887
  transition: all 0.15s;
573
888
  }
574
889
 
575
- .btn-secondary:hover {
576
- border-color: #a8a29e;
577
- background-color: #fafaf9;
890
+ .view-toggle-btn:hover {
891
+ color: var(--foreground);
892
+ }
893
+
894
+ .view-toggle-btn.active {
895
+ background-color: var(--card);
896
+ color: var(--card-foreground);
897
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08);
578
898
  }
579
899
 
580
900
  .btn-primary {
581
901
  display: flex;
582
902
  cursor: pointer;
583
903
  align-items: center;
584
- gap: 0.375rem;
904
+ gap: 0.5rem;
585
905
  border-radius: 0.75rem;
586
906
  border: 0;
587
- background-color: #ea580c;
588
- padding: 0.5rem 0.875rem;
589
- font-size: 0.875rem;
907
+ background-color: color-mix(in srgb, var(--svelte) 90%, transparent);
908
+ padding: 0.5rem 1rem;
909
+ font-size: 0.85rem;
590
910
  font-weight: 500;
591
911
  color: white;
592
912
  transition: all 0.15s;
913
+ box-shadow: 0 1px 3px 0 color-mix(in srgb, var(--svelte) 50%, transparent);
593
914
  }
594
915
 
595
916
  .btn-primary:hover {
596
- background-color: #c2410c;
917
+ background-color: var(--svelte);
918
+ box-shadow: 0 4px 6px -1px color-mix(in srgb, var(--svelte) 50%, transparent);
597
919
  }
598
920
 
599
- .btn-icon {
600
- font-size: 1rem;
921
+ .send-btn-icon {
922
+ width: 1rem;
923
+ height: 1rem;
601
924
  }
602
925
 
603
926
  .preview-container {
604
927
  flex: 1;
605
928
  overflow: hidden;
606
- background-color: #fafaf9;
929
+ background-color: var(--background);
607
930
  padding: 1rem;
608
931
  }
609
932
 
@@ -611,34 +934,36 @@
611
934
  height: 100%;
612
935
  width: 100%;
613
936
  border-radius: 0.75rem;
614
- border: 1px solid #e7e5e4;
615
- background-color: white;
937
+ border: 1px solid var(--border);
938
+ background-color: var(--card);
616
939
  }
617
940
 
618
- .code-section {
941
+ .code-view {
942
+ flex: 1;
619
943
  overflow: auto;
620
- border-top: 1px solid #e7e5e4;
621
- background-color: #fafaf9;
622
- }
623
-
624
- .code-summary {
625
- cursor: pointer;
626
- padding: 0.75rem 1.5rem;
627
- font-weight: 500;
628
- color: #57534e;
629
- user-select: none;
944
+ background-color: var(--background);
945
+ padding: 1rem;
630
946
  }
631
947
 
632
- .code-summary:hover {
633
- background-color: #f5f5f4;
948
+ .code-view-content {
949
+ height: 100%;
950
+ width: 100%;
951
+ border-radius: 0.75rem;
952
+ border: 1px solid var(--border);
953
+ background-color: #101010;
954
+ overflow: auto;
634
955
  }
635
956
 
636
957
  .code-content {
637
- height: 100%;
638
- overflow-y: scroll;
639
958
  font-size: 0.75rem;
640
- padding: 1rem;
641
- background-color: #0d1117;
959
+ padding: 1rem 1.25rem;
960
+ }
961
+
962
+ .code-empty {
963
+ padding: 1.5rem;
964
+ font-size: 0.875rem;
965
+ color: var(--muted-foreground);
966
+ text-align: center;
642
967
  }
643
968
 
644
969
  .modal-overlay {
@@ -655,7 +980,7 @@
655
980
  width: 100%;
656
981
  max-width: 28rem;
657
982
  border-radius: 0.75rem;
658
- background-color: white;
983
+ background-color: var(--card);
659
984
  padding: 1.5rem;
660
985
  box-shadow:
661
986
  0 20px 25px -5px rgb(0 0 0 / 0.1),
@@ -663,28 +988,33 @@
663
988
  }
664
989
 
665
990
  .modal-title {
666
- margin-bottom: 1rem;
991
+ margin: 0 0 1.5rem;
667
992
  font-size: 1.25rem;
668
993
  font-weight: 600;
669
- color: #1c1917;
994
+ line-height: 1.2;
995
+ letter-spacing: -0.01em;
996
+ color: var(--card-foreground);
670
997
  }
671
998
 
672
999
  .success-message {
673
1000
  margin-bottom: 1rem;
674
1001
  border-radius: 0.75rem;
675
1002
  background-color: #f0fdf4;
676
- padding: 1rem;
1003
+ border: 1px solid #bbf7d0;
1004
+ padding: 1.25rem;
677
1005
  text-align: center;
678
1006
  }
679
1007
 
680
1008
  .success-icon {
681
- margin-bottom: 0.5rem;
682
- font-size: 1.875rem;
1009
+ margin-bottom: 0.75rem;
1010
+ font-size: 2rem;
683
1011
  }
684
1012
 
685
1013
  .success-text {
686
1014
  margin: 0;
687
- font-weight: 500;
1015
+ font-size: 0.875rem;
1016
+ font-weight: 600;
1017
+ line-height: 1.5;
688
1018
  color: #166534;
689
1019
  }
690
1020
 
@@ -694,61 +1024,72 @@
694
1024
 
695
1025
  .form-label {
696
1026
  display: block;
697
- margin-bottom: 0.25rem;
1027
+ margin-bottom: 0.5rem;
698
1028
  font-size: 0.875rem;
699
- font-weight: 500;
700
- color: #57534e;
1029
+ font-weight: 600;
1030
+ color: var(--foreground);
701
1031
  }
702
1032
 
703
1033
  .form-input {
704
1034
  width: 100%;
705
1035
  border-radius: 0.75rem;
706
- border: 1px solid #d6d3d1;
707
- padding: 0.5rem 0.75rem;
1036
+ border: 1px solid var(--input);
1037
+ padding: 0.625rem 0.875rem;
708
1038
  font-size: 0.875rem;
1039
+ background-color: var(--card);
1040
+ color: var(--card-foreground);
1041
+ transition: all 0.15s;
1042
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
709
1043
  }
710
1044
 
711
1045
  .form-input:focus {
712
1046
  outline: none;
713
- border-color: #ea580c;
714
- box-shadow: 0 0 0 2px rgba(234, 88, 12, 0.2);
1047
+ border-color: var(--svelte);
1048
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--svelte) 10%, transparent);
715
1049
  }
716
1050
 
717
1051
  .form-help {
718
- margin-top: 0.25rem;
719
- font-size: 0.75rem;
720
- color: #78716c;
1052
+ margin-top: 0.5rem;
1053
+ font-size: 0.8125rem;
1054
+ line-height: 1.5;
1055
+ color: var(--muted-foreground);
721
1056
  }
722
1057
 
723
1058
  .error-message {
724
1059
  margin-bottom: 1rem;
725
1060
  border-radius: 0.75rem;
726
1061
  background-color: #fef2f2;
727
- padding: 0.75rem;
1062
+ border: 1px solid #fecaca;
1063
+ padding: 0.75rem 1rem;
728
1064
  font-size: 0.875rem;
1065
+ font-weight: 500;
1066
+ line-height: 1.5;
729
1067
  color: #991b1b;
730
1068
  }
731
1069
 
732
1070
  .modal-actions {
733
1071
  display: flex;
734
1072
  justify-content: flex-end;
735
- gap: 0.5rem;
1073
+ gap: 0.75rem;
1074
+ margin-top: 0.5rem;
736
1075
  }
737
1076
 
738
1077
  .btn-cancel {
739
1078
  cursor: pointer;
740
1079
  border-radius: 0.75rem;
741
- border: 1px solid #d6d3d1;
742
- background-color: white;
1080
+ border: 1px solid var(--border);
1081
+ background-color: var(--muted);
743
1082
  padding: 0.5rem 1rem;
744
1083
  font-size: 0.875rem;
745
- font-weight: 500;
746
- color: #57534e;
1084
+ font-weight: 600;
1085
+ color: var(--secondary-foreground);
747
1086
  transition: all 0.15s;
1087
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
748
1088
  }
749
1089
 
750
- .btn-cancel:hover {
751
- background-color: #fafaf9;
1090
+ .btn-cancel:hover:not(:disabled) {
1091
+ background-color: var(--secondary);
1092
+ color: var(--foreground);
752
1093
  }
753
1094
 
754
1095
  .btn-cancel:disabled {
@@ -760,16 +1101,18 @@
760
1101
  cursor: pointer;
761
1102
  border-radius: 0.75rem;
762
1103
  border: 0;
763
- background-color: #ea580c;
1104
+ background-color: color-mix(in srgb, var(--svelte) 90%, transparent);
764
1105
  padding: 0.5rem 1rem;
765
1106
  font-size: 0.875rem;
766
- font-weight: 500;
1107
+ font-weight: 600;
767
1108
  color: white;
768
1109
  transition: all 0.15s;
1110
+ box-shadow: 0 1px 3px 0 color-mix(in srgb, var(--svelte) 50%, transparent);
769
1111
  }
770
1112
 
771
- .btn-submit:hover {
772
- background-color: #c2410c;
1113
+ .btn-submit:hover:not(:disabled) {
1114
+ background-color: var(--svelte);
1115
+ box-shadow: 0 4px 6px -1px color-mix(in srgb, var(--svelte) 50%, transparent);
773
1116
  }
774
1117
 
775
1118
  .btn-submit:disabled {