@easy-web/content-blocks 1.0.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 (54) hide show
  1. package/README.md +332 -0
  2. package/dist/components/__tests__/UniversalMedia.test.d.ts +2 -0
  3. package/dist/components/__tests__/UniversalMedia.test.d.ts.map +1 -0
  4. package/dist/components/__tests__/UniversalMedia.test.js +46 -0
  5. package/dist/components/__tests__/UniversalMedia.test.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/schemas/__tests__/notFound.test.d.ts +2 -0
  11. package/dist/schemas/__tests__/notFound.test.d.ts.map +1 -0
  12. package/dist/schemas/__tests__/notFound.test.js +29 -0
  13. package/dist/schemas/__tests__/notFound.test.js.map +1 -0
  14. package/dist/schemas/galleries.d.ts +319 -0
  15. package/dist/schemas/galleries.d.ts.map +1 -0
  16. package/dist/schemas/galleries.js +136 -0
  17. package/dist/schemas/galleries.js.map +1 -0
  18. package/dist/schemas/notFound.d.ts +35 -0
  19. package/dist/schemas/notFound.d.ts.map +1 -0
  20. package/dist/schemas/notFound.js +30 -0
  21. package/dist/schemas/notFound.js.map +1 -0
  22. package/package.json +51 -0
  23. package/src/components/.gitkeep +0 -0
  24. package/src/components/BlogPostCard.astro +79 -0
  25. package/src/components/Card.astro +70 -0
  26. package/src/components/CardGrid.astro +29 -0
  27. package/src/components/ContactSection.astro +73 -0
  28. package/src/components/CtaSection.astro +82 -0
  29. package/src/components/DraftBanner.astro +33 -0
  30. package/src/components/Footer.astro +81 -0
  31. package/src/components/GalleryCarousel.astro +296 -0
  32. package/src/components/GalleryFeatureHighlight.astro +153 -0
  33. package/src/components/GalleryHeroSlider.astro +362 -0
  34. package/src/components/GalleryImageGrid.astro +143 -0
  35. package/src/components/GalleryLightboxGrid.astro +353 -0
  36. package/src/components/GalleryMasonryGrid.astro +127 -0
  37. package/src/components/GallerySection.astro +69 -0
  38. package/src/components/Header.astro +210 -0
  39. package/src/components/HeaderCentered.astro +231 -0
  40. package/src/components/HeaderFlyout.astro +373 -0
  41. package/src/components/HeaderHideOnScroll.astro +237 -0
  42. package/src/components/Hero.astro +78 -0
  43. package/src/components/LanguageNotice.astro +32 -0
  44. package/src/components/LanguageSwitch.astro +67 -0
  45. package/src/components/LegalLayout.astro +53 -0
  46. package/src/components/NotFound.astro +124 -0
  47. package/src/components/Prose.astro +156 -0
  48. package/src/components/Section.astro +37 -0
  49. package/src/components/ThemeToggle.astro +82 -0
  50. package/src/components/UniversalMedia.astro +102 -0
  51. package/src/components/__tests__/UniversalMedia.test.ts +65 -0
  52. package/src/schemas/__tests__/notFound.test.ts +32 -0
  53. package/src/schemas/galleries.ts +152 -0
  54. package/src/schemas/notFound.ts +33 -0
@@ -0,0 +1,153 @@
1
+ ---
2
+ import type { GalleryFeatureHighlightData } from '../schemas/galleries';
3
+
4
+ type Props = Omit<GalleryFeatureHighlightData, 'kind' | 'locale' | 'translationKey'>;
5
+
6
+ const {
7
+ title,
8
+ gap = 'lg',
9
+ items,
10
+ } = Astro.props;
11
+
12
+ const sectionClass = ['ew-feature-highlight', `ew-feature-highlight--gap-${gap}`].join(' ');
13
+ ---
14
+
15
+ {items.length > 0 && (
16
+ <section class="ew-feature-highlight-wrap">
17
+ <div class="ew-feature-highlight-wrap__inner">
18
+ {title && <h2 class="ew-feature-highlight-wrap__title">{title}</h2>}
19
+ <div class={sectionClass}>
20
+ {items.map((item, i) => {
21
+ const position = item.imagePosition ?? (i % 2 === 0 ? 'right' : 'left');
22
+ const rowClass = `ew-feature-highlight__row ew-feature-highlight__row--image-${position}`;
23
+ return (
24
+ <article class={rowClass}>
25
+ <div class="ew-feature-highlight__media">
26
+ <img
27
+ src={item.src}
28
+ alt={item.alt}
29
+ class="ew-feature-highlight__img"
30
+ loading="lazy"
31
+ />
32
+ </div>
33
+ <div class="ew-feature-highlight__content">
34
+ <h3 class="ew-feature-highlight__title">{item.title}</h3>
35
+ <p class="ew-feature-highlight__description">{item.description}</p>
36
+ {item.href && item.cta && (
37
+ <a href={item.href} class="ew-feature-highlight__cta">{item.cta}</a>
38
+ )}
39
+ </div>
40
+ </article>
41
+ );
42
+ })}
43
+ </div>
44
+ </div>
45
+ </section>
46
+ )}
47
+
48
+ <style>
49
+ .ew-feature-highlight-wrap {
50
+ padding: var(--ew-space-8) var(--ew-space-5);
51
+ }
52
+ .ew-feature-highlight-wrap__inner {
53
+ max-width: 72rem;
54
+ margin: 0 auto;
55
+ }
56
+ .ew-feature-highlight-wrap__title {
57
+ font-family: var(--ew-font-sans);
58
+ font-size: var(--ew-text-xl);
59
+ font-weight: 700;
60
+ color: var(--ew-on-surface);
61
+ margin: 0 0 var(--ew-space-5);
62
+ line-height: var(--ew-leading-tight);
63
+ }
64
+
65
+ .ew-feature-highlight {
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+ .ew-feature-highlight--gap-sm { gap: var(--ew-space-5); }
70
+ .ew-feature-highlight--gap-md { gap: var(--ew-space-7); }
71
+ .ew-feature-highlight--gap-lg { gap: var(--ew-space-8); }
72
+
73
+ .ew-feature-highlight__row {
74
+ display: grid;
75
+ grid-template-columns: 1fr 1fr;
76
+ align-items: center;
77
+ gap: var(--ew-space-7);
78
+ }
79
+
80
+ .ew-feature-highlight__row--image-left .ew-feature-highlight__media { order: 1; }
81
+ .ew-feature-highlight__row--image-left .ew-feature-highlight__content { order: 2; }
82
+ .ew-feature-highlight__row--image-right .ew-feature-highlight__media { order: 2; }
83
+ .ew-feature-highlight__row--image-right .ew-feature-highlight__content { order: 1; }
84
+
85
+ @media (max-width: 768px) {
86
+ .ew-feature-highlight__row {
87
+ grid-template-columns: 1fr;
88
+ gap: var(--ew-space-4);
89
+ }
90
+ .ew-feature-highlight__row .ew-feature-highlight__media { order: 1; }
91
+ .ew-feature-highlight__row .ew-feature-highlight__content { order: 2; }
92
+ }
93
+
94
+ .ew-feature-highlight__media {
95
+ overflow: hidden;
96
+ border-radius: var(--ew-radius-md);
97
+ }
98
+
99
+ .ew-feature-highlight__img {
100
+ width: 100%;
101
+ height: auto;
102
+ display: block;
103
+ aspect-ratio: 4 / 3;
104
+ object-fit: cover;
105
+ }
106
+
107
+ .ew-feature-highlight__content {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: var(--ew-space-3);
111
+ }
112
+
113
+ .ew-feature-highlight__title {
114
+ font-family: var(--ew-font-sans);
115
+ font-size: var(--ew-text-xl);
116
+ font-weight: 700;
117
+ color: var(--ew-on-surface);
118
+ margin: 0;
119
+ line-height: var(--ew-leading-tight);
120
+ }
121
+
122
+ .ew-feature-highlight__description {
123
+ font-family: var(--ew-font-sans);
124
+ font-size: var(--ew-text-base);
125
+ color: var(--ew-on-surface);
126
+ margin: 0;
127
+ line-height: var(--ew-leading-loose);
128
+ }
129
+
130
+ .ew-feature-highlight__cta {
131
+ align-self: flex-start;
132
+ display: inline-block;
133
+ background: var(--ew-primary);
134
+ color: var(--ew-on-primary);
135
+ padding: var(--ew-space-2) var(--ew-space-5);
136
+ border-radius: var(--ew-radius-md);
137
+ text-decoration: none;
138
+ font-family: var(--ew-font-sans);
139
+ font-weight: 600;
140
+ font-size: var(--ew-text-base);
141
+ margin-top: var(--ew-space-2);
142
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
143
+ }
144
+ .ew-feature-highlight__cta:hover,
145
+ .ew-feature-highlight__cta:focus-visible {
146
+ transform: translateY(-1px);
147
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
148
+ }
149
+ .ew-feature-highlight__cta:focus-visible {
150
+ outline: 2px solid var(--ew-primary);
151
+ outline-offset: 2px;
152
+ }
153
+ </style>
@@ -0,0 +1,362 @@
1
+ ---
2
+ import type { GalleryHeroSliderData } from '../schemas/galleries';
3
+
4
+ type Props = Omit<GalleryHeroSliderData, 'kind' | 'locale' | 'translationKey'>;
5
+
6
+ const {
7
+ title,
8
+ autoplay = true,
9
+ interval = 5000,
10
+ height = 'lg',
11
+ slides,
12
+ } = Astro.props;
13
+
14
+ const sliderClass = `ew-hero-slider ew-hero-slider--height-${height}`;
15
+ ---
16
+
17
+ {slides.length > 0 && (
18
+ <section
19
+ class={sliderClass}
20
+ data-autoplay={String(autoplay)}
21
+ data-interval={String(interval)}
22
+ aria-roledescription="carousel"
23
+ aria-label={title ?? 'Hero slider'}
24
+ tabindex="0"
25
+ >
26
+ {title && <h2 class="ew-hero-slider__heading">{title}</h2>}
27
+
28
+ <div class="ew-hero-slider__viewport">
29
+ {slides.map((slide, i) => (
30
+ <article
31
+ class="ew-hero-slider__slide"
32
+ data-active={i === 0 ? 'true' : 'false'}
33
+ aria-hidden={i !== 0}
34
+ aria-roledescription="slide"
35
+ aria-label={`${i + 1} / ${slides.length}`}
36
+ >
37
+ <img
38
+ src={slide.src}
39
+ alt={slide.alt}
40
+ class="ew-hero-slider__img"
41
+ loading={i === 0 ? 'eager' : 'lazy'}
42
+ />
43
+ <div class="ew-hero-slider__overlay">
44
+ <div class="ew-hero-slider__content">
45
+ <h3 class="ew-hero-slider__title">{slide.title}</h3>
46
+ {slide.subtitle && (
47
+ <p class="ew-hero-slider__subtitle">{slide.subtitle}</p>
48
+ )}
49
+ {slide.href && slide.cta && (
50
+ <a href={slide.href} class="ew-hero-slider__cta">{slide.cta}</a>
51
+ )}
52
+ </div>
53
+ </div>
54
+ </article>
55
+ ))}
56
+ </div>
57
+
58
+ {slides.length > 1 && (
59
+ <>
60
+ <button
61
+ type="button"
62
+ class="ew-hero-slider__arrow ew-hero-slider__arrow--prev"
63
+ aria-label="Previous slide"
64
+ data-direction="prev"
65
+ >
66
+ <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
67
+ <polyline points="15 18 9 12 15 6"></polyline>
68
+ </svg>
69
+ </button>
70
+ <button
71
+ type="button"
72
+ class="ew-hero-slider__arrow ew-hero-slider__arrow--next"
73
+ aria-label="Next slide"
74
+ data-direction="next"
75
+ >
76
+ <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
77
+ <polyline points="9 18 15 12 9 6"></polyline>
78
+ </svg>
79
+ </button>
80
+
81
+ <div class="ew-hero-slider__dots" role="tablist" aria-label="Slide navigation">
82
+ {slides.map((_, i) => (
83
+ <button
84
+ type="button"
85
+ class="ew-hero-slider__dot"
86
+ data-active={i === 0 ? 'true' : 'false'}
87
+ data-index={i}
88
+ role="tab"
89
+ aria-label={`Go to slide ${i + 1}`}
90
+ aria-selected={i === 0 ? 'true' : 'false'}
91
+ ></button>
92
+ ))}
93
+ </div>
94
+ </>
95
+ )}
96
+ </section>
97
+ )}
98
+
99
+ <script>
100
+ type Slider = HTMLElement;
101
+
102
+ const initSlider = (slider: Slider) => {
103
+ const slides = Array.from(slider.querySelectorAll<HTMLElement>('.ew-hero-slider__slide'));
104
+ const dots = Array.from(slider.querySelectorAll<HTMLElement>('.ew-hero-slider__dot'));
105
+ if (slides.length <= 1) return;
106
+
107
+ let current = 0;
108
+ let timer: ReturnType<typeof setInterval> | null = null;
109
+ const intervalMs = Number(slider.dataset.interval) || 5000;
110
+ const autoplay = slider.dataset.autoplay === 'true';
111
+ const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
112
+
113
+ const go = (idx: number) => {
114
+ const next = ((idx % slides.length) + slides.length) % slides.length;
115
+ if (next === current) return;
116
+ slides[current].dataset.active = 'false';
117
+ slides[current].setAttribute('aria-hidden', 'true');
118
+ slides[next].dataset.active = 'true';
119
+ slides[next].setAttribute('aria-hidden', 'false');
120
+ if (dots[current]) {
121
+ dots[current].dataset.active = 'false';
122
+ dots[current].setAttribute('aria-selected', 'false');
123
+ }
124
+ if (dots[next]) {
125
+ dots[next].dataset.active = 'true';
126
+ dots[next].setAttribute('aria-selected', 'true');
127
+ }
128
+ current = next;
129
+ };
130
+
131
+ const stop = () => {
132
+ if (timer) {
133
+ clearInterval(timer);
134
+ timer = null;
135
+ }
136
+ };
137
+ const start = () => {
138
+ stop();
139
+ if (autoplay && !reduced) {
140
+ timer = setInterval(() => go(current + 1), intervalMs);
141
+ }
142
+ };
143
+
144
+ slider.querySelector<HTMLElement>('.ew-hero-slider__arrow--prev')
145
+ ?.addEventListener('click', () => { go(current - 1); start(); });
146
+ slider.querySelector<HTMLElement>('.ew-hero-slider__arrow--next')
147
+ ?.addEventListener('click', () => { go(current + 1); start(); });
148
+ dots.forEach((dot, i) => {
149
+ dot.addEventListener('click', () => { go(i); start(); });
150
+ });
151
+
152
+ slider.addEventListener('mouseenter', stop);
153
+ slider.addEventListener('mouseleave', start);
154
+ slider.addEventListener('focusin', stop);
155
+ slider.addEventListener('focusout', start);
156
+
157
+ slider.addEventListener('keydown', (e: KeyboardEvent) => {
158
+ if (e.key === 'ArrowLeft') {
159
+ e.preventDefault();
160
+ go(current - 1);
161
+ start();
162
+ } else if (e.key === 'ArrowRight') {
163
+ e.preventDefault();
164
+ go(current + 1);
165
+ start();
166
+ }
167
+ });
168
+
169
+ start();
170
+ };
171
+
172
+ document.querySelectorAll<HTMLElement>('.ew-hero-slider').forEach(initSlider);
173
+ </script>
174
+
175
+ <style>
176
+ .ew-hero-slider {
177
+ position: relative;
178
+ overflow: hidden;
179
+ background: var(--ew-surface-muted);
180
+ outline: none;
181
+ }
182
+ .ew-hero-slider:focus-visible {
183
+ outline: 3px solid var(--ew-primary);
184
+ outline-offset: -3px;
185
+ }
186
+
187
+ .ew-hero-slider--height-sm .ew-hero-slider__viewport { height: 20rem; }
188
+ .ew-hero-slider--height-md .ew-hero-slider__viewport { height: 30rem; }
189
+ .ew-hero-slider--height-lg .ew-hero-slider__viewport { height: 40rem; }
190
+ .ew-hero-slider--height-full .ew-hero-slider__viewport { height: 100vh; }
191
+
192
+ @media (max-width: 768px) {
193
+ .ew-hero-slider--height-md .ew-hero-slider__viewport { height: 24rem; }
194
+ .ew-hero-slider--height-lg .ew-hero-slider__viewport { height: 28rem; }
195
+ }
196
+
197
+ .ew-hero-slider__heading {
198
+ font-family: var(--ew-font-sans);
199
+ font-size: var(--ew-text-xl);
200
+ font-weight: 700;
201
+ color: var(--ew-on-surface);
202
+ margin: 0 0 var(--ew-space-4);
203
+ padding: 0 var(--ew-space-5);
204
+ line-height: var(--ew-leading-tight);
205
+ }
206
+
207
+ .ew-hero-slider__viewport {
208
+ position: relative;
209
+ width: 100%;
210
+ }
211
+
212
+ .ew-hero-slider__slide {
213
+ position: absolute;
214
+ inset: 0;
215
+ margin: 0;
216
+ opacity: 0;
217
+ transition: opacity 0.6s ease;
218
+ pointer-events: none;
219
+ }
220
+ .ew-hero-slider__slide[data-active='true'] {
221
+ opacity: 1;
222
+ pointer-events: auto;
223
+ }
224
+ @media (prefers-reduced-motion: reduce) {
225
+ .ew-hero-slider__slide {
226
+ transition: none;
227
+ }
228
+ }
229
+
230
+ .ew-hero-slider__img {
231
+ width: 100%;
232
+ height: 100%;
233
+ object-fit: cover;
234
+ display: block;
235
+ }
236
+
237
+ .ew-hero-slider__overlay {
238
+ position: absolute;
239
+ inset: 0;
240
+ display: flex;
241
+ align-items: flex-end;
242
+ background: linear-gradient(
243
+ to top,
244
+ rgba(0, 0, 0, 0.6) 0%,
245
+ rgba(0, 0, 0, 0.2) 50%,
246
+ transparent 100%
247
+ );
248
+ }
249
+
250
+ .ew-hero-slider__content {
251
+ max-width: 48rem;
252
+ padding: var(--ew-space-8) var(--ew-space-6);
253
+ color: #fff;
254
+ }
255
+ @media (min-width: 768px) {
256
+ .ew-hero-slider__content {
257
+ padding: var(--ew-space-10) var(--ew-space-8);
258
+ }
259
+ }
260
+
261
+ .ew-hero-slider__title {
262
+ font-family: var(--ew-font-sans);
263
+ font-size: var(--ew-text-3xl);
264
+ font-weight: 700;
265
+ margin: 0 0 var(--ew-space-3);
266
+ line-height: var(--ew-leading-tight);
267
+ }
268
+ @media (min-width: 768px) {
269
+ .ew-hero-slider__title { font-size: var(--ew-text-3xl); }
270
+ }
271
+
272
+ .ew-hero-slider__subtitle {
273
+ font-family: var(--ew-font-sans);
274
+ font-size: var(--ew-text-lg);
275
+ margin: 0 0 var(--ew-space-5);
276
+ line-height: var(--ew-leading-normal);
277
+ opacity: 0.95;
278
+ }
279
+
280
+ .ew-hero-slider__cta {
281
+ display: inline-block;
282
+ background: var(--ew-primary);
283
+ color: var(--ew-on-primary);
284
+ padding: var(--ew-space-3) var(--ew-space-6);
285
+ border-radius: var(--ew-radius-md);
286
+ text-decoration: none;
287
+ font-family: var(--ew-font-sans);
288
+ font-weight: 600;
289
+ font-size: var(--ew-text-base);
290
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
291
+ }
292
+ .ew-hero-slider__cta:hover,
293
+ .ew-hero-slider__cta:focus-visible {
294
+ transform: translateY(-1px);
295
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
296
+ }
297
+ .ew-hero-slider__cta:focus-visible {
298
+ outline: 2px solid #fff;
299
+ outline-offset: 2px;
300
+ }
301
+
302
+ .ew-hero-slider__arrow {
303
+ position: absolute;
304
+ top: 50%;
305
+ transform: translateY(-50%);
306
+ width: 3rem;
307
+ height: 3rem;
308
+ border-radius: var(--ew-radius-full);
309
+ border: none;
310
+ background: rgba(0, 0, 0, 0.5);
311
+ color: #fff;
312
+ cursor: pointer;
313
+ display: inline-flex;
314
+ align-items: center;
315
+ justify-content: center;
316
+ transition: background 0.15s ease;
317
+ z-index: 2;
318
+ }
319
+ .ew-hero-slider__arrow:hover,
320
+ .ew-hero-slider__arrow:focus-visible {
321
+ background: rgba(0, 0, 0, 0.75);
322
+ }
323
+ .ew-hero-slider__arrow:focus-visible {
324
+ outline: 2px solid #fff;
325
+ outline-offset: 2px;
326
+ }
327
+ .ew-hero-slider__arrow--prev { left: var(--ew-space-4); }
328
+ .ew-hero-slider__arrow--next { right: var(--ew-space-4); }
329
+
330
+ .ew-hero-slider__dots {
331
+ position: absolute;
332
+ bottom: var(--ew-space-4);
333
+ left: 0;
334
+ right: 0;
335
+ display: flex;
336
+ justify-content: center;
337
+ gap: var(--ew-space-2);
338
+ z-index: 2;
339
+ }
340
+ .ew-hero-slider__dot {
341
+ width: 0.625rem;
342
+ height: 0.625rem;
343
+ border-radius: var(--ew-radius-full);
344
+ border: none;
345
+ background: rgba(255, 255, 255, 0.5);
346
+ cursor: pointer;
347
+ padding: 0;
348
+ transition: background 0.15s ease, transform 0.15s ease;
349
+ }
350
+ .ew-hero-slider__dot[data-active='true'] {
351
+ background: #fff;
352
+ transform: scale(1.25);
353
+ }
354
+ .ew-hero-slider__dot:hover,
355
+ .ew-hero-slider__dot:focus-visible {
356
+ background: rgba(255, 255, 255, 0.85);
357
+ }
358
+ .ew-hero-slider__dot:focus-visible {
359
+ outline: 2px solid #fff;
360
+ outline-offset: 2px;
361
+ }
362
+ </style>
@@ -0,0 +1,143 @@
1
+ ---
2
+ import type { GalleryImageGridData } from '../schemas/galleries';
3
+
4
+ type Props = Omit<GalleryImageGridData, 'kind' | 'locale' | 'translationKey'>;
5
+
6
+ const {
7
+ title,
8
+ columns = 3,
9
+ gap = 'md',
10
+ aspectRatio = 'square',
11
+ items,
12
+ } = Astro.props;
13
+
14
+ const gridClass = [
15
+ 'ew-gallery-grid',
16
+ `ew-gallery-grid--cols-${columns}`,
17
+ `ew-gallery-grid--gap-${gap}`,
18
+ ].join(' ');
19
+
20
+ const ratioClass = `ew-gallery-img--ratio-${aspectRatio.replace('/', '-')}`;
21
+ ---
22
+
23
+ {items.length > 0 && (
24
+ <section class="ew-gallery">
25
+ <div class="ew-gallery__inner">
26
+ {title && <h2 class="ew-gallery__title">{title}</h2>}
27
+ <div class={gridClass}>
28
+ {items.map((item) => {
29
+ const figure = (
30
+ <figure class="ew-gallery__item">
31
+ <img
32
+ src={item.src}
33
+ alt={item.alt}
34
+ class={`ew-gallery__img ${ratioClass}`}
35
+ loading="lazy"
36
+ />
37
+ {item.caption && (
38
+ <figcaption class="ew-gallery__caption">{item.caption}</figcaption>
39
+ )}
40
+ </figure>
41
+ );
42
+ return item.href ? (
43
+ <a href={item.href} class="ew-gallery__link" aria-label={item.alt}>
44
+ {figure}
45
+ </a>
46
+ ) : figure;
47
+ })}
48
+ </div>
49
+ </div>
50
+ </section>
51
+ )}
52
+
53
+ <style>
54
+ .ew-gallery {
55
+ padding: var(--ew-space-8) var(--ew-space-5);
56
+ }
57
+ .ew-gallery__inner {
58
+ max-width: 72rem;
59
+ margin: 0 auto;
60
+ }
61
+ .ew-gallery__title {
62
+ font-family: var(--ew-font-sans);
63
+ font-size: var(--ew-text-xl);
64
+ font-weight: 700;
65
+ color: var(--ew-on-surface);
66
+ margin: 0 0 var(--ew-space-5);
67
+ line-height: var(--ew-leading-tight);
68
+ }
69
+
70
+ .ew-gallery-grid {
71
+ display: grid;
72
+ gap: var(--ew-space-4);
73
+ }
74
+ .ew-gallery-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
75
+ .ew-gallery-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
76
+ .ew-gallery-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
77
+ .ew-gallery-grid--cols-6 { grid-template-columns: repeat(6, 1fr); }
78
+
79
+ .ew-gallery-grid--gap-sm { gap: var(--ew-space-2); }
80
+ .ew-gallery-grid--gap-md { gap: var(--ew-space-4); }
81
+ .ew-gallery-grid--gap-lg { gap: var(--ew-space-6); }
82
+
83
+ @media (max-width: 1024px) {
84
+ .ew-gallery-grid--cols-6 { grid-template-columns: repeat(4, 1fr); }
85
+ }
86
+ @media (max-width: 768px) {
87
+ .ew-gallery-grid--cols-3,
88
+ .ew-gallery-grid--cols-4,
89
+ .ew-gallery-grid--cols-6 {
90
+ grid-template-columns: repeat(2, 1fr);
91
+ }
92
+ }
93
+ @media (max-width: 480px) {
94
+ .ew-gallery-grid--cols-2,
95
+ .ew-gallery-grid--cols-3,
96
+ .ew-gallery-grid--cols-4,
97
+ .ew-gallery-grid--cols-6 {
98
+ grid-template-columns: 1fr;
99
+ }
100
+ }
101
+
102
+ .ew-gallery__item {
103
+ margin: 0;
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: var(--ew-space-2);
107
+ }
108
+ .ew-gallery__img {
109
+ width: 100%;
110
+ height: auto;
111
+ object-fit: cover;
112
+ border-radius: var(--ew-radius-md);
113
+ display: block;
114
+ transition: transform 0.2s ease;
115
+ }
116
+ .ew-gallery__img--ratio-square { aspect-ratio: 1 / 1; }
117
+ .ew-gallery__img--ratio-4-3 { aspect-ratio: 4 / 3; }
118
+ .ew-gallery__img--ratio-16-9 { aspect-ratio: 16 / 9; }
119
+ .ew-gallery__img--ratio-auto { aspect-ratio: auto; }
120
+
121
+ .ew-gallery__caption {
122
+ font-family: var(--ew-font-sans);
123
+ font-size: var(--ew-text-sm);
124
+ color: var(--ew-muted);
125
+ line-height: var(--ew-leading-normal);
126
+ text-align: center;
127
+ }
128
+
129
+ .ew-gallery__link {
130
+ text-decoration: none;
131
+ color: inherit;
132
+ display: block;
133
+ }
134
+ .ew-gallery__link:hover .ew-gallery__img,
135
+ .ew-gallery__link:focus-visible .ew-gallery__img {
136
+ transform: scale(1.02);
137
+ }
138
+ .ew-gallery__link:focus-visible {
139
+ outline: 2px solid var(--ew-primary);
140
+ outline-offset: 2px;
141
+ border-radius: var(--ew-radius-md);
142
+ }
143
+ </style>