emdash-plugin-avdeb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1192 @@
1
+ ---
2
+ /**
3
+ * Renders AVDEB products in every layout: grid, carousel, list, spotlight, and the single-product
4
+ * variants (card / horizontal / hero). Output is links and images; the only script is the
5
+ * optional carousel arrows (no network, no cookies). All styles use :where() so site CSS wins.
6
+ */
7
+ import type { ProductVariant } from "../constants.ts";
8
+ import type { EmbedData } from "../payload.ts";
9
+ import {
10
+ buildCards,
11
+ disclosureFor,
12
+ effectiveColumns,
13
+ resolveDisplay,
14
+ rootStyle,
15
+ viewAllHref,
16
+ relFor,
17
+ type DisplayOverrides,
18
+ } from "../view.ts";
19
+
20
+ interface Props {
21
+ data: EmbedData;
22
+ overrides?: DisplayOverrides;
23
+ heading?: string;
24
+ intro?: string;
25
+ viewAll?: boolean;
26
+ /** Level of the section heading (card titles use the next level). Default 2. */
27
+ headingLevel?: 2 | 3 | 4 | 5;
28
+ /** Render one featured product instead of a collection. */
29
+ single?: { variant: ProductVariant; badge?: string; note?: string };
30
+ class?: string;
31
+ }
32
+
33
+ const {
34
+ data,
35
+ overrides = {},
36
+ heading = "",
37
+ intro = "",
38
+ viewAll = true,
39
+ headingLevel = 2,
40
+ single,
41
+ class: className,
42
+ } = Astro.props;
43
+
44
+ const display = resolveDisplay(data.display, overrides);
45
+ const locale = Astro.currentLocale ?? "en-US";
46
+ const cards = buildCards(
47
+ single ? data.products.slice(0, 1) : data.products,
48
+ display,
49
+ { locale },
50
+ );
51
+ const layout = single ? "single" : display.layout;
52
+ const isCarousel = layout === "carousel";
53
+ const columns = single ? 1 : effectiveColumns(display, cards.length);
54
+ const allHref =
55
+ !single && viewAll
56
+ ? viewAllHref(data.query, data.products, data.ref)
57
+ : null;
58
+ const disclosure = disclosureFor(display, data.ref);
59
+ const featuredFirst = single
60
+ ? single.variant !== "card"
61
+ : layout === "spotlight";
62
+
63
+ const Heading = `h${headingLevel}`;
64
+ const CardTitle = `h${Math.min(6, headingLevel + 1)}`;
65
+ const label = heading || (single ? cards[0]?.title : "") || "AVDEB products";
66
+ const hasHeader = Boolean(heading || intro || allHref || isCarousel);
67
+ ---
68
+
69
+ {cards.length === 0 ? (
70
+ <Fragment set:html="<!-- AVDEB Products: nothing to show for this embed -->" />
71
+ ) : (
72
+ <section
73
+ class:list={[
74
+ "avdeb",
75
+ `avdeb--${layout}`,
76
+ single && `avdeb--${single.variant}`,
77
+ `avdeb--${display.cardStyle}`,
78
+ className,
79
+ ]}
80
+ data-theme={display.theme}
81
+ data-avdeb-carousel={isCarousel ? "" : undefined}
82
+ style={rootStyle(display, columns)}
83
+ aria-label={label}
84
+ >
85
+ {hasHeader && (
86
+ <header class="avdeb__header">
87
+ {(heading || intro) && (
88
+ <div class="avdeb__titles">
89
+ {heading && (
90
+ <Heading class="avdeb__heading">{heading}</Heading>
91
+ )}
92
+ {intro && <p class="avdeb__intro">{intro}</p>}
93
+ </div>
94
+ )}
95
+ {(allHref || isCarousel) && (
96
+ <div class="avdeb__tools">
97
+ {allHref && (
98
+ <a
99
+ class="avdeb__all"
100
+ href={allHref}
101
+ rel={relFor(allHref)}
102
+ target={display.newTab ? "_blank" : undefined}
103
+ >
104
+ View all
105
+ <svg
106
+ viewBox="0 0 16 16"
107
+ width="16"
108
+ height="16"
109
+ aria-hidden="true"
110
+ >
111
+ <path
112
+ d="M6 3.5 10.5 8 6 12.5"
113
+ fill="none"
114
+ stroke="currentColor"
115
+ stroke-width="1.6"
116
+ stroke-linecap="round"
117
+ stroke-linejoin="round"
118
+ />
119
+ </svg>
120
+ </a>
121
+ )}
122
+ {isCarousel && (
123
+ <div class="avdeb__nav">
124
+ <button
125
+ type="button"
126
+ class="avdeb__navbtn"
127
+ data-avdeb-prev
128
+ aria-label="Previous products"
129
+ hidden
130
+ >
131
+ <svg
132
+ viewBox="0 0 16 16"
133
+ width="16"
134
+ height="16"
135
+ aria-hidden="true"
136
+ >
137
+ <path
138
+ d="M10 3.5 5.5 8 10 12.5"
139
+ fill="none"
140
+ stroke="currentColor"
141
+ stroke-width="1.6"
142
+ stroke-linecap="round"
143
+ stroke-linejoin="round"
144
+ />
145
+ </svg>
146
+ </button>
147
+ <button
148
+ type="button"
149
+ class="avdeb__navbtn"
150
+ data-avdeb-next
151
+ aria-label="Next products"
152
+ hidden
153
+ >
154
+ <svg
155
+ viewBox="0 0 16 16"
156
+ width="16"
157
+ height="16"
158
+ aria-hidden="true"
159
+ >
160
+ <path
161
+ d="M6 3.5 10.5 8 6 12.5"
162
+ fill="none"
163
+ stroke="currentColor"
164
+ stroke-width="1.6"
165
+ stroke-linecap="round"
166
+ stroke-linejoin="round"
167
+ />
168
+ </svg>
169
+ </button>
170
+ </div>
171
+ )}
172
+ </div>
173
+ )}
174
+ </header>
175
+ )}
176
+
177
+ <ul
178
+ class="avdeb__items"
179
+ role="list"
180
+ data-avdeb-track={isCarousel ? "" : undefined}
181
+ tabindex={isCarousel ? 0 : undefined}
182
+ aria-label={isCarousel ? `${label} — scroll for more` : undefined}
183
+ >
184
+ {cards.map((c, i) => {
185
+ const featured = featuredFirst && i === 0;
186
+ return (
187
+ <li class="avdeb__item">
188
+ <article
189
+ class:list={[
190
+ "avdeb-card",
191
+ featured && "avdeb-card--featured",
192
+ single?.variant === "hero" &&
193
+ "avdeb-card--hero",
194
+ ]}
195
+ >
196
+ <div class="avdeb-card__media">
197
+ {c.image ? (
198
+ <img
199
+ src={c.image}
200
+ alt={c.title}
201
+ width={c.width}
202
+ height={c.height}
203
+ loading={single ? "eager" : "lazy"}
204
+ decoding="async"
205
+ />
206
+ ) : (
207
+ <svg
208
+ class="avdeb-card__placeholder"
209
+ viewBox="0 0 24 24"
210
+ width="40"
211
+ height="40"
212
+ aria-hidden="true"
213
+ >
214
+ <path
215
+ d="M4 7.5 12 3l8 4.5v9L12 21l-8-4.5v-9Z M4 7.5l8 4.5 8-4.5 M12 12v9"
216
+ fill="none"
217
+ stroke="currentColor"
218
+ stroke-width="1.3"
219
+ stroke-linejoin="round"
220
+ />
221
+ </svg>
222
+ )}
223
+ {(single?.badge || c.discount || c.isNew) && (
224
+ <div
225
+ class="avdeb-card__badges"
226
+ aria-hidden="true"
227
+ >
228
+ {single?.badge && (
229
+ <span class="avdeb-badge avdeb-badge--pick">
230
+ {single.badge}
231
+ </span>
232
+ )}
233
+ {c.discount && (
234
+ <span class="avdeb-badge avdeb-badge--sale">
235
+ −{c.discount}%
236
+ </span>
237
+ )}
238
+ {c.isNew && (
239
+ <span class="avdeb-badge">New</span>
240
+ )}
241
+ </div>
242
+ )}
243
+ </div>
244
+ <div class="avdeb-card__body">
245
+ <div class="avdeb-card__main">
246
+ {c.creator && (
247
+ <p class="avdeb-card__by">
248
+ by {c.creator}
249
+ </p>
250
+ )}
251
+ <CardTitle class="avdeb-card__title">
252
+ <a
253
+ class="avdeb-card__link"
254
+ href={c.href}
255
+ rel={c.rel}
256
+ target={c.target}
257
+ >
258
+ {c.title}
259
+ </a>
260
+ </CardTitle>
261
+ {single?.note && (
262
+ <p class="avdeb-card__note">
263
+ {single.note}
264
+ </p>
265
+ )}
266
+ </div>
267
+ <div class="avdeb-card__footer">
268
+ {c.price && (
269
+ <p class="avdeb-card__price">
270
+ {c.compareAt && (
271
+ <del>
272
+ <span class="avdeb-sr">
273
+ Was{" "}
274
+ </span>
275
+ {c.compareAt}
276
+ </del>
277
+ )}
278
+ <span>
279
+ {c.compareAt && (
280
+ <span class="avdeb-sr">
281
+ now{" "}
282
+ </span>
283
+ )}
284
+ {c.price}
285
+ </span>
286
+ </p>
287
+ )}
288
+ <span
289
+ class="avdeb-card__cta"
290
+ aria-hidden="true"
291
+ >
292
+ <span class="avdeb-card__cta-label">
293
+ {display.ctaLabel}
294
+ </span>
295
+ <svg
296
+ viewBox="0 0 16 16"
297
+ width="14"
298
+ height="14"
299
+ >
300
+ <path
301
+ d="M3 8h9.5M8.5 4 12.5 8l-4 4"
302
+ fill="none"
303
+ stroke="currentColor"
304
+ stroke-width="1.6"
305
+ stroke-linecap="round"
306
+ stroke-linejoin="round"
307
+ />
308
+ </svg>
309
+ </span>
310
+ </div>
311
+ </div>
312
+ </article>
313
+ </li>
314
+ );
315
+ })}
316
+ </ul>
317
+
318
+ {disclosure && (
319
+ <p class="avdeb__disclosure">
320
+ <svg
321
+ viewBox="0 0 16 16"
322
+ width="14"
323
+ height="14"
324
+ aria-hidden="true"
325
+ >
326
+ <circle
327
+ cx="8"
328
+ cy="8"
329
+ r="6.25"
330
+ fill="none"
331
+ stroke="currentColor"
332
+ stroke-width="1.3"
333
+ />
334
+ <path
335
+ d="M8 7.2v4M8 4.8v.1"
336
+ fill="none"
337
+ stroke="currentColor"
338
+ stroke-width="1.5"
339
+ stroke-linecap="round"
340
+ />
341
+ </svg>
342
+ {disclosure}
343
+ </p>
344
+ )}
345
+ </section>
346
+ )}
347
+
348
+ <script>
349
+ function setup(root: HTMLElement) {
350
+ if (root.dataset.avdebReady) return;
351
+ root.dataset.avdebReady = "1";
352
+ const track = root.querySelector<HTMLElement>("[data-avdeb-track]");
353
+ const prev = root.querySelector<HTMLButtonElement>("[data-avdeb-prev]");
354
+ const next = root.querySelector<HTMLButtonElement>("[data-avdeb-next]");
355
+ if (!track || !prev || !next) return;
356
+
357
+ const rtl = getComputedStyle(track).direction === "rtl";
358
+ const smooth = !matchMedia("(prefers-reduced-motion: reduce)").matches;
359
+ const update = () => {
360
+ const max = track.scrollWidth - track.clientWidth - 2;
361
+ const x = Math.abs(track.scrollLeft);
362
+ prev.disabled = x <= 2;
363
+ next.disabled = x >= max;
364
+ const overflow = max > 0;
365
+ prev.hidden = next.hidden = !overflow;
366
+ root.toggleAttribute("data-avdeb-overflow", overflow);
367
+ };
368
+ const go = (dir: 1 | -1) =>
369
+ track.scrollBy({
370
+ left: dir * (rtl ? -1 : 1) * track.clientWidth * 0.9,
371
+ behavior: smooth ? "smooth" : "auto",
372
+ });
373
+
374
+ prev.addEventListener("click", () => go(-1));
375
+ next.addEventListener("click", () => go(1));
376
+ track.addEventListener("scroll", update, { passive: true });
377
+ track.addEventListener("keydown", (e) => {
378
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
379
+ e.preventDefault();
380
+ go((e.key === "ArrowRight") !== rtl ? 1 : -1);
381
+ }
382
+ });
383
+ if ("ResizeObserver" in window)
384
+ new ResizeObserver(update).observe(track);
385
+ update();
386
+ }
387
+
388
+ const run = () =>
389
+ document
390
+ .querySelectorAll<HTMLElement>("[data-avdeb-carousel]")
391
+ .forEach(setup);
392
+ run();
393
+ document.addEventListener("astro:page-load", run);
394
+ </script>
395
+
396
+ <style is:global>
397
+ /* ── Tokens ─────────────────────────────────────────────────────────────── */
398
+ :where(.avdeb) {
399
+ --avdeb-accent: #3b6548;
400
+ --avdeb-accent-contrast: #fff;
401
+ --avdeb-accent-text: var(--avdeb-accent);
402
+ --avdeb-surface: transparent;
403
+ --avdeb-card-bg: rgba(127, 127, 127, 0.045);
404
+ --avdeb-border: rgba(127, 127, 127, 0.22);
405
+ --avdeb-border-soft: rgba(127, 127, 127, 0.12);
406
+ --avdeb-media-bg: rgba(127, 127, 127, 0.08);
407
+ --avdeb-muted-opacity: 0.72;
408
+ --avdeb-badge-bg: rgba(255, 255, 255, 0.92);
409
+ --avdeb-badge-text: #141414;
410
+ --avdeb-shadow:
411
+ 0 1px 2px rgba(15, 23, 15, 0.05),
412
+ 0 10px 28px -14px rgba(15, 23, 15, 0.25);
413
+ --avdeb-shadow-hover:
414
+ 0 2px 6px rgba(15, 23, 15, 0.06),
415
+ 0 22px 44px -18px rgba(15, 23, 15, 0.35);
416
+ --avdeb-gap: 1rem;
417
+ --avdeb-pad: 0.9rem 1rem 1rem;
418
+ --avdeb-font-title: inherit;
419
+ --avdeb-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
420
+ --avdeb-cols: 3;
421
+ --avdeb-radius: 16px;
422
+ --avdeb-ratio: 1 / 1;
423
+
424
+ container: avdeb / inline-size;
425
+ margin-block: 2em;
426
+ color: inherit;
427
+ font: inherit;
428
+ }
429
+
430
+ @supports (color: color-mix(in srgb, red 50%, blue)) {
431
+ :where(.avdeb) {
432
+ --avdeb-card-bg: color-mix(in srgb, currentColor 3.5%, transparent);
433
+ --avdeb-border: color-mix(in srgb, currentColor 16%, transparent);
434
+ --avdeb-border-soft: color-mix(
435
+ in srgb,
436
+ currentColor 8%,
437
+ transparent
438
+ );
439
+ --avdeb-media-bg: color-mix(in srgb, currentColor 6%, transparent);
440
+ --avdeb-muted: color-mix(in srgb, currentColor 64%, transparent);
441
+ --avdeb-muted-opacity: 1;
442
+ /* Readable on any background: pulls the accent toward the site's text colour. */
443
+ --avdeb-accent-text: color-mix(
444
+ in srgb,
445
+ var(--avdeb-accent) 70%,
446
+ currentColor
447
+ );
448
+ }
449
+ }
450
+
451
+ :where(.avdeb[data-theme="light"]),
452
+ :where(.avdeb[data-theme="auto"]) {
453
+ --avdeb-surface: #f4f6f3;
454
+ --avdeb-card-bg: #fff;
455
+ --avdeb-border: #dde3dc;
456
+ --avdeb-border-soft: #e8ece7;
457
+ --avdeb-media-bg: #eceee9;
458
+ --avdeb-muted: #5d665e;
459
+ --avdeb-muted-opacity: 1;
460
+ color: #19201a;
461
+ }
462
+
463
+ :where(.avdeb[data-theme="dark"]) {
464
+ --avdeb-surface: #0f1310;
465
+ --avdeb-card-bg: #181d19;
466
+ --avdeb-border: #2b332c;
467
+ --avdeb-border-soft: #232a24;
468
+ --avdeb-media-bg: #212822;
469
+ --avdeb-muted: #a3ada4;
470
+ --avdeb-muted-opacity: 1;
471
+ --avdeb-badge-bg: rgba(15, 19, 16, 0.86);
472
+ --avdeb-badge-text: #f2f4f2;
473
+ --avdeb-shadow:
474
+ 0 1px 2px rgba(0, 0, 0, 0.4), 0 14px 32px -16px rgba(0, 0, 0, 0.7);
475
+ --avdeb-shadow-hover:
476
+ 0 2px 6px rgba(0, 0, 0, 0.45), 0 24px 48px -18px rgba(0, 0, 0, 0.85);
477
+ color: #eef1ee;
478
+ }
479
+
480
+ @media (prefers-color-scheme: dark) {
481
+ :where(.avdeb[data-theme="auto"]) {
482
+ --avdeb-surface: #0f1310;
483
+ --avdeb-card-bg: #181d19;
484
+ --avdeb-border: #2b332c;
485
+ --avdeb-border-soft: #232a24;
486
+ --avdeb-media-bg: #212822;
487
+ --avdeb-muted: #a3ada4;
488
+ --avdeb-badge-bg: rgba(15, 19, 16, 0.86);
489
+ --avdeb-badge-text: #f2f4f2;
490
+ --avdeb-shadow:
491
+ 0 1px 2px rgba(0, 0, 0, 0.4),
492
+ 0 14px 32px -16px rgba(0, 0, 0, 0.7);
493
+ --avdeb-shadow-hover:
494
+ 0 2px 6px rgba(0, 0, 0, 0.45),
495
+ 0 24px 48px -18px rgba(0, 0, 0, 0.85);
496
+ color: #eef1ee;
497
+ }
498
+ }
499
+
500
+ @supports (color: color-mix(in srgb, red 50%, blue)) {
501
+ :where(.avdeb[data-theme="dark"]) {
502
+ --avdeb-accent-text: color-mix(
503
+ in srgb,
504
+ var(--avdeb-accent) 55%,
505
+ #fff
506
+ );
507
+ }
508
+ @media (prefers-color-scheme: dark) {
509
+ :where(.avdeb[data-theme="auto"]) {
510
+ --avdeb-accent-text: color-mix(
511
+ in srgb,
512
+ var(--avdeb-accent) 55%,
513
+ #fff
514
+ );
515
+ }
516
+ }
517
+ }
518
+
519
+ :where(
520
+ .avdeb[data-theme="light"],
521
+ .avdeb[data-theme="dark"],
522
+ .avdeb[data-theme="auto"]
523
+ ) {
524
+ background: var(--avdeb-surface);
525
+ padding: clamp(1rem, 2.5vw, 1.75rem);
526
+ border-radius: calc(var(--avdeb-radius) + 6px);
527
+ }
528
+
529
+ :where(.avdeb) :where(*, *::before, *::after) {
530
+ box-sizing: border-box;
531
+ }
532
+
533
+ :where(.avdeb-sr) {
534
+ position: absolute;
535
+ inline-size: 1px;
536
+ block-size: 1px;
537
+ overflow: hidden;
538
+ clip-path: inset(50%);
539
+ white-space: nowrap;
540
+ }
541
+
542
+ /* ── Header ─────────────────────────────────────────────────────────────── */
543
+ :where(.avdeb__header) {
544
+ display: flex;
545
+ flex-wrap: wrap;
546
+ align-items: flex-end;
547
+ justify-content: space-between;
548
+ gap: 0.75rem 1.5rem;
549
+ margin-block-end: 1.1rem;
550
+ }
551
+
552
+ :where(.avdeb__heading) {
553
+ margin: 0;
554
+ font-family: var(--avdeb-font-title);
555
+ font-size: clamp(1.15rem, 1rem + 0.7vw, 1.55rem);
556
+ line-height: 1.2;
557
+ letter-spacing: -0.01em;
558
+ }
559
+
560
+ :where(.avdeb__intro) {
561
+ margin: 0.4rem 0 0;
562
+ max-inline-size: 62ch;
563
+ color: var(--avdeb-muted, currentColor);
564
+ opacity: var(--avdeb-muted-opacity);
565
+ line-height: 1.55;
566
+ }
567
+
568
+ :where(.avdeb__tools) {
569
+ display: flex;
570
+ align-items: center;
571
+ gap: 0.75rem;
572
+ margin-inline-start: auto;
573
+ }
574
+
575
+ :where(.avdeb__all) {
576
+ display: inline-flex;
577
+ align-items: center;
578
+ gap: 0.3em;
579
+ color: var(--avdeb-accent-text);
580
+ font-size: 0.95em;
581
+ font-weight: 600;
582
+ text-decoration: none;
583
+ white-space: nowrap;
584
+ }
585
+
586
+ :where(.avdeb__all:hover) {
587
+ text-decoration: underline;
588
+ text-underline-offset: 0.2em;
589
+ }
590
+
591
+ :where(.avdeb__all svg, .avdeb-card__cta svg, .avdeb-btn svg) {
592
+ flex: none;
593
+ transition: transform 0.25s var(--avdeb-ease);
594
+ }
595
+
596
+ :where(.avdeb__all:hover svg) {
597
+ transform: translateX(2px);
598
+ }
599
+
600
+ :where(.avdeb__nav) {
601
+ display: flex;
602
+ gap: 0.4rem;
603
+ }
604
+
605
+ :where(.avdeb__navbtn) {
606
+ display: grid;
607
+ place-items: center;
608
+ inline-size: 2.4rem;
609
+ block-size: 2.4rem;
610
+ padding: 0;
611
+ border: 1px solid var(--avdeb-border);
612
+ border-radius: 999px;
613
+ background: var(--avdeb-card-bg);
614
+ color: inherit;
615
+ cursor: pointer;
616
+ transition:
617
+ border-color 0.2s,
618
+ color 0.2s,
619
+ opacity 0.2s,
620
+ transform 0.2s var(--avdeb-ease);
621
+ }
622
+
623
+ :where(.avdeb__navbtn:hover:not(:disabled)) {
624
+ border-color: var(--avdeb-accent);
625
+ color: var(--avdeb-accent-text);
626
+ }
627
+
628
+ :where(.avdeb__navbtn:active:not(:disabled)) {
629
+ transform: scale(0.94);
630
+ }
631
+
632
+ :where(.avdeb__navbtn:disabled) {
633
+ opacity: 0.35;
634
+ cursor: default;
635
+ }
636
+
637
+ :where(.avdeb__navbtn[hidden]) {
638
+ display: none;
639
+ }
640
+
641
+ :where(.avdeb:dir(rtl))
642
+ :where(.avdeb__navbtn svg, .avdeb__all svg, .avdeb-card__cta svg) {
643
+ scale: -1 1;
644
+ }
645
+
646
+ /* ── Collection layouts ─────────────────────────────────────────────────── */
647
+ :where(.avdeb__items) {
648
+ display: grid;
649
+ grid-template-columns: repeat(var(--avdeb-cols), minmax(0, 1fr));
650
+ gap: var(--avdeb-gap);
651
+ margin: 0;
652
+ padding: 0;
653
+ list-style: none;
654
+ }
655
+
656
+ :where(.avdeb__item) {
657
+ container: avdeb-item / inline-size;
658
+ display: flex;
659
+ min-inline-size: 0;
660
+ margin: 0;
661
+ padding: 0;
662
+ }
663
+
664
+ @container avdeb (max-width: 46rem) {
665
+ :where(.avdeb--grid, .avdeb--spotlight) :where(.avdeb__items) {
666
+ grid-template-columns: repeat(
667
+ min(var(--avdeb-cols), 3),
668
+ minmax(0, 1fr)
669
+ );
670
+ }
671
+ }
672
+
673
+ @container avdeb (max-width: 34rem) {
674
+ :where(.avdeb--grid, .avdeb--spotlight) :where(.avdeb__items) {
675
+ grid-template-columns: repeat(
676
+ min(var(--avdeb-cols), 2),
677
+ minmax(0, 1fr)
678
+ );
679
+ }
680
+ }
681
+
682
+ @container avdeb (max-width: 19rem) {
683
+ :where(.avdeb--grid, .avdeb--spotlight) :where(.avdeb__items) {
684
+ grid-template-columns: minmax(0, 1fr);
685
+ }
686
+ }
687
+
688
+ @supports not (container-type: inline-size) {
689
+ @media (max-width: 640px) {
690
+ :where(.avdeb--grid, .avdeb--spotlight) :where(.avdeb__items) {
691
+ grid-template-columns: repeat(
692
+ min(var(--avdeb-cols), 2),
693
+ minmax(0, 1fr)
694
+ );
695
+ }
696
+ }
697
+ }
698
+
699
+ /* Carousel: native scroll-snap; arrows appear once the script runs and there's overflow. */
700
+ :where(.avdeb--carousel) :where(.avdeb__items) {
701
+ grid-template-columns: none;
702
+ grid-auto-flow: column;
703
+ grid-auto-columns: calc(
704
+ (100% - (var(--avdeb-cols) - 1) * var(--avdeb-gap)) /
705
+ var(--avdeb-cols)
706
+ );
707
+ overflow-x: auto;
708
+ overscroll-behavior-x: contain;
709
+ scroll-snap-type: x mandatory;
710
+ scrollbar-width: none;
711
+ padding: 0.35rem 0.35rem 1.25rem;
712
+ margin: -0.35rem -0.35rem -1.25rem;
713
+ scroll-padding-inline: 0.35rem;
714
+ }
715
+
716
+ :where(.avdeb--carousel) :where(.avdeb__items)::-webkit-scrollbar {
717
+ display: none;
718
+ }
719
+
720
+ :where(.avdeb--carousel) :where(.avdeb__items:focus-visible) {
721
+ outline: 2px solid var(--avdeb-accent);
722
+ outline-offset: 2px;
723
+ border-radius: var(--avdeb-radius);
724
+ }
725
+
726
+ :where(.avdeb--carousel) :where(.avdeb__item) {
727
+ scroll-snap-align: start;
728
+ }
729
+
730
+ @container avdeb (max-width: 46rem) {
731
+ :where(.avdeb--carousel) :where(.avdeb__items) {
732
+ grid-auto-columns: calc((100% - var(--avdeb-gap)) / 2.2);
733
+ }
734
+ }
735
+
736
+ @container avdeb (max-width: 30rem) {
737
+ :where(.avdeb--carousel) :where(.avdeb__items) {
738
+ grid-auto-columns: 78%;
739
+ }
740
+ }
741
+
742
+ :where(.avdeb--list) :where(.avdeb__items) {
743
+ grid-template-columns: minmax(0, 1fr);
744
+ gap: 0.75rem;
745
+ }
746
+
747
+ /* Spotlight
748
+ /* Spotlight: the first product spans the row as a featured card. */
749
+ :where(.avdeb--spotlight) :where(.avdeb__item:first-child) {
750
+ grid-column: 1 / -1;
751
+ }
752
+
753
+ /* Single product */
754
+ :where(.avdeb--single) :where(.avdeb__items) {
755
+ grid-template-columns: minmax(0, 1fr);
756
+ }
757
+
758
+ :where(.avdeb--card) :where(.avdeb__items) {
759
+ max-inline-size: 22rem;
760
+ }
761
+
762
+ /* ── Card ───────────────────────────────────────────────────────────────── */
763
+ :where(.avdeb-card) {
764
+ position: relative;
765
+ isolation: isolate;
766
+ display: flex;
767
+ flex-direction: column;
768
+ inline-size: 100%;
769
+ margin: 0;
770
+ background: var(--avdeb-card-bg);
771
+ border-radius: var(--avdeb-radius);
772
+ transition:
773
+ transform 0.35s var(--avdeb-ease),
774
+ box-shadow 0.35s var(--avdeb-ease),
775
+ border-color 0.2s;
776
+ }
777
+
778
+ :where(.avdeb--elevated) :where(.avdeb-card) {
779
+ border: 1px solid var(--avdeb-border-soft);
780
+ box-shadow: var(--avdeb-shadow);
781
+ }
782
+
783
+ :where(.avdeb--outline) :where(.avdeb-card) {
784
+ border: 1px solid var(--avdeb-border);
785
+ }
786
+
787
+ :where(.avdeb--minimal) :where(.avdeb-card) {
788
+ background: transparent;
789
+ --avdeb-pad: 0.75rem 0.15rem 0.25rem;
790
+ }
791
+
792
+ @media (hover: hover) {
793
+ :where(.avdeb--elevated) :where(.avdeb-card:hover) {
794
+ transform: translateY(-4px);
795
+ box-shadow: var(--avdeb-shadow-hover);
796
+ }
797
+
798
+ :where(.avdeb--outline) :where(.avdeb-card:hover) {
799
+ border-color: var(--avdeb-accent);
800
+ }
801
+
802
+ :where(.avdeb--minimal)
803
+ :where(.avdeb-card:hover)
804
+ :where(.avdeb-card__link) {
805
+ text-decoration: underline;
806
+ text-underline-offset: 0.2em;
807
+ }
808
+
809
+ :where(.avdeb-card:hover) :where(.avdeb-card__media img) {
810
+ transform: scale(1.045);
811
+ }
812
+
813
+ :where(.avdeb-card:hover) :where(.avdeb-card__cta svg) {
814
+ transform: translateX(3px);
815
+ }
816
+ }
817
+
818
+ :where(.avdeb-card__media) {
819
+ position: relative;
820
+ display: grid;
821
+ place-items: center;
822
+ aspect-ratio: var(--avdeb-ratio);
823
+ overflow: hidden;
824
+ background: var(--avdeb-media-bg);
825
+ border-radius: 0;
826
+ border-start-start-radius: calc(var(--avdeb-radius) - 1px);
827
+ border-start-end-radius: calc(var(--avdeb-radius) - 1px);
828
+ }
829
+
830
+ :where(.avdeb--minimal) :where(.avdeb-card__media) {
831
+ border-radius: var(--avdeb-radius);
832
+ }
833
+
834
+ :where(.avdeb-card__media img) {
835
+ display: block;
836
+ inline-size: 100%;
837
+ block-size: 100%;
838
+ object-fit: cover;
839
+ transition: transform 0.7s var(--avdeb-ease);
840
+ }
841
+
842
+ :where(.avdeb-card__placeholder) {
843
+ opacity: 0.35;
844
+ }
845
+
846
+ :where(.avdeb-card__badges) {
847
+ position: absolute;
848
+ inset-block-start: 0.65rem;
849
+ inset-inline-start: 0.65rem;
850
+ z-index: 2;
851
+ display: flex;
852
+ flex-wrap: wrap;
853
+ gap: 0.35rem;
854
+ pointer-events: none;
855
+ }
856
+
857
+ :where(.avdeb-badge) {
858
+ padding: 0.38em 0.65em;
859
+ border-radius: 999px;
860
+ background: var(--avdeb-badge-bg);
861
+ color: var(--avdeb-badge-text);
862
+ font-size: 0.7rem;
863
+ font-weight: 700;
864
+ letter-spacing: 0.04em;
865
+ line-height: 1;
866
+ text-transform: uppercase;
867
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14);
868
+ -webkit-backdrop-filter: blur(8px);
869
+ backdrop-filter: blur(8px);
870
+ }
871
+
872
+ :where(.avdeb-badge--sale) {
873
+ background: var(--avdeb-accent);
874
+ color: var(--avdeb-accent-contrast);
875
+ }
876
+
877
+ :where(.avdeb-badge--pick) {
878
+ background: #141414;
879
+ color: #fff;
880
+ }
881
+
882
+ :where(.avdeb-card__body) {
883
+ display: flex;
884
+ flex: 1;
885
+ flex-direction: column;
886
+ gap: 0.7rem;
887
+ min-inline-size: 0;
888
+ padding: var(--avdeb-pad);
889
+ }
890
+
891
+ :where(.avdeb-card__main) {
892
+ display: flex;
893
+ flex-direction: column;
894
+ gap: 0.25rem;
895
+ min-inline-size: 0;
896
+ }
897
+
898
+ :where(.avdeb-card__by) {
899
+ margin: 0;
900
+ color: var(--avdeb-muted, currentColor);
901
+ opacity: var(--avdeb-muted-opacity);
902
+ font-size: 0.8rem;
903
+ letter-spacing: 0.01em;
904
+ }
905
+
906
+ :where(.avdeb-card__title) {
907
+ display: -webkit-box;
908
+ margin: 0;
909
+ overflow: hidden;
910
+ font-family: var(--avdeb-font-title);
911
+ font-size: 1rem;
912
+ font-weight: 600;
913
+ line-height: 1.35;
914
+ -webkit-box-orient: vertical;
915
+ -webkit-line-clamp: 2;
916
+ line-clamp: 2;
917
+ }
918
+
919
+ :where(.avdeb-card__link) {
920
+ color: inherit;
921
+ text-decoration: none;
922
+ outline: none;
923
+ }
924
+
925
+ /* The title link covers the whole card: one tab stop, a meaningful link name. */
926
+ :where(.avdeb-card__link)::after {
927
+ content: "";
928
+ position: absolute;
929
+ inset: 0;
930
+ z-index: 1;
931
+ border-radius: var(--avdeb-radius);
932
+ }
933
+
934
+ :where(.avdeb-card__link:focus-visible)::after {
935
+ outline: 2px solid var(--avdeb-accent);
936
+ outline-offset: 3px;
937
+ }
938
+
939
+ :where(.avdeb-card__note) {
940
+ margin: 0.4rem 0 0;
941
+ padding-inline-start: 0.85rem;
942
+ border-inline-start: 3px solid var(--avdeb-accent);
943
+ font-size: 0.95rem;
944
+ line-height: 1.6;
945
+ white-space: pre-line;
946
+ }
947
+
948
+ :where(.avdeb-card__footer) {
949
+ display: flex;
950
+ flex-wrap: wrap;
951
+ align-items: center;
952
+ justify-content: space-between;
953
+ gap: 0.5rem 0.75rem;
954
+ margin-block-start: auto;
955
+ }
956
+
957
+ :where(.avdeb-card__price) {
958
+ display: flex;
959
+ align-items: baseline;
960
+ gap: 0.45em;
961
+ margin: 0;
962
+ font-size: 1.02rem;
963
+ font-weight: 700;
964
+ font-variant-numeric: tabular-nums;
965
+ }
966
+
967
+ :where(.avdeb-card__price del) {
968
+ color: var(--avdeb-muted, currentColor);
969
+ opacity: var(--avdeb-muted-opacity);
970
+ font-size: 0.85em;
971
+ font-weight: 500;
972
+ text-decoration-thickness: 1px;
973
+ }
974
+
975
+ :where(.avdeb-card__cta) {
976
+ display: inline-flex;
977
+ align-items: center;
978
+ gap: 0.35em;
979
+ color: var(--avdeb-accent-text);
980
+ font-size: 0.875rem;
981
+ font-weight: 600;
982
+ white-space: nowrap;
983
+ }
984
+
985
+ /* ── List layout: compact "shop the post" rows (after the base card rules) ── */
986
+ :where(.avdeb--list) :where(.avdeb-card) {
987
+ flex-direction: row;
988
+ align-items: center;
989
+ gap: 1rem;
990
+ padding: 0.7rem;
991
+ }
992
+
993
+ :where(.avdeb--list) :where(.avdeb-card__media) {
994
+ flex: 0 0 clamp(4.25rem, 16cqi, 6.5rem);
995
+ border-radius: calc(var(--avdeb-radius) - 4px);
996
+ }
997
+
998
+ :where(.avdeb--list) :where(.avdeb-card__body) {
999
+ flex-direction: row;
1000
+ flex-wrap: wrap;
1001
+ align-items: center;
1002
+ gap: 0.5rem 1rem;
1003
+ padding: 0;
1004
+ }
1005
+
1006
+ :where(.avdeb--list) :where(.avdeb-card__main) {
1007
+ flex: 1 1 12rem;
1008
+ }
1009
+
1010
+ :where(.avdeb--list) :where(.avdeb-card__footer) {
1011
+ flex: 0 1 auto;
1012
+ max-inline-size: 100%;
1013
+ margin-block-start: 0;
1014
+ gap: 0.4rem 1rem;
1015
+ }
1016
+
1017
+ :where(.avdeb--list) :where(.avdeb-card__badges) {
1018
+ inset-block-start: 0.35rem;
1019
+ inset-inline-start: 0.35rem;
1020
+ }
1021
+
1022
+ :where(.avdeb--list) :where(.avdeb-badge) {
1023
+ font-size: 0.6rem;
1024
+ }
1025
+
1026
+ @media (hover: hover) {
1027
+ :where(.avdeb--list.avdeb--elevated) :where(.avdeb-card:hover) {
1028
+ transform: translateY(-2px);
1029
+ }
1030
+ }
1031
+
1032
+ /* ── Featured card (spotlight first item, horizontal & hero products) ──── */
1033
+ :where(.avdeb-card--featured) {
1034
+ display: grid;
1035
+ grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr);
1036
+ align-items: stretch;
1037
+ min-block-size: clamp(17rem, 38cqi, 25rem);
1038
+ }
1039
+
1040
+ /* The image fills its column (cover) instead of forcing a tall square. */
1041
+ :where(.avdeb-card--featured) :where(.avdeb-card__media) {
1042
+ aspect-ratio: auto;
1043
+ block-size: 100%;
1044
+ min-block-size: 14rem;
1045
+ border-radius: 0;
1046
+ border-start-start-radius: calc(var(--avdeb-radius) - 1px);
1047
+ border-end-start-radius: calc(var(--avdeb-radius) - 1px);
1048
+ }
1049
+
1050
+ :where(.avdeb--minimal)
1051
+ :where(.avdeb-card--featured)
1052
+ :where(.avdeb-card__media) {
1053
+ border-radius: var(--avdeb-radius);
1054
+ }
1055
+
1056
+ :where(.avdeb-card--featured) :where(.avdeb-card__media img) {
1057
+ position: absolute;
1058
+ inset: 0;
1059
+ }
1060
+
1061
+ :where(.avdeb-card--featured) :where(.avdeb-card__body) {
1062
+ justify-content: center;
1063
+ gap: 1.1rem;
1064
+ padding: clamp(1.1rem, 3cqi, 2rem);
1065
+ }
1066
+
1067
+ :where(.avdeb-card--featured) :where(.avdeb-card__title) {
1068
+ font-size: clamp(1.15rem, 0.9rem + 1.6cqi, 1.6rem);
1069
+ line-height: 1.25;
1070
+ -webkit-line-clamp: 3;
1071
+ line-clamp: 3;
1072
+ }
1073
+
1074
+ :where(.avdeb-card--featured) :where(.avdeb-card__footer) {
1075
+ margin-block-start: 0;
1076
+ }
1077
+
1078
+ :where(.avdeb-card--featured, .avdeb--list .avdeb-card)
1079
+ :where(.avdeb-card__cta) {
1080
+ padding: 0.7em 1.15em;
1081
+ border-radius: 999px;
1082
+ background: var(--avdeb-accent);
1083
+ color: var(--avdeb-accent-contrast);
1084
+ box-shadow: 0 8px 20px -10px var(--avdeb-accent);
1085
+ }
1086
+
1087
+ :where(.avdeb--list .avdeb-card) :where(.avdeb-card__cta) {
1088
+ padding: 0.55em 0.95em;
1089
+ font-size: 0.82rem;
1090
+ box-shadow: none;
1091
+ }
1092
+
1093
+ /* Tight list rows: the CTA becomes a round arrow button (the whole row is the link). */
1094
+ @container avdeb (max-width: 30rem) {
1095
+ :where(.avdeb--list) :where(.avdeb-card__cta-label) {
1096
+ display: none;
1097
+ }
1098
+
1099
+ :where(.avdeb--list .avdeb-card) :where(.avdeb-card__cta) {
1100
+ display: grid;
1101
+ place-items: center;
1102
+ inline-size: 2.1rem;
1103
+ block-size: 2.1rem;
1104
+ padding: 0;
1105
+ }
1106
+ }
1107
+
1108
+ :where(.avdeb-card--hero) {
1109
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
1110
+ min-block-size: clamp(20rem, 48cqi, 32rem);
1111
+ }
1112
+
1113
+ :where(.avdeb-card--hero) :where(.avdeb-card__title) {
1114
+ font-size: clamp(1.35rem, 1rem + 2.4cqi, 2.15rem);
1115
+ letter-spacing: -0.015em;
1116
+ -webkit-line-clamp: 4;
1117
+ line-clamp: 4;
1118
+ }
1119
+
1120
+ :where(.avdeb-card--hero) :where(.avdeb-card__price) {
1121
+ font-size: 1.25rem;
1122
+ }
1123
+
1124
+ :where(.avdeb--minimal) :where(.avdeb-card--featured) {
1125
+ gap: 1.25rem;
1126
+ }
1127
+
1128
+ @container avdeb (max-width: 34rem) {
1129
+ :where(.avdeb-card--featured, .avdeb-card--hero) {
1130
+ grid-template-columns: minmax(0, 1fr);
1131
+ min-block-size: 0;
1132
+ }
1133
+
1134
+ :where(.avdeb-card--featured) :where(.avdeb-card__media) {
1135
+ aspect-ratio: var(--avdeb-ratio);
1136
+ block-size: auto;
1137
+ min-block-size: 0;
1138
+ border-radius: 0;
1139
+ border-start-start-radius: calc(var(--avdeb-radius) - 1px);
1140
+ border-start-end-radius: calc(var(--avdeb-radius) - 1px);
1141
+ }
1142
+ }
1143
+
1144
+ /* ── Disclosure ─────────────────────────────────────────────────────────── */
1145
+ /* Narrow cards (e.g. 4 columns): stack price and CTA so every card lines up. */
1146
+ @container avdeb-item (max-width: 17rem) {
1147
+ :where(.avdeb-card:not(.avdeb-card--featured))
1148
+ :where(.avdeb-card__footer) {
1149
+ flex-direction: column;
1150
+ align-items: flex-start;
1151
+ gap: 0.4rem;
1152
+ }
1153
+ }
1154
+
1155
+ :where(.avdeb__disclosure) {
1156
+ display: flex;
1157
+ align-items: center;
1158
+ gap: 0.45em;
1159
+ margin: 1.1rem 0 0;
1160
+ color: var(--avdeb-muted, currentColor);
1161
+ opacity: var(--avdeb-muted-opacity);
1162
+ font-size: 0.78rem;
1163
+ line-height: 1.4;
1164
+ }
1165
+
1166
+ :where(.avdeb__disclosure svg) {
1167
+ flex: none;
1168
+ }
1169
+
1170
+ /* ── Motion & print ─────────────────────────────────────────────────────── */
1171
+ @media (prefers-reduced-motion: reduce) {
1172
+ :where(.avdeb) :where(*) {
1173
+ transition: none !important;
1174
+ }
1175
+
1176
+ :where(.avdeb-card:hover),
1177
+ :where(.avdeb-card:hover) :where(img) {
1178
+ transform: none !important;
1179
+ }
1180
+ }
1181
+
1182
+ @media print {
1183
+ :where(.avdeb__nav) {
1184
+ display: none;
1185
+ }
1186
+
1187
+ :where(.avdeb-card) {
1188
+ box-shadow: none;
1189
+ break-inside: avoid;
1190
+ }
1191
+ }
1192
+ </style>