@autumnsgrove/groveengine 0.8.6 → 0.9.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 (80) hide show
  1. package/dist/components/admin/GutterManager.svelte +213 -101
  2. package/dist/components/admin/MarkdownEditor.svelte +6 -3
  3. package/dist/components/custom/GutterItem.svelte +8 -2
  4. package/dist/components/quota/UpgradePrompt.svelte +1 -0
  5. package/dist/config/domain-blocklist.d.ts +59 -0
  6. package/dist/config/domain-blocklist.js +731 -0
  7. package/dist/config/index.d.ts +3 -1
  8. package/dist/config/index.js +2 -1
  9. package/dist/config/offensive-blocklist.d.ts +44 -0
  10. package/dist/config/offensive-blocklist.js +751 -0
  11. package/dist/config/terrarium.d.ts +109 -0
  12. package/dist/config/terrarium.js +125 -0
  13. package/dist/styles/tokens.css +90 -0
  14. package/dist/types/dom-to-image-more.d.ts +39 -0
  15. package/dist/ui/components/chrome/Footer.svelte +137 -0
  16. package/dist/ui/components/chrome/Footer.svelte.d.ts +11 -0
  17. package/dist/ui/components/chrome/FooterMinimal.svelte +75 -0
  18. package/dist/ui/components/chrome/FooterMinimal.svelte.d.ts +10 -0
  19. package/dist/ui/components/chrome/Header.svelte +113 -0
  20. package/dist/ui/components/chrome/Header.svelte.d.ts +11 -0
  21. package/dist/ui/components/chrome/HeaderMinimal.svelte +68 -0
  22. package/dist/ui/components/chrome/HeaderMinimal.svelte.d.ts +9 -0
  23. package/dist/ui/components/chrome/MobileMenu.svelte +145 -0
  24. package/dist/ui/components/chrome/MobileMenu.svelte.d.ts +9 -0
  25. package/dist/ui/components/chrome/ThemeToggle.svelte +34 -0
  26. package/dist/ui/components/chrome/ThemeToggle.svelte.d.ts +3 -0
  27. package/dist/ui/components/chrome/defaults.d.ts +6 -0
  28. package/dist/ui/components/chrome/defaults.js +65 -0
  29. package/dist/ui/components/chrome/index.d.ts +13 -0
  30. package/dist/ui/components/chrome/index.js +14 -0
  31. package/dist/ui/components/chrome/types.d.ts +19 -0
  32. package/dist/ui/components/chrome/types.js +8 -0
  33. package/dist/ui/components/content/RoadmapPreview.svelte +2 -1
  34. package/dist/ui/components/forms/ContentSearch.svelte +406 -0
  35. package/dist/ui/components/forms/ContentSearch.svelte.d.ts +71 -0
  36. package/dist/ui/components/forms/filterUtils.d.ts +138 -0
  37. package/dist/ui/components/forms/filterUtils.js +240 -0
  38. package/dist/ui/components/forms/index.d.ts +2 -0
  39. package/dist/ui/components/forms/index.js +5 -1
  40. package/dist/ui/components/gallery/ImageGallery.svelte +3 -0
  41. package/dist/ui/components/gallery/Lightbox.svelte +3 -0
  42. package/dist/ui/components/gallery/ZoomableImage.svelte +1 -0
  43. package/dist/ui/components/icons/index.d.ts +2 -1
  44. package/dist/ui/components/icons/index.js +14 -3
  45. package/dist/ui/components/icons/lucide.d.ts +213 -0
  46. package/dist/ui/components/icons/lucide.js +224 -0
  47. package/dist/ui/components/terrarium/AssetPalette.svelte +207 -0
  48. package/dist/ui/components/terrarium/AssetPalette.svelte.d.ts +7 -0
  49. package/dist/ui/components/terrarium/Canvas.svelte +231 -0
  50. package/dist/ui/components/terrarium/Canvas.svelte.d.ts +14 -0
  51. package/dist/ui/components/terrarium/ExportDialog.svelte +307 -0
  52. package/dist/ui/components/terrarium/ExportDialog.svelte.d.ts +18 -0
  53. package/dist/ui/components/terrarium/PaletteItem.svelte +169 -0
  54. package/dist/ui/components/terrarium/PaletteItem.svelte.d.ts +9 -0
  55. package/dist/ui/components/terrarium/PlacedAsset.svelte +222 -0
  56. package/dist/ui/components/terrarium/PlacedAsset.svelte.d.ts +11 -0
  57. package/dist/ui/components/terrarium/Terrarium.svelte +266 -0
  58. package/dist/ui/components/terrarium/Terrarium.svelte.d.ts +3 -0
  59. package/dist/ui/components/terrarium/Toolbar.svelte +299 -0
  60. package/dist/ui/components/terrarium/Toolbar.svelte.d.ts +24 -0
  61. package/dist/ui/components/terrarium/index.d.ts +31 -0
  62. package/dist/ui/components/terrarium/index.js +33 -0
  63. package/dist/ui/components/terrarium/terrariumState.svelte.d.ts +45 -0
  64. package/dist/ui/components/terrarium/terrariumState.svelte.js +291 -0
  65. package/dist/ui/components/terrarium/types.d.ts +139 -0
  66. package/dist/ui/components/terrarium/types.js +43 -0
  67. package/dist/ui/components/terrarium/utils/export.d.ts +48 -0
  68. package/dist/ui/components/terrarium/utils/export.js +148 -0
  69. package/dist/ui/components/ui/CollapsibleSection.svelte +2 -0
  70. package/dist/ui/components/ui/GlassConfirmDialog.svelte +9 -0
  71. package/dist/ui/components/ui/GlassOverlay.svelte +2 -1
  72. package/dist/ui/components/ui/Input.svelte +9 -1
  73. package/dist/ui/components/ui/Input.svelte.d.ts +2 -0
  74. package/dist/ui/components/ui/Textarea.svelte +9 -1
  75. package/dist/ui/components/ui/Textarea.svelte.d.ts +2 -0
  76. package/dist/ui/stores/index.d.ts +6 -0
  77. package/dist/ui/stores/index.js +6 -0
  78. package/dist/ui/stores/season.d.ts +14 -0
  79. package/dist/ui/stores/season.js +65 -0
  80. package/package.json +27 -4
@@ -4,6 +4,7 @@
4
4
  import Dialog from "../../ui/components/ui/Dialog.svelte";
5
5
  import Select from "../../ui/components/ui/Select.svelte";
6
6
  import { toast } from "../../ui/components/ui/toast";
7
+ import { MessageSquare, ImageIcon, Images, Pin, Plus, ChevronUp, ChevronDown, Pencil, X } from "lucide-svelte";
7
8
 
8
9
  /**
9
10
  * @typedef {Object} GutterItem
@@ -122,7 +123,7 @@
122
123
  /** @param {number} index */
123
124
  function deleteItem(index) {
124
125
  gutterItems = gutterItems.filter((/** @type {GutterItem} */ _, /** @type {number} */ i) => i !== index);
125
- toast.success("Gutter item deleted");
126
+ toast.success("Vine removed");
126
127
  }
127
128
 
128
129
  /**
@@ -249,38 +250,38 @@
249
250
  return "";
250
251
  }
251
252
 
252
- /** @param {string} type */
253
- function getTypeIcon(type) {
254
- switch (type) {
255
- case "comment":
256
- return "💬";
257
- case "photo":
258
- return "🖼️";
259
- case "gallery":
260
- return "🎞️";
261
- default:
262
- return "📌";
263
- }
264
- }
265
253
  </script>
266
254
 
267
- <div class="gutter-manager">
268
- <div class="gutter-header">
269
- <h3>Gutter Content</h3>
270
- <button class="add-btn" onclick={openAddModal}>+ Add Item</button>
255
+ <div class="vines-manager">
256
+ <div class="vines-header">
257
+ <h3>Vines</h3>
258
+ <button class="add-btn" onclick={openAddModal}>
259
+ <Plus class="btn-icon" />
260
+ <span>Add Item</span>
261
+ </button>
271
262
  </div>
272
263
 
273
264
  {#if gutterItems.length === 0}
274
265
  <div class="empty-state">
275
- <p>No gutter items yet.</p>
266
+ <p>No vines yet.</p>
276
267
  <p class="hint">Add comments, images, or galleries that appear alongside your content.</p>
277
268
  </div>
278
269
  {:else}
279
- <div class="gutter-list">
270
+ <div class="vines-list">
280
271
  {#each gutterItems as item, index (index)}
281
- <div class="gutter-item">
272
+ <div class="vine-item">
282
273
  <div class="item-header">
283
- <span class="item-type">{getTypeIcon(item.type)}</span>
274
+ <span class="item-type">
275
+ {#if item.type === "comment"}
276
+ <MessageSquare class="type-icon" />
277
+ {:else if item.type === "photo"}
278
+ <ImageIcon class="type-icon" />
279
+ {:else if item.type === "gallery"}
280
+ <Images class="type-icon" />
281
+ {:else}
282
+ <Pin class="type-icon" />
283
+ {/if}
284
+ </span>
284
285
  <span class="item-anchor" title={item.anchor}>{item.anchor || "No anchor"}</span>
285
286
  <div class="item-actions">
286
287
  <button
@@ -288,23 +289,35 @@
288
289
  onclick={() => moveItem(index, -1)}
289
290
  disabled={index === 0}
290
291
  title="Move up"
291
- >↑</button>
292
+ aria-label="Move item up"
293
+ >
294
+ <ChevronUp class="action-icon" />
295
+ </button>
292
296
  <button
293
297
  class="action-btn"
294
298
  onclick={() => moveItem(index, 1)}
295
299
  disabled={index === gutterItems.length - 1}
296
300
  title="Move down"
297
- >↓</button>
301
+ aria-label="Move item down"
302
+ >
303
+ <ChevronDown class="action-icon" />
304
+ </button>
298
305
  <button
299
306
  class="action-btn"
300
307
  onclick={() => openEditModal(index)}
301
308
  title="Edit"
302
- >✎</button>
309
+ aria-label="Edit item"
310
+ >
311
+ <Pencil class="action-icon" />
312
+ </button>
303
313
  <button
304
314
  class="action-btn delete"
305
315
  onclick={() => deleteItem(index)}
306
316
  title="Delete"
307
- >×</button>
317
+ aria-label="Delete item"
318
+ >
319
+ <X class="action-icon" />
320
+ </button>
308
321
  </div>
309
322
  </div>
310
323
  <div class="item-preview">{getItemPreview(item)}</div>
@@ -315,7 +328,7 @@
315
328
  </div>
316
329
 
317
330
  <!-- Add/Edit Modal -->
318
- <Dialog bind:open={showAddModal} title={editingIndex !== null ? "Edit Gutter Item" : "Add Gutter Item"}>
331
+ <Dialog bind:open={showAddModal} title={editingIndex !== null ? "Edit Vine" : "Add Vine"}>
319
332
  {#snippet children()}
320
333
  <div class="form-group">
321
334
  <label for="item-type">Type</label>
@@ -500,49 +513,71 @@
500
513
  </Dialog>
501
514
 
502
515
  <style>
503
- .gutter-manager {
504
- background: #1e1e1e;
505
- border: 1px solid #3a3a3a;
506
- border-radius: 8px;
516
+ .vines-manager {
517
+ background: var(--glass-bg);
518
+ backdrop-filter: blur(12px);
519
+ border: 1px solid var(--grove-overlay-15);
520
+ border-radius: 12px;
507
521
  overflow: hidden;
508
522
  }
509
523
 
510
- .gutter-header {
524
+ .vines-header {
511
525
  display: flex;
512
526
  justify-content: space-between;
513
527
  align-items: center;
514
- padding: 0.75rem 1rem;
515
- background: #252526;
516
- border-bottom: 1px solid #3a3a3a;
528
+ padding: 0.875rem 1rem;
529
+ background: var(--grove-overlay-5);
530
+ border-bottom: 1px solid var(--grove-border-subtle);
517
531
  }
518
532
 
519
- .gutter-header h3 {
533
+ .vines-header h3 {
520
534
  margin: 0;
521
- font-size: 0.9rem;
522
- color: #8bc48b;
535
+ font-size: 0.95rem;
536
+ color: var(--color-primary);
523
537
  font-weight: 600;
524
538
  }
525
539
 
540
+ :global(.dark) .vines-header h3 {
541
+ color: #86efac;
542
+ }
543
+
526
544
  .add-btn {
527
- padding: 0.35rem 0.75rem;
528
- background: #2d4a2d;
529
- color: #a8dca8;
530
- border: 1px solid #3d5a3d;
531
- border-radius: 4px;
545
+ display: flex;
546
+ align-items: center;
547
+ gap: 0.35rem;
548
+ padding: 0.4rem 0.75rem;
549
+ background: var(--grove-overlay-10);
550
+ color: var(--color-primary);
551
+ border: 1px solid var(--grove-border);
552
+ border-radius: var(--border-radius-button);
532
553
  font-size: 0.8rem;
554
+ font-weight: 500;
533
555
  cursor: pointer;
534
556
  transition: all 0.15s ease;
535
557
  }
536
558
 
559
+ :global(.dark) .add-btn {
560
+ color: #86efac;
561
+ }
562
+
537
563
  .add-btn:hover {
538
- background: #3d5a3d;
539
- color: #c8f0c8;
564
+ background: var(--grove-overlay-18);
565
+ border-color: var(--grove-border-strong);
566
+ }
567
+
568
+ :global(.btn-icon) {
569
+ width: 0.875rem;
570
+ height: 0.875rem;
540
571
  }
541
572
 
542
573
  .empty-state {
543
574
  padding: 2rem 1rem;
544
575
  text-align: center;
545
- color: #6a6a6a;
576
+ color: var(--color-text-muted);
577
+ }
578
+
579
+ :global(.dark) .empty-state {
580
+ color: var(--grove-text-muted);
546
581
  }
547
582
 
548
583
  .empty-state p {
@@ -551,19 +586,32 @@
551
586
 
552
587
  .empty-state .hint {
553
588
  font-size: 0.85rem;
554
- color: #5a5a5a;
589
+ color: var(--color-text-subtle);
590
+ }
591
+
592
+ :global(.dark) .empty-state .hint {
593
+ color: var(--grove-text-subtle);
555
594
  }
556
595
 
557
- .gutter-list {
596
+ .vines-list {
558
597
  padding: 0.5rem;
559
598
  }
560
599
 
561
- .gutter-item {
562
- background: #252526;
563
- border: 1px solid #3a3a3a;
564
- border-radius: 4px;
565
- padding: 0.5rem 0.75rem;
600
+ .vine-item {
601
+ background: var(--glass-bg-medium);
602
+ border: 1px solid var(--grove-border-subtle);
603
+ border-radius: 8px;
604
+ padding: 0.625rem 0.875rem;
566
605
  margin-bottom: 0.5rem;
606
+ transition: border-color 0.15s ease;
607
+ }
608
+
609
+ .vine-item:hover {
610
+ border-color: var(--grove-overlay-25);
611
+ }
612
+
613
+ :global(.dark) .vine-item:hover {
614
+ border-color: var(--grove-overlay-30);
567
615
  }
568
616
 
569
617
  .item-header {
@@ -573,38 +621,70 @@
573
621
  }
574
622
 
575
623
  .item-type {
576
- font-size: 1rem;
624
+ display: flex;
625
+ align-items: center;
626
+ justify-content: center;
627
+ color: var(--color-primary);
628
+ }
629
+
630
+ :global(.dark) .item-type {
631
+ color: #86efac;
632
+ }
633
+
634
+ :global(.type-icon) {
635
+ width: 1rem;
636
+ height: 1rem;
577
637
  }
578
638
 
579
639
  .item-anchor {
580
640
  flex: 1;
581
641
  font-family: monospace;
582
642
  font-size: 0.8rem;
583
- color: #9d9d9d;
643
+ color: var(--color-text-muted);
584
644
  white-space: nowrap;
585
645
  overflow: hidden;
586
646
  text-overflow: ellipsis;
587
647
  }
588
648
 
649
+ :global(.dark) .item-anchor {
650
+ color: var(--grove-text-strong);
651
+ }
652
+
589
653
  .item-actions {
590
654
  display: flex;
591
- gap: 0.25rem;
655
+ gap: 0.125rem;
592
656
  }
593
657
 
594
658
  .action-btn {
595
- padding: 0.2rem 0.4rem;
659
+ display: flex;
660
+ align-items: center;
661
+ justify-content: center;
662
+ padding: 0.25rem;
596
663
  background: transparent;
597
664
  border: 1px solid transparent;
598
- color: #6a6a6a;
599
- border-radius: 3px;
665
+ color: var(--color-text-subtle);
666
+ border-radius: 4px;
600
667
  cursor: pointer;
601
- font-size: 0.85rem;
602
668
  transition: all 0.15s ease;
603
669
  }
604
670
 
671
+ :global(.dark) .action-btn {
672
+ color: var(--grove-text-subtle);
673
+ }
674
+
675
+ :global(.action-icon) {
676
+ width: 0.875rem;
677
+ height: 0.875rem;
678
+ }
679
+
605
680
  .action-btn:hover:not(:disabled) {
606
- background: #3a3a3a;
607
- color: #d4d4d4;
681
+ background: var(--grove-overlay-10);
682
+ color: var(--color-primary);
683
+ }
684
+
685
+ :global(.dark) .action-btn:hover:not(:disabled) {
686
+ background: var(--grove-overlay-15);
687
+ color: #86efac;
608
688
  }
609
689
 
610
690
  .action-btn:disabled {
@@ -613,20 +693,29 @@
613
693
  }
614
694
 
615
695
  .action-btn.delete:hover {
616
- background: rgba(215, 58, 73, 0.2);
617
- color: #f85149;
696
+ background: rgba(239, 68, 68, 0.1);
697
+ color: #ef4444;
698
+ }
699
+
700
+ :global(.dark) .action-btn.delete:hover {
701
+ background: rgba(239, 68, 68, 0.15);
702
+ color: #f87171;
618
703
  }
619
704
 
620
705
  .item-preview {
621
706
  margin-top: 0.35rem;
622
707
  font-size: 0.8rem;
623
- color: #6a6a6a;
708
+ color: var(--color-text-subtle);
624
709
  white-space: nowrap;
625
710
  overflow: hidden;
626
711
  text-overflow: ellipsis;
627
712
  }
628
713
 
629
- /* Form Styles */
714
+ :global(.dark) .item-preview {
715
+ color: var(--grove-text-subtle);
716
+ }
717
+
718
+ /* Form Styles - These appear in the Dialog component */
630
719
  .form-group {
631
720
  margin-bottom: 1rem;
632
721
  }
@@ -636,23 +725,23 @@
636
725
  display: block;
637
726
  margin-bottom: 0.4rem;
638
727
  font-size: 0.85rem;
639
- color: #9d9d9d;
728
+ color: var(--color-text-muted);
640
729
  }
641
730
 
642
731
  .form-input {
643
732
  width: 100%;
644
733
  padding: 0.5rem 0.75rem;
645
- background: #252526;
646
- border: 1px solid #3a3a3a;
647
- border-radius: 4px;
648
- color: #d4d4d4;
734
+ background: var(--color-bg-secondary);
735
+ border: 1px solid var(--color-border);
736
+ border-radius: var(--border-radius-small);
737
+ color: var(--color-text);
649
738
  font-size: 0.9rem;
650
739
  font-family: inherit;
651
740
  }
652
741
 
653
742
  .form-input:focus {
654
743
  outline: none;
655
- border-color: #4a7c4a;
744
+ border-color: var(--color-primary);
656
745
  }
657
746
 
658
747
  .form-textarea {
@@ -665,14 +754,14 @@
665
754
  display: block;
666
755
  margin-top: 0.35rem;
667
756
  font-size: 0.75rem;
668
- color: #6a6a6a;
757
+ color: var(--color-text-subtle);
669
758
  }
670
759
 
671
760
  .form-hint code {
672
- background: #252526;
761
+ background: var(--color-bg-secondary);
673
762
  padding: 0.1rem 0.3rem;
674
763
  border-radius: 2px;
675
- color: #ce9178;
764
+ color: var(--color-primary);
676
765
  }
677
766
 
678
767
  .anchor-input-row,
@@ -696,31 +785,37 @@
696
785
 
697
786
  .anchors-label {
698
787
  font-size: 0.75rem;
699
- color: #6a6a6a;
788
+ color: var(--color-text-subtle);
700
789
  }
701
790
 
702
791
  .anchor-chip {
703
792
  padding: 0.2rem 0.5rem;
704
- background: #252526;
705
- border: 1px solid #3a3a3a;
793
+ background: var(--grove-overlay-10);
794
+ border: 1px solid var(--grove-border);
706
795
  border-radius: 12px;
707
- color: #9d9d9d;
796
+ color: var(--color-text-muted);
708
797
  font-size: 0.7rem;
709
798
  font-family: monospace;
710
799
  cursor: pointer;
800
+ transition: all 0.15s ease;
711
801
  }
712
802
 
713
803
  .anchor-chip:hover {
714
- background: #3a3a3a;
715
- color: #d4d4d4;
804
+ background: var(--grove-overlay-20);
805
+ color: var(--color-primary);
806
+ }
807
+
808
+ :global(.dark) .anchor-chip:hover {
809
+ color: #86efac;
716
810
  }
717
811
 
718
812
  .image-preview {
719
813
  margin-top: 0.5rem;
720
814
  max-height: 150px;
721
815
  overflow: hidden;
722
- border-radius: 4px;
723
- background: #252526;
816
+ border-radius: 8px;
817
+ background: var(--color-bg-secondary);
818
+ border: 1px solid var(--color-border);
724
819
  }
725
820
 
726
821
  .image-preview img {
@@ -741,10 +836,10 @@
741
836
  display: flex;
742
837
  gap: 0.5rem;
743
838
  align-items: center;
744
- background: #252526;
839
+ background: var(--color-bg-secondary);
745
840
  padding: 0.5rem;
746
- border-radius: 4px;
747
- border: 1px solid #3a3a3a;
841
+ border-radius: 8px;
842
+ border: 1px solid var(--color-border);
748
843
  }
749
844
 
750
845
  .gallery-thumb {
@@ -752,7 +847,7 @@
752
847
  height: 50px;
753
848
  -o-object-fit: cover;
754
849
  object-fit: cover;
755
- border-radius: 3px;
850
+ border-radius: 4px;
756
851
  }
757
852
 
758
853
  .gallery-image-fields {
@@ -766,25 +861,37 @@
766
861
  padding: 0.25rem 0.5rem;
767
862
  background: transparent;
768
863
  border: none;
769
- color: #f85149;
864
+ color: #ef4444;
770
865
  font-size: 1.2rem;
771
866
  cursor: pointer;
867
+ transition: color 0.15s ease;
868
+ }
869
+
870
+ .remove-btn:hover {
871
+ color: #f87171;
772
872
  }
773
873
 
774
874
  .add-image-btn {
775
875
  padding: 0.5rem;
776
876
  background: transparent;
777
- border: 1px dashed #3a3a3a;
778
- border-radius: 4px;
779
- color: #6a6a6a;
877
+ border: 1px dashed var(--grove-overlay-30);
878
+ border-radius: 8px;
879
+ color: var(--color-text-muted);
780
880
  cursor: pointer;
781
881
  font-size: 0.85rem;
782
882
  width: 100%;
883
+ transition: all 0.15s ease;
783
884
  }
784
885
 
785
886
  .add-image-btn:hover {
786
- border-color: #4a7c4a;
787
- color: #8bc48b;
887
+ border-color: var(--color-primary);
888
+ color: var(--color-primary);
889
+ background: var(--grove-overlay-5);
890
+ }
891
+
892
+ :global(.dark) .add-image-btn:hover {
893
+ border-color: #86efac;
894
+ color: #86efac;
788
895
  }
789
896
 
790
897
  /* Image Picker */
@@ -805,8 +912,9 @@
805
912
  max-height: 400px;
806
913
  overflow-y: auto;
807
914
  padding: 0.5rem;
808
- background: #252526;
809
- border-radius: 4px;
915
+ background: var(--color-bg-secondary);
916
+ border-radius: 8px;
917
+ border: 1px solid var(--color-border);
810
918
  }
811
919
 
812
920
  .loading,
@@ -814,22 +922,26 @@
814
922
  grid-column: 1 / -1;
815
923
  text-align: center;
816
924
  padding: 2rem;
817
- color: #6a6a6a;
925
+ color: var(--color-text-muted);
818
926
  }
819
927
 
820
928
  .image-option {
821
929
  display: flex;
822
930
  flex-direction: column;
823
- background: #1e1e1e;
931
+ background: var(--color-bg-secondary);
824
932
  border: 2px solid transparent;
825
- border-radius: 4px;
933
+ border-radius: 6px;
826
934
  padding: 0.25rem;
827
935
  cursor: pointer;
828
936
  transition: border-color 0.15s ease;
829
937
  }
830
938
 
831
939
  .image-option:hover {
832
- border-color: #4a7c4a;
940
+ border-color: var(--color-primary);
941
+ }
942
+
943
+ :global(.dark) .image-option:hover {
944
+ border-color: #86efac;
833
945
  }
834
946
 
835
947
  .image-option img {
@@ -837,12 +949,12 @@
837
949
  aspect-ratio: 1;
838
950
  -o-object-fit: cover;
839
951
  object-fit: cover;
840
- border-radius: 2px;
952
+ border-radius: 4px;
841
953
  }
842
954
 
843
955
  .image-name {
844
956
  font-size: 0.65rem;
845
- color: #6a6a6a;
957
+ color: var(--color-text-subtle);
846
958
  margin-top: 0.25rem;
847
959
  white-space: nowrap;
848
960
  overflow: hidden;
@@ -824,9 +824,8 @@
824
824
 
825
825
  <!-- Full Preview Modal -->
826
826
  {#if showFullPreview}
827
- <div class="full-preview-modal" role="dialog" aria-modal="true" aria-label="Full article preview">
828
- <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
829
- <div class="full-preview-backdrop" onclick={() => (showFullPreview = false)}></div>
827
+ <div class="full-preview-modal" role="dialog" aria-modal="true" aria-label="Full article preview" tabindex="-1" onkeydown={(e) => e.key === 'Escape' && (showFullPreview = false)}>
828
+ <button type="button" class="full-preview-backdrop" onclick={() => (showFullPreview = false)} aria-label="Close preview"></button>
830
829
  <div class="full-preview-container">
831
830
  <header class="full-preview-header">
832
831
  <h2>:: full preview</h2>
@@ -1100,6 +1099,7 @@
1100
1099
  .toolbar-btn.full-preview-btn .key {
1101
1100
  color: #9ac5ff;
1102
1101
  }
1102
+ /* svelte-ignore css-unused-selector */
1103
1103
  .toolbar-divider {
1104
1104
  color: #4a4a4a;
1105
1105
  margin: 0 0.25rem;
@@ -1775,6 +1775,9 @@
1775
1775
  position: absolute;
1776
1776
  inset: 0;
1777
1777
  background: rgba(0, 0, 0, 0.7);
1778
+ border: none;
1779
+ padding: 0;
1780
+ cursor: pointer;
1778
1781
  }
1779
1782
  .full-preview-container {
1780
1783
  position: relative;
@@ -31,8 +31,14 @@
31
31
 
32
32
  <div class="gutter-item" data-anchor={item.anchor || ''}>
33
33
  {#if item.type === 'comment' || item.type === 'markdown'}
34
- <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
35
- <div class="gutter-comment" onclick={handleContentClick}>
34
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
35
+ <div
36
+ class="gutter-comment"
37
+ onclick={handleContentClick}
38
+ onkeydown={(e) => e.key === 'Enter' && handleContentClick(e)}
39
+ role="group"
40
+ aria-label="Gutter annotation - click images to enlarge"
41
+ >
36
42
  {@html sanitizeHTML(item.content)}
37
43
  </div>
38
44
  {:else if item.type === 'photo' || item.type === 'image'}
@@ -173,6 +173,7 @@
173
173
 
174
174
  {#if open}
175
175
  <!-- Backdrop -->
176
+ <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
176
177
  <div
177
178
  class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
178
179
  onclick={onClose}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Loam - Domain Blocklist Configuration
3
+ *
4
+ * Part of the Loam name protection system.
5
+ * Comprehensive list of blocked usernames/subdomains organized by category.
6
+ *
7
+ * @see docs/specs/loam-spec.md
8
+ * @module domain-blocklist
9
+ */
10
+ export type BlocklistReason = 'system' | 'grove_service' | 'trademark' | 'impersonation' | 'offensive' | 'fraud' | 'future_reserved';
11
+ /**
12
+ * Array of valid blocklist reasons for runtime validation
13
+ * Use this to validate database values before type assertion
14
+ */
15
+ export declare const VALID_BLOCKLIST_REASONS: BlocklistReason[];
16
+ export interface BlocklistEntry {
17
+ username: string;
18
+ reason: BlocklistReason;
19
+ category?: string;
20
+ }
21
+ export declare const SYSTEM_RESERVED: string[];
22
+ export declare const GROVE_SERVICES: string[];
23
+ export declare const GROVE_TRADEMARKS: string[];
24
+ export declare const IMPERSONATION_TERMS: string[];
25
+ export declare const FRAUD_TERMS: string[];
26
+ export declare const FUTURE_RESERVED: string[];
27
+ /**
28
+ * Complete blocklist with all categories
29
+ */
30
+ export declare const COMPLETE_BLOCKLIST: BlocklistEntry[];
31
+ /**
32
+ * Fast lookup Set for validation (checks existence only)
33
+ */
34
+ export declare const BLOCKED_USERNAMES: Set<string>;
35
+ /**
36
+ * Fast lookup Map for validation with reason (O(1) lookup)
37
+ */
38
+ export declare const BLOCKED_USERNAMES_MAP: Map<string, BlocklistReason>;
39
+ /**
40
+ * Check if a username is blocked
41
+ * @param username - The username to check (will be lowercased)
42
+ * @returns The blocking reason if blocked, null if allowed
43
+ */
44
+ export declare function isUsernameBlocked(username: string): BlocklistReason | null;
45
+ /**
46
+ * Get a user-friendly error message for a blocked username
47
+ * @param reason - The blocking reason
48
+ * @returns A user-friendly error message
49
+ */
50
+ export declare function getBlockedMessage(reason: BlocklistReason): string;
51
+ /**
52
+ * Validation configuration
53
+ */
54
+ export declare const VALIDATION_CONFIG: {
55
+ minLength: number;
56
+ maxLength: number;
57
+ pattern: RegExp;
58
+ patternDescription: string;
59
+ };