@aparte/plugin-cards 0.0.0-stage → 0.29.1

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 (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -2
  3. package/dist/carousel-DwgHvriV.js +63 -0
  4. package/dist/carousel-DwgHvriV.js.map +1 -0
  5. package/dist/controls-BYUm4aRV.js +62 -0
  6. package/dist/controls-BYUm4aRV.js.map +1 -0
  7. package/dist/format-BFq79ydr.js +728 -0
  8. package/dist/format-BFq79ydr.js.map +1 -0
  9. package/dist/index.d.ts +30 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +120 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/items/icons.d.ts +12 -0
  14. package/dist/items/icons.d.ts.map +1 -0
  15. package/dist/items/index.d.ts +100 -0
  16. package/dist/items/index.d.ts.map +1 -0
  17. package/dist/items.js +698 -0
  18. package/dist/items.js.map +1 -0
  19. package/dist/kit/carousel.d.ts +17 -0
  20. package/dist/kit/carousel.d.ts.map +1 -0
  21. package/dist/kit/controls.d.ts +51 -0
  22. package/dist/kit/controls.d.ts.map +1 -0
  23. package/dist/kit/define.d.ts +201 -0
  24. package/dist/kit/define.d.ts.map +1 -0
  25. package/dist/kit/dom.d.ts +40 -0
  26. package/dist/kit/dom.d.ts.map +1 -0
  27. package/dist/kit/fence.d.ts +39 -0
  28. package/dist/kit/fence.d.ts.map +1 -0
  29. package/dist/kit/format.d.ts +49 -0
  30. package/dist/kit/format.d.ts.map +1 -0
  31. package/dist/kit/rating.d.ts +19 -0
  32. package/dist/kit/rating.d.ts.map +1 -0
  33. package/dist/kit/sparkline.d.ts +19 -0
  34. package/dist/kit/sparkline.d.ts.map +1 -0
  35. package/dist/kit/strings.d.ts +139 -0
  36. package/dist/kit/strings.d.ts.map +1 -0
  37. package/dist/link/icons.d.ts +16 -0
  38. package/dist/link/icons.d.ts.map +1 -0
  39. package/dist/link/index.d.ts +98 -0
  40. package/dist/link/index.d.ts.map +1 -0
  41. package/dist/link.js +387 -0
  42. package/dist/link.js.map +1 -0
  43. package/dist/media/icons.d.ts +28 -0
  44. package/dist/media/icons.d.ts.map +1 -0
  45. package/dist/media/index.d.ts +107 -0
  46. package/dist/media/index.d.ts.map +1 -0
  47. package/dist/media.js +610 -0
  48. package/dist/media.js.map +1 -0
  49. package/dist/order/icons.d.ts +49 -0
  50. package/dist/order/icons.d.ts.map +1 -0
  51. package/dist/order/index.d.ts +204 -0
  52. package/dist/order/index.d.ts.map +1 -0
  53. package/dist/order.js +1843 -0
  54. package/dist/order.js.map +1 -0
  55. package/dist/place/icons.d.ts +18 -0
  56. package/dist/place/icons.d.ts.map +1 -0
  57. package/dist/place/index.d.ts +207 -0
  58. package/dist/place/index.d.ts.map +1 -0
  59. package/dist/place.js +1456 -0
  60. package/dist/place.js.map +1 -0
  61. package/dist/plan/icons.d.ts +25 -0
  62. package/dist/plan/icons.d.ts.map +1 -0
  63. package/dist/plan/index.d.ts +153 -0
  64. package/dist/plan/index.d.ts.map +1 -0
  65. package/dist/plan.js +1191 -0
  66. package/dist/plan.js.map +1 -0
  67. package/dist/product/icons.d.ts +16 -0
  68. package/dist/product/icons.d.ts.map +1 -0
  69. package/dist/product/index.d.ts +133 -0
  70. package/dist/product/index.d.ts.map +1 -0
  71. package/dist/product.js +1245 -0
  72. package/dist/product.js.map +1 -0
  73. package/dist/rating-CHVajQ2b.js +35 -0
  74. package/dist/rating-CHVajQ2b.js.map +1 -0
  75. package/dist/score/icons.d.ts +35 -0
  76. package/dist/score/icons.d.ts.map +1 -0
  77. package/dist/score/index.d.ts +185 -0
  78. package/dist/score/index.d.ts.map +1 -0
  79. package/dist/score.js +1843 -0
  80. package/dist/score.js.map +1 -0
  81. package/dist/sparkline-C-cA9obz.js +56 -0
  82. package/dist/sparkline-C-cA9obz.js.map +1 -0
  83. package/dist/stats/icons.d.ts +19 -0
  84. package/dist/stats/icons.d.ts.map +1 -0
  85. package/dist/stats/index.d.ts +137 -0
  86. package/dist/stats/index.d.ts.map +1 -0
  87. package/dist/stats.js +1085 -0
  88. package/dist/stats.js.map +1 -0
  89. package/dist/stock/chart.d.ts +135 -0
  90. package/dist/stock/chart.d.ts.map +1 -0
  91. package/dist/stock/icons.d.ts +15 -0
  92. package/dist/stock/icons.d.ts.map +1 -0
  93. package/dist/stock/index.d.ts +195 -0
  94. package/dist/stock/index.d.ts.map +1 -0
  95. package/dist/stock.js +1013 -0
  96. package/dist/stock.js.map +1 -0
  97. package/dist/weather/conditions.d.ts +25 -0
  98. package/dist/weather/conditions.d.ts.map +1 -0
  99. package/dist/weather/icons.d.ts +12 -0
  100. package/dist/weather/icons.d.ts.map +1 -0
  101. package/dist/weather/index.d.ts +146 -0
  102. package/dist/weather/index.d.ts.map +1 -0
  103. package/dist/weather.js +1162 -0
  104. package/dist/weather.js.map +1 -0
  105. package/package.json +137 -3
package/dist/items.js ADDED
@@ -0,0 +1,698 @@
1
+ import { c as carousel } from "./carousel-DwgHvriV.js";
2
+ import { b as defineCard, q as str, n as intlTag, o as languageOf, r as word, e as el, s as safeUrl, h as formatNumber, w as webUrl, m as glyph } from "./format-BFq79ydr.js";
3
+ const ITEMS_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="6" width="12" height="14" rx="2.2"/><path d="M18 7.5v10.3a2.2 2.2 0 0 1-1.2 2" opacity="0.7"/><path d="M21 9v7.6" opacity="0.45"/><path d="M6 15.5l2.3-2.6a1 1 0 0 1 1.5 0l2.2 2.6"/><circle cx="11.2" cy="10.2" r="1.2"/></svg>`;
4
+ const IMAGE_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="4.5" width="18" height="15" rx="2.5"/><circle cx="8.6" cy="9.6" r="1.6"/><path d="M3.5 17.2l4.9-4.6a1.4 1.4 0 0 1 1.9 0l3.4 3.2"/><path d="M12.4 14.6l2.4-2.2a1.4 1.4 0 0 1 1.9 0l3.8 3.6"/></svg>`;
5
+ const itemsStyles = `/* =========================================
6
+ The items card — @aparte/plugin-cards/items
7
+ =========================================
8
+ Three variants of one card, chosen by \`data-variant\` on its root:
9
+ carousel tiles in a row that scrolls sideways (the kit's carousel)
10
+ grid the same tiles, two columns, one when the card is narrow
11
+ list rows inside one card: a square thumbnail, the text, one button
12
+ One item, in the carousel or the grid, is one tile at the card's full width.
13
+
14
+ In the carousel and the grid each tile is its own card, so the root lays them out and
15
+ draws no frame of its own — a boxed row of boxes reads as a form. The list keeps the
16
+ frame: its rows are parts of one card, divided by hairlines.
17
+ */
18
+
19
+ .aparte-items-card {
20
+ /* The picture's box in a tile; a single item's is wider. */
21
+ --aparte-items-media-ratio: 4 / 3;
22
+ /* A tile's width in the carousel (the kit's slide size reads it). */
23
+ --aparte-items-tile-size: 13.5rem;
24
+ /* The thumbnail's side in a list row. */
25
+ --aparte-items-thumb-size: 4.5rem;
26
+ --aparte-items-tile-radius: var(--aparte-radius-lg);
27
+ /* Behind a missing picture: a wash of the accent on the surface, in either theme. */
28
+ --aparte-items-placeholder: linear-gradient(
29
+ 135deg,
30
+ color-mix(in oklab, var(--aparte-primary) 16%, var(--aparte-surface-2)) 0%,
31
+ var(--aparte-surface-2) 55%,
32
+ color-mix(in oklab, var(--aparte-text) 6%, var(--aparte-surface-2)) 100%
33
+ );
34
+ --aparte-items-placeholder-ink: color-mix(in oklab, var(--aparte-text) 32%, transparent);
35
+ --aparte-items-shadow: 0 1px 2px rgb(0 0 0 / 0.05), 0 8px 20px -14px rgb(0 0 0 / 0.35);
36
+ --aparte-items-shadow-hover: 0 2px 4px rgb(0 0 0 / 0.06), 0 14px 28px -14px rgb(0 0 0 / 0.42);
37
+ }
38
+
39
+ .aparte-items-card[data-variant="carousel"] { --aparte-rich-card-max-inline-size: 34rem; }
40
+ .aparte-items-card[data-variant="grid"] { --aparte-rich-card-max-inline-size: 30rem; }
41
+ .aparte-items-card[data-variant="list"] { --aparte-rich-card-max-inline-size: 28rem; }
42
+
43
+ /* One item is one card, as wide as a card reads well — not as wide as a row of tiles. */
44
+ .aparte-items-card:not([data-variant="list"]):has(> .aparte-items[data-count="1"]) {
45
+ --aparte-rich-card-max-inline-size: 22rem;
46
+ --aparte-items-media-ratio: 16 / 9;
47
+ }
48
+
49
+ /* Tiles are cards: the root only lays them out. */
50
+ .aparte-items-card:not([data-variant="list"])[data-state="ready"],
51
+ .aparte-items-card:not([data-variant="list"])[data-state="loading"] {
52
+ overflow: visible;
53
+ border-color: transparent;
54
+ border-radius: 0;
55
+ background: transparent;
56
+ }
57
+
58
+ .aparte-items-card:not([data-variant="list"]) > .aparte-rich-card__actions {
59
+ padding-inline: 0;
60
+ padding-block-end: 0;
61
+ border-block-start: 0;
62
+ background: transparent;
63
+ }
64
+
65
+ .aparte-items {
66
+ container: aparte-items / inline-size;
67
+ }
68
+
69
+ /* ── The card's title ─────────────────────────────────────────────────── */
70
+
71
+ .aparte-items__header {
72
+ display: flex;
73
+ align-items: baseline;
74
+ justify-content: space-between;
75
+ gap: var(--aparte-space-4);
76
+ padding-block-end: var(--aparte-space-4);
77
+ }
78
+
79
+ .aparte-items-card[data-variant="list"] .aparte-items__header {
80
+ padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-3);
81
+ }
82
+
83
+ .aparte-items__heading {
84
+ min-inline-size: 0;
85
+ overflow: hidden;
86
+ font-size: var(--aparte-font-size-md);
87
+ font-weight: var(--aparte-font-weight-semibold);
88
+ letter-spacing: -0.005em;
89
+ text-overflow: ellipsis;
90
+ white-space: nowrap;
91
+ }
92
+
93
+ .aparte-items__count {
94
+ flex: none;
95
+ color: var(--aparte-text-muted);
96
+ font-size: var(--aparte-font-size-xs);
97
+ }
98
+
99
+ /* ── One item ─────────────────────────────────────────────────────────── */
100
+
101
+ .aparte-items__item {
102
+ position: relative;
103
+ min-inline-size: 0;
104
+ }
105
+
106
+ .aparte-items__tile {
107
+ display: flex;
108
+ flex-direction: column;
109
+ block-size: 100%;
110
+ box-sizing: border-box;
111
+ overflow: hidden;
112
+ border: var(--aparte-border-width) solid var(--aparte-border);
113
+ border-radius: var(--aparte-items-tile-radius);
114
+ background: var(--aparte-surface-1);
115
+ color: var(--aparte-text);
116
+ box-shadow: var(--aparte-items-shadow);
117
+ transition:
118
+ transform var(--aparte-duration-slow) var(--aparte-ease),
119
+ box-shadow var(--aparte-duration-slow) var(--aparte-ease),
120
+ border-color var(--aparte-duration-slow) var(--aparte-ease);
121
+ }
122
+
123
+ /* The picture: a fixed box, so a slow or missing image moves nothing below it. */
124
+ .aparte-items__media {
125
+ position: relative;
126
+ display: grid;
127
+ flex: none;
128
+ place-items: center;
129
+ aspect-ratio: var(--aparte-items-media-ratio);
130
+ overflow: hidden;
131
+ background: var(--aparte-items-placeholder);
132
+ color: var(--aparte-items-placeholder-ink);
133
+ --aparte-rich-card-glyph-size: 2rem;
134
+ }
135
+
136
+ /* The glyph only where there is no picture: not through a transparent PNG, nor while
137
+ a slow one arrives. */
138
+ .aparte-items__media:not([data-empty]) .aparte-items__placeholder { visibility: hidden; }
139
+
140
+ .aparte-items__image {
141
+ position: absolute;
142
+ inset: 0;
143
+ inline-size: 100%;
144
+ block-size: 100%;
145
+ object-fit: cover;
146
+ transition: transform var(--aparte-duration-slower) var(--aparte-ease);
147
+ }
148
+
149
+ /* The badge over the picture's corner: the solid badge recipe, lifted off the photo. */
150
+ .aparte-items__media .aparte-items__badge {
151
+ position: absolute;
152
+ inset-block-start: var(--aparte-space-4);
153
+ inset-inline-start: var(--aparte-space-4);
154
+ max-inline-size: calc(100% - var(--aparte-space-8));
155
+ overflow: hidden;
156
+ box-shadow: 0 1px 4px rgb(0 0 0 / 0.22);
157
+ text-overflow: ellipsis;
158
+ white-space: nowrap;
159
+ }
160
+
161
+ .aparte-items__body {
162
+ display: flex;
163
+ flex: 1;
164
+ flex-direction: column;
165
+ gap: var(--aparte-space-1);
166
+ min-inline-size: 0;
167
+ padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-6);
168
+ }
169
+
170
+ .aparte-items__title {
171
+ display: -webkit-box;
172
+ overflow: hidden;
173
+ color: var(--aparte-text);
174
+ font-size: var(--aparte-font-size-base);
175
+ font-weight: var(--aparte-font-weight-semibold);
176
+ line-height: 1.3;
177
+ letter-spacing: -0.01em;
178
+ text-decoration: none;
179
+ overflow-wrap: anywhere;
180
+ -webkit-box-orient: vertical;
181
+ -webkit-line-clamp: 2;
182
+ line-clamp: 2;
183
+ }
184
+
185
+ .aparte-items__subtitle {
186
+ overflow: hidden;
187
+ color: var(--aparte-text-muted);
188
+ font-size: var(--aparte-font-size-sm);
189
+ text-overflow: ellipsis;
190
+ white-space: nowrap;
191
+ }
192
+
193
+ .aparte-items__description {
194
+ display: -webkit-box;
195
+ margin: var(--aparte-space-1) 0 0;
196
+ overflow: hidden;
197
+ color: var(--aparte-text-muted);
198
+ font-size: var(--aparte-font-size-sm);
199
+ line-height: 1.45;
200
+ overflow-wrap: anywhere;
201
+ -webkit-box-orient: vertical;
202
+ -webkit-line-clamp: 3;
203
+ line-clamp: 3;
204
+ }
205
+
206
+ /* The details sit at the foot of the text, so they line up across tiles of one row.
207
+ The first line is the one a person scans for — a price, a date — so it reads first. */
208
+ .aparte-items__meta {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 1px;
212
+ margin: auto 0 0;
213
+ padding: var(--aparte-space-3) 0 0;
214
+ list-style: none;
215
+ color: var(--aparte-text-muted);
216
+ font-size: var(--aparte-font-size-xs);
217
+ }
218
+
219
+ .aparte-items__meta > li {
220
+ overflow: hidden;
221
+ text-overflow: ellipsis;
222
+ white-space: nowrap;
223
+ }
224
+
225
+ .aparte-items__meta > li:first-child {
226
+ color: var(--aparte-text);
227
+ font-size: var(--aparte-font-size-sm);
228
+ font-weight: var(--aparte-font-weight-semibold);
229
+ }
230
+
231
+ /* The buttons sit above the stretched link, so a click on one is the button's. */
232
+ .aparte-items__actions {
233
+ position: relative;
234
+ z-index: 2;
235
+ display: flex;
236
+ flex-wrap: wrap;
237
+ gap: var(--aparte-space-3);
238
+ padding: 0 var(--aparte-space-6) var(--aparte-space-6);
239
+ }
240
+
241
+ .aparte-items__tile .aparte-items__action {
242
+ flex: 1 1 auto;
243
+ justify-content: center;
244
+ }
245
+
246
+ /* ── The link: the title's, stretched over the whole item ───────────── */
247
+
248
+ .aparte-items__link {
249
+ color: inherit;
250
+ text-decoration: none;
251
+ outline: none;
252
+ }
253
+
254
+ .aparte-items__link::after {
255
+ content: "";
256
+ position: absolute;
257
+ inset: 0;
258
+ z-index: 1;
259
+ border-radius: inherit;
260
+ }
261
+
262
+ .aparte-items__tile .aparte-items__link::after { border-radius: calc(var(--aparte-items-tile-radius) - var(--aparte-border-width)); }
263
+
264
+ /* The focus ring goes round the whole item it opens, drawn inside its edge so neither
265
+ the tile's corners nor the carousel's scroller can clip it. */
266
+ .aparte-items__link:focus-visible::after {
267
+ outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
268
+ outline-offset: calc(var(--aparte-focus-outline-width) * -1);
269
+ }
270
+
271
+ .aparte-items__item[data-link] .aparte-items__link:focus-visible {
272
+ text-decoration: underline;
273
+ text-underline-offset: 0.18em;
274
+ }
275
+
276
+ @media (hover: hover) {
277
+ .aparte-items__tile[data-link]:hover {
278
+ border-color: color-mix(in oklab, var(--aparte-text) 16%, var(--aparte-border));
279
+ box-shadow: var(--aparte-items-shadow-hover);
280
+ transform: translateY(-2px);
281
+ }
282
+
283
+ .aparte-items__tile[data-link]:hover .aparte-items__image { transform: scale(1.04); }
284
+
285
+ .aparte-items__item[data-link]:hover .aparte-items__link {
286
+ text-decoration: underline;
287
+ text-decoration-color: color-mix(in oklab, currentColor 40%, transparent);
288
+ text-underline-offset: 0.18em;
289
+ }
290
+ }
291
+
292
+ /* ── Carousel ─────────────────────────────────────────────────────────── */
293
+
294
+ .aparte-items .aparte-rich-card__carousel {
295
+ --aparte-rich-card-slide-size: var(--aparte-items-tile-size);
296
+ }
297
+
298
+ /* A deeper shadow than the kit's room below the tiles allows. */
299
+ .aparte-items .aparte-rich-card__track {
300
+ --aparte-rich-card-track-inset-end: var(--aparte-space-8);
301
+ }
302
+
303
+ /* ── Grid ─────────────────────────────────────────────────────────────── */
304
+
305
+ .aparte-items__list {
306
+ margin: 0;
307
+ padding: 0;
308
+ list-style: none;
309
+ }
310
+
311
+ .aparte-items__grid {
312
+ display: grid;
313
+ grid-template-columns: repeat(2, minmax(0, 1fr));
314
+ gap: var(--aparte-space-5);
315
+ }
316
+
317
+ .aparte-items__grid > .aparte-items__entry {
318
+ display: flex;
319
+ min-inline-size: 0;
320
+ }
321
+
322
+ .aparte-items__grid > .aparte-items__entry > .aparte-items__tile { flex: 1; }
323
+
324
+ @container aparte-items (max-width: 420px) {
325
+ .aparte-items__grid { grid-template-columns: minmax(0, 1fr); }
326
+ }
327
+
328
+ /* ── List ─────────────────────────────────────────────────────────────── */
329
+
330
+ .aparte-items__rows > .aparte-items__entry + .aparte-items__entry {
331
+ border-block-start: var(--aparte-border-width) solid var(--aparte-border);
332
+ }
333
+
334
+ .aparte-items-card[data-variant="list"] .aparte-items__header + .aparte-items__rows {
335
+ border-block-start: var(--aparte-border-width) solid var(--aparte-border);
336
+ }
337
+
338
+ .aparte-items__row {
339
+ display: grid;
340
+ grid-template-columns: var(--aparte-items-thumb-size) minmax(0, 1fr) auto;
341
+ align-items: center;
342
+ gap: var(--aparte-space-5);
343
+ padding: var(--aparte-space-4) var(--aparte-space-6);
344
+ transition: background-color var(--aparte-duration-base) var(--aparte-ease);
345
+ }
346
+
347
+ /* No item of the list has a picture: no thumbnail column. */
348
+ .aparte-items__row[data-bare] { grid-template-columns: minmax(0, 1fr) auto; }
349
+
350
+ .aparte-items__row .aparte-items__media {
351
+ --aparte-items-media-ratio: 1;
352
+ --aparte-rich-card-glyph-size: 1.5rem;
353
+ border-radius: var(--aparte-radius-md);
354
+ }
355
+
356
+ /* A hairline round the thumbnail, so a white product shot does not melt into the card. */
357
+ .aparte-items__row .aparte-items__media::after {
358
+ content: "";
359
+ position: absolute;
360
+ inset: 0;
361
+ border-radius: inherit;
362
+ box-shadow: inset 0 0 0 var(--aparte-border-width) color-mix(in oklab, var(--aparte-text) 8%, transparent);
363
+ pointer-events: none;
364
+ }
365
+
366
+ .aparte-items__row .aparte-items__body {
367
+ gap: 2px;
368
+ padding: 0;
369
+ }
370
+
371
+ .aparte-items__title-line {
372
+ display: flex;
373
+ align-items: baseline;
374
+ gap: var(--aparte-space-3);
375
+ min-inline-size: 0;
376
+ }
377
+
378
+ .aparte-items__title-line .aparte-items__badge { flex: none; }
379
+
380
+ .aparte-items__row .aparte-items__title {
381
+ font-size: var(--aparte-font-size-md);
382
+ -webkit-line-clamp: 1;
383
+ line-clamp: 1;
384
+ }
385
+
386
+ .aparte-items__row .aparte-items__description {
387
+ margin-block-start: 0;
388
+ -webkit-line-clamp: 2;
389
+ line-clamp: 2;
390
+ }
391
+
392
+ /* In a row the details run on one line, a dot between them. */
393
+ .aparte-items__row .aparte-items__meta {
394
+ flex-flow: row wrap;
395
+ column-gap: 0;
396
+ padding-block-start: var(--aparte-space-1);
397
+ }
398
+
399
+ .aparte-items__row .aparte-items__meta > li:first-child { font-size: var(--aparte-font-size-xs); }
400
+
401
+ .aparte-items__row .aparte-items__meta > li + li::before {
402
+ content: "·";
403
+ margin-inline: var(--aparte-space-2);
404
+ color: var(--aparte-text-muted);
405
+ }
406
+
407
+ .aparte-items__row .aparte-items__actions { padding: 0; }
408
+
409
+ @media (hover: hover) {
410
+ .aparte-items__row[data-link]:hover { background: var(--aparte-surface-2); }
411
+ }
412
+
413
+ /* A narrow card: the button goes under the text rather than squeezing it. */
414
+ @container aparte-items (max-width: 360px) {
415
+ .aparte-items__row {
416
+ grid-template-columns: var(--aparte-items-thumb-size) minmax(0, 1fr);
417
+ align-items: start;
418
+ }
419
+ .aparte-items__row .aparte-items__actions { grid-column: 2; }
420
+ .aparte-items__row,
421
+ .aparte-items-card[data-variant="list"] .aparte-items__header { padding-inline: var(--aparte-space-5); }
422
+ }
423
+
424
+ /* ── Skeleton ─────────────────────────────────────────────────────────── */
425
+
426
+ .aparte-items__skeleton--carousel {
427
+ display: grid;
428
+ grid-auto-flow: column;
429
+ grid-auto-columns: var(--aparte-items-tile-size);
430
+ gap: var(--aparte-space-4);
431
+ overflow: hidden;
432
+ }
433
+
434
+ .aparte-items__skeleton--grid {
435
+ display: grid;
436
+ grid-template-columns: repeat(2, minmax(0, 1fr));
437
+ gap: var(--aparte-space-5);
438
+ }
439
+
440
+ .aparte-items__skeleton--list > .aparte-items__row + .aparte-items__row {
441
+ border-block-start: var(--aparte-border-width) solid var(--aparte-border);
442
+ }
443
+
444
+ .aparte-items__skeleton .aparte-items__tile { box-shadow: none; }
445
+
446
+ .aparte-items .aparte-items__sk-media {
447
+ block-size: auto;
448
+ inline-size: 100%;
449
+ aspect-ratio: var(--aparte-items-media-ratio);
450
+ border-radius: 0;
451
+ }
452
+
453
+ .aparte-items .aparte-items__row .aparte-items__sk-media {
454
+ aspect-ratio: 1;
455
+ border-radius: var(--aparte-radius-md);
456
+ }
457
+
458
+ .aparte-items .aparte-items__sk-title { inline-size: 85%; }
459
+
460
+ @media (prefers-reduced-motion: reduce) {
461
+ .aparte-items__tile[data-link]:hover,
462
+ .aparte-items__tile[data-link]:hover .aparte-items__image { transform: none; }
463
+ }
464
+
465
+ @media (max-width: 380px) {
466
+ .aparte-items-card { --aparte-items-tile-size: 12rem; }
467
+ .aparte-items__body { padding: var(--aparte-space-4) var(--aparte-space-5) var(--aparte-space-5); }
468
+ .aparte-items__actions { padding: 0 var(--aparte-space-5) var(--aparte-space-5); }
469
+ }
470
+ `;
471
+ const W = {
472
+ cardsItemsTitle: { en: "Results", fr: "Résultats", de: "Ergebnisse", es: "Resultados", it: "Risultati", ja: "結果", ko: "결과", pt: "Resultados", ru: "Результаты", zh: "结果" },
473
+ // Russian counts with a colon so one form serves every number, as the kit's reviews do.
474
+ cardsItemsCount: { en: "{n} items", fr: "{n} éléments", de: "{n} Einträge", es: "{n} elementos", it: "{n} elementi", ja: "{n}件", ko: "{n}개 항목", pt: "{n} itens", ru: "Элементов: {n}", zh: "{n} 项" },
475
+ cardsItemsNewTab: { en: "opens in a new tab", fr: "s’ouvre dans un nouvel onglet", de: "öffnet in einem neuen Tab", es: "se abre en una pestaña nueva", it: "si apre in una nuova scheda", ja: "新しいタブで開きます", ko: "새 탭에서 열림", pt: "abre em uma nova aba", ru: "откроется в новой вкладке", zh: "在新标签页中打开" }
476
+ };
477
+ const ITEMS_MAX = 10;
478
+ const META_MAX = 3;
479
+ const ACTIONS_MAX = 2;
480
+ function readImage(value, alt) {
481
+ let src = value;
482
+ let text = alt;
483
+ if (value && typeof value === "object") {
484
+ const o = value;
485
+ src = o["src"] ?? o["url"];
486
+ text = o["alt"] ?? alt;
487
+ }
488
+ const url = webUrl(src);
489
+ return url ? { src: url, alt: str(text) ?? "" } : void 0;
490
+ }
491
+ function readAction(value) {
492
+ if (!value || typeof value !== "object") return null;
493
+ const o = value;
494
+ const label = str(o["label"]);
495
+ if (!label) return null;
496
+ const action = { label };
497
+ if (o["url"] !== void 0) {
498
+ const url = webUrl(o["url"]);
499
+ if (!url) return null;
500
+ action.url = url;
501
+ }
502
+ if (typeof o["id"] === "string") action.id = o["id"];
503
+ if (o["primary"] === true) action.primary = true;
504
+ return action.url || action.id !== void 0 ? action : null;
505
+ }
506
+ function readItem(raw) {
507
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
508
+ const r = raw;
509
+ const title = str(r["title"] ?? r["name"]);
510
+ if (!title) return null;
511
+ const meta = (Array.isArray(r["meta"]) ? r["meta"] : r["meta"] !== void 0 ? [r["meta"]] : []).map(str).filter((m) => m !== void 0).slice(0, META_MAX);
512
+ const actions = (Array.isArray(r["actions"]) ? r["actions"] : []).map(readAction).filter((a) => a !== null).slice(0, ACTIONS_MAX);
513
+ const url = safeUrl(r["url"] ?? r["link"] ?? r["href"]);
514
+ return {
515
+ title,
516
+ subtitle: str(r["subtitle"]),
517
+ description: str(r["description"] ?? r["snippet"] ?? r["summary"]),
518
+ image: readImage(r["image"] ?? r["imageUrl"] ?? r["image_url"] ?? r["thumbnail"], r["alt"] ?? r["imageAlt"]),
519
+ badge: str(r["badge"]),
520
+ meta,
521
+ ...url ? { url } : {},
522
+ actions,
523
+ id: str(r["id"])
524
+ };
525
+ }
526
+ function readItems(raw) {
527
+ let list2;
528
+ let title;
529
+ if (Array.isArray(raw)) {
530
+ list2 = raw;
531
+ } else if (raw && typeof raw === "object" && Array.isArray(raw["items"])) {
532
+ const r = raw;
533
+ list2 = r["items"];
534
+ title = str(r["title"]);
535
+ } else {
536
+ list2 = [raw];
537
+ }
538
+ const items = list2.map(readItem).filter((i) => i !== null).slice(0, ITEMS_MAX);
539
+ return items.length ? { ...title ? { title } : {}, items } : null;
540
+ }
541
+ function countText(config, n) {
542
+ return word(config, "cardsItemsCount", W.cardsItemsCount, { n: formatNumber(config, n) });
543
+ }
544
+ function labelOf(data, config) {
545
+ if (data?.title) return data.title;
546
+ const items = data?.items ?? [];
547
+ if (items.length === 1) return items[0].title;
548
+ if (items.length > 1) return countText(config, items.length);
549
+ return word(config, "cardsItemsTitle", W.cardsItemsTitle);
550
+ }
551
+ function media(item, badge) {
552
+ const box = el(
553
+ "div",
554
+ { class: "aparte-items__media", attrs: { "data-empty": !item.image } },
555
+ glyph(IMAGE_ICON, "aparte-rich-card__glyph aparte-items__placeholder")
556
+ );
557
+ if (item.image) {
558
+ const img = el("img", {
559
+ class: "aparte-items__image",
560
+ attrs: {
561
+ src: item.image.src,
562
+ alt: item.image.alt,
563
+ loading: "lazy",
564
+ decoding: "async",
565
+ // A third-party picture learns nothing of the page that shows it.
566
+ referrerpolicy: "no-referrer"
567
+ }
568
+ });
569
+ img.addEventListener("error", () => {
570
+ img.remove();
571
+ box.setAttribute("data-empty", "");
572
+ }, { once: true });
573
+ box.append(img);
574
+ }
575
+ if (badge && item.badge) box.append(badgeOf(item.badge, "aparte-badge--solid"));
576
+ return box;
577
+ }
578
+ function badgeOf(text, fill) {
579
+ return el("span", { class: `aparte-badge aparte-badge--primary ${fill} aparte-badge--sm aparte-items__badge`, text });
580
+ }
581
+ function titleOf(ctx, item) {
582
+ if (!item.url) return el("span", { class: "aparte-items__title", text: item.title });
583
+ const hint = el("span", { class: "aparte-sr-only" });
584
+ ctx.bind(hint, () => ` (${word(ctx.config, "cardsItemsNewTab", W.cardsItemsNewTab)})`);
585
+ return el("a", {
586
+ class: "aparte-items__title aparte-items__link",
587
+ attrs: { href: item.url, target: "_blank", rel: "noopener noreferrer" }
588
+ }, item.title, hint);
589
+ }
590
+ function details(item) {
591
+ return [
592
+ item.subtitle ? el("span", { class: "aparte-items__subtitle", text: item.subtitle }) : null,
593
+ item.description ? el("p", { class: "aparte-items__description", text: item.description }) : null,
594
+ item.meta.length ? el("ul", { class: "aparte-items__meta", attrs: { role: "list" } }, ...item.meta.map((m) => el("li", { text: m }))) : null
595
+ ];
596
+ }
597
+ function actionsOf(ctx, item, max) {
598
+ const buttons = item.actions.map((action) => ctx.action(action, item, "aparte-items__action")).filter((b) => b !== null).slice(0, max);
599
+ return buttons.length ? el("div", { class: "aparte-items__actions" }, ...buttons) : null;
600
+ }
601
+ function tile(ctx, item) {
602
+ const body = el("div", { class: "aparte-items__body" }, titleOf(ctx, item), ...details(item));
603
+ return el(
604
+ "article",
605
+ { class: "aparte-items__item aparte-items__tile", attrs: { "data-link": !!item.url } },
606
+ media(item, true),
607
+ body,
608
+ actionsOf(ctx, item, ACTIONS_MAX)
609
+ );
610
+ }
611
+ function row(ctx, item, pictures) {
612
+ const head = el("div", { class: "aparte-items__title-line" }, titleOf(ctx, item), item.badge ? badgeOf(item.badge, "aparte-badge--soft") : null);
613
+ const body = el("div", { class: "aparte-items__body" }, head, ...details(item));
614
+ return el(
615
+ "article",
616
+ { class: "aparte-items__item aparte-items__row", attrs: { "data-link": !!item.url, "data-bare": !pictures } },
617
+ pictures ? media(item, false) : null,
618
+ body,
619
+ actionsOf(ctx, item, 1)
620
+ );
621
+ }
622
+ function header(ctx, title, count) {
623
+ if (!title) return null;
624
+ const head = el("div", { class: "aparte-items__header" }, el("span", { class: "aparte-items__heading", text: title }));
625
+ if (count > 1) {
626
+ const n = el("span", { class: "aparte-items__count" });
627
+ ctx.bind(n, () => countText(ctx.config, count));
628
+ head.append(n);
629
+ }
630
+ return head;
631
+ }
632
+ function list(ctx, data, draw, className) {
633
+ const ul = el(
634
+ "ul",
635
+ { class: `aparte-items__list ${className}`, attrs: { role: "list" } },
636
+ ...data.items.map((item) => el("li", { class: "aparte-items__entry" }, draw(ctx, item)))
637
+ );
638
+ ctx.bind(ul, () => labelOf(data, ctx.config), "aria-label");
639
+ return ul;
640
+ }
641
+ function render(data, ctx) {
642
+ const count = data.items.length;
643
+ const root = el("div", { class: "aparte-items", attrs: { "data-count": count } }, header(ctx, data.title, count));
644
+ if (ctx.variant === "list") {
645
+ const pictures = data.items.some((item) => item.image);
646
+ root.append(list(ctx, data, (c, item) => row(c, item, pictures), "aparte-items__rows"));
647
+ } else if (count === 1) {
648
+ root.append(tile(ctx, data.items[0]));
649
+ } else if (ctx.variant === "grid") {
650
+ root.append(list(ctx, data, tile, "aparte-items__grid"));
651
+ } else {
652
+ root.append(carousel(ctx, data.items.map((item) => tile(ctx, item)), { label: () => labelOf(data, ctx.config) }));
653
+ }
654
+ return [root];
655
+ }
656
+ function skeleton(partial, ctx) {
657
+ const block = (cls, kind = "rect") => el("span", { class: `aparte-skeleton aparte-skeleton--${kind} ${cls}`, attrs: { "aria-hidden": "true" } });
658
+ const text = () => el("div", { class: "aparte-items__body" }, block("aparte-items__sk-title", "text"), block("aparte-items__sk-line", "text"));
659
+ const root = el("div", { class: "aparte-items" }, header(ctx, partial?.title, 0));
660
+ const shapes = ctx.variant === "list" ? [0, 1, 2].map(() => el("div", { class: "aparte-items__item aparte-items__row" }, block("aparte-items__sk-media"), text())) : Array.from({ length: ctx.variant === "grid" ? 2 : 3 }, () => el("div", { class: "aparte-items__item aparte-items__tile" }, block("aparte-items__sk-media"), text()));
661
+ root.append(el("div", { class: `aparte-items__skeleton aparte-items__skeleton--${ctx.variant}`, attrs: { "aria-hidden": "true" } }, ...shapes));
662
+ return [root];
663
+ }
664
+ function fallback(data, ctx) {
665
+ const titles = data.items.map((i) => i.title);
666
+ let joined;
667
+ try {
668
+ joined = new Intl.ListFormat(intlTag(ctx.config) ?? languageOf(ctx.config), { type: "conjunction" }).format(titles);
669
+ } catch {
670
+ joined = titles.join(", ");
671
+ }
672
+ return `${data.title ?? word(ctx.config, "cardsItemsTitle", W.cardsItemsTitle)}: ${joined}.`;
673
+ }
674
+ const itemsCard = defineCard({
675
+ name: "items",
676
+ variants: ["carousel", "list", "grid"],
677
+ styles: itemsStyles,
678
+ icon: ITEMS_ICON,
679
+ normalize: readItems,
680
+ render,
681
+ skeleton,
682
+ label: (data, ctx) => labelOf(data, ctx.config),
683
+ fallback,
684
+ partial: (input) => {
685
+ const title = str(input["title"] ?? input["query"] ?? input["q"]);
686
+ return title ? { title } : null;
687
+ }
688
+ });
689
+ function setupItemsCard(options = {}) {
690
+ return itemsCard.setup(options);
691
+ }
692
+ export {
693
+ ITEMS_MAX,
694
+ itemsCard,
695
+ readItems,
696
+ setupItemsCard
697
+ };
698
+ //# sourceMappingURL=items.js.map