@autumnsgrove/groveengine 0.1.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 (219) hide show
  1. package/README.md +163 -0
  2. package/dist/auth/jwt.d.ts +14 -0
  3. package/dist/auth/jwt.js +109 -0
  4. package/dist/auth/session.d.ts +42 -0
  5. package/dist/auth/session.js +105 -0
  6. package/dist/components/admin/GutterManager.svelte +910 -0
  7. package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
  8. package/dist/components/admin/MarkdownEditor.svelte +3114 -0
  9. package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
  10. package/dist/components/custom/CollapsibleSection.svelte +74 -0
  11. package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
  12. package/dist/components/custom/ContentWithGutter.svelte +646 -0
  13. package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
  14. package/dist/components/custom/GutterItem.svelte +201 -0
  15. package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
  16. package/dist/components/custom/LeftGutter.svelte +271 -0
  17. package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
  18. package/dist/components/custom/MobileTOC.svelte +273 -0
  19. package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
  20. package/dist/components/custom/TableOfContents.svelte +163 -0
  21. package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
  22. package/dist/components/gallery/ImageGallery.svelte +681 -0
  23. package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
  24. package/dist/components/gallery/Lightbox.svelte +107 -0
  25. package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
  26. package/dist/components/gallery/LightboxCaption.svelte +25 -0
  27. package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
  28. package/dist/components/gallery/ZoomableImage.svelte +163 -0
  29. package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
  30. package/dist/components/ui/Accordion.svelte +74 -0
  31. package/dist/components/ui/Accordion.svelte.d.ts +42 -0
  32. package/dist/components/ui/Badge.svelte +48 -0
  33. package/dist/components/ui/Badge.svelte.d.ts +26 -0
  34. package/dist/components/ui/Button.svelte +74 -0
  35. package/dist/components/ui/Button.svelte.d.ts +34 -0
  36. package/dist/components/ui/Card.svelte +102 -0
  37. package/dist/components/ui/Card.svelte.d.ts +46 -0
  38. package/dist/components/ui/Dialog.svelte +91 -0
  39. package/dist/components/ui/Dialog.svelte.d.ts +43 -0
  40. package/dist/components/ui/Input.svelte +81 -0
  41. package/dist/components/ui/Input.svelte.d.ts +35 -0
  42. package/dist/components/ui/Select.svelte +69 -0
  43. package/dist/components/ui/Select.svelte.d.ts +36 -0
  44. package/dist/components/ui/Sheet.svelte +98 -0
  45. package/dist/components/ui/Sheet.svelte.d.ts +45 -0
  46. package/dist/components/ui/Skeleton.svelte +31 -0
  47. package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
  48. package/dist/components/ui/Table.svelte +59 -0
  49. package/dist/components/ui/Table.svelte.d.ts +44 -0
  50. package/dist/components/ui/Tabs.svelte +76 -0
  51. package/dist/components/ui/Tabs.svelte.d.ts +41 -0
  52. package/dist/components/ui/Textarea.svelte +81 -0
  53. package/dist/components/ui/Textarea.svelte.d.ts +35 -0
  54. package/dist/components/ui/Toast.svelte +18 -0
  55. package/dist/components/ui/Toast.svelte.d.ts +7 -0
  56. package/dist/components/ui/accordion/accordion-content.svelte +24 -0
  57. package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
  58. package/dist/components/ui/accordion/accordion-item.svelte +12 -0
  59. package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
  60. package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
  61. package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
  62. package/dist/components/ui/accordion/index.d.ts +6 -0
  63. package/dist/components/ui/accordion/index.js +8 -0
  64. package/dist/components/ui/badge/badge.svelte +50 -0
  65. package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
  66. package/dist/components/ui/badge/index.d.ts +2 -0
  67. package/dist/components/ui/badge/index.js +2 -0
  68. package/dist/components/ui/button/button.svelte +82 -0
  69. package/dist/components/ui/button/button.svelte.d.ts +132 -0
  70. package/dist/components/ui/button/index.d.ts +2 -0
  71. package/dist/components/ui/button/index.js +4 -0
  72. package/dist/components/ui/card/card-content.svelte +16 -0
  73. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  74. package/dist/components/ui/card/card-description.svelte +16 -0
  75. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  76. package/dist/components/ui/card/card-footer.svelte +16 -0
  77. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  78. package/dist/components/ui/card/card-header.svelte +16 -0
  79. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  80. package/dist/components/ui/card/card-title.svelte +25 -0
  81. package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
  82. package/dist/components/ui/card/card.svelte +20 -0
  83. package/dist/components/ui/card/card.svelte.d.ts +5 -0
  84. package/dist/components/ui/card/index.d.ts +7 -0
  85. package/dist/components/ui/card/index.js +9 -0
  86. package/dist/components/ui/dialog/dialog-content.svelte +38 -0
  87. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
  88. package/dist/components/ui/dialog/dialog-description.svelte +16 -0
  89. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  90. package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
  91. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
  92. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  93. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  94. package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
  95. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  96. package/dist/components/ui/dialog/dialog-title.svelte +16 -0
  97. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  98. package/dist/components/ui/dialog/index.d.ts +12 -0
  99. package/dist/components/ui/dialog/index.js +14 -0
  100. package/dist/components/ui/index.d.ts +26 -0
  101. package/dist/components/ui/index.js +29 -0
  102. package/dist/components/ui/input/index.d.ts +2 -0
  103. package/dist/components/ui/input/index.js +4 -0
  104. package/dist/components/ui/input/input.svelte +46 -0
  105. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  106. package/dist/components/ui/select/index.d.ts +11 -0
  107. package/dist/components/ui/select/index.js +13 -0
  108. package/dist/components/ui/select/select-content.svelte +39 -0
  109. package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
  110. package/dist/components/ui/select/select-group-heading.svelte +16 -0
  111. package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
  112. package/dist/components/ui/select/select-item.svelte +37 -0
  113. package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
  114. package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
  115. package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
  116. package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
  117. package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
  118. package/dist/components/ui/select/select-separator.svelte +13 -0
  119. package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
  120. package/dist/components/ui/select/select-trigger.svelte +24 -0
  121. package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
  122. package/dist/components/ui/separator/index.d.ts +2 -0
  123. package/dist/components/ui/separator/index.js +4 -0
  124. package/dist/components/ui/separator/separator.svelte +22 -0
  125. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  126. package/dist/components/ui/sheet/index.d.ts +12 -0
  127. package/dist/components/ui/sheet/index.js +14 -0
  128. package/dist/components/ui/sheet/sheet-content.svelte +53 -0
  129. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
  130. package/dist/components/ui/sheet/sheet-description.svelte +16 -0
  131. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
  132. package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
  133. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  134. package/dist/components/ui/sheet/sheet-header.svelte +20 -0
  135. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
  136. package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
  137. package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
  138. package/dist/components/ui/sheet/sheet-title.svelte +16 -0
  139. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
  140. package/dist/components/ui/skeleton/index.d.ts +2 -0
  141. package/dist/components/ui/skeleton/index.js +4 -0
  142. package/dist/components/ui/skeleton/skeleton.svelte +17 -0
  143. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
  144. package/dist/components/ui/table/index.d.ts +9 -0
  145. package/dist/components/ui/table/index.js +11 -0
  146. package/dist/components/ui/table/table-body.svelte +16 -0
  147. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  148. package/dist/components/ui/table/table-caption.svelte +16 -0
  149. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  150. package/dist/components/ui/table/table-cell.svelte +20 -0
  151. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  152. package/dist/components/ui/table/table-footer.svelte +16 -0
  153. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  154. package/dist/components/ui/table/table-head.svelte +23 -0
  155. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  156. package/dist/components/ui/table/table-header.svelte +16 -0
  157. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  158. package/dist/components/ui/table/table-row.svelte +23 -0
  159. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  160. package/dist/components/ui/table/table.svelte +18 -0
  161. package/dist/components/ui/table/table.svelte.d.ts +5 -0
  162. package/dist/components/ui/tabs/index.d.ts +6 -0
  163. package/dist/components/ui/tabs/index.js +8 -0
  164. package/dist/components/ui/tabs/tabs-content.svelte +19 -0
  165. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  166. package/dist/components/ui/tabs/tabs-list.svelte +19 -0
  167. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  168. package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
  169. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  170. package/dist/components/ui/textarea/index.d.ts +2 -0
  171. package/dist/components/ui/textarea/index.js +4 -0
  172. package/dist/components/ui/textarea/textarea.svelte +24 -0
  173. package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
  174. package/dist/components/ui/toast.d.ts +86 -0
  175. package/dist/components/ui/toast.js +99 -0
  176. package/dist/db/schema.sql +238 -0
  177. package/dist/index.d.ts +14 -0
  178. package/dist/index.js +20 -0
  179. package/dist/payments/index.d.ts +33 -0
  180. package/dist/payments/index.js +47 -0
  181. package/dist/payments/shop.d.ts +165 -0
  182. package/dist/payments/shop.js +588 -0
  183. package/dist/payments/stripe/client.d.ts +231 -0
  184. package/dist/payments/stripe/client.js +198 -0
  185. package/dist/payments/stripe/index.d.ts +18 -0
  186. package/dist/payments/stripe/index.js +17 -0
  187. package/dist/payments/stripe/provider.d.ts +50 -0
  188. package/dist/payments/stripe/provider.js +530 -0
  189. package/dist/payments/types.d.ts +355 -0
  190. package/dist/payments/types.js +7 -0
  191. package/dist/server/logger.d.ts +53 -0
  192. package/dist/server/logger.js +252 -0
  193. package/dist/styles/content.css +514 -0
  194. package/dist/styles/tokens.css +175 -0
  195. package/dist/utils/api.d.ts +20 -0
  196. package/dist/utils/api.js +109 -0
  197. package/dist/utils/cn.d.ts +15 -0
  198. package/dist/utils/cn.js +18 -0
  199. package/dist/utils/csrf.d.ts +22 -0
  200. package/dist/utils/csrf.js +72 -0
  201. package/dist/utils/debounce.d.ts +7 -0
  202. package/dist/utils/debounce.js +14 -0
  203. package/dist/utils/gallery.d.ts +66 -0
  204. package/dist/utils/gallery.js +181 -0
  205. package/dist/utils/gutter.d.ts +54 -0
  206. package/dist/utils/gutter.js +169 -0
  207. package/dist/utils/imageProcessor.d.ts +58 -0
  208. package/dist/utils/imageProcessor.js +205 -0
  209. package/dist/utils/json.d.ts +17 -0
  210. package/dist/utils/json.js +26 -0
  211. package/dist/utils/markdown.d.ts +101 -0
  212. package/dist/utils/markdown.js +947 -0
  213. package/dist/utils/sanitize.d.ts +25 -0
  214. package/dist/utils/sanitize.js +127 -0
  215. package/dist/utils/validation.d.ts +46 -0
  216. package/dist/utils/validation.js +169 -0
  217. package/dist/utils.d.ts +5 -0
  218. package/dist/utils.js +5 -0
  219. package/package.json +129 -0
@@ -0,0 +1,681 @@
1
+ <script>
2
+ import ZoomableImage from './ZoomableImage.svelte';
3
+ import LightboxCaption from './LightboxCaption.svelte';
4
+
5
+ /**
6
+ * ImageGallery - Multi-image gallery with navigation
7
+ * Similar to The Verge's implementation
8
+ *
9
+ * @prop {Array} images - Array of image objects with url, alt, and optional caption
10
+ * @example
11
+ * <ImageGallery images={[
12
+ * { url: 'https://...', alt: 'Description', caption: 'Photo caption' }
13
+ * ]} />
14
+ */
15
+ let { images = [] } = $props();
16
+
17
+ let currentIndex = $state(0);
18
+ let touchStartX = $state(0);
19
+ let touchEndX = $state(0);
20
+ let galleryElement = $state();
21
+
22
+ // Lightbox state
23
+ let lightboxOpen = $state(false);
24
+
25
+ /**
26
+ * Safely get the current image, handling race conditions when images prop changes
27
+ * Returns a fallback object if the current index is invalid to prevent undefined access
28
+ */
29
+ let currentImage = $derived.by(() => {
30
+ if (!images || images.length === 0) {
31
+ return { url: '', alt: '', caption: '' };
32
+ }
33
+ const safeIndex = Math.max(0, Math.min(currentIndex, images.length - 1));
34
+ return images[safeIndex] || { url: '', alt: '', caption: '' };
35
+ });
36
+
37
+ // Navigation cooldown to prevent double-tap
38
+ let isNavigating = $state(false);
39
+ const NAVIGATION_COOLDOWN_MS = 300;
40
+
41
+ // Image loading states
42
+ let imageLoading = $state(true);
43
+ let imageError = $state(false);
44
+
45
+ function openLightbox() {
46
+ lightboxOpen = true;
47
+ }
48
+
49
+ function closeLightbox() {
50
+ lightboxOpen = false;
51
+ }
52
+
53
+ // Navigation functions with cooldown to prevent double-tap
54
+ function goToNext() {
55
+ if (isNavigating || currentIndex >= images.length - 1) return;
56
+
57
+ isNavigating = true;
58
+ imageLoading = true;
59
+ imageError = false;
60
+ currentIndex++;
61
+
62
+ setTimeout(() => {
63
+ isNavigating = false;
64
+ }, NAVIGATION_COOLDOWN_MS);
65
+ }
66
+
67
+ function goToPrevious() {
68
+ if (isNavigating || currentIndex <= 0) return;
69
+
70
+ isNavigating = true;
71
+ imageLoading = true;
72
+ imageError = false;
73
+ currentIndex--;
74
+
75
+ setTimeout(() => {
76
+ isNavigating = false;
77
+ }, NAVIGATION_COOLDOWN_MS);
78
+ }
79
+
80
+ function goToIndex(index) {
81
+ if (isNavigating || index < 0 || index >= images.length || index === currentIndex) return;
82
+
83
+ isNavigating = true;
84
+ imageLoading = true;
85
+ imageError = false;
86
+ currentIndex = index;
87
+
88
+ setTimeout(() => {
89
+ isNavigating = false;
90
+ }, NAVIGATION_COOLDOWN_MS);
91
+ }
92
+
93
+ // Image loading handlers
94
+ function handleImageLoad() {
95
+ imageLoading = false;
96
+ imageError = false;
97
+ }
98
+
99
+ function handleImageError() {
100
+ imageLoading = false;
101
+ imageError = true;
102
+ }
103
+
104
+ // Keyboard navigation
105
+ function handleKeydown(event) {
106
+ // Handle Escape to close lightbox
107
+ if (event.key === 'Escape' && lightboxOpen) {
108
+ closeLightbox();
109
+ return;
110
+ }
111
+
112
+ if (event.key === 'ArrowRight') {
113
+ goToNext();
114
+ } else if (event.key === 'ArrowLeft') {
115
+ goToPrevious();
116
+ }
117
+ }
118
+
119
+ // Touch/swipe support
120
+ function handleTouchStart(event) {
121
+ touchStartX = event.touches[0].clientX;
122
+ }
123
+
124
+ function handleTouchMove(event) {
125
+ touchEndX = event.touches[0].clientX;
126
+ }
127
+
128
+ function handleTouchEnd() {
129
+ const swipeThreshold = 50;
130
+ const diff = touchStartX - touchEndX;
131
+
132
+ if (Math.abs(diff) > swipeThreshold) {
133
+ if (diff > 0) {
134
+ goToNext();
135
+ } else {
136
+ goToPrevious();
137
+ }
138
+ }
139
+ touchStartX = 0;
140
+ touchEndX = 0;
141
+ }
142
+
143
+ // Get current image
144
+ $effect(() => {
145
+ // Reset index if images array changes and current index is out of bounds
146
+ if (currentIndex >= images.length && images.length > 0) {
147
+ currentIndex = images.length - 1;
148
+ }
149
+ });
150
+
151
+ // Reset loading state when images prop changes
152
+ $effect(() => {
153
+ if (images && images.length > 0) {
154
+ imageLoading = true;
155
+ imageError = false;
156
+ }
157
+ });
158
+ </script>
159
+
160
+ <svelte:window onkeydown={handleKeydown} />
161
+
162
+ {#if images && images.length > 0}
163
+ <div
164
+ class="gallery-container"
165
+ bind:this={galleryElement}
166
+ ontouchstart={handleTouchStart}
167
+ ontouchmove={handleTouchMove}
168
+ ontouchend={handleTouchEnd}
169
+ role="region"
170
+ aria-label="Image gallery"
171
+ tabindex="0"
172
+ >
173
+ <!-- Main image display -->
174
+ <div class="gallery-image-wrapper">
175
+ <button class="image-expand-button" onclick={openLightbox} aria-label="View full size">
176
+ {#if imageLoading}
177
+ <div class="image-loading">
178
+ <div class="loading-spinner"></div>
179
+ </div>
180
+ {/if}
181
+
182
+ {#if imageError}
183
+ <div class="image-error">
184
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
185
+ <circle cx="12" cy="12" r="10"></circle>
186
+ <line x1="12" y1="8" x2="12" y2="12"></line>
187
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
188
+ </svg>
189
+ <span>Failed to load image</span>
190
+ </div>
191
+ {/if}
192
+
193
+ <img
194
+ src={currentImage.url}
195
+ alt={currentImage.alt || `Image ${currentIndex + 1}`}
196
+ class="gallery-image"
197
+ class:hidden={imageError}
198
+ onload={handleImageLoad}
199
+ onerror={handleImageError}
200
+ />
201
+ </button>
202
+
203
+ <!-- Navigation arrows -->
204
+ {#if images.length > 1}
205
+ <button
206
+ class="nav-button nav-prev"
207
+ onclick={goToPrevious}
208
+ disabled={currentIndex === 0}
209
+ aria-label="Previous image"
210
+ >
211
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
212
+ <polyline points="15 18 9 12 15 6"></polyline>
213
+ </svg>
214
+ </button>
215
+
216
+ <button
217
+ class="nav-button nav-next"
218
+ onclick={goToNext}
219
+ disabled={currentIndex === images.length - 1}
220
+ aria-label="Next image"
221
+ >
222
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
223
+ <polyline points="9 18 15 12 9 6"></polyline>
224
+ </svg>
225
+ </button>
226
+ {/if}
227
+ </div>
228
+
229
+ <!-- Info panel (progress, counter, caption) -->
230
+ {#if images.length > 1 || currentImage.caption}
231
+ <div class="gallery-info">
232
+ {#if images.length > 1}
233
+ <!-- Progress dots -->
234
+ <div class="gallery-progress">
235
+ <div class="progress-dots">
236
+ {#each images as _, index (index)}
237
+ <button
238
+ class="progress-dot"
239
+ class:active={index === currentIndex}
240
+ onclick={() => goToIndex(index)}
241
+ aria-label={`Go to image ${index + 1}`}
242
+ ></button>
243
+ {/each}
244
+ </div>
245
+ </div>
246
+
247
+ <!-- Counter -->
248
+ <div class="gallery-counter">
249
+ {currentIndex + 1}/{images.length}
250
+ </div>
251
+ {/if}
252
+
253
+ <!-- Caption -->
254
+ {#if currentImage.caption}
255
+ <div class="gallery-caption">
256
+ {currentImage.caption}
257
+ </div>
258
+ {/if}
259
+ </div>
260
+ {/if}
261
+ </div>
262
+
263
+ <!-- Lightbox modal -->
264
+ {#if lightboxOpen}
265
+ <div
266
+ class="lightbox-backdrop"
267
+ onclick={(e) => e.target === e.currentTarget && closeLightbox()}
268
+ role="dialog"
269
+ aria-modal="true"
270
+ aria-label="Image viewer"
271
+ >
272
+ <button class="lightbox-close" onclick={closeLightbox} aria-label="Close">
273
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
274
+ <line x1="18" y1="6" x2="6" y2="18"></line>
275
+ <line x1="6" y1="6" x2="18" y2="18"></line>
276
+ </svg>
277
+ </button>
278
+
279
+ <div class="lightbox-content" onclick={(e) => e.target === e.currentTarget && closeLightbox()}>
280
+ <ZoomableImage
281
+ src={currentImage.url}
282
+ alt={currentImage.alt || `Image ${currentIndex + 1}`}
283
+ isActive={lightboxOpen}
284
+ class="lightbox-image"
285
+ />
286
+
287
+ <!-- Navigation arrows in lightbox -->
288
+ {#if images.length > 1}
289
+ <button
290
+ class="lightbox-nav lightbox-prev"
291
+ onclick={goToPrevious}
292
+ disabled={currentIndex === 0}
293
+ aria-label="Previous image"
294
+ >
295
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
296
+ <polyline points="15 18 9 12 15 6"></polyline>
297
+ </svg>
298
+ </button>
299
+
300
+ <button
301
+ class="lightbox-nav lightbox-next"
302
+ onclick={goToNext}
303
+ disabled={currentIndex === images.length - 1}
304
+ aria-label="Next image"
305
+ >
306
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
307
+ <polyline points="9 18 15 12 9 6"></polyline>
308
+ </svg>
309
+ </button>
310
+ {/if}
311
+ </div>
312
+
313
+ <!-- Caption in lightbox -->
314
+ <LightboxCaption caption={currentImage.caption} />
315
+
316
+ <!-- Thumbnail strip -->
317
+ {#if images.length > 1}
318
+ <div class="lightbox-thumbnails">
319
+ {#each images as image, index (index)}
320
+ <button
321
+ class="thumbnail-button"
322
+ class:active={index === currentIndex}
323
+ onclick={() => goToIndex(index)}
324
+ aria-label={`View image ${index + 1}`}
325
+ >
326
+ <img src={image.url} alt={image.alt || `Thumbnail ${index + 1}`} />
327
+ </button>
328
+ {/each}
329
+ </div>
330
+ {/if}
331
+ </div>
332
+ {/if}
333
+ {/if}
334
+
335
+ <style>
336
+ .gallery-container {
337
+ position: relative;
338
+ width: 100%;
339
+ margin: 1.5rem 0;
340
+ outline: none;
341
+ }
342
+ .gallery-container:focus {
343
+ outline: 2px solid #5865f2;
344
+ outline-offset: 4px;
345
+ border-radius: 8px;
346
+ }
347
+ .gallery-image-wrapper {
348
+ position: relative;
349
+ width: 100%;
350
+ background: #000;
351
+ border-radius: 8px;
352
+ overflow: hidden;
353
+ }
354
+ .gallery-image {
355
+ width: 100%;
356
+ height: auto;
357
+ display: block;
358
+ max-height: 70vh;
359
+ -o-object-fit: contain;
360
+ object-fit: contain;
361
+ }
362
+ .gallery-image.hidden {
363
+ visibility: hidden;
364
+ }
365
+ /* Loading spinner */
366
+ .image-loading {
367
+ position: absolute;
368
+ top: 50%;
369
+ left: 50%;
370
+ transform: translate(-50%, -50%);
371
+ z-index: 5;
372
+ }
373
+ .loading-spinner {
374
+ width: 40px;
375
+ height: 40px;
376
+ border: 3px solid rgba(255, 255, 255, 0.3);
377
+ border-top-color: #5865f2;
378
+ border-radius: 50%;
379
+ animation: spin 0.8s linear infinite;
380
+ }
381
+ @keyframes spin {
382
+ to {
383
+ transform: rotate(360deg);
384
+ }
385
+ }
386
+ /* Error state */
387
+ .image-error {
388
+ position: absolute;
389
+ top: 50%;
390
+ left: 50%;
391
+ transform: translate(-50%, -50%);
392
+ display: flex;
393
+ flex-direction: column;
394
+ align-items: center;
395
+ gap: 0.5rem;
396
+ color: #9ca3af;
397
+ z-index: 5;
398
+ }
399
+ .image-error svg {
400
+ width: 48px;
401
+ height: 48px;
402
+ }
403
+ .image-error span {
404
+ font-size: 0.875rem;
405
+ }
406
+ /* Navigation buttons */
407
+ .nav-button {
408
+ position: absolute;
409
+ top: 50%;
410
+ transform: translateY(-50%);
411
+ width: 48px;
412
+ height: 48px;
413
+ border-radius: 50%;
414
+ background: #5865f2;
415
+ border: none;
416
+ cursor: pointer;
417
+ display: flex;
418
+ align-items: center;
419
+ justify-content: center;
420
+ color: white;
421
+ transition: all 0.2s ease;
422
+ z-index: 10;
423
+ opacity: 0.9;
424
+ }
425
+ .nav-button:hover:not(:disabled) {
426
+ background: #4752c4;
427
+ transform: translateY(-50%) scale(1.05);
428
+ opacity: 1;
429
+ }
430
+ .nav-button:disabled {
431
+ opacity: 0.3;
432
+ cursor: not-allowed;
433
+ }
434
+ .nav-button svg {
435
+ width: 24px;
436
+ height: 24px;
437
+ }
438
+ .nav-prev {
439
+ left: 16px;
440
+ }
441
+ .nav-next {
442
+ right: 16px;
443
+ }
444
+ /* Info panel - unified background for progress, counter, caption */
445
+ .gallery-info {
446
+ background: #f9fafb;
447
+ border-radius: 0 0 8px 8px;
448
+ }
449
+ :global(.dark) .gallery-info {
450
+ background: #1f2937;
451
+ }
452
+ /* Progress indicators */
453
+ .gallery-progress {
454
+ display: flex;
455
+ justify-content: center;
456
+ padding: 12px 0 8px;
457
+ }
458
+ .progress-dots {
459
+ display: flex;
460
+ gap: 6px;
461
+ }
462
+ .progress-dot {
463
+ width: 12px;
464
+ height: 12px;
465
+ border-radius: 50%;
466
+ background: #d1d5db;
467
+ border: none;
468
+ padding: 0;
469
+ cursor: pointer;
470
+ transition: all 0.2s ease;
471
+ }
472
+ .progress-dot:hover {
473
+ background: #9ca3af;
474
+ transform: scale(1.2);
475
+ }
476
+ .progress-dot.active {
477
+ background: #5865f2;
478
+ width: 28px;
479
+ border-radius: 6px;
480
+ box-shadow: 0 2px 4px rgba(88, 101, 242, 0.3);
481
+ }
482
+ :global(.dark) .progress-dot {
483
+ background: #4b5563;
484
+ }
485
+ :global(.dark) .progress-dot:hover {
486
+ background: #6b7280;
487
+ }
488
+ :global(.dark) .progress-dot.active {
489
+ background: #5865f2;
490
+ }
491
+ /* Counter */
492
+ .gallery-counter {
493
+ text-align: center;
494
+ font-size: 0.875rem;
495
+ color: #6b7280;
496
+ padding-bottom: 8px;
497
+ }
498
+ :global(.dark) .gallery-counter {
499
+ color: #9ca3af;
500
+ }
501
+ /* Caption */
502
+ .gallery-caption {
503
+ padding: 12px 16px;
504
+ font-size: 0.9rem;
505
+ color: #374151;
506
+ line-height: 1.5;
507
+ font-style: italic;
508
+ }
509
+ :global(.dark) .gallery-caption {
510
+ color: #d1d5db;
511
+ }
512
+ /* Image expand button */
513
+ .image-expand-button {
514
+ padding: 0;
515
+ border: none;
516
+ background: none;
517
+ cursor: pointer;
518
+ display: block;
519
+ width: 100%;
520
+ }
521
+ .image-expand-button:hover .gallery-image {
522
+ opacity: 0.95;
523
+ }
524
+ /* Lightbox styles */
525
+ .lightbox-backdrop {
526
+ position: fixed;
527
+ top: 0;
528
+ left: 0;
529
+ right: 0;
530
+ bottom: 0;
531
+ background: rgba(0, 0, 0, 0.95);
532
+ display: flex;
533
+ flex-direction: column;
534
+ align-items: center;
535
+ justify-content: center;
536
+ z-index: 9999;
537
+ padding: 1rem;
538
+ }
539
+ .lightbox-close {
540
+ position: absolute;
541
+ top: 1rem;
542
+ right: 1rem;
543
+ width: 48px;
544
+ height: 48px;
545
+ border-radius: 50%;
546
+ background: rgba(255, 255, 255, 0.1);
547
+ border: none;
548
+ cursor: pointer;
549
+ display: flex;
550
+ align-items: center;
551
+ justify-content: center;
552
+ color: white;
553
+ transition: background 0.2s;
554
+ z-index: 10;
555
+ }
556
+ .lightbox-close:hover {
557
+ background: rgba(255, 255, 255, 0.2);
558
+ }
559
+ .lightbox-close svg {
560
+ width: 24px;
561
+ height: 24px;
562
+ }
563
+ .lightbox-content {
564
+ position: relative;
565
+ display: flex;
566
+ align-items: center;
567
+ justify-content: center;
568
+ flex: 1;
569
+ width: 100%;
570
+ max-height: calc(100vh - 140px);
571
+ overflow: auto;
572
+ }
573
+ :global(.lightbox-content .lightbox-image) {
574
+ max-width: 90vw;
575
+ max-height: calc(100vh - 140px);
576
+ -o-object-fit: contain;
577
+ object-fit: contain;
578
+ border-radius: 4px;
579
+ }
580
+ .lightbox-nav {
581
+ position: absolute;
582
+ top: 50%;
583
+ transform: translateY(-50%);
584
+ width: 48px;
585
+ height: 48px;
586
+ border-radius: 50%;
587
+ background: rgba(255, 255, 255, 0.1);
588
+ border: none;
589
+ cursor: pointer;
590
+ display: flex;
591
+ align-items: center;
592
+ justify-content: center;
593
+ color: white;
594
+ transition: background 0.2s;
595
+ }
596
+ .lightbox-nav:hover:not(:disabled) {
597
+ background: rgba(255, 255, 255, 0.2);
598
+ }
599
+ .lightbox-nav:disabled {
600
+ opacity: 0.3;
601
+ cursor: not-allowed;
602
+ }
603
+ .lightbox-nav svg {
604
+ width: 24px;
605
+ height: 24px;
606
+ }
607
+ .lightbox-prev {
608
+ left: 1rem;
609
+ }
610
+ .lightbox-next {
611
+ right: 1rem;
612
+ }
613
+ /* Thumbnail strip */
614
+ .lightbox-thumbnails {
615
+ display: flex;
616
+ gap: 0.5rem;
617
+ padding: 1rem;
618
+ overflow-x: auto;
619
+ max-width: 100%;
620
+ justify-content: center;
621
+ }
622
+ .thumbnail-button {
623
+ flex-shrink: 0;
624
+ width: 60px;
625
+ height: 60px;
626
+ padding: 0;
627
+ border: 2px solid transparent;
628
+ border-radius: 6px;
629
+ overflow: hidden;
630
+ cursor: pointer;
631
+ background: none;
632
+ transition: border-color 0.2s, opacity 0.2s;
633
+ opacity: 0.6;
634
+ }
635
+ .thumbnail-button:hover {
636
+ opacity: 0.9;
637
+ }
638
+ .thumbnail-button.active {
639
+ border-color: white;
640
+ opacity: 1;
641
+ }
642
+ .thumbnail-button img {
643
+ width: 100%;
644
+ height: 100%;
645
+ -o-object-fit: cover;
646
+ object-fit: cover;
647
+ }
648
+ /* Responsive */
649
+ @media (max-width: 640px) {
650
+ .nav-button {
651
+ width: 40px;
652
+ height: 40px;
653
+ }
654
+ .nav-button svg {
655
+ width: 20px;
656
+ height: 20px;
657
+ }
658
+ .nav-prev {
659
+ left: 8px;
660
+ }
661
+ .nav-next {
662
+ right: 8px;
663
+ }
664
+ .gallery-caption {
665
+ font-size: 0.85rem;
666
+ padding: 10px 12px;
667
+ }
668
+ .lightbox-nav {
669
+ width: 40px;
670
+ height: 40px;
671
+ }
672
+ .lightbox-nav svg {
673
+ width: 20px;
674
+ height: 20px;
675
+ }
676
+ .thumbnail-button {
677
+ width: 50px;
678
+ height: 50px;
679
+ }
680
+ }
681
+ </style>
@@ -0,0 +1,11 @@
1
+ export default ImageGallery;
2
+ type ImageGallery = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const ImageGallery: import("svelte").Component<{
7
+ images?: any[];
8
+ }, {}, "">;
9
+ type $$ComponentProps = {
10
+ images?: any[];
11
+ };