@contentrain/astro-kit 0.30.0 → 0.32.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 (67) hide show
  1. package/README.md +25 -2
  2. package/catalog.json +3305 -83
  3. package/components/_shared/Section.astro +18 -3
  4. package/components/_shared/measured.ts +152 -0
  5. package/components/_shared/types.ts +40 -0
  6. package/components/band/Band.astro +132 -0
  7. package/components/band/fixtures.json +8 -0
  8. package/components/band/meta.json +323 -0
  9. package/components/card-grid/CardGrid.astro +7 -3
  10. package/components/card-grid/fixtures.json +2 -1
  11. package/components/card-grid/meta.json +111 -0
  12. package/components/contact-details/ContactDetails.astro +7 -2
  13. package/components/contact-details/meta.json +113 -1
  14. package/components/contact-form/ContactForm.astro +48 -12
  15. package/components/contact-form/fixtures.json +169 -9
  16. package/components/contact-form/meta.json +283 -15
  17. package/components/cta/Cta.astro +52 -12
  18. package/components/cta/fixtures.json +5 -1
  19. package/components/cta/meta.json +11 -2
  20. package/components/faq/Faq.astro +60 -17
  21. package/components/faq/fixtures.json +75 -0
  22. package/components/faq/meta.json +8 -2
  23. package/components/feature-list/FeatureList.astro +34 -6
  24. package/components/feature-list/fixtures.json +2 -1
  25. package/components/feature-list/meta.json +125 -1
  26. package/components/gallery/Gallery.astro +47 -13
  27. package/components/gallery/fixtures.json +275 -0
  28. package/components/gallery/meta.json +266 -14
  29. package/components/hero/Hero.astro +14 -8
  30. package/components/hero/fixtures.json +2 -1
  31. package/components/hero/meta.json +111 -0
  32. package/components/logo-cloud/LogoCloud.astro +85 -21
  33. package/components/logo-cloud/fixtures.json +239 -0
  34. package/components/logo-cloud/meta.json +145 -5
  35. package/components/newsletter/Newsletter.astro +5 -1
  36. package/components/newsletter/meta.json +113 -1
  37. package/components/post-card/PostCard.astro +4 -2
  38. package/components/post-card/meta.json +2 -1
  39. package/components/post-list/PostList.astro +114 -0
  40. package/components/post-list/fixtures.json +387 -0
  41. package/components/post-list/meta.json +308 -0
  42. package/components/pricing/Pricing.astro +128 -24
  43. package/components/pricing/fixtures.json +288 -4
  44. package/components/pricing/meta.json +188 -1
  45. package/components/split/Split.astro +76 -14
  46. package/components/split/fixtures.json +8 -1
  47. package/components/split/meta.json +226 -4
  48. package/components/stats/Stats.astro +42 -10
  49. package/components/stats/fixtures.json +173 -0
  50. package/components/stats/meta.json +153 -5
  51. package/components/steps/Steps.astro +36 -8
  52. package/components/steps/fixtures.json +217 -5
  53. package/components/steps/meta.json +157 -3
  54. package/components/tabs/Tabs.astro +5 -2
  55. package/components/tabs/meta.json +2 -1
  56. package/components/team/Team.astro +52 -8
  57. package/components/team/fixtures.json +217 -4
  58. package/components/team/meta.json +168 -3
  59. package/components/team/team.ts +9 -0
  60. package/components/testimonial/Testimonial.astro +71 -21
  61. package/components/testimonial/fixtures.json +73 -0
  62. package/components/testimonial/meta.json +167 -3
  63. package/mapping/classic.json +7 -1
  64. package/mapping/divi.json +21 -4
  65. package/mapping/elementor.json +33 -8
  66. package/mapping/gutenberg.json +20 -4
  67. package/package.json +2 -2
@@ -5,7 +5,9 @@
5
5
  "category": "section",
6
6
  "uses": [],
7
7
  "shared": [
8
- "Section.astro"
8
+ "Section.astro",
9
+ "measured.ts",
10
+ "types.ts"
9
11
  ],
10
12
  "variants": {
11
13
  "columns": {
@@ -85,6 +87,128 @@
85
87
  },
86
88
  "description": "The features, in order (Elementor icon list or icon boxes without images, Gutenberg list of features, Divi blurbs with icons)."
87
89
  },
90
+ "bare": {
91
+ "type": "boolean",
92
+ "default": false,
93
+ "content": false,
94
+ "description": "Only the list, with no section of its own; a split section uses it under its text."
95
+ },
96
+ "titleLevel": {
97
+ "type": "integer",
98
+ "default": 3,
99
+ "content": false,
100
+ "description": "With `bare`: the item titles' heading level (the section's plus one)."
101
+ },
102
+ "measured": {
103
+ "type": "object",
104
+ "content": false,
105
+ "description": "The source section's measured style (MeasuredStyle): frame width, padding, fill, image, columns per width, gap, text tone, radius, minimum height — all in pixels. Absent, the site's tokens; present, it wins over tone and columns. Values that are not a number, a colour or a known keyword are dropped.",
106
+ "fields": {
107
+ "containerPx": {
108
+ "type": "integer",
109
+ "min": 0,
110
+ "max": 2560,
111
+ "description": "The frame's content width."
112
+ },
113
+ "padY": {
114
+ "type": "integer",
115
+ "min": 0,
116
+ "max": 400,
117
+ "description": "Top and bottom padding."
118
+ },
119
+ "padX": {
120
+ "type": "integer",
121
+ "min": 0,
122
+ "max": 200,
123
+ "description": "The frame's side padding (gutter)."
124
+ },
125
+ "bg": {
126
+ "type": "object",
127
+ "description": "The section's background.",
128
+ "fields": {
129
+ "color": {
130
+ "type": "string",
131
+ "description": "Fill colour: hex, rgb(a) or hsl(a)."
132
+ },
133
+ "image": {
134
+ "type": "string",
135
+ "description": "Background image address (http(s) or root-relative), drawn behind the content."
136
+ },
137
+ "overlay": {
138
+ "type": "string",
139
+ "description": "Colour or linear/radial gradient laid over the image or the fill."
140
+ },
141
+ "size": {
142
+ "type": "string",
143
+ "description": "cover, contain or auto; anything else draws as cover."
144
+ },
145
+ "position": {
146
+ "type": "string",
147
+ "description": "One or two of center/top/bottom/left/right or percentages."
148
+ }
149
+ }
150
+ },
151
+ "columns": {
152
+ "type": "object",
153
+ "description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
154
+ "fields": {
155
+ "390": {
156
+ "type": "integer",
157
+ "min": 1,
158
+ "max": 6,
159
+ "description": "Phone."
160
+ },
161
+ "768": {
162
+ "type": "integer",
163
+ "min": 1,
164
+ "max": 6,
165
+ "description": "Tablet."
166
+ },
167
+ "1280": {
168
+ "type": "integer",
169
+ "min": 1,
170
+ "max": 6,
171
+ "description": "Desktop."
172
+ }
173
+ }
174
+ },
175
+ "gap": {
176
+ "type": "integer",
177
+ "min": 0,
178
+ "max": 200,
179
+ "description": "Gap between items."
180
+ },
181
+ "tone": {
182
+ "type": "select",
183
+ "options": [
184
+ "light",
185
+ "dark"
186
+ ],
187
+ "description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
188
+ },
189
+ "align": {
190
+ "type": "select",
191
+ "options": [
192
+ "start",
193
+ "center",
194
+ "end"
195
+ ],
196
+ "description": "Where the heading and lead sit; read by a component whose own align is not set."
197
+ },
198
+ "radius": {
199
+ "type": "integer",
200
+ "min": 0,
201
+ "max": 200,
202
+ "description": "Card and item corner radius."
203
+ },
204
+ "minHeight": {
205
+ "type": "integer",
206
+ "min": 0,
207
+ "max": 2000,
208
+ "description": "The section's minimum height."
209
+ }
210
+ }
211
+ },
88
212
  "id": {
89
213
  "type": "string",
90
214
  "content": false,
@@ -2,9 +2,13 @@
2
2
  // Images in a grid or in columns, with optional captions. Opening an image
3
3
  // shows it large in a native popover — no script: `popovertarget` opens it,
4
4
  // Escape and a click outside close it. `link` opens the image file instead.
5
+ // Layouts: grid, masonry, a scroll-snap `row`, and a `mosaic` whose first image
6
+ // spans 2×2. The popover shows one image; next/previous would need script.
5
7
  import { tv } from 'tailwind-variants'
6
8
  import KitImage from '../_shared/KitImage.astro'
7
9
  import Section from '../_shared/Section.astro'
10
+ import { measuredGrid } from '../_shared/measured'
11
+ import type { MeasuredStyle } from '../_shared/types'
8
12
  import { imageOf, type ImageSource } from '../_shared/image'
9
13
  import { type ImageInput } from '../_shared/types'
10
14
 
@@ -13,6 +17,8 @@ export interface GalleryItem {
13
17
  image: ImageSource
14
18
  imageAlt?: string | undefined
15
19
  caption?: string | undefined
20
+ /** The full-size file for the viewer, where the source's node points at the original; otherwise the image itself. */
21
+ full?: ImageSource | undefined
16
22
  }
17
23
 
18
24
  const gallery = tv({
@@ -20,7 +26,7 @@ const gallery = tv({
20
26
  heading: 'mb-8 text-2xl [font-weight:var(--font-weight-heading,700)] tracking-tight md:text-3xl',
21
27
  list: 'gap-3 md:gap-4',
22
28
  item: 'm-0',
23
- frame: 'block w-full cursor-zoom-in overflow-hidden rounded-[var(--radius-image,var(--radius-card))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',
29
+ frame: 'group relative block w-full cursor-zoom-in overflow-hidden rounded-[var(--radius-image,var(--radius-card))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',
24
30
  thumb: 'h-full w-full object-cover transition-transform duration-300 hover:scale-[1.02]',
25
31
  caption: 'mt-2 text-sm opacity-75',
26
32
  viewer: 'm-auto max-h-[92vh] max-w-[92vw] bg-transparent p-0 backdrop:bg-ink/85',
@@ -29,47 +35,74 @@ const gallery = tv({
29
35
  layout: {
30
36
  grid: { list: 'grid', frame: 'aspect-square' },
31
37
  masonry: { list: 'columns-2 [&>*]:mb-3 md:[&>*]:mb-4', item: 'break-inside-avoid' },
38
+ // A strip scrolled sideways, each tile snapping into place; it is a keyboard-focusable region (see the markup).
39
+ row: { list: 'flex snap-x snap-mandatory overflow-x-auto pb-2 [scrollbar-width:thin]', item: 'w-64 shrink-0 snap-start md:w-80', frame: 'aspect-[4/3]' },
40
+ // The first tile takes 2×2 of the grid; the rest fill around it.
41
+ mosaic: { list: 'grid grid-flow-dense', item: '[&:first-child]:col-span-2 [&:first-child]:row-span-2' },
32
42
  },
33
- columns: { '2': { list: 'grid-cols-2' }, '3': { list: 'grid-cols-2 md:grid-cols-3' }, '4': { list: 'grid-cols-2 md:grid-cols-4' } },
43
+ aspect: { square: { frame: 'aspect-square' }, '4:3': { frame: 'aspect-[4/3]' }, '16:9': { frame: 'aspect-video' }, auto: { frame: 'aspect-auto' } },
44
+ gap: { sm: { list: 'gap-1.5 md:gap-2' }, md: {}, lg: { list: 'gap-5 md:gap-8' } },
45
+ // Under the image, or laid over its foot on hover and keyboard focus — always visible below `md`.
46
+ captionStyle: { below: {}, overlay: { caption: 'absolute inset-x-0 bottom-0 m-0 bg-ink/70 p-2 text-surface md:translate-y-full md:transition-transform md:group-focus-within:translate-y-0 md:group-hover:translate-y-0' } },
47
+ hoverEffect: { none: {}, zoom: { thumb: 'motion-safe:group-hover:scale-105' } },
48
+ columns: { '2': { list: 'grid-cols-2' }, '3': { list: 'grid-cols-2 md:grid-cols-3' }, '4': { list: 'grid-cols-2 md:grid-cols-4' }, '5': { list: 'grid-cols-2 md:grid-cols-5' }, '6': { list: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-6' } },
34
49
  open: { popover: {}, link: {}, none: { frame: 'cursor-default' } },
35
50
  },
36
51
  compoundVariants: [
37
52
  { layout: 'masonry', columns: '3', class: { list: 'md:columns-3' } },
38
53
  { layout: 'masonry', columns: '4', class: { list: 'md:columns-4' } },
54
+ { layout: 'masonry', columns: '5', class: { list: 'md:columns-5' } },
55
+ { layout: 'masonry', columns: '6', class: { list: 'md:columns-3 lg:columns-6' } },
56
+ // A row and a mosaic size by their own tiles, not by `columns`.
57
+ { layout: 'row', class: { list: 'grid-cols-none md:grid-cols-none' } },
58
+ { layout: 'masonry', aspect: ['square', '4:3', '16:9'], class: { frame: 'aspect-auto' } },
39
59
  ],
40
- defaultVariants: { layout: 'grid', columns: '3', open: 'popover' },
60
+ defaultVariants: { layout: 'grid', columns: '3', open: 'popover', aspect: 'square', gap: 'md', captionStyle: 'below', hoverEffect: 'none' },
41
61
  })
42
62
 
43
63
  interface Props {
44
64
  items: GalleryItem[]
45
65
  heading?: string | undefined
46
- layout?: 'grid' | 'masonry' | undefined
47
- columns?: '2' | '3' | '4' | undefined
66
+ layout?: 'grid' | 'masonry' | 'row' | 'mosaic' | undefined
67
+ columns?: '2' | '3' | '4' | '5' | '6' | undefined
68
+ /** Tile shape (a masonry keeps each image's own). */
69
+ aspect?: 'square' | '4:3' | '16:9' | 'auto' | undefined
70
+ gap?: 'sm' | 'md' | 'lg' | undefined
71
+ captionStyle?: 'below' | 'overlay' | undefined
72
+ hoverEffect?: 'none' | 'zoom' | undefined
73
+ /** Prefix of the enlarge button's name for readers ("Enlarge"). */
74
+ /** Name of the scrolling row's region when there is no heading ("Images"). */
75
+ regionLabel?: string | undefined
76
+ enlargeLabel?: string | undefined
48
77
  /** What a click on an image does: show it large in place, open the file, or nothing. */
49
78
  open?: 'popover' | 'link' | 'none' | undefined
50
79
  /** Text of the viewer's close button ("Close"). */
51
80
  closeLabel?: string | undefined
52
81
  tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
82
+ /** The source section's measured style (`MeasuredStyle`): frame, padding, fill, background image, columns per width, gap. Absent, the site's tokens; present, it wins over tone and columns. */
83
+ measured?: MeasuredStyle | undefined
53
84
  id?: string | undefined
54
85
  }
55
86
 
56
- const { items: given, heading, layout, columns, open = 'popover', closeLabel = 'Close', tone, id = 'gallery' } = Astro.props
57
- const items = given.flatMap(item => { const image = imageOf(item.image, item.imageAlt); return image ? [{ ...item, image }] : [] }) satisfies Array<{ image: ImageInput, caption?: string | undefined }>
58
- const s = gallery({ layout, columns, open })
87
+ const { items: given, heading, layout, columns, aspect, gap, captionStyle, hoverEffect, enlargeLabel = 'Enlarge', regionLabel = 'Images', open = 'popover', closeLabel = 'Close', tone, measured, id = 'gallery' } = Astro.props
88
+ const items = given.flatMap(item => { const image = imageOf(item.image, item.imageAlt); return image ? [{ ...item, image, full: imageOf(item.full, item.imageAlt) }] : [] }) satisfies Array<{ image: ImageInput, caption?: string | undefined }>
89
+ const s = gallery({ layout, columns, open, aspect, gap, captionStyle, hoverEffect })
59
90
  const headingId = heading ? `${id}-title` : undefined
60
91
  const sizes = columns === '2' ? '50vw' : columns === '4' ? '(min-width: 768px) 25vw, 50vw' : '(min-width: 768px) 33vw, 50vw'
61
92
  ---
62
93
  {items.length > 0 && (
63
- <Section tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
94
+ <Section measured={measured} tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
64
95
  {heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
65
- <ul class={s.list()} role="list">
96
+ {/* A scrolling strip is a labelled, focusable region so the keyboard can scroll it. */}
97
+ <div tabindex={layout === 'row' ? 0 : undefined} role={layout === 'row' ? 'region' : undefined} aria-label={layout === 'row' ? heading ?? regionLabel : undefined}>
98
+ <ul class={s.list({ class: layout === 'grid' || layout === 'mosaic' ? measuredGrid(measured) : undefined })} role="list">
66
99
  {items.map((item, i) => {
67
100
  const viewer = `${id}-image-${i + 1}`
68
101
  return (
69
102
  <li class={s.item()}>
70
- <figure class="m-0">
103
+ <figure class={captionStyle === 'overlay' ? 'group relative m-0 overflow-hidden' : 'm-0'}>
71
104
  {open === 'popover'
72
- ? <button type="button" popovertarget={viewer} class={s.frame()} aria-label={item.image.alt || item.caption || undefined}><KitImage image={item.image} sizes={sizes} class={s.thumb()} /></button>
105
+ ? <button type="button" popovertarget={viewer} class={s.frame()} aria-label={`${enlargeLabel}: ${item.image.alt || item.caption || ''}`.trim()}><KitImage image={item.image} sizes={sizes} class={s.thumb()} /></button>
73
106
  : open === 'link'
74
107
  ? <a href={item.image.src} class={s.frame()}><KitImage image={item.image} sizes={sizes} class={s.thumb()} /></a>
75
108
  : <div class={s.frame()}><KitImage image={item.image} sizes={sizes} class={s.thumb()} /></div>}
@@ -78,7 +111,7 @@ const sizes = columns === '2' ? '50vw' : columns === '4' ? '(min-width: 768px) 2
78
111
  {open === 'popover' && (
79
112
  <div id={viewer} popover class={s.viewer()}>
80
113
  <figure class="m-0 flex flex-col items-center gap-3">
81
- <KitImage image={item.image} sizes="92vw" class="max-h-[80vh] w-auto rounded-[var(--radius-image,var(--radius-card))] object-contain" />
114
+ <KitImage image={item.full ?? item.image} sizes="92vw" class="max-h-[80vh] w-auto rounded-[var(--radius-image,var(--radius-card))] object-contain" />
82
115
  {item.caption && <figcaption class="text-sm text-surface">{item.caption}</figcaption>}
83
116
  <button type="button" popovertarget={viewer} popovertargetaction="hide" class="rounded-md border border-surface/40 px-4 py-2 text-sm text-surface hover:bg-surface/10">{closeLabel}</button>
84
117
  </figure>
@@ -88,5 +121,6 @@ const sizes = columns === '2' ? '50vw' : columns === '4' ? '(min-width: 768px) 2
88
121
  )
89
122
  })}
90
123
  </ul>
124
+ </div>
91
125
  </Section>
92
126
  )}
@@ -187,5 +187,280 @@
187
187
  }
188
188
  ]
189
189
  }
190
+ },
191
+ {
192
+ "name": "row",
193
+ "props": {
194
+ "layout": "row",
195
+ "heading": "Strip",
196
+ "id": "strip",
197
+ "items": [
198
+ {
199
+ "image": {
200
+ "src": "/kit-fixtures/photo-1.svg",
201
+ "alt": "Photo 1",
202
+ "width": 1200,
203
+ "height": 800
204
+ },
205
+ "caption": "Porto at dawn"
206
+ },
207
+ {
208
+ "image": {
209
+ "src": "/kit-fixtures/photo-2.svg",
210
+ "alt": "Photo 2",
211
+ "width": 1200,
212
+ "height": 800
213
+ },
214
+ "caption": "Train to Coimbra"
215
+ },
216
+ {
217
+ "image": {
218
+ "src": "/kit-fixtures/photo-3.svg",
219
+ "alt": "Photo 3",
220
+ "width": 1200,
221
+ "height": 800
222
+ },
223
+ "caption": "Market day"
224
+ },
225
+ {
226
+ "image": {
227
+ "src": "/kit-fixtures/photo-4.svg",
228
+ "alt": "Photo 4",
229
+ "width": 1200,
230
+ "height": 800
231
+ },
232
+ "caption": "Harbour"
233
+ },
234
+ {
235
+ "image": {
236
+ "src": "/kit-fixtures/photo-5.svg",
237
+ "alt": "Photo 5",
238
+ "width": 1200,
239
+ "height": 800
240
+ },
241
+ "caption": "Old town"
242
+ },
243
+ {
244
+ "image": {
245
+ "src": "/kit-fixtures/photo-6.svg",
246
+ "alt": "Photo 6",
247
+ "width": 1200,
248
+ "height": 800
249
+ },
250
+ "caption": "Last light"
251
+ }
252
+ ]
253
+ }
254
+ },
255
+ {
256
+ "name": "mosaic-wide-overlay",
257
+ "props": {
258
+ "layout": "mosaic",
259
+ "aspect": "16:9",
260
+ "captionStyle": "overlay",
261
+ "columns": "4",
262
+ "items": [
263
+ {
264
+ "image": {
265
+ "src": "/kit-fixtures/photo-1.svg",
266
+ "alt": "Photo 1",
267
+ "width": 1200,
268
+ "height": 800
269
+ },
270
+ "caption": "Porto at dawn"
271
+ },
272
+ {
273
+ "image": {
274
+ "src": "/kit-fixtures/photo-2.svg",
275
+ "alt": "Photo 2",
276
+ "width": 1200,
277
+ "height": 800
278
+ },
279
+ "caption": "Train to Coimbra"
280
+ },
281
+ {
282
+ "image": {
283
+ "src": "/kit-fixtures/photo-3.svg",
284
+ "alt": "Photo 3",
285
+ "width": 1200,
286
+ "height": 800
287
+ },
288
+ "caption": "Market day"
289
+ },
290
+ {
291
+ "image": {
292
+ "src": "/kit-fixtures/photo-4.svg",
293
+ "alt": "Photo 4",
294
+ "width": 1200,
295
+ "height": 800
296
+ },
297
+ "caption": "Harbour"
298
+ },
299
+ {
300
+ "image": {
301
+ "src": "/kit-fixtures/photo-5.svg",
302
+ "alt": "Photo 5",
303
+ "width": 1200,
304
+ "height": 800
305
+ },
306
+ "caption": "Old town"
307
+ },
308
+ {
309
+ "image": {
310
+ "src": "/kit-fixtures/photo-6.svg",
311
+ "alt": "Photo 6",
312
+ "width": 1200,
313
+ "height": 800
314
+ },
315
+ "caption": "Last light"
316
+ }
317
+ ]
318
+ }
319
+ },
320
+ {
321
+ "name": "aspect-43-zoom",
322
+ "props": {
323
+ "aspect": "4:3",
324
+ "hoverEffect": "zoom",
325
+ "gap": "lg",
326
+ "columns": "5",
327
+ "items": [
328
+ {
329
+ "image": {
330
+ "src": "/kit-fixtures/photo-1.svg",
331
+ "alt": "Photo 1",
332
+ "width": 1200,
333
+ "height": 800
334
+ },
335
+ "caption": "Porto at dawn"
336
+ },
337
+ {
338
+ "image": {
339
+ "src": "/kit-fixtures/photo-2.svg",
340
+ "alt": "Photo 2",
341
+ "width": 1200,
342
+ "height": 800
343
+ },
344
+ "caption": "Train to Coimbra"
345
+ },
346
+ {
347
+ "image": {
348
+ "src": "/kit-fixtures/photo-3.svg",
349
+ "alt": "Photo 3",
350
+ "width": 1200,
351
+ "height": 800
352
+ },
353
+ "caption": "Market day"
354
+ },
355
+ {
356
+ "image": {
357
+ "src": "/kit-fixtures/photo-4.svg",
358
+ "alt": "Photo 4",
359
+ "width": 1200,
360
+ "height": 800
361
+ },
362
+ "caption": "Harbour"
363
+ },
364
+ {
365
+ "image": {
366
+ "src": "/kit-fixtures/photo-5.svg",
367
+ "alt": "Photo 5",
368
+ "width": 1200,
369
+ "height": 800
370
+ },
371
+ "caption": "Old town"
372
+ },
373
+ {
374
+ "image": {
375
+ "src": "/kit-fixtures/photo-6.svg",
376
+ "alt": "Photo 6",
377
+ "width": 1200,
378
+ "height": 800
379
+ },
380
+ "caption": "Last light"
381
+ }
382
+ ]
383
+ }
384
+ },
385
+ {
386
+ "name": "gap-small-auto",
387
+ "props": {
388
+ "aspect": "auto",
389
+ "gap": "sm",
390
+ "columns": "6",
391
+ "open": "link",
392
+ "items": [
393
+ {
394
+ "image": {
395
+ "src": "/kit-fixtures/photo-1.svg",
396
+ "alt": "Photo 1",
397
+ "width": 1200,
398
+ "height": 800
399
+ },
400
+ "caption": "Porto at dawn"
401
+ },
402
+ {
403
+ "image": {
404
+ "src": "/kit-fixtures/photo-2.svg",
405
+ "alt": "Photo 2",
406
+ "width": 1200,
407
+ "height": 800
408
+ },
409
+ "caption": "Train to Coimbra"
410
+ },
411
+ {
412
+ "image": {
413
+ "src": "/kit-fixtures/photo-3.svg",
414
+ "alt": "Photo 3",
415
+ "width": 1200,
416
+ "height": 800
417
+ },
418
+ "caption": "Market day"
419
+ },
420
+ {
421
+ "image": {
422
+ "src": "/kit-fixtures/photo-4.svg",
423
+ "alt": "Photo 4",
424
+ "width": 1200,
425
+ "height": 800
426
+ },
427
+ "caption": "Harbour"
428
+ },
429
+ {
430
+ "image": {
431
+ "src": "/kit-fixtures/photo-5.svg",
432
+ "alt": "Photo 5",
433
+ "width": 1200,
434
+ "height": 800
435
+ },
436
+ "caption": "Old town"
437
+ },
438
+ {
439
+ "image": {
440
+ "src": "/kit-fixtures/photo-6.svg",
441
+ "alt": "Photo 6",
442
+ "width": 1200,
443
+ "height": 800
444
+ },
445
+ "caption": "Last light"
446
+ }
447
+ ]
448
+ }
449
+ },
450
+ {
451
+ "name": "single",
452
+ "props": {
453
+ "items": [
454
+ {
455
+ "image": {
456
+ "src": "/kit-fixtures/photo-1.svg",
457
+ "alt": "Photo 1",
458
+ "width": 1200,
459
+ "height": 800
460
+ },
461
+ "caption": "Porto at dawn"
462
+ }
463
+ ]
464
+ }
190
465
  }
191
466
  ]