@contentrain/astro-kit 0.31.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 (48) hide show
  1. package/catalog.json +2531 -345
  2. package/components/_shared/measured.ts +10 -0
  3. package/components/contact-form/ContactForm.astro +48 -12
  4. package/components/contact-form/fixtures.json +169 -9
  5. package/components/contact-form/meta.json +283 -15
  6. package/components/cta/Cta.astro +52 -12
  7. package/components/cta/fixtures.json +5 -1
  8. package/components/cta/meta.json +11 -2
  9. package/components/faq/Faq.astro +60 -17
  10. package/components/faq/fixtures.json +75 -0
  11. package/components/faq/meta.json +7 -1
  12. package/components/feature-list/FeatureList.astro +28 -4
  13. package/components/feature-list/meta.json +15 -3
  14. package/components/gallery/Gallery.astro +47 -13
  15. package/components/gallery/fixtures.json +275 -0
  16. package/components/gallery/meta.json +266 -14
  17. package/components/logo-cloud/LogoCloud.astro +85 -21
  18. package/components/logo-cloud/fixtures.json +239 -0
  19. package/components/logo-cloud/meta.json +144 -5
  20. package/components/post-card/PostCard.astro +4 -2
  21. package/components/post-card/meta.json +2 -1
  22. package/components/post-list/PostList.astro +114 -0
  23. package/components/post-list/fixtures.json +387 -0
  24. package/components/post-list/meta.json +308 -0
  25. package/components/pricing/Pricing.astro +128 -24
  26. package/components/pricing/fixtures.json +288 -4
  27. package/components/pricing/meta.json +187 -1
  28. package/components/split/Split.astro +76 -14
  29. package/components/split/fixtures.json +8 -1
  30. package/components/split/meta.json +225 -4
  31. package/components/stats/Stats.astro +42 -10
  32. package/components/stats/fixtures.json +173 -0
  33. package/components/stats/meta.json +150 -4
  34. package/components/steps/Steps.astro +36 -8
  35. package/components/steps/fixtures.json +217 -5
  36. package/components/steps/meta.json +156 -3
  37. package/components/team/Team.astro +52 -8
  38. package/components/team/fixtures.json +217 -4
  39. package/components/team/meta.json +167 -3
  40. package/components/team/team.ts +9 -0
  41. package/components/testimonial/Testimonial.astro +71 -21
  42. package/components/testimonial/fixtures.json +73 -0
  43. package/components/testimonial/meta.json +166 -3
  44. package/mapping/classic.json +7 -1
  45. package/mapping/divi.json +21 -4
  46. package/mapping/elementor.json +33 -8
  47. package/mapping/gutenberg.json +20 -4
  48. package/package.json +1 -1
@@ -135,5 +135,244 @@
135
135
  "tone": "accent",
136
136
  "style": "grayscale"
137
137
  }
138
+ },
139
+ {
140
+ "name": "row-start-large",
141
+ "props": {
142
+ "heading": "Trusted by",
143
+ "items": [
144
+ {
145
+ "image": "/kit-fixtures/logo.svg",
146
+ "imageAlt": "Company 1",
147
+ "href": "https://example.com"
148
+ },
149
+ {
150
+ "image": "/kit-fixtures/logo.svg",
151
+ "imageAlt": "Company 2"
152
+ },
153
+ {
154
+ "image": "/kit-fixtures/logo.svg",
155
+ "imageAlt": "Company 3",
156
+ "href": "https://example.com"
157
+ },
158
+ {
159
+ "image": "/kit-fixtures/logo.svg",
160
+ "imageAlt": "Company 4"
161
+ },
162
+ {
163
+ "image": "/kit-fixtures/logo.svg",
164
+ "imageAlt": "Company 5",
165
+ "href": "https://example.com"
166
+ },
167
+ {
168
+ "image": "/kit-fixtures/logo.svg",
169
+ "imageAlt": "Company 6"
170
+ }
171
+ ],
172
+ "layout": "row",
173
+ "align": "start",
174
+ "logoHeight": "lg"
175
+ }
176
+ },
177
+ {
178
+ "name": "row-small-mono-inverse",
179
+ "props": {
180
+ "heading": "Trusted by",
181
+ "items": [
182
+ {
183
+ "image": "/kit-fixtures/logo.svg",
184
+ "imageAlt": "Company 1",
185
+ "href": "https://example.com"
186
+ },
187
+ {
188
+ "image": "/kit-fixtures/logo.svg",
189
+ "imageAlt": "Company 2"
190
+ },
191
+ {
192
+ "image": "/kit-fixtures/logo.svg",
193
+ "imageAlt": "Company 3",
194
+ "href": "https://example.com"
195
+ },
196
+ {
197
+ "image": "/kit-fixtures/logo.svg",
198
+ "imageAlt": "Company 4"
199
+ },
200
+ {
201
+ "image": "/kit-fixtures/logo.svg",
202
+ "imageAlt": "Company 5",
203
+ "href": "https://example.com"
204
+ },
205
+ {
206
+ "image": "/kit-fixtures/logo.svg",
207
+ "imageAlt": "Company 6"
208
+ }
209
+ ],
210
+ "layout": "row",
211
+ "logoHeight": "sm",
212
+ "style": "mono-light",
213
+ "tone": "inverse"
214
+ }
215
+ },
216
+ {
217
+ "name": "marquee",
218
+ "props": {
219
+ "heading": "Trusted by",
220
+ "items": [
221
+ {
222
+ "image": "/kit-fixtures/logo.svg",
223
+ "imageAlt": "Company 1",
224
+ "href": "https://example.com"
225
+ },
226
+ {
227
+ "image": "/kit-fixtures/logo.svg",
228
+ "imageAlt": "Company 2"
229
+ },
230
+ {
231
+ "image": "/kit-fixtures/logo.svg",
232
+ "imageAlt": "Company 3",
233
+ "href": "https://example.com"
234
+ },
235
+ {
236
+ "image": "/kit-fixtures/logo.svg",
237
+ "imageAlt": "Company 4"
238
+ },
239
+ {
240
+ "image": "/kit-fixtures/logo.svg",
241
+ "imageAlt": "Company 5",
242
+ "href": "https://example.com"
243
+ },
244
+ {
245
+ "image": "/kit-fixtures/logo.svg",
246
+ "imageAlt": "Company 6"
247
+ }
248
+ ],
249
+ "layout": "marquee",
250
+ "tone": "muted"
251
+ }
252
+ },
253
+ {
254
+ "name": "five-columns",
255
+ "props": {
256
+ "heading": "Trusted by",
257
+ "items": [
258
+ {
259
+ "image": "/kit-fixtures/logo.svg",
260
+ "imageAlt": "Company 1",
261
+ "href": "https://example.com"
262
+ },
263
+ {
264
+ "image": "/kit-fixtures/logo.svg",
265
+ "imageAlt": "Company 2"
266
+ },
267
+ {
268
+ "image": "/kit-fixtures/logo.svg",
269
+ "imageAlt": "Company 3",
270
+ "href": "https://example.com"
271
+ },
272
+ {
273
+ "image": "/kit-fixtures/logo.svg",
274
+ "imageAlt": "Company 4"
275
+ },
276
+ {
277
+ "image": "/kit-fixtures/logo.svg",
278
+ "imageAlt": "Company 5",
279
+ "href": "https://example.com"
280
+ },
281
+ {
282
+ "image": "/kit-fixtures/logo.svg",
283
+ "imageAlt": "Company 6"
284
+ }
285
+ ],
286
+ "columns": "5"
287
+ }
288
+ },
289
+ {
290
+ "name": "six-columns",
291
+ "props": {
292
+ "heading": "Trusted by",
293
+ "items": [
294
+ {
295
+ "image": "/kit-fixtures/logo.svg",
296
+ "imageAlt": "Company 1",
297
+ "href": "https://example.com"
298
+ },
299
+ {
300
+ "image": "/kit-fixtures/logo.svg",
301
+ "imageAlt": "Company 2"
302
+ },
303
+ {
304
+ "image": "/kit-fixtures/logo.svg",
305
+ "imageAlt": "Company 3",
306
+ "href": "https://example.com"
307
+ },
308
+ {
309
+ "image": "/kit-fixtures/logo.svg",
310
+ "imageAlt": "Company 4"
311
+ },
312
+ {
313
+ "image": "/kit-fixtures/logo.svg",
314
+ "imageAlt": "Company 5",
315
+ "href": "https://example.com"
316
+ },
317
+ {
318
+ "image": "/kit-fixtures/logo.svg",
319
+ "imageAlt": "Company 6"
320
+ }
321
+ ],
322
+ "columns": "6"
323
+ }
324
+ },
325
+ {
326
+ "name": "measured-columns",
327
+ "props": {
328
+ "heading": "Trusted by",
329
+ "items": [
330
+ {
331
+ "image": "/kit-fixtures/logo.svg",
332
+ "imageAlt": "Company 1",
333
+ "href": "https://example.com"
334
+ },
335
+ {
336
+ "image": "/kit-fixtures/logo.svg",
337
+ "imageAlt": "Company 2"
338
+ },
339
+ {
340
+ "image": "/kit-fixtures/logo.svg",
341
+ "imageAlt": "Company 3",
342
+ "href": "https://example.com"
343
+ },
344
+ {
345
+ "image": "/kit-fixtures/logo.svg",
346
+ "imageAlt": "Company 4"
347
+ },
348
+ {
349
+ "image": "/kit-fixtures/logo.svg",
350
+ "imageAlt": "Company 5",
351
+ "href": "https://example.com"
352
+ },
353
+ {
354
+ "image": "/kit-fixtures/logo.svg",
355
+ "imageAlt": "Company 6"
356
+ },
357
+ {
358
+ "image": "/kit-fixtures/logo.svg",
359
+ "imageAlt": "Company 7",
360
+ "href": "https://example.com"
361
+ },
362
+ {
363
+ "image": "/kit-fixtures/logo.svg",
364
+ "imageAlt": "Company 8"
365
+ }
366
+ ],
367
+ "measured": {
368
+ "columns": {
369
+ "390": 2,
370
+ "768": 3,
371
+ "1280": 6
372
+ },
373
+ "gap": 32,
374
+ "padY": 48
375
+ }
376
+ }
138
377
  }
139
378
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "logo-cloud",
3
3
  "name": "Logo cloud",
4
- "description": "A grid of client or partner logos, each optionally linked, under an optional heading (\"Trusted by\").",
4
+ "description": "A grid, a row or an endless strip of client or partner logos, each optionally linked, under an optional heading (\"Trusted by\").",
5
5
  "category": "section",
6
6
  "uses": [],
7
7
  "shared": [
@@ -12,22 +12,51 @@
12
12
  "types.ts"
13
13
  ],
14
14
  "variants": {
15
+ "layout": {
16
+ "options": [
17
+ "grid",
18
+ "row",
19
+ "marquee"
20
+ ],
21
+ "default": "grid",
22
+ "description": "grid: logos in columns. row: one line that wraps. marquee: an endless CSS-only strip that stands still for reduced motion."
23
+ },
15
24
  "columns": {
16
25
  "options": [
17
26
  "2",
18
27
  "3",
19
- "4"
28
+ "4",
29
+ "5",
30
+ "6"
20
31
  ],
21
32
  "default": "4",
22
- "description": "Logos per row on wide screens; two on phones, three on tablets."
33
+ "description": "Logos per row on wide screens (grid layout); two on phones, three on tablets."
34
+ },
35
+ "logoHeight": {
36
+ "options": [
37
+ "sm",
38
+ "md",
39
+ "lg"
40
+ ],
41
+ "default": "md",
42
+ "description": "Height of every logo (the width follows): small, medium or large."
23
43
  },
24
44
  "style": {
25
45
  "options": [
26
46
  "plain",
27
- "grayscale"
47
+ "grayscale",
48
+ "mono-light"
28
49
  ],
29
50
  "default": "plain",
30
- "description": "plain: logos in their own colours. grayscale: muted grey, full colour on hover or focus."
51
+ "description": "plain: logos in their own colours. grayscale: muted grey, full colour on hover or focus. mono-light: drawn white, for a dark band."
52
+ },
53
+ "align": {
54
+ "options": [
55
+ "center",
56
+ "start"
57
+ ],
58
+ "default": "center",
59
+ "description": "Alignment of the heading and of the logos in a row."
31
60
  },
32
61
  "tone": {
33
62
  "options": [
@@ -74,6 +103,116 @@
74
103
  },
75
104
  "description": "The logos, in order (a Gutenberg gallery or columns of logos, Elementor image carousel/gallery of logos, Divi images)."
76
105
  },
106
+ "measured": {
107
+ "type": "object",
108
+ "content": false,
109
+ "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.",
110
+ "fields": {
111
+ "containerPx": {
112
+ "type": "integer",
113
+ "min": 0,
114
+ "max": 2560,
115
+ "description": "The frame's content width."
116
+ },
117
+ "padY": {
118
+ "type": "integer",
119
+ "min": 0,
120
+ "max": 400,
121
+ "description": "Top and bottom padding."
122
+ },
123
+ "padX": {
124
+ "type": "integer",
125
+ "min": 0,
126
+ "max": 200,
127
+ "description": "The frame's side padding (gutter)."
128
+ },
129
+ "bg": {
130
+ "type": "object",
131
+ "description": "The section's background.",
132
+ "fields": {
133
+ "color": {
134
+ "type": "string",
135
+ "description": "Fill colour: hex, rgb(a) or hsl(a)."
136
+ },
137
+ "image": {
138
+ "type": "string",
139
+ "description": "Background image address (http(s) or root-relative), drawn behind the content."
140
+ },
141
+ "overlay": {
142
+ "type": "string",
143
+ "description": "Colour or linear/radial gradient laid over the image or the fill."
144
+ },
145
+ "size": {
146
+ "type": "string",
147
+ "description": "cover, contain or auto; anything else draws as cover."
148
+ },
149
+ "position": {
150
+ "type": "string",
151
+ "description": "One or two of center/top/bottom/left/right or percentages."
152
+ }
153
+ }
154
+ },
155
+ "columns": {
156
+ "type": "object",
157
+ "description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.",
158
+ "fields": {
159
+ "390": {
160
+ "type": "integer",
161
+ "min": 1,
162
+ "max": 6,
163
+ "description": "Phone."
164
+ },
165
+ "768": {
166
+ "type": "integer",
167
+ "min": 1,
168
+ "max": 6,
169
+ "description": "Tablet."
170
+ },
171
+ "1280": {
172
+ "type": "integer",
173
+ "min": 1,
174
+ "max": 6,
175
+ "description": "Desktop."
176
+ }
177
+ }
178
+ },
179
+ "gap": {
180
+ "type": "integer",
181
+ "min": 0,
182
+ "max": 200,
183
+ "description": "Gap between items."
184
+ },
185
+ "tone": {
186
+ "type": "select",
187
+ "options": [
188
+ "light",
189
+ "dark"
190
+ ],
191
+ "description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."
192
+ },
193
+ "align": {
194
+ "type": "select",
195
+ "options": [
196
+ "start",
197
+ "center",
198
+ "end"
199
+ ],
200
+ "description": "Where the heading and lead sit; read by a component whose own align is not set."
201
+ },
202
+ "radius": {
203
+ "type": "integer",
204
+ "min": 0,
205
+ "max": 200,
206
+ "description": "Card and item corner radius."
207
+ },
208
+ "minHeight": {
209
+ "type": "integer",
210
+ "min": 0,
211
+ "max": 2000,
212
+ "description": "The section's minimum height."
213
+ }
214
+ }
215
+ },
77
216
  "id": {
78
217
  "type": "string",
79
218
  "content": false,
@@ -50,6 +50,8 @@ interface Props {
50
50
  layout?: 'vertical' | 'horizontal' | 'minimal' | undefined
51
51
  surface?: 'plain' | 'card' | undefined
52
52
  byline?: 'inline' | 'below' | undefined
53
+ /** Clamp the excerpt to this many lines; absent, the layout's own (3 vertical, 2 otherwise). */
54
+ excerptLines?: 2 | 3 | 4 | undefined
53
55
  /** The title in the site's link colour, as themes that colour every link do (Hello Elementor). */
54
56
  linkTitle?: boolean | undefined
55
57
  /** The image's `sizes` hint; absent, the card's own guess. A list passes the slot its columns give. */
@@ -60,7 +62,7 @@ interface Props {
60
62
  imageAspect?: string | undefined
61
63
  }
62
64
 
63
- const { title, href, excerpt, date: rawDate, locale = 'en', image, category, author, headingLevel = 2, layout, surface, byline, linkTitle, sizes, imageHeight, imageAspect } = Astro.props
65
+ const { title, href, excerpt, date: rawDate, locale = 'en', image, category, author, headingLevel = 2, layout, surface, byline, linkTitle, excerptLines, sizes, imageHeight, imageAspect } = Astro.props
64
66
  const date = rawDate === undefined ? undefined : new Date(rawDate)
65
67
  const slots = card({ layout, surface, byline })
66
68
  const below = byline === 'below'
@@ -97,7 +99,7 @@ const Heading = `h${headingLevel}` as 'h2' | 'h3'
97
99
  {date && <time datetime={date.toISOString()}>{shown}</time>}
98
100
  </p>
99
101
  )}
100
- {excerpt && <p class={slots.excerpt()}>{excerpt}</p>}
102
+ {excerpt && <p class={slots.excerpt({ class: excerptLines ? { 2: 'line-clamp-2', 3: 'line-clamp-3', 4: 'line-clamp-4' }[excerptLines] : undefined })}>{excerpt}</p>}
101
103
  {!below && author && <p class="text-sm text-ink-muted">{author}</p>}
102
104
  </div>
103
105
  </article>
@@ -23,7 +23,8 @@
23
23
  "linkTitle": { "type": "boolean", "default": false, "description": "The title in the site's link colour (--color-link), as themes that colour every link do." },
24
24
  "sizes": { "type": "string", "description": "The image's `sizes` hint; absent, the card's own guess. A list passes the slot its columns give." },
25
25
  "imageHeight": { "type": "string", "description": "The image block's height as the source sets it (a CSS length or clamp()), cropped to fit; wins over imageAspect." },
26
- "imageAspect": { "type": "string", "description": "The image block's aspect ratio (16 / 9) where the source sets an aspect ratio; absent, 16 / 9." }
26
+ "imageAspect": { "type": "string", "description": "The image block's aspect ratio (16 / 9) where the source sets an aspect ratio; absent, 16 / 9." },
27
+ "excerptLines": { "type": "integer", "min": 2, "max": 4, "content": false, "description": "Clamp the excerpt to this many lines; absent, the layout's own (3 vertical, 2 otherwise)." }
27
28
  },
28
29
  "slots": {},
29
30
  "js": "none",
@@ -0,0 +1,114 @@
1
+ ---
2
+ // A list of posts as cards: a grid, stacked rows, a featured first post over a
3
+ // grid, or a compact title-and-date list. The posts are the site's own — fed
4
+ // by the Contentrain query SDK, never copied from a page — and the `show*`
5
+ // switches decide which parts of each card the design displays.
6
+ import { tv } from 'tailwind-variants'
7
+ import Section from '../_shared/Section.astro'
8
+ import { measuredGrid } from '../_shared/measured'
9
+ import type { MeasuredStyle } from '../_shared/types'
10
+ import PostCard from '../post-card/PostCard.astro'
11
+ import type { ImageInput, LinkInput } from '../_shared/types'
12
+
13
+ export interface PostItem {
14
+ title: string
15
+ href: string
16
+ excerpt?: string | undefined
17
+ date?: Date | string | undefined
18
+ image?: ImageInput | undefined
19
+ category?: LinkInput | undefined
20
+ author?: string | undefined
21
+ }
22
+
23
+ const list = tv({
24
+ slots: {
25
+ header: 'mb-10 flex max-w-2xl flex-col gap-3',
26
+ heading: 'text-3xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-4xl',
27
+ intro: 'text-lg opacity-80',
28
+ items: 'grid gap-8',
29
+ more: 'mt-10',
30
+ },
31
+ variants: {
32
+ layout: {
33
+ grid: { items: 'sm:grid-cols-2' },
34
+ list: { items: 'gap-6' },
35
+ // The first post spans the whole row, image beside text; the others fill the grid under it.
36
+ featured: { items: 'sm:grid-cols-2 [&>li:first-child]:sm:col-span-2' },
37
+ compact: { items: 'gap-4' },
38
+ },
39
+ columns: { '2': {}, '3': {}, '4': {} },
40
+ },
41
+ compoundVariants: [
42
+ { layout: ['grid', 'featured'], columns: '3', class: { items: 'lg:grid-cols-3 [&>li:first-child]:lg:col-span-3' } },
43
+ { layout: ['grid', 'featured'], columns: '4', class: { items: 'md:grid-cols-2 lg:grid-cols-4 [&>li:first-child]:lg:col-span-4' } },
44
+ ],
45
+ defaultVariants: { layout: 'grid', columns: '3' },
46
+ })
47
+
48
+ interface Props {
49
+ heading?: string | undefined
50
+ intro?: string | undefined
51
+ posts: PostItem[]
52
+ layout?: 'grid' | 'list' | 'featured' | 'compact' | undefined
53
+ /** Cards per row on wide screens (grid and featured). */
54
+ columns?: '2' | '3' | '4' | undefined
55
+ /** Which parts of a card the design shows. */
56
+ showImage?: boolean | undefined
57
+ showExcerpt?: boolean | undefined
58
+ showAuthor?: boolean | undefined
59
+ showCategory?: boolean | undefined
60
+ excerptLines?: 2 | 3 | 4 | undefined
61
+ /** BCP 47 locale the dates are written in. */
62
+ locale?: string | undefined
63
+ /** Card surface: none, or each post its own card. */
64
+ surface?: 'plain' | 'card' | undefined
65
+ /** Where the full archive lives; with `moreLabel` a link follows the list. */
66
+ moreHref?: string | undefined
67
+ moreLabel?: string | undefined
68
+ tone?: 'default' | 'muted' | 'inverse' | 'accent' | undefined
69
+ /** 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. */
70
+ measured?: MeasuredStyle | undefined
71
+ id?: string | undefined
72
+ }
73
+
74
+ const { heading, intro, posts, layout = 'grid', columns, showImage = true, showExcerpt = true, showAuthor = true, showCategory = true, excerptLines, locale, surface, moreHref, moreLabel, tone, measured, id = 'posts' } = Astro.props
75
+ const s = list({ layout, columns })
76
+ const headingId = heading ? `${id}-title` : undefined
77
+ // Cards sit one heading level under the section's; with no section heading they are the page's h2s.
78
+ const headingLevel = heading ? 3 : 2
79
+ const sizes = layout === 'list' || layout === 'compact' ? '256px' : columns === '4' ? '(min-width: 1024px) 25vw, 50vw' : columns === '2' ? '(min-width: 640px) 50vw, 100vw' : '(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw'
80
+ // A featured first post is read as a horizontal card; the rest take the grid's vertical one.
81
+ const cardLayout = (i: number) => layout === 'compact' ? 'minimal' : layout === 'list' || (layout === 'featured' && i === 0) ? 'horizontal' : 'vertical'
82
+ ---
83
+ {posts.length > 0 && (
84
+ <Section measured={measured} tone={tone} id={id} aria-labelledby={headingId}>
85
+ {(heading || intro) && (
86
+ <header class={s.header()}>
87
+ {heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
88
+ {intro && <p class={s.intro()} data-kit-text>{intro}</p>}
89
+ </header>
90
+ )}
91
+ <ul class={s.items({ class: layout === 'grid' || layout === 'featured' ? measuredGrid(measured) : undefined })}>
92
+ {posts.map((post, i) => (
93
+ <li>
94
+ <PostCard
95
+ title={post.title}
96
+ href={post.href}
97
+ excerpt={showExcerpt && layout !== 'compact' ? post.excerpt : undefined}
98
+ date={post.date}
99
+ locale={locale}
100
+ image={showImage && layout !== 'compact' ? post.image : undefined}
101
+ category={showCategory && layout !== 'compact' ? post.category : undefined}
102
+ author={showAuthor && layout !== 'compact' ? post.author : undefined}
103
+ headingLevel={headingLevel}
104
+ layout={cardLayout(i)}
105
+ surface={surface}
106
+ excerptLines={excerptLines}
107
+ sizes={sizes}
108
+ />
109
+ </li>
110
+ ))}
111
+ </ul>
112
+ {moreHref && moreLabel && <p class={s.more()}><a href={moreHref} class="font-medium text-accent-text underline underline-offset-4">{moreLabel}</a></p>}
113
+ </Section>
114
+ )}