@nok-integration/storefront-react 0.6.1 → 0.11.3

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.
@@ -35,9 +35,15 @@
35
35
  --color-text-strong: #f9fafb;
36
36
  --color-surface-default: #f9fafa;
37
37
  --color-surface-soft: #f0f2f2;
38
+ /* semantic/base/surface/full-color/subtle — one step darker than `soft`. Same value as
39
+ --color-border-default, but a separate token in DAZN's library; keep them separate. */
40
+ --color-surface-subtle: #dddedf;
38
41
  --color-surface-max: #080e12;
39
42
  --color-border-default: #dddedf;
40
43
  --color-border-subtle: #ced0d2;
44
+ /* semantic/base/border/transparent/soft. Alpha rather than a flat grey on purpose: it
45
+ is drawn over cards of different backgrounds and has to darken whatever is beneath. */
46
+ --color-border-transparent-soft: rgba(0, 0, 0, 0.1);
41
47
  --color-state-text-success: #147e4a;
42
48
  --color-state-surface-success-soft: #def1e9;
43
49
  --color-state-surface-critical-default: #d12424;
@@ -93,9 +99,11 @@
93
99
  --color-text-disabled: #6b7378;
94
100
  --color-surface-default: #080e12;
95
101
  --color-surface-soft: #171e23;
102
+ --color-surface-subtle: #2a3238;
96
103
  --color-surface-max: #f9fafa;
97
104
  --color-border-default: #2a3238;
98
105
  --color-border-subtle: #2a3238;
106
+ --color-border-transparent-soft: rgba(255, 255, 255, 0.12);
99
107
  }
100
108
 
101
109
  /*
@@ -112,9 +120,37 @@
112
120
  --color-text-disabled: #9ea2a4;
113
121
  --color-surface-default: #f9fafa;
114
122
  --color-surface-soft: #f0f2f2;
123
+ --color-surface-subtle: #dddedf;
115
124
  --color-surface-max: #080e12;
116
125
  --color-border-default: #dddedf;
117
126
  --color-border-subtle: #ced0d2;
127
+ --color-border-transparent-soft: rgba(0, 0, 0, 0.1);
128
+ }
129
+
130
+ /*
131
+ * Screen-reader-only text, shipped from HERE rather than from our app's `globals.css`.
132
+ *
133
+ * Three components use this class — `PriceBlock`'s "Was", and the two selectors' legends —
134
+ * and until now only our own app defined it. A host mounting the SDK got the text
135
+ * **rendered**: a price reading "Was £96.00 £67.00" with no styling to hide the first two
136
+ * words. Same failure as the typography and the dark palette before it, and invisible in
137
+ * the app that happens to carry the rule.
138
+ *
139
+ * It is deliberately NOT `position: absolute`, which is the usual recipe. An absolutely
140
+ * positioned element with no positioned ancestor resolves against the initial containing
141
+ * block, so an overflow container cannot clip it: one of these inside the PDP's rail put
142
+ * its box at x=420 and made the whole document scroll sideways by 45px. Out of flow was
143
+ * never the requirement — being unreadable and unmeasurable is, and a 1px clipped
144
+ * inline-block does that without escaping anything.
145
+ */
146
+ .visually-hidden {
147
+ display: inline-block;
148
+ width: 1px;
149
+ height: 1px;
150
+ overflow: hidden;
151
+ clip-path: inset(50%);
152
+ white-space: nowrap;
153
+ vertical-align: baseline;
118
154
  }
119
155
  ._badge_1bagf_1 {
120
156
  display: inline-flex;
@@ -247,7 +283,7 @@
247
283
  * (serif, in a page that never set one) and its own background.
248
284
  */
249
285
 
250
- ._surface_168jv_10 {
286
+ ._surface_nj74o_10 {
251
287
  font-family: var(--font-family-base);
252
288
  font-weight: var(--font-weight-regular);
253
289
  font-size: var(--type-paragraph-size);
@@ -258,9 +294,9 @@
258
294
  text-rendering: optimizeLegibility;
259
295
  }
260
296
 
261
- ._surface_168jv_10 *,
262
- ._surface_168jv_10 *::before,
263
- ._surface_168jv_10 *::after {
297
+ ._surface_nj74o_10 *,
298
+ ._surface_nj74o_10 *::before,
299
+ ._surface_nj74o_10 *::after {
264
300
  box-sizing: border-box;
265
301
  }
266
302
 
@@ -272,27 +308,35 @@
272
308
  * through a screen of the opposite palette) is invisible in our own app, where the body
273
309
  * already matches, and so goes unnoticed until a partner reports it.
274
310
  */
275
- ._fill_168jv_35 {
311
+ ._fill_nj74o_35 {
276
312
  /* Declared twice on purpose, and the order matters.
277
313
  `dvh` landed in Chrome 108 / iOS 15.4, and this package supports React 16.14 because
278
314
  that is what DAZN's app runs — an app that old can be paired with a WebView that old.
279
315
  A browser that does not understand `dvh` drops the second declaration and keeps the
280
- first, so the worst case is `vh`'s URL-bar inaccuracy rather than no fill at all. */
316
+ first, so the worst case is `vh`'s URL-bar inaccuracy rather than no fill at all.
317
+
318
+ `--surface-top` is what the host has already used above us, measured at mount (see
319
+ useTopOffset). A plain `100dvh` is a full viewport starting wherever the screen
320
+ happens to begin, so anything above it — a header, a page heading — makes the document
321
+ taller than the viewport by exactly that much. On the cart that was 109px of surface
322
+ below the sticky CTA, which is the difference between a bar on the bottom edge and a
323
+ bar you can scroll past. It defaults to 0, so a screen that mounts at the top of the
324
+ page is unaffected and nothing depends on the effect having run. */
281
325
  min-height: 100vh;
282
- min-height: 100dvh;
326
+ min-height: calc(100dvh - var(--surface-top, 0px));
283
327
  }
284
328
 
285
- ._surface_168jv_10 :is(h1, h2, h3) {
329
+ ._surface_nj74o_10 :is(h1, h2, h3) {
286
330
  margin: 0;
287
331
  font-weight: var(--font-weight-bold);
288
332
  }
289
333
 
290
- ._surface_168jv_10 h1 {
334
+ ._surface_nj74o_10 h1 {
291
335
  font-size: var(--type-heading-lg-size);
292
336
  line-height: var(--type-heading-lg-line);
293
337
  }
294
338
 
295
- ._surface_168jv_10 h2 {
339
+ ._surface_nj74o_10 h2 {
296
340
  font-size: var(--type-heading-md-size);
297
341
  line-height: var(--type-heading-md-line);
298
342
  }
@@ -311,27 +355,27 @@
311
355
  * Restricting the rule to unclassed anchors keeps what it is for — a bare `<a>` must never
312
356
  * render browser-blue — while leaving any anchor that carries a class in charge of itself.
313
357
  */
314
- ._surface_168jv_10 a:not([class]) {
358
+ ._surface_nj74o_10 a:not([class]) {
315
359
  color: inherit;
316
360
  }
317
361
 
318
- ._surface_168jv_10 img {
362
+ ._surface_nj74o_10 img {
319
363
  max-width: 100%;
320
364
  height: auto;
321
365
  }
322
366
 
323
367
  /* Visible focus for keyboard users — a WCAG 2.1 AA launch gate, so it cannot be left
324
368
  to the host to remember. */
325
- ._surface_168jv_10 :focus-visible {
369
+ ._surface_nj74o_10 :focus-visible {
326
370
  outline: 2px solid var(--color-surface-max);
327
371
  outline-offset: 2px;
328
372
  border-radius: var(--radius-4);
329
373
  }
330
374
 
331
375
  @media (prefers-reduced-motion: reduce) {
332
- ._surface_168jv_10 *,
333
- ._surface_168jv_10 *::before,
334
- ._surface_168jv_10 *::after {
376
+ ._surface_nj74o_10 *,
377
+ ._surface_nj74o_10 *::before,
378
+ ._surface_nj74o_10 *::after {
335
379
  animation-duration: 0.001ms !important;
336
380
  animation-iteration-count: 1 !important;
337
381
  transition-duration: 0.001ms !important;
@@ -397,7 +441,7 @@
397
441
  ._action_ney0g_57 {
398
442
  margin: var(--space-8) var(--space-16) 0;
399
443
  }
400
- ._root_f9nel_1 {
444
+ ._root_1u83t_1 {
401
445
  display: flex;
402
446
  flex-direction: column;
403
447
  align-items: center;
@@ -406,25 +450,31 @@
406
450
  padding: var(--space-32) var(--space-24);
407
451
  }
408
452
 
409
- ._icon_f9nel_10 {
453
+ ._icon_1u83t_10 {
410
454
  font-size: 32px;
411
455
  line-height: 1;
412
456
  }
413
457
 
414
- ._title_f9nel_15 {
458
+ ._title_1u83t_15 {
415
459
  font-size: var(--type-heading-md-size);
416
460
  line-height: var(--type-heading-md-line);
417
461
  }
418
462
 
419
- ._body_f9nel_20 {
463
+ ._body_1u83t_20 {
420
464
  margin: 0;
421
465
  color: var(--color-text-subtle);
422
466
  font-size: var(--type-paragraph-size);
423
467
  max-width: 34ch;
424
468
  }
425
469
 
426
- ._action_f9nel_27 {
427
- margin-top: var(--space-8);
470
+ /*
471
+ * The one place the empty state is drawn is the cart (Figma 4451:48190), and it separates
472
+ * the action from the copy far more than it separates the two lines of copy: 24 against 6.
473
+ * With the `gap` above, this lands the button 28px below the last line where the frame has
474
+ * 28 — it was 20, close enough to the paragraph above to read as part of it.
475
+ */
476
+ ._action_1u83t_33 {
477
+ margin-top: var(--space-16);
428
478
  }
429
479
  ._root_1lbpx_1 {
430
480
  display: flex;
@@ -445,7 +495,7 @@
445
495
  color: var(--color-text-subtle);
446
496
  max-width: 34ch;
447
497
  }
448
- ._viewport_mxa9g_1 {
498
+ ._viewport_1lt9i_1 {
449
499
  position: fixed;
450
500
  left: 0;
451
501
  right: 0;
@@ -459,7 +509,7 @@
459
509
  z-index: 1000;
460
510
  }
461
511
 
462
- ._toast_mxa9g_15 {
512
+ ._toast_1lt9i_15 {
463
513
  display: flex;
464
514
  align-items: center;
465
515
  justify-content: space-between;
@@ -476,12 +526,19 @@
476
526
  pointer-events: auto;
477
527
  }
478
528
 
479
- ._message_mxa9g_32 {
529
+ ._text_1lt9i_32 {
530
+ display: flex;
531
+ flex-direction: column;
532
+ gap: var(--space-2);
533
+ min-width: 0;
534
+ }
535
+
536
+ ._message_1lt9i_39 {
480
537
  min-width: 0;
481
538
  }
482
539
 
483
540
  /* Meets the 44px tap target via padding while staying visually inline with the text. */
484
- ._action_mxa9g_37 {
541
+ ._action_1lt9i_44 {
485
542
  flex-shrink: 0;
486
543
  min-height: var(--tap-target-min);
487
544
  padding: 0 var(--space-12);
@@ -494,26 +551,76 @@
494
551
  cursor: pointer;
495
552
  }
496
553
 
497
- ._action_mxa9g_37:focus-visible {
554
+ ._action_1lt9i_44:focus-visible {
498
555
  outline: 2px solid currentColor;
499
556
  outline-offset: 2px;
500
557
  border-radius: var(--radius-4);
501
558
  }
502
559
 
503
- ._neutral_mxa9g_56 {
560
+ ._neutral_1lt9i_63 {
504
561
  background: var(--color-surface-max);
505
562
  color: var(--color-text-strong);
506
563
  }
507
564
 
508
- ._success_mxa9g_61 {
565
+ ._success_1lt9i_68 {
509
566
  background: var(--color-state-text-success);
510
567
  color: var(--color-text-strong);
511
568
  }
512
569
 
513
- ._critical_mxa9g_66 {
570
+ ._critical_1lt9i_73 {
514
571
  background: var(--color-state-surface-critical-default);
515
572
  color: var(--color-text-strong);
516
573
  }
574
+
575
+ /* Figma `toast-added-to-cart` (4432:35943). Fixed palette rather than theme tokens: the
576
+ card is dark in every state, and it renders over any screen, light or dark. The hexes
577
+ are the design's own — #c7cccf is not in our token set. */
578
+ ._rich_1lt9i_81 {
579
+ justify-content: flex-start;
580
+ gap: var(--space-12);
581
+ padding: var(--space-12) var(--space-16) var(--space-12) var(--space-12);
582
+ background: #0c161c;
583
+ color: #ffffff;
584
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
585
+ }
586
+
587
+ ._rich_1lt9i_81 ._message_1lt9i_39 {
588
+ font-size: var(--type-trimmed-sm-size);
589
+ font-weight: var(--font-weight-bold);
590
+ line-height: normal;
591
+ }
592
+
593
+ ._rich_1lt9i_81 ._description_1lt9i_96 {
594
+ font-size: var(--type-trimmed-xs-size);
595
+ font-weight: var(--font-weight-regular);
596
+ line-height: normal;
597
+ color: #c7cccf;
598
+ /* Truncate rather than wrap — a long title would otherwise push "View cart" off. */
599
+ overflow: hidden;
600
+ text-overflow: ellipsis;
601
+ white-space: nowrap;
602
+ }
603
+
604
+ /* Trailing edge, without a justify-content that would also split thumb from text. */
605
+ ._rich_1lt9i_81 ._action_1lt9i_44 {
606
+ margin-left: auto;
607
+ text-decoration: none;
608
+ }
609
+
610
+ ._thumb_1lt9i_113 {
611
+ position: relative;
612
+ flex-shrink: 0;
613
+ width: 40px;
614
+ height: 40px;
615
+ border-radius: var(--radius-8);
616
+ overflow: hidden;
617
+ /* Visible while the image loads, and behind a transparent PNG. */
618
+ background: rgba(255, 255, 255, 0.08);
619
+ }
620
+
621
+ ._thumbImage_1lt9i_124 {
622
+ object-fit: cover;
623
+ }
517
624
  /* Figma mobile/badge/sale (sm): success-soft bg, success text, icon + -NN%, radius-4. */
518
625
  ._badge_156ya_2 {
519
626
  display: inline-flex;
@@ -535,7 +642,7 @@
535
642
  display: block;
536
643
  flex-shrink: 0;
537
644
  }
538
- ._root_l5uj5_1 {
645
+ ._root_n3nm5_1 {
539
646
  display: inline-flex;
540
647
  align-items: baseline;
541
648
  gap: var(--space-8);
@@ -544,1693 +651,2538 @@
544
651
 
545
652
  /* Alignment variants. Cards align on the price baseline row with a tight 4px gap; the
546
653
  PDP price row centres the price against its sale badge (6px). */
547
- ._center_l5uj5_10 {
654
+ ._center_n3nm5_10 {
548
655
  align-items: center;
549
656
  gap: var(--space-6);
550
657
  }
551
- ._end_l5uj5_14 {
658
+ ._end_n3nm5_14 {
552
659
  align-items: flex-end;
553
660
  gap: var(--space-4);
554
661
  }
555
662
 
556
663
  /* Size sets the row's font-size so the price and its struck was-price match (per the
557
664
  product-card, where both are 16px). */
558
- ._sm_l5uj5_21 {
665
+ ._sm_n3nm5_21 {
559
666
  font-size: var(--type-trimmed-sm-size);
560
667
  }
561
- ._md_l5uj5_24 {
668
+ ._md_n3nm5_24 {
562
669
  font-size: var(--type-trimmed-md-size);
563
670
  }
564
- ._lg_l5uj5_27 {
671
+ ._lg_n3nm5_27 {
565
672
  font-size: var(--type-heading-md-size);
566
673
  }
567
674
 
568
- ._price_l5uj5_31 {
675
+ ._price_n3nm5_31 {
569
676
  font-weight: var(--font-weight-bold);
570
677
  color: var(--color-text-default);
571
678
  }
572
679
 
573
- ._compareAt_l5uj5_36 {
680
+ ._compareAt_n3nm5_36 {
574
681
  color: var(--color-text-disabled);
575
682
  text-decoration: line-through;
576
683
  font-weight: var(--font-weight-regular);
577
684
  }
578
- ._tile_1dy20_1 {
685
+
686
+ /* On a product card the struck price carries the same weight as the live one (Figma
687
+ `mobile/product-card`). Scoped to the card variant so the PDP's row is untouched. */
688
+ ._strikethrough_n3nm5_44 ._compareAt_n3nm5_36 {
689
+ font-weight: var(--font-weight-bold);
690
+ }
691
+ /*
692
+ * Figma `mobile/order/delivery-widget` (1638:134987). Every row is exactly 32px tall and
693
+ * every connector exactly 32px, stacked with no gap — so the whole column is a multiple of
694
+ * 32 and the circles land on a 64px rhythm.
695
+ */
696
+
697
+ ._timeline_1j37h_7 {
698
+ list-style: none;
699
+ margin: 0;
700
+ padding: 0;
579
701
  display: flex;
580
702
  flex-direction: column;
581
- gap: var(--space-12);
582
- text-decoration: none;
583
- color: inherit;
584
703
  }
585
704
 
586
- ._media_1dy20_9 {
587
- position: relative;
588
- width: 100%;
589
- /* Product-card image is 165:220 (3:4), radius-6 (Figma), sharp not pill-round. */
590
- aspect-ratio: 165 / 220;
591
- border-radius: var(--radius-6);
592
- overflow: hidden;
593
- background: var(--color-surface-soft);
705
+ ._row_1j37h_15 {
706
+ display: flex;
707
+ flex-direction: column;
594
708
  }
595
709
 
596
- ._image_1dy20_19 {
597
- object-fit: cover;
710
+ ._line_1j37h_20 {
711
+ display: flex;
712
+ align-items: center;
713
+ gap: var(--space-8);
714
+ height: 32px;
598
715
  }
599
716
 
600
- ._placeholder_1dy20_23 {
601
- width: 100%;
602
- height: 100%;
603
- background: var(--color-surface-soft);
717
+ ._marker_1j37h_27 {
718
+ position: relative;
719
+ flex-shrink: 0;
720
+ width: 32px;
721
+ height: 32px;
604
722
  }
605
723
 
606
- /* TOP PICK flag top-left over the image, white surface, radius-2, uppercase. Fixed
607
- colours (not theme tokens): it always sits over a light product image. */
608
- ._topBadge_1dy20_31 {
724
+ /* Drawn at 28 inside a 32 column: the disc reads 24px and the plate behind it 26px. */
725
+ ._tick_1j37h_35 {
609
726
  position: absolute;
610
- top: var(--space-8);
611
- left: var(--space-8);
612
- /* The badge string comes from product data, not from a fixed vocabulary. The design
613
- only ever shows short flags (TOP PICK, FEW LEFT), so nothing constrained the width —
614
- and a longer one ("99% HAPPY CUSTOMERS") ran past the tile and was sliced mid-word by
615
- the media box's own overflow. Truncating inside the tile is the honest failure. */
616
- max-width: calc(100% - var(--space-8) * 2);
617
- overflow: hidden;
618
- text-overflow: ellipsis;
619
- padding: var(--space-4) var(--space-6);
620
- border-radius: var(--radius-2);
621
- background: #f9fafa;
622
- color: #0c161c;
623
- font-size: var(--type-trimmed-xs-size);
624
- line-height: var(--type-trimmed-xs-line);
625
- font-weight: var(--font-weight-bold);
626
- letter-spacing: 0.12px;
627
- text-transform: uppercase;
628
- white-space: nowrap;
727
+ top: 50%;
728
+ left: 50%;
729
+ transform: translate(-50%, -50%);
730
+ display: block;
629
731
  }
630
732
 
631
- ._saleBadge_1dy20_54 {
733
+ /* A step still to come: a 24px ring with an 18px well, not a filled dot. */
734
+ ._pending_1j37h_44 {
632
735
  position: absolute;
633
- bottom: var(--space-8);
634
- left: var(--space-8);
736
+ top: 50%;
737
+ left: 50%;
738
+ transform: translate(-50%, -50%);
739
+ width: 24px;
740
+ height: 24px;
741
+ border-radius: var(--radius-full);
742
+ background: var(--color-surface-subtle);
635
743
  }
636
744
 
637
- ._oos_1dy20_60 {
745
+ ._pending_1j37h_44::after {
746
+ content: '';
638
747
  position: absolute;
639
- top: var(--space-8);
640
- left: var(--space-8);
748
+ top: 50%;
749
+ left: 50%;
750
+ transform: translate(-50%, -50%);
751
+ width: 18px;
752
+ height: 18px;
753
+ border-radius: var(--radius-full);
754
+ background: var(--color-surface-soft);
641
755
  }
642
756
 
643
- ._bottom_1dy20_66 {
757
+ /* Its own 32px block between two rows, centred on the marker column. */
758
+ ._connector_1j37h_68 {
644
759
  display: flex;
645
- flex-direction: column;
646
- gap: var(--space-16);
760
+ align-items: center;
761
+ justify-content: center;
762
+ width: 32px;
763
+ height: 32px;
764
+ }
765
+
766
+ ._connector_1j37h_68::before {
767
+ content: '';
768
+ width: 1px;
769
+ height: 24px;
770
+ border-radius: var(--radius-full);
771
+ background: var(--color-surface-subtle);
647
772
  }
648
773
 
649
- ._titlePrice_1dy20_72 {
774
+ ._text_1j37h_84 {
650
775
  display: flex;
651
776
  flex-direction: column;
652
- gap: var(--space-8);
777
+ gap: 10px;
778
+ min-width: 0;
653
779
  }
654
780
 
655
- ._title_1dy20_72 {
656
- font-size: var(--type-paragraph-size);
657
- line-height: 1.3;
658
- font-weight: var(--font-weight-regular);
659
- color: var(--color-text-default);
660
- display: -webkit-box;
661
- -webkit-line-clamp: 2;
662
- line-clamp: 2;
663
- -webkit-box-orient: vertical;
781
+ /*
782
+ * The design trims both text boxes to the cap height, which is what makes a two-line row
783
+ * fit in 32px. `text-box-trim` is too new for the webviews this package runs in, so the
784
+ * line-height IS the cap height — same box, same 11 + 10 + 10 = 31px block.
785
+ *
786
+ * The padding is what the trim costs. `overflow` has to be hidden for the ellipsis, and it
787
+ * clips in both directions, so an 11px box beheads every descender — the `g` and `y` in
788
+ * "Packing your order". The padding gives the glyphs somewhere to go and the equal negative
789
+ * margin takes it back out of the layout, leaving the box 11px and the text uncut.
790
+ */
791
+ ._label_1j37h_101 {
792
+ font-size: var(--type-trimmed-md-size);
793
+ line-height: 11px;
794
+ white-space: nowrap;
664
795
  overflow: hidden;
665
- /* Reserve both lines rather than only capping at two. The clamp stops a long title
666
- overflowing but lets a short one collapse to one line, and in a two-column grid that
667
- pulls its price up level with the neighbour's title — a ragged price baseline across
668
- the row, which reads as broken alignment rather than as varying title lengths. */
669
- min-height: calc(2 * 1.3em);
796
+ text-overflow: ellipsis;
797
+ padding-top: 4px;
798
+ margin-top: -4px;
799
+ padding-bottom: 6px;
800
+ margin-bottom: -6px;
670
801
  }
671
802
 
672
- /* Colour swatches (Figma product-card): 0.5px-bordered boxes with a 14px colour square;
673
- the first reads as selected (dark border). */
674
- ._swatches_1dy20_97 {
675
- display: flex;
676
- gap: var(--space-4);
677
- align-items: center;
803
+ ._past_1j37h_113 {
804
+ font-weight: var(--font-weight-regular);
805
+ color: var(--color-text-subtle);
678
806
  }
679
807
 
680
- ._swatch_1dy20_97 {
681
- display: inline-flex;
682
- padding: 2px;
683
- border: 0.5px solid var(--color-border-subtle);
684
- border-radius: var(--radius-2);
808
+ ._current_1j37h_118 {
809
+ font-weight: var(--font-weight-bold);
810
+ color: var(--color-text-default);
685
811
  }
686
812
 
687
- ._swatchActive_1dy20_110 {
688
- border-color: var(--color-surface-max);
813
+ ._future_1j37h_123 {
814
+ font-weight: var(--font-weight-regular);
815
+ color: var(--color-text-disabled);
689
816
  }
690
817
 
691
- ._swatchDot_1dy20_114 {
692
- display: block;
693
- width: 14px;
694
- height: 14px;
695
- border-radius: var(--radius-2);
818
+ ._date_1j37h_128 {
819
+ font-size: var(--type-trimmed-sm-size);
820
+ line-height: 10px;
821
+ font-weight: var(--font-weight-regular);
822
+ color: var(--color-text-disabled);
823
+ white-space: nowrap;
696
824
  }
697
- ._bar_fn0g9_1 {
825
+ /* Figma mobile/checkout/summary-row: 45px rows, hairline between, both sides bold. */
826
+ ._summary_1h18y_2 {
827
+ margin: 0;
698
828
  display: flex;
699
- align-items: center;
700
- gap: var(--space-8);
701
- padding: var(--space-8) var(--space-16);
829
+ flex-direction: column;
702
830
  }
703
831
 
704
- ._chips_fn0g9_8 {
832
+ ._row_1h18y_8 {
705
833
  display: flex;
834
+ align-items: center;
835
+ justify-content: space-between;
706
836
  gap: var(--space-8);
707
- overflow-x: auto;
708
- flex: 1 1 auto;
709
- -webkit-overflow-scrolling: touch;
837
+ min-height: 45px;
838
+ padding: var(--space-12) 0;
839
+ font-size: var(--type-trimmed-sm-size);
840
+ font-weight: var(--font-weight-bold);
841
+ color: var(--color-text-default);
710
842
  }
711
843
 
712
- ._chip_fn0g9_8 {
713
- flex: 0 0 auto;
714
- min-height: var(--tap-target-min);
715
- padding: 0 var(--space-16);
716
- border-radius: var(--radius-full);
717
- border: 1px solid var(--color-border-default);
718
- background: var(--color-surface-default);
719
- color: var(--color-text-default);
720
- font-family: inherit;
721
- font-size: var(--type-trimmed-sm-size);
722
- cursor: pointer;
844
+ ._row_1h18y_8 + ._row_1h18y_8 {
845
+ border-top: 1px solid var(--color-border-default);
723
846
  }
724
847
 
725
- ._on_fn0g9_29 {
726
- background: var(--color-surface-max);
727
- color: var(--color-text-strong);
728
- border-color: var(--color-surface-max);
848
+ ._row_1h18y_8 dt,
849
+ ._row_1h18y_8 dd {
850
+ margin: 0;
729
851
  }
730
852
 
731
- ._clear_fn0g9_35 {
732
- flex: 0 0 auto;
733
- min-height: var(--tap-target-min);
734
- padding: 0 var(--space-8);
735
- border: none;
736
- background: transparent;
853
+ ._row_1h18y_8 dd {
854
+ line-height: var(--type-caption-sm-line);
855
+ white-space: nowrap;
856
+ font-variant-numeric: tabular-nums;
857
+ }
858
+
859
+ /* "Free" is a merchandising signal, not a number — success tone, per the design. */
860
+ ._free_1h18y_36 {
861
+ color: var(--color-state-text-success);
862
+ font-weight: var(--font-weight-bold);
863
+ }
864
+
865
+ /* "Calculated at checkout". Set quieter than a real figure so it never reads as one. */
866
+ ._pending_1h18y_42 {
867
+ font-size: var(--type-trimmed-xs-size);
737
868
  color: var(--color-text-subtle);
738
- font-family: inherit;
739
- font-size: var(--type-trimmed-sm-size);
740
- text-decoration: underline;
741
- cursor: pointer;
742
869
  }
743
- ._wrap_15ntf_1 {
870
+ ._page_1eqkm_1 {
744
871
  display: flex;
745
872
  flex-direction: column;
746
- gap: var(--space-12);
873
+ gap: var(--space-24);
874
+ padding: var(--space-24) var(--space-16);
747
875
  }
748
876
 
749
- ._grid_15ntf_7 {
750
- list-style: none;
751
- margin: 0;
752
- padding: 0 var(--space-16);
753
- display: grid;
754
- grid-template-columns: repeat(2, 1fr);
755
- gap: var(--space-16);
756
- }
757
- ._rail_6ckcw_1 {
877
+ /* ── Status + timeline ─────────────────────────────────────────────────────── */
878
+
879
+ ._status_1eqkm_10 {
758
880
  display: flex;
759
881
  flex-direction: column;
760
- gap: var(--space-12);
882
+ gap: var(--space-24);
761
883
  }
762
884
 
763
- ._header_6ckcw_7 {
885
+ /* The timeline and its button are one block in the design — 16px apart, 24px from the
886
+ heading above them. */
887
+ ._widget_1eqkm_18 {
764
888
  display: flex;
765
- align-items: baseline;
766
- justify-content: space-between;
767
- padding: 0 var(--space-16);
889
+ flex-direction: column;
890
+ gap: var(--space-16);
768
891
  }
769
892
 
770
- ._title_6ckcw_14 {
771
- font-size: var(--type-heading-md-size);
772
- /* Figma rail header: 20px, line-height 1.38. */
773
- line-height: 1.38;
893
+ ._title_1eqkm_24 {
894
+ display: flex;
895
+ flex-direction: column;
896
+ gap: var(--space-12);
774
897
  }
775
898
 
776
- ._seeAll_6ckcw_20 {
777
- font-size: var(--type-trimmed-sm-size);
778
- color: var(--color-text-subtle);
779
- text-decoration: none;
899
+ ._headline_1eqkm_30 {
900
+ margin: 0;
901
+ font-size: var(--type-heading-md-size);
902
+ line-height: 1.32;
903
+ font-weight: var(--font-weight-bold);
780
904
  }
781
905
 
782
- ._track_6ckcw_26 {
783
- display: flex;
784
- gap: var(--space-12);
785
- list-style: none;
906
+ ._subtitle_1eqkm_37 {
786
907
  margin: 0;
787
- padding: 0 var(--space-16) var(--space-8);
788
- overflow-x: auto;
789
- scroll-snap-type: x mandatory;
790
- /* Keep the 16px leading/trailing inset under mandatory snap: without this the
791
- snapport start sits at the border edge, so the first item snaps to x=0 and the
792
- left padding is scrolled away — misaligning tiles from the section heading. */
793
- scroll-padding-inline: var(--space-16);
794
- -webkit-overflow-scrolling: touch;
908
+ display: flex;
909
+ align-items: center;
910
+ gap: var(--space-8);
911
+ flex-wrap: wrap;
912
+ font-size: var(--type-paragraph-size);
913
+ font-weight: var(--font-weight-regular);
914
+ color: var(--color-text-subtle);
795
915
  }
796
916
 
797
- ._item_6ckcw_41 {
798
- flex: 0 0 auto;
799
- width: 45vw;
800
- max-width: 180px;
801
- scroll-snap-align: start;
917
+ ._dot_1eqkm_48 {
918
+ width: 4px;
919
+ height: 4px;
920
+ border-radius: var(--radius-full);
921
+ background: currentColor;
922
+ flex-shrink: 0;
802
923
  }
803
924
 
804
- /*
805
- * Inset variant the rail lives INSIDE an already-padded column (the PDP content
806
- * block, 16px each side) rather than bleeding edge-to-edge like the home rails. The
807
- * parent supplies the 16px inset, so the header/track add none of their own, and tiles
808
- * size to show exactly two across the content width (per the PDP design: rail x16 w343).
809
- */
810
- ._inset_6ckcw_54 ._header_6ckcw_7 {
811
- padding: 0;
925
+ /* Figma `mobile/button`, as an anchor: the destination is the carrier's own site, so this
926
+ is a link and has to behave like one middle-click, long-press, open in new tab. */
927
+ ._trackButton_1eqkm_58 {
928
+ display: flex;
929
+ align-items: center;
930
+ justify-content: center;
931
+ min-height: 48px;
932
+ padding: var(--space-12);
933
+ border-radius: var(--radius-8);
934
+ background: var(--color-surface-max);
935
+ color: var(--color-text-strong);
936
+ font-size: var(--type-trimmed-md-size);
937
+ font-weight: var(--font-weight-bold);
938
+ text-align: center;
939
+ text-decoration: none;
812
940
  }
813
941
 
814
- ._inset_6ckcw_54 ._track_6ckcw_26 {
815
- padding-left: 0;
816
- padding-right: 0;
817
- scroll-padding-inline: 0;
942
+ ._trackButton_1eqkm_58:focus-visible {
943
+ outline: none;
944
+ box-shadow: var(--focus-ring);
818
945
  }
819
946
 
820
- ._inset_6ckcw_54 ._item_6ckcw_41 {
821
- /* Fixed 165px cards (Figma product-card size=default). At 375 two fit the 343 content
822
- width exactly; more scroll horizontally. */
823
- width: 165px;
824
- max-width: none;
947
+ ._srOnly_1eqkm_78 {
948
+ position: absolute;
949
+ width: 1px;
950
+ height: 1px;
951
+ padding: 0;
952
+ margin: -1px;
953
+ overflow: hidden;
954
+ clip: rect(0, 0, 0, 0);
955
+ white-space: nowrap;
956
+ border: 0;
825
957
  }
826
- ._gallery_h8m00_1 {
827
- position: relative;
958
+
959
+ /* ── Items card ────────────────────────────────────────────────────────────── */
960
+
961
+ ._itemsCard_1eqkm_92 {
828
962
  display: flex;
829
963
  flex-direction: column;
830
- gap: var(--space-12);
964
+ border: 1px solid var(--color-border-transparent-soft);
965
+ border-radius: var(--radius-18);
966
+ overflow: hidden;
831
967
  }
832
968
 
833
- ._track_h8m00_8 {
969
+ ._cardHeader_1eqkm_100 {
834
970
  display: flex;
835
- overflow-x: auto;
836
- scroll-snap-type: x mandatory;
837
- -webkit-overflow-scrolling: touch;
838
- /* Hero media is 375×485 (portrait) and fully sharp (no rounded corners) — it sits
839
- edge-to-edge and flush under the header. */
840
- aspect-ratio: 375 / 485;
841
- background: var(--color-surface-soft);
842
- }
843
-
844
- ._slide_h8m00_19 {
845
- position: relative;
846
- flex: 0 0 100%;
847
- scroll-snap-align: center;
848
- aspect-ratio: 375 / 485;
849
- }
850
-
851
- ._image_h8m00_26 {
852
- object-fit: cover;
971
+ align-items: center;
972
+ gap: var(--space-12);
973
+ min-height: 60px;
974
+ padding: 0 var(--space-16);
853
975
  }
854
976
 
855
- ._placeholder_h8m00_30 {
856
- width: 100%;
857
- aspect-ratio: 375 / 485;
858
- background: var(--color-surface-soft);
977
+ ._cardTitle_1eqkm_108 {
978
+ margin: 0;
979
+ font-size: var(--type-heading-md-size);
980
+ line-height: 1.38;
981
+ font-weight: var(--font-weight-bold);
859
982
  }
860
983
 
861
- /* Share button — top-right chrome over the image (mobile/button: 32px, soft, radius-8). */
862
- ._share_h8m00_37 {
863
- position: absolute;
864
- top: var(--space-16);
865
- right: var(--space-16);
984
+ ._count_1eqkm_115 {
866
985
  display: inline-flex;
867
986
  align-items: center;
868
987
  justify-content: center;
869
- width: 32px;
870
- height: 32px;
871
- padding: 0;
872
- border: none;
873
- border-radius: var(--radius-8);
874
- background: var(--color-surface-soft);
875
- color: var(--color-text-default);
876
- cursor: pointer;
988
+ min-width: 16px;
989
+ height: 16px;
990
+ padding: 0 var(--space-4);
991
+ border-radius: var(--radius-full);
992
+ background: var(--color-surface-subtle);
993
+ font-size: var(--type-trimmed-xs-size);
994
+ font-weight: var(--font-weight-bold);
995
+ letter-spacing: 0.12px;
996
+ font-variant-numeric: tabular-nums;
877
997
  }
878
998
 
879
- ._dots_h8m00_54 {
999
+ ._lines_1eqkm_130 {
1000
+ list-style: none;
1001
+ margin: 0;
1002
+ padding: 0;
880
1003
  display: flex;
881
- justify-content: center;
882
- gap: var(--space-6);
1004
+ flex-direction: column;
1005
+ border-top: 1px solid var(--color-border-default);
883
1006
  }
884
1007
 
885
- ._dot_h8m00_54 {
886
- width: 8px;
887
- height: 8px;
888
- padding: 0;
889
- border: none;
890
- border-radius: var(--radius-full);
891
- background: var(--color-border-default);
892
- cursor: pointer;
893
- /* Enlarge the hit area to >=44px without growing the visual dot. */
894
- position: relative;
1008
+ ._line_1eqkm_130 {
1009
+ display: flex;
1010
+ align-items: center;
1011
+ gap: var(--space-12);
1012
+ padding: var(--space-12) var(--space-16);
895
1013
  }
896
1014
 
897
- ._dot_h8m00_54::after {
898
- content: '';
899
- position: absolute;
900
- inset: -18px;
1015
+ /* Between lines only — a rule under the last one would float above the card's edge. The
1016
+ 12px either side of it comes from the rows' own padding. */
1017
+ ._line_1eqkm_130 + ._line_1eqkm_130 {
1018
+ border-top: 1px solid var(--color-border-default);
901
1019
  }
902
1020
 
903
- ._dotActive_h8m00_78 {
904
- background: var(--color-surface-max);
1021
+ ._thumb_1eqkm_152 {
1022
+ position: relative;
1023
+ flex-shrink: 0;
1024
+ width: 48px;
1025
+ height: 48px;
1026
+ border-radius: var(--radius-8);
1027
+ overflow: hidden;
1028
+ background: var(--color-surface-soft);
905
1029
  }
906
- /*
907
- * Size-guide bottom sheet. Uses the same scrim + rounded-top surface as the other PDP
908
- * overlays so it reads as one system. Fixed light surface: the PDP is light in every
909
- * theme, and an inverted sheet over a light page would be jarring.
910
- */
911
- ._scrim_8wz6r_6 {
912
- position: fixed;
913
- inset: 0;
914
- z-index: 60;
915
- display: flex;
916
- align-items: flex-end;
917
- justify-content: center;
918
- background: rgba(8, 14, 18, 0.5);
919
- animation: _fade_8wz6r_1 160ms ease-out;
1030
+
1031
+ ._image_1eqkm_162 {
1032
+ object-fit: cover;
920
1033
  }
921
1034
 
922
- ._sheet_8wz6r_17 {
1035
+ ._placeholder_1eqkm_166 {
923
1036
  width: 100%;
924
- max-width: 480px;
925
- max-height: 80vh;
926
- overflow-y: auto;
927
- padding: var(--space-8) var(--space-16) var(--space-24);
928
- border-radius: var(--radius-18) var(--radius-18) 0 0;
929
- background: #ffffff;
930
- color: #16181a;
931
- animation: _rise_8wz6r_1 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
1037
+ height: 100%;
932
1038
  }
933
1039
 
934
- ._grabber_8wz6r_29 {
935
- width: 40px;
936
- height: 4px;
937
- margin: 0 auto var(--space-12);
938
- border-radius: 2px;
939
- background: #d8dcde;
1040
+ /* Title and price share a top edge; the thumbnail centres against the pair. */
1041
+ ._lineBody_1eqkm_172 {
1042
+ display: flex;
1043
+ align-items: flex-start;
1044
+ justify-content: space-between;
1045
+ flex: 1 1 0;
1046
+ min-width: 0;
940
1047
  }
941
1048
 
942
- ._header_8wz6r_37 {
1049
+ ._lineText_1eqkm_180 {
943
1050
  display: flex;
944
- align-items: center;
945
- justify-content: space-between;
1051
+ flex-direction: column;
946
1052
  gap: var(--space-12);
947
- margin-bottom: var(--space-16);
1053
+ flex: 1 1 0;
1054
+ min-width: 0;
1055
+ padding-right: var(--space-16);
948
1056
  }
949
1057
 
950
- ._title_8wz6r_45 {
951
- margin: 0;
952
- font-size: var(--type-heading-sm-size, 20px);
1058
+ ._lineTitle_1eqkm_189 {
1059
+ font-size: var(--type-trimmed-sm-size);
953
1060
  font-weight: var(--font-weight-bold);
1061
+ white-space: nowrap;
1062
+ overflow: hidden;
1063
+ text-overflow: ellipsis;
954
1064
  }
955
1065
 
956
- ._close_8wz6r_51 {
957
- display: inline-flex;
958
- align-items: center;
959
- justify-content: center;
960
- width: 32px;
961
- height: 32px;
962
- flex-shrink: 0;
963
- border: none;
964
- border-radius: 50%;
965
- background: #f2f4f5;
966
- color: #16181a;
967
- cursor: pointer;
1066
+ ._lineDetails_1eqkm_197 {
1067
+ display: flex;
1068
+ gap: var(--space-12);
1069
+ font-size: var(--type-trimmed-sm-size);
1070
+ color: var(--color-text-subtle);
1071
+ min-width: 0;
968
1072
  }
969
- ._close_8wz6r_51:focus-visible {
970
- outline: 2px solid #16181a;
971
- outline-offset: 2px;
1073
+
1074
+ ._linePrice_1eqkm_205 {
1075
+ flex-shrink: 0;
1076
+ font-size: var(--type-trimmed-md-size);
1077
+ font-weight: var(--font-weight-bold);
1078
+ white-space: nowrap;
1079
+ font-variant-numeric: tabular-nums;
972
1080
  }
973
1081
 
974
- /* The chart can be wider than the sheet, so it scrolls inside its own container
975
- rather than making the sheet scroll sideways. */
976
- ._tableWrap_8wz6r_71 {
977
- overflow-x: auto;
978
- margin-bottom: var(--space-16);
1082
+ /* ── Address + payment ─────────────────────────────────────────────────────── */
1083
+
1084
+ ._details_1eqkm_215 {
1085
+ display: flex;
1086
+ flex-direction: column;
1087
+ gap: var(--space-16);
1088
+ padding: var(--space-16);
1089
+ background: var(--color-surface-soft);
1090
+ border: 1px solid var(--color-border-default);
1091
+ border-radius: var(--radius-18);
1092
+ /* The design nests this card inside a second, one-step-darker ring. Drawn as a shadow
1093
+ rather than a wrapper element: the two borders are coincident, so it renders the same. */
1094
+ box-shadow: 0 0 0 1px var(--color-border-subtle);
979
1095
  }
980
1096
 
981
- ._table_8wz6r_71 {
982
- width: 100%;
983
- border-collapse: collapse;
984
- font-size: var(--type-trimmed-sm-size, 14px);
1097
+ ._detailBlock_1eqkm_228 {
1098
+ display: flex;
1099
+ flex-direction: column;
1100
+ gap: var(--space-8);
985
1101
  }
986
1102
 
987
- ._table_8wz6r_71 th,
988
- ._table_8wz6r_71 td {
989
- padding: var(--space-8) var(--space-12);
990
- text-align: left;
991
- border-bottom: 1px solid #e2e6e8;
992
- white-space: nowrap;
1103
+ /* The payment block sits a step looser than the address one. */
1104
+ ._detailBlock_1eqkm_228._payment_1eqkm_235 {
1105
+ gap: var(--space-12);
993
1106
  }
994
1107
 
995
- ._table_8wz6r_71 thead th {
1108
+ ._detailLabel_1eqkm_239 {
1109
+ margin: 0;
1110
+ font-size: var(--type-trimmed-sm-size);
996
1111
  font-weight: var(--font-weight-bold);
997
- background: #f7f8f9;
998
1112
  }
999
1113
 
1000
- ._notes_8wz6r_95 {
1114
+ ._detailValue_1eqkm_245 {
1001
1115
  margin: 0;
1002
- padding-left: var(--space-16);
1003
1116
  display: flex;
1004
- flex-direction: column;
1005
- gap: var(--space-6);
1006
- font-size: var(--type-trimmed-sm-size, 14px);
1007
- color: #4a5257;
1117
+ align-items: center;
1118
+ gap: var(--space-8);
1119
+ font-size: var(--type-trimmed-sm-size);
1120
+ line-height: var(--type-caption-sm-line);
1121
+ color: var(--color-text-subtle);
1008
1122
  }
1009
1123
 
1010
- @keyframes _fade_8wz6r_1 {
1011
- from { opacity: 0; }
1012
- to { opacity: 1; }
1124
+ /* The masked digits carry the same weight as the brand beside them. */
1125
+ ._last4_1eqkm_256 {
1126
+ font-weight: var(--font-weight-bold);
1013
1127
  }
1014
- @keyframes _rise_8wz6r_1 {
1015
- from { transform: translateY(12%); }
1016
- to { transform: translateY(0); }
1128
+
1129
+ /* The brand as DAZN send it — lowercase on the wire, capitalised for display rather than
1130
+ matched against a list, so an unfamiliar one still renders properly. */
1131
+ ._brand_1eqkm_262 {
1132
+ text-transform: capitalize;
1133
+ font-weight: var(--font-weight-bold);
1134
+ color: var(--color-text-default);
1017
1135
  }
1018
1136
 
1019
- @media (prefers-reduced-motion: reduce) {
1020
- ._scrim_8wz6r_6,
1021
- ._sheet_8wz6r_17 {
1022
- animation: none;
1023
- }
1137
+ ._divider_1eqkm_268 {
1138
+ height: 1px;
1139
+ background: var(--color-border-default);
1024
1140
  }
1025
- ._root_mx6g7_1 {
1141
+
1142
+ /* ── Order summary ─────────────────────────────────────────────────────────── */
1143
+
1144
+ ._summaryCard_1eqkm_275 {
1026
1145
  display: flex;
1027
1146
  flex-direction: column;
1028
- gap: var(--space-12);
1147
+ background: var(--color-surface-soft);
1148
+ border: 1px solid var(--color-border-default);
1149
+ border-radius: var(--radius-18);
1150
+ overflow: hidden;
1029
1151
  }
1030
1152
 
1031
- ._header_mx6g7_7 {
1153
+ ._summaryCard_1eqkm_275 ._cardTitle_1eqkm_108 {
1032
1154
  display: flex;
1033
1155
  align-items: center;
1156
+ min-height: 60px;
1157
+ padding: 0 var(--space-16);
1034
1158
  }
1035
1159
 
1036
- ._title_mx6g7_12 {
1037
- margin: 0;
1038
- font-size: var(--type-trimmed-md-size);
1039
- line-height: var(--type-trimmed-md-line);
1040
- font-weight: var(--font-weight-bold);
1041
- color: var(--color-text-default);
1160
+ /* The shared summary rows are drawn edge-to-edge in the cart; inside this card they need
1161
+ the card's own gutter. */
1162
+ ._summaryCard_1eqkm_275 > :last-child {
1163
+ padding: 0 var(--space-16);
1042
1164
  }
1043
-
1044
- ._chips_mx6g7_20 {
1165
+ /* 8 where Figma declares 12, for the reason set out on `.titlePrice` below: the gap sits
1166
+ against a text layer whose CSS line box carries leading that Figma's does not. */
1167
+ ._tile_1u9l8_3 {
1045
1168
  display: flex;
1046
- flex-wrap: wrap;
1169
+ flex-direction: column;
1047
1170
  gap: var(--space-8);
1171
+ text-decoration: none;
1172
+ color: inherit;
1048
1173
  }
1049
1174
 
1050
- /* 48px square chips per the mobile/pdp/size component. */
1051
- ._chip_mx6g7_20 {
1052
- display: inline-flex;
1053
- align-items: center;
1054
- justify-content: center;
1055
- width: 48px;
1056
- height: 48px;
1057
- padding: 0;
1058
- border: 1px solid var(--color-border-default);
1059
- border-radius: var(--radius-8);
1060
- background: var(--color-surface-default);
1061
- color: var(--color-text-subtle);
1062
- font-family: inherit;
1063
- font-size: var(--type-trimmed-sm-size);
1064
- font-weight: var(--font-weight-bold);
1065
- cursor: pointer;
1175
+ ._media_1u9l8_11 {
1176
+ position: relative;
1177
+ width: 100%;
1178
+ /* Product-card image is 165:220 (3:4), radius-6 (Figma), sharp not pill-round. */
1179
+ aspect-ratio: 165 / 220;
1180
+ border-radius: var(--radius-6);
1181
+ overflow: hidden;
1182
+ background: var(--color-surface-soft);
1066
1183
  }
1067
1184
 
1068
- ._selected_mx6g7_44 {
1069
- border-color: var(--color-surface-max);
1070
- background: var(--color-surface-max);
1071
- color: var(--color-text-strong);
1185
+ ._image_1u9l8_21 {
1186
+ object-fit: cover;
1072
1187
  }
1073
1188
 
1074
- ._disabled_mx6g7_50 {
1075
- cursor: not-allowed;
1076
- color: var(--color-text-disabled);
1077
- text-decoration: line-through;
1189
+ ._placeholder_1u9l8_25 {
1190
+ width: 100%;
1191
+ height: 100%;
1078
1192
  background: var(--color-surface-soft);
1079
1193
  }
1080
1194
 
1081
- ._error_mx6g7_57 {
1082
- margin: 0;
1083
- font-size: var(--type-trimmed-sm-size);
1195
+ /* TOP PICK flag — top-left over the image, white surface, radius-2, uppercase. Fixed
1196
+ colours (not theme tokens): it always sits over a light product image. */
1197
+ ._topBadge_1u9l8_33 {
1198
+ position: absolute;
1199
+ top: var(--space-8);
1200
+ left: var(--space-8);
1201
+ /* The badge string comes from product data, not from a fixed vocabulary. The design
1202
+ only ever shows short flags (TOP PICK, FEW LEFT), so nothing constrained the width —
1203
+ and a longer one ("99% HAPPY CUSTOMERS") ran past the tile and was sliced mid-word by
1204
+ the media box's own overflow. Truncating inside the tile is the honest failure. */
1205
+ max-width: calc(100% - var(--space-8) * 2);
1206
+ overflow: hidden;
1207
+ text-overflow: ellipsis;
1208
+ padding: var(--space-4) var(--space-6);
1209
+ border-radius: var(--radius-2);
1210
+ background: #f9fafa;
1211
+ color: #0c161c;
1212
+ font-size: var(--type-trimmed-xs-size);
1213
+ line-height: var(--type-trimmed-xs-line);
1084
1214
  font-weight: var(--font-weight-bold);
1085
- color: var(--color-state-surface-critical-default);
1215
+ letter-spacing: 0.12px;
1216
+ text-transform: uppercase;
1217
+ white-space: nowrap;
1086
1218
  }
1087
1219
 
1088
- ._sizeGuide_mx6g7_64 {
1089
- display: inline-flex;
1220
+ ._saleBadge_1u9l8_56 {
1221
+ position: absolute;
1222
+ bottom: var(--space-8);
1223
+ left: var(--space-8);
1224
+ }
1225
+
1226
+ /* Scrim over the whole image, not a corner flag (Figma out-of-stock state). */
1227
+ ._oos_1u9l8_63 {
1228
+ position: absolute;
1229
+ inset: 0;
1230
+ display: flex;
1090
1231
  align-items: center;
1091
- gap: var(--space-2);
1092
- align-self: flex-start;
1093
- padding: 0;
1094
- border: none;
1095
- background: none;
1096
- color: var(--color-text-subtle);
1097
- font-family: inherit;
1232
+ justify-content: center;
1233
+ border-radius: var(--radius-6);
1234
+ background: rgba(0, 0, 0, 0.45);
1235
+ color: #ffffff;
1098
1236
  font-size: var(--type-trimmed-sm-size);
1099
- cursor: pointer;
1237
+ font-weight: var(--font-weight-bold);
1100
1238
  }
1101
1239
 
1102
- ._infoIcon_mx6g7_78 {
1103
- display: block;
1104
- }
1105
- ._root_5qbaw_1 {
1240
+ /*
1241
+ * 8, where Figma declares 16 — and this is not a drift, so please do not "correct" it.
1242
+ *
1243
+ * Every text layer in this card is `text-box-trim: trim-both` with a cap-alphabetic edge:
1244
+ * Figma's boxes stop at the cap height and the baseline, so its 16 is 16 of visible white.
1245
+ * A CSS line box carries the font's leading above and below the glyphs, so the same 16
1246
+ * renders as 23 — measured off both, title glyph bottom to price glyph top: Figma 15, ours
1247
+ * 23. Halving the declared gap puts ours at 15.
1248
+ *
1249
+ * `text-box-trim` would let us write the design's own number, and it is Chrome 133+ only.
1250
+ * This package still supports the WebView that ships with React 16-era DAZN builds.
1251
+ */
1252
+ ._bottom_1u9l8_88 {
1106
1253
  display: flex;
1107
1254
  flex-direction: column;
1108
- gap: var(--space-12);
1255
+ gap: var(--space-8);
1109
1256
  }
1110
1257
 
1111
- ._header_5qbaw_7 {
1112
- margin: 0;
1258
+ ._titlePrice_1u9l8_1 {
1113
1259
  display: flex;
1114
- gap: var(--space-4);
1115
- font-size: var(--type-trimmed-md-size);
1116
- }
1117
-
1118
- ._title_5qbaw_14 {
1119
- font-weight: var(--font-weight-bold);
1120
- color: var(--color-text-default);
1260
+ flex-direction: column;
1261
+ gap: var(--space-8);
1121
1262
  }
1122
1263
 
1123
- ._value_5qbaw_19 {
1264
+ ._title_1u9l8_1 {
1265
+ font-size: var(--type-paragraph-size);
1266
+ line-height: 1.3;
1124
1267
  font-weight: var(--font-weight-regular);
1125
- color: var(--color-text-subtle);
1268
+ color: var(--color-text-default);
1269
+ display: -webkit-box;
1270
+ -webkit-line-clamp: 2;
1271
+ line-clamp: 2;
1272
+ -webkit-box-orient: vertical;
1273
+ overflow: hidden;
1274
+ /* Reserve both lines rather than only capping at two. The clamp stops a long title
1275
+ overflowing but lets a short one collapse to one line, and in a two-column grid that
1276
+ pulls its price up level with the neighbour's title — a ragged price baseline across
1277
+ the row, which reads as broken alignment rather than as varying title lengths. */
1278
+ min-height: calc(2 * 1.3em);
1126
1279
  }
1127
1280
 
1128
- ._tiles_5qbaw_24 {
1281
+ /* Colour swatches (Figma product-card): 0.5px-bordered boxes with a 14px colour square;
1282
+ the first reads as selected (dark border). */
1283
+ ._swatches_1u9l8_119 {
1129
1284
  display: flex;
1130
- flex-wrap: wrap;
1131
- gap: var(--space-8);
1285
+ gap: var(--space-4);
1286
+ align-items: center;
1132
1287
  }
1133
1288
 
1134
- /* 79×105 image tile, radius-8. Selected gets a dark 2px border (Figma color/tile). */
1135
- ._tile_5qbaw_24 {
1136
- position: relative;
1137
- width: 79px;
1138
- height: 105px;
1139
- padding: 0;
1140
- /* Figma color/tile: 1px dark border on the selected tile, none otherwise. */
1141
- border: 1px solid transparent;
1142
- border-radius: var(--radius-8);
1143
- overflow: hidden;
1144
- background: var(--color-surface-soft);
1145
- cursor: pointer;
1289
+ ._swatch_1u9l8_119 {
1290
+ display: inline-flex;
1291
+ padding: 2px;
1292
+ border: 0.5px solid var(--color-border-subtle);
1293
+ border-radius: var(--radius-2);
1146
1294
  }
1147
1295
 
1148
- ._selected_5qbaw_44 {
1296
+ ._swatchActive_1u9l8_132 {
1149
1297
  border-color: var(--color-surface-max);
1150
1298
  }
1151
1299
 
1152
- ._image_5qbaw_48 {
1153
- object-fit: cover;
1300
+ ._swatchDot_1u9l8_136 {
1301
+ display: block;
1302
+ width: 14px;
1303
+ height: 14px;
1304
+ border-radius: var(--radius-2);
1154
1305
  }
1155
1306
 
1156
- ._placeholder_5qbaw_52 {
1157
- position: absolute;
1158
- inset: 0;
1159
- background: var(--color-surface-soft);
1307
+ /*
1308
+ * Spotlight card (Figma `size=large`, 3668:12485). Full-bleed with its own 16px gutter —
1309
+ * the block that places it adds none — a taller 343:457 image and an 18px title.
1310
+ */
1311
+ ._large_1u9l8_147 {
1312
+ padding: 0 var(--space-16);
1160
1313
  }
1161
1314
 
1162
- /* "N left" low-stock badge (Figma badge/sale/size2): warning tones, top-right of tile. */
1163
- ._stockBadge_5qbaw_59 {
1164
- position: absolute;
1165
- top: var(--space-4);
1166
- right: var(--space-4);
1167
- display: inline-flex;
1168
- align-items: center;
1169
- height: 20px;
1170
- padding: var(--space-4);
1171
- border-radius: var(--radius-4);
1172
- background: var(--color-state-surface-warning-soft);
1173
- color: var(--color-state-text-warning);
1174
- font-size: var(--type-trimmed-xs-size);
1175
- line-height: var(--type-trimmed-xs-line);
1176
- font-weight: var(--font-weight-bold);
1177
- white-space: nowrap;
1315
+ ._large_1u9l8_147 ._media_1u9l8_11 {
1316
+ aspect-ratio: 343 / 457;
1178
1317
  }
1179
1318
 
1180
- ._soldOut_5qbaw_76 {
1181
- cursor: not-allowed;
1319
+ /*
1320
+ * No two-line reservation here. It exists so that neighbouring cards in a grid or rail
1321
+ * keep their prices on one baseline, and the spotlight card is alone on its row — there is
1322
+ * nothing to line up with, so all it reserves is empty space under a one-line title.
1323
+ * Figma's note on this variant is "height hugs".
1324
+ */
1325
+ ._large_1u9l8_147 ._title_1u9l8_1 {
1326
+ font-size: 18px;
1327
+ min-height: 0;
1182
1328
  }
1183
-
1184
- ._soldOutLabel_5qbaw_80 {
1185
- position: absolute;
1186
- inset: 0;
1329
+ ._bar_fn0g9_1 {
1187
1330
  display: flex;
1188
1331
  align-items: center;
1189
- justify-content: center;
1190
- background: rgba(249, 250, 250, 0.72);
1191
- color: var(--color-text-subtle);
1192
- font-size: var(--type-trimmed-xs-size);
1193
- font-weight: var(--font-weight-bold);
1194
- text-transform: uppercase;
1195
- }
1196
- ._fieldset_1mnz1_1 {
1197
- border: none;
1198
- margin: 0;
1199
- padding: 0;
1200
- min-width: 0;
1201
- }
1202
-
1203
- ._legend_1mnz1_8 {
1204
- font-size: var(--type-trimmed-sm-size);
1205
- font-weight: var(--font-weight-bold);
1206
- color: var(--color-text-subtle);
1207
- margin-bottom: var(--space-8);
1208
- padding: 0;
1332
+ gap: var(--space-8);
1333
+ padding: var(--space-8) var(--space-16);
1209
1334
  }
1210
1335
 
1211
- ._options_1mnz1_16 {
1336
+ ._chips_fn0g9_8 {
1212
1337
  display: flex;
1213
- flex-wrap: wrap;
1214
1338
  gap: var(--space-8);
1339
+ overflow-x: auto;
1340
+ flex: 1 1 auto;
1341
+ -webkit-overflow-scrolling: touch;
1215
1342
  }
1216
1343
 
1217
- ._option_1mnz1_16 {
1344
+ ._chip_fn0g9_8 {
1345
+ flex: 0 0 auto;
1218
1346
  min-height: var(--tap-target-min);
1219
- min-width: var(--tap-target-min);
1220
1347
  padding: 0 var(--space-16);
1221
- border-radius: var(--radius-8);
1348
+ border-radius: var(--radius-full);
1222
1349
  border: 1px solid var(--color-border-default);
1223
1350
  background: var(--color-surface-default);
1224
1351
  color: var(--color-text-default);
1225
1352
  font-family: inherit;
1226
1353
  font-size: var(--type-trimmed-sm-size);
1227
- font-weight: var(--font-weight-bold);
1228
1354
  cursor: pointer;
1229
1355
  }
1230
1356
 
1231
- ._selected_1mnz1_36 {
1232
- border-color: var(--color-surface-max);
1357
+ ._on_fn0g9_29 {
1233
1358
  background: var(--color-surface-max);
1234
1359
  color: var(--color-text-strong);
1360
+ border-color: var(--color-surface-max);
1235
1361
  }
1236
1362
 
1237
- ._disabled_1mnz1_42 {
1238
- cursor: not-allowed;
1239
- color: var(--color-text-disabled);
1240
- text-decoration: line-through;
1241
- background: var(--color-surface-soft);
1363
+ ._clear_fn0g9_35 {
1364
+ flex: 0 0 auto;
1365
+ min-height: var(--tap-target-min);
1366
+ padding: 0 var(--space-8);
1367
+ border: none;
1368
+ background: transparent;
1369
+ color: var(--color-text-subtle);
1370
+ font-family: inherit;
1371
+ font-size: var(--type-trimmed-sm-size);
1372
+ text-decoration: underline;
1373
+ cursor: pointer;
1242
1374
  }
1243
- ._root_9s1n4_1 {
1375
+ ._wrap_15ntf_1 {
1244
1376
  display: flex;
1245
1377
  flex-direction: column;
1246
- gap: var(--space-4);
1378
+ gap: var(--space-12);
1247
1379
  }
1248
1380
 
1249
- ._title_9s1n4_7 {
1250
- margin: 0 0 var(--space-8);
1251
- font-size: var(--type-trimmed-md-size);
1252
- line-height: var(--type-trimmed-md-line);
1253
- font-weight: var(--font-weight-bold);
1381
+ ._grid_15ntf_7 {
1382
+ list-style: none;
1383
+ margin: 0;
1384
+ padding: 0 var(--space-16);
1385
+ display: grid;
1386
+ grid-template-columns: repeat(2, 1fr);
1387
+ gap: var(--space-16);
1388
+ }
1389
+ /* The block owns its vertical rhythm (Figma padding 24/16) — the page stacks blocks flush. */
1390
+ ._rail_5z5e9_2 {
1391
+ display: flex;
1392
+ flex-direction: column;
1393
+ gap: var(--space-12);
1394
+ padding: var(--space-24) 0;
1395
+ }
1396
+
1397
+ ._header_5z5e9_9 {
1398
+ display: flex;
1399
+ align-items: baseline;
1400
+ justify-content: space-between;
1401
+ padding: 0 var(--space-16);
1402
+ }
1403
+
1404
+ ._title_5z5e9_16 {
1405
+ font-size: var(--type-heading-md-size);
1406
+ /* Figma rail header: 20px, line-height 1.38. */
1407
+ line-height: 1.38;
1408
+ }
1409
+
1410
+ ._seeAll_5z5e9_22 {
1411
+ font-size: var(--type-trimmed-sm-size);
1412
+ color: var(--color-text-subtle);
1413
+ text-decoration: none;
1414
+ }
1415
+
1416
+ ._track_5z5e9_28 {
1417
+ display: flex;
1418
+ gap: var(--space-12);
1419
+ list-style: none;
1420
+ margin: 0;
1421
+ padding: 0 var(--space-16) var(--space-8);
1422
+ overflow-x: auto;
1423
+ scroll-snap-type: x mandatory;
1424
+ /* Keep the 16px leading/trailing inset under mandatory snap: without this the
1425
+ snapport start sits at the border edge, so the first item snaps to x=0 and the
1426
+ left padding is scrolled away — misaligning tiles from the section heading. */
1427
+ scroll-padding-inline: var(--space-16);
1428
+ -webkit-overflow-scrolling: touch;
1429
+ }
1430
+
1431
+ /* Fixed 165px, as the card is drawn. At 375 two fit the 343 content width and the third
1432
+ peeks, which is the signal the design asks for. */
1433
+ ._item_5z5e9_45 {
1434
+ flex: 0 0 auto;
1435
+ width: 165px;
1436
+ scroll-snap-align: start;
1437
+ }
1438
+
1439
+ /*
1440
+ * Inset variant — the rail lives INSIDE an already-padded column (the PDP content
1441
+ * block, 16px each side) rather than bleeding edge-to-edge like the home rails. The
1442
+ * parent supplies the 16px inset, so the header/track add none of their own, and tiles
1443
+ * size to show exactly two across the content width (per the PDP design: rail x16 w343).
1444
+ */
1445
+ ._inset_5z5e9_57 ._header_5z5e9_9 {
1446
+ padding: 0;
1447
+ }
1448
+
1449
+ ._inset_5z5e9_57 ._track_5z5e9_28 {
1450
+ padding-left: 0;
1451
+ padding-right: 0;
1452
+ scroll-padding-inline: 0;
1453
+ }
1454
+
1455
+ /*
1456
+ * No vertical padding inside the PDP. The standalone rail carries 24px of its own so it
1457
+ * can sit between flush blocks on the home screen; the PDP content column already spaces
1458
+ * its blocks 32px apart, so keeping it here made the gap above the rail 56px.
1459
+ */
1460
+ ._inset_5z5e9_57 {
1461
+ padding: 0;
1462
+ }
1463
+
1464
+ ._inset_5z5e9_57 ._item_5z5e9_45 {
1465
+ /* Fixed 165px cards (Figma product-card size=default). At 375 two fit the 343 content
1466
+ width exactly; more scroll horizontally. */
1467
+ width: 165px;
1468
+ max-width: none;
1469
+ }
1470
+ ._overlay_s4eai_1 {
1471
+ position: fixed;
1472
+ inset: 0;
1473
+ z-index: 900;
1474
+ display: flex;
1475
+ align-items: flex-end;
1476
+ justify-content: center;
1477
+ }
1478
+
1479
+ ._scrim_s4eai_10 {
1480
+ position: absolute;
1481
+ inset: 0;
1482
+ background: rgba(8, 14, 18, 0.5);
1483
+ }
1484
+
1485
+ ._sheet_s4eai_16 {
1486
+ position: relative;
1487
+ width: 100%;
1488
+ max-width: 480px;
1489
+ max-height: 90dvh;
1490
+ background: var(--color-surface-default);
1491
+ border-radius: var(--radius-18) var(--radius-18) 0 0;
1492
+ display: flex;
1493
+ flex-direction: column;
1494
+ animation: _slide-up_s4eai_1 200ms ease;
1495
+ padding-bottom: var(--safe-area-bottom);
1496
+ }
1497
+
1498
+ @keyframes _slide-up_s4eai_1 {
1499
+ from {
1500
+ transform: translateY(100%);
1501
+ }
1502
+ to {
1503
+ transform: translateY(0);
1504
+ }
1505
+ }
1506
+
1507
+ /* Drag handle strip. `touch-action: none` stops the browser claiming the vertical
1508
+ gesture as a page scroll before the pointer handlers see it. Confined to this strip
1509
+ so drags starting on the scrollable body still scroll the body. */
1510
+ ._grabArea_s4eai_41 {
1511
+ padding: var(--space-8) 0 var(--space-4);
1512
+ touch-action: none;
1513
+ cursor: grab;
1514
+ }
1515
+
1516
+ ._grabArea_s4eai_41:active {
1517
+ cursor: grabbing;
1518
+ }
1519
+
1520
+ ._grabber_s4eai_51 {
1521
+ width: 40px;
1522
+ height: 4px;
1523
+ margin: 0 auto;
1524
+ border-radius: 2px;
1525
+ background: var(--color-border-default);
1526
+ }
1527
+
1528
+ ._header_s4eai_59 {
1529
+ display: flex;
1530
+ align-items: center;
1531
+ justify-content: space-between;
1532
+ padding: var(--space-16);
1533
+ border-bottom: 1px solid var(--color-border-default);
1534
+ }
1535
+
1536
+ ._title_s4eai_67 {
1537
+ font-size: var(--type-heading-md-size);
1538
+ }
1539
+
1540
+ ._close_s4eai_71 {
1541
+ min-width: var(--tap-target-min);
1542
+ min-height: var(--tap-target-min);
1543
+ display: inline-flex;
1544
+ align-items: center;
1545
+ justify-content: center;
1546
+ border: none;
1547
+ background: transparent;
1548
+ font-size: var(--type-trimmed-md-size);
1549
+ cursor: pointer;
1550
+ color: var(--color-text-subtle);
1551
+ }
1552
+
1553
+ ._body_s4eai_84 {
1554
+ overflow-y: auto;
1555
+ padding: var(--space-16);
1556
+ flex: 1 1 auto;
1557
+ }
1558
+
1559
+ ._footer_s4eai_90 {
1560
+ border-top: 1px solid var(--color-border-default);
1561
+ padding: var(--space-16);
1562
+ }
1563
+ ._wrap_1rmip_1 {
1564
+ display: inline-flex;
1565
+ flex-direction: column;
1566
+ align-items: flex-start;
1567
+ gap: var(--space-4);
1568
+ }
1569
+
1570
+ /* Figma `mobile/stepper` (3605:8351): a filled 106×32 chip, not an outlined pill. Fixed
1571
+ width, so the `+` does not shift between qty 9 and 10. */
1572
+ ._stepper_1rmip_10 {
1573
+ display: inline-flex;
1574
+ align-items: center;
1575
+ justify-content: space-between;
1576
+ width: 106px;
1577
+ height: 32px;
1578
+ padding: 0 var(--space-8);
1579
+ border-radius: var(--radius-8);
1580
+ background: var(--color-surface-subtle);
1581
+ overflow: hidden;
1582
+ }
1583
+
1584
+ /* Why the increment stopped. Subtle, but present — never a silently dead control. */
1585
+ ._cap_1rmip_23 {
1586
+ font-size: var(--type-trimmed-xs-size, 12px);
1587
+ font-weight: var(--font-weight-regular, 400);
1588
+ color: var(--color-text-subtle);
1589
+ }
1590
+
1591
+ /* 20px icon in a 32px row, per the component note. Below our 44px `--tap-target-min`
1592
+ because the design fixes the chip at 32; still clears WCAG 2.2 SC 2.5.8 (24px). */
1593
+ ._button_1rmip_31 {
1594
+ width: 32px;
1595
+ height: 32px;
1596
+ margin: 0 -6px; /* pulls the 32px target out to the chip's 8px padding edge */
1597
+ display: inline-flex;
1598
+ align-items: center;
1599
+ justify-content: center;
1600
+ border: none;
1601
+ background: transparent;
1602
+ border-radius: var(--radius-4);
1603
+ color: var(--color-text-default);
1604
+ cursor: pointer;
1605
+ }
1606
+
1607
+ ._button_1rmip_31:disabled {
1608
+ color: var(--color-text-disabled);
1609
+ cursor: not-allowed;
1610
+ }
1611
+
1612
+ ._value_1rmip_50 {
1613
+ min-width: 2ch;
1614
+ text-align: center;
1615
+ font-size: var(--type-trimmed-md-size);
1616
+ line-height: var(--type-trimmed-md-line);
1617
+ font-weight: var(--font-weight-bold);
1254
1618
  color: var(--color-text-default);
1619
+ font-variant-numeric: tabular-nums;
1620
+ }
1621
+ ._row_1xbbn_1 {
1622
+ display: flex;
1623
+ gap: var(--space-16);
1624
+ align-items: center;
1625
+ }
1626
+
1627
+ ._pending_1xbbn_7 {
1628
+ opacity: 0.6;
1629
+ }
1630
+
1631
+ ._media_1xbbn_11 {
1632
+ position: relative;
1633
+ flex-shrink: 0;
1634
+ width: 100px;
1635
+ height: 100px;
1636
+ border-radius: var(--radius-8);
1637
+ overflow: hidden;
1638
+ background: var(--color-surface-soft);
1639
+ }
1640
+
1641
+ ._image_1xbbn_21 {
1642
+ object-fit: cover;
1643
+ }
1644
+
1645
+ ._placeholder_1xbbn_25 {
1646
+ width: 100%;
1647
+ height: 100%;
1648
+ }
1649
+
1650
+ /* Bottom-left over the thumbnail, per the design. */
1651
+ ._saleBadge_1xbbn_31 {
1652
+ position: absolute;
1653
+ left: var(--space-6);
1654
+ bottom: var(--space-6);
1655
+ }
1656
+
1657
+ /*
1658
+ * The cart's out-of-stock line (Figma cart state 4458:49239). Same scrim over the whole
1659
+ * thumbnail as the product card, because it is the same statement — this one is not
1660
+ * buyable. In the cart it also has to survive being read next to a line that IS, which a
1661
+ * corner flag does not.
1662
+ *
1663
+ * Radius matches the 8px thumbnail rather than the card's 6.
1664
+ */
1665
+ ._oos_1xbbn_45 {
1666
+ position: absolute;
1667
+ inset: 0;
1668
+ display: flex;
1669
+ align-items: center;
1670
+ justify-content: center;
1671
+ border-radius: var(--radius-8);
1672
+ background: rgba(0, 0, 0, 0.45);
1673
+ color: #ffffff;
1674
+ font-size: var(--type-trimmed-sm-size);
1675
+ font-weight: var(--font-weight-bold);
1676
+ }
1677
+
1678
+ ._info_1xbbn_58 {
1679
+ display: flex;
1680
+ flex-direction: column;
1681
+ gap: var(--space-16);
1682
+ flex: 1 1 0;
1683
+ min-width: 0;
1684
+ }
1685
+
1686
+ ._head_1xbbn_66 {
1687
+ display: flex;
1688
+ align-items: flex-start;
1689
+ justify-content: space-between;
1690
+ gap: var(--space-8);
1691
+ }
1692
+
1693
+ ._meta_1xbbn_73 {
1694
+ display: flex;
1695
+ flex-direction: column;
1696
+ gap: var(--space-12);
1697
+ min-width: 0;
1698
+ }
1699
+
1700
+ ._title_1xbbn_80 {
1701
+ font-size: var(--type-trimmed-md-size);
1702
+ font-weight: var(--font-weight-bold);
1703
+ /* Product titles are catalogue copy and can run long; unbounded, the title pushes the
1704
+ price out of alignment down the list. */
1705
+ display: -webkit-box;
1706
+ -webkit-line-clamp: 2;
1707
+ -webkit-box-orient: vertical;
1708
+ overflow: hidden;
1709
+ }
1710
+
1711
+ /* "Color: White Size: M" */
1712
+ ._details_1xbbn_92 {
1713
+ display: flex;
1714
+ flex-wrap: wrap;
1715
+ gap: var(--space-6);
1716
+ min-width: 0;
1717
+ }
1718
+
1719
+ ._detail_1xbbn_92 {
1720
+ font-size: var(--type-trimmed-sm-size);
1721
+ font-weight: var(--font-weight-regular);
1722
+ color: var(--color-text-subtle);
1723
+ white-space: nowrap;
1724
+ }
1725
+
1726
+ ._price_1xbbn_106 {
1727
+ display: flex;
1728
+ flex-direction: column;
1729
+ align-items: flex-end;
1730
+ gap: var(--space-6);
1731
+ flex-shrink: 0;
1732
+ }
1733
+
1734
+ ._amount_1xbbn_114 {
1735
+ font-size: var(--type-trimmed-md-size);
1736
+ font-weight: var(--font-weight-bold);
1737
+ white-space: nowrap;
1738
+ }
1739
+
1740
+ ._was_1xbbn_120 {
1741
+ font-size: var(--type-trimmed-sm-size);
1742
+ font-weight: var(--font-weight-regular);
1743
+ color: var(--color-text-disabled);
1744
+ text-decoration: line-through;
1745
+ white-space: nowrap;
1746
+ }
1747
+ ._box_1cf1a_1 {
1748
+ display: flex;
1749
+ flex-direction: column;
1750
+ gap: var(--space-12);
1751
+ padding: var(--space-16);
1752
+ border-radius: var(--radius-8);
1753
+ background: var(--color-surface-soft);
1754
+ }
1755
+
1756
+ ._row_1cf1a_10 {
1757
+ display: flex;
1758
+ align-items: center;
1759
+ gap: var(--space-12);
1760
+ }
1761
+
1762
+ ._icon_1cf1a_16 {
1763
+ display: block;
1764
+ flex-shrink: 0;
1765
+ color: var(--color-text-default);
1766
+ }
1767
+
1768
+ ._label_1cf1a_22 {
1769
+ margin: 0;
1770
+ font-size: var(--type-trimmed-sm-size);
1771
+ font-weight: var(--font-weight-bold);
1772
+ color: var(--color-text-default);
1773
+ }
1774
+
1775
+ ._divider_1cf1a_29 {
1776
+ height: 1px;
1777
+ background: var(--color-border-default);
1778
+ }
1779
+ ._bar_1aly7_1 {
1780
+ position: sticky;
1781
+ bottom: 0;
1782
+ left: 0;
1783
+ right: 0;
1784
+ display: flex;
1785
+ flex-direction: column;
1786
+ gap: var(--space-6);
1787
+ /* pt-16 / pb-24 in both variants. */
1788
+ padding: var(--space-16) var(--space-16) calc(var(--space-24) + var(--safe-area-bottom));
1789
+ background: var(--color-surface-default);
1790
+ z-index: 50;
1791
+ }
1792
+
1793
+ /* Figma mobile/pdp/cta-bar — a 6% lift, not the heavy shelf we had. */
1794
+ ._floating_1aly7_16 {
1795
+ border-radius: var(--radius-12) var(--radius-12) 0 0;
1796
+ box-shadow: 0 -4px 6px rgba(176, 176, 176, 0.06);
1797
+ }
1798
+
1799
+ /* Figma cart. */
1800
+ ._divider_1aly7_22 {
1801
+ border-top: 1px solid var(--color-border-default);
1802
+ }
1803
+
1804
+ ._info_1aly7_26 {
1805
+ display: flex;
1806
+ align-items: center;
1807
+ justify-content: space-between;
1808
+ font-weight: var(--font-weight-bold);
1809
+ }
1810
+
1811
+ ._actions_1aly7_33 {
1812
+ display: flex;
1813
+ gap: var(--space-12);
1814
+ }
1815
+
1816
+ ._actions_1aly7_33 > * {
1817
+ flex: 1 1 0;
1818
+ }
1819
+ /*
1820
+ * Shown while the cart is locked for checkout (contracts 2.11.0 `Cart.locked`).
1821
+ *
1822
+ * Warning tones rather than critical: nothing has gone wrong, the fan simply cannot edit
1823
+ * right now. Critical red here would read as a failure and push people to abandon a
1824
+ * checkout that is working.
1825
+ */
1826
+ /*
1827
+ * The surface has a minimum height; without this the screen inside it stops at its content
1828
+ * and the sticky CTA stops with it, leaving a strip of bare surface underneath. Column
1829
+ * chain rather than a height: the surface stretches its child, the screen takes what is
1830
+ * left, and `.body` absorbs the slack so the bar is pushed onto the bottom edge on a short
1831
+ * cart and still sticks on a long one.
1832
+ */
1833
+ ._surface_1t90m_15 {
1834
+ display: flex;
1835
+ flex-direction: column;
1836
+ }
1837
+
1838
+ ._screen_1t90m_20 {
1839
+ display: flex;
1840
+ flex-direction: column;
1841
+ flex: 1 1 auto;
1842
+ }
1843
+
1844
+ ._body_1t90m_12 {
1845
+ display: flex;
1846
+ flex-direction: column;
1847
+ gap: var(--space-20);
1848
+ padding: 0 var(--space-16);
1849
+ flex: 1 1 auto;
1850
+ }
1851
+
1852
+ ._items_1t90m_34 {
1853
+ display: flex;
1854
+ flex-direction: column;
1855
+ gap: var(--space-20);
1856
+ }
1857
+
1858
+ ._sectionHeader_1t90m_40 {
1859
+ display: flex;
1860
+ align-items: center;
1861
+ gap: var(--space-12);
1862
+ }
1863
+
1864
+ ._sectionTitle_1t90m_46 {
1865
+ margin: 0;
1866
+ font-size: var(--type-heading-md-size);
1867
+ line-height: 1.38;
1868
+ font-weight: var(--font-weight-bold);
1869
+ }
1870
+
1871
+ ._count_1t90m_53 {
1872
+ display: inline-flex;
1873
+ align-items: center;
1874
+ justify-content: center;
1875
+ min-width: 16px;
1876
+ height: 16px;
1877
+ padding: 0 var(--space-4);
1878
+ border-radius: var(--radius-full);
1879
+ background: var(--color-surface-subtle);
1880
+ font-size: var(--type-trimmed-xs-size);
1881
+ font-weight: var(--font-weight-bold);
1882
+ letter-spacing: 0.12px;
1883
+ font-variant-numeric: tabular-nums;
1884
+ }
1885
+
1886
+ /*
1887
+ * 20, not 12. Figma's `items` is one column at `gap-20` holding the section header and
1888
+ * every line, so the space under "Items" and the space between two lines are the same
1889
+ * measurement — 148 → 268 → 388, on 100px lines.
1890
+ */
1891
+ ._lines_1t90m_73 {
1892
+ list-style: none;
1893
+ margin: 0;
1894
+ padding: 0;
1895
+ display: flex;
1896
+ flex-direction: column;
1897
+ gap: var(--space-20);
1898
+ }
1899
+
1900
+ ._lockNotice_1t90m_82 {
1901
+ margin: 0;
1902
+ padding: var(--space-12) var(--space-16);
1903
+ background: var(--color-state-surface-warning-soft);
1904
+ color: var(--color-text-default);
1905
+ border-bottom: 1px solid var(--color-border-subtle);
1906
+ font-size: 14px;
1907
+ line-height: 1.4;
1908
+ }
1909
+ ._gallery_h8m00_1 {
1910
+ position: relative;
1911
+ display: flex;
1912
+ flex-direction: column;
1913
+ gap: var(--space-12);
1914
+ }
1915
+
1916
+ ._track_h8m00_8 {
1917
+ display: flex;
1918
+ overflow-x: auto;
1919
+ scroll-snap-type: x mandatory;
1920
+ -webkit-overflow-scrolling: touch;
1921
+ /* Hero media is 375×485 (portrait) and fully sharp (no rounded corners) — it sits
1922
+ edge-to-edge and flush under the header. */
1923
+ aspect-ratio: 375 / 485;
1924
+ background: var(--color-surface-soft);
1925
+ }
1926
+
1927
+ ._slide_h8m00_19 {
1928
+ position: relative;
1929
+ flex: 0 0 100%;
1930
+ scroll-snap-align: center;
1931
+ aspect-ratio: 375 / 485;
1255
1932
  }
1256
1933
 
1257
- ._body_9s1n4_15 {
1258
- margin: 0;
1259
- font-size: var(--type-paragraph-size);
1260
- line-height: var(--type-paragraph-line);
1261
- color: var(--color-text-subtle);
1262
- white-space: pre-line;
1934
+ ._image_h8m00_26 {
1935
+ object-fit: cover;
1263
1936
  }
1264
1937
 
1265
- /* Collapsed: clamp to three lines. */
1266
- ._clamped_9s1n4_24 {
1267
- display: -webkit-box;
1268
- -webkit-line-clamp: 3;
1269
- -webkit-box-orient: vertical;
1270
- overflow: hidden;
1938
+ ._placeholder_h8m00_30 {
1939
+ width: 100%;
1940
+ aspect-ratio: 375 / 485;
1941
+ background: var(--color-surface-soft);
1271
1942
  }
1272
1943
 
1273
- ._toggle_9s1n4_31 {
1274
- align-self: flex-start;
1944
+ /* Share button — top-right chrome over the image (mobile/button: 32px, soft, radius-8). */
1945
+ ._share_h8m00_37 {
1946
+ position: absolute;
1947
+ top: var(--space-16);
1948
+ right: var(--space-16);
1949
+ display: inline-flex;
1950
+ align-items: center;
1951
+ justify-content: center;
1952
+ width: 32px;
1953
+ height: 32px;
1275
1954
  padding: 0;
1276
1955
  border: none;
1277
- background: none;
1278
- color: var(--color-text-subtle);
1279
- font-family: inherit;
1280
- font-size: var(--type-paragraph-size);
1281
- /* Figma: "more"/"Less" is a regular-weight subtle link, not bold. */
1282
- font-weight: var(--font-weight-regular);
1283
- cursor: pointer;
1284
- }
1285
- ._box_1cf1a_1 {
1286
- display: flex;
1287
- flex-direction: column;
1288
- gap: var(--space-12);
1289
- padding: var(--space-16);
1290
1956
  border-radius: var(--radius-8);
1291
1957
  background: var(--color-surface-soft);
1958
+ color: var(--color-text-default);
1959
+ cursor: pointer;
1292
1960
  }
1293
1961
 
1294
- ._row_1cf1a_10 {
1962
+ ._dots_h8m00_54 {
1295
1963
  display: flex;
1296
- align-items: center;
1297
- gap: var(--space-12);
1964
+ justify-content: center;
1965
+ gap: var(--space-6);
1298
1966
  }
1299
1967
 
1300
- ._icon_1cf1a_16 {
1301
- display: block;
1302
- flex-shrink: 0;
1303
- color: var(--color-text-default);
1968
+ ._dot_h8m00_54 {
1969
+ width: 8px;
1970
+ height: 8px;
1971
+ padding: 0;
1972
+ border: none;
1973
+ border-radius: var(--radius-full);
1974
+ background: var(--color-border-default);
1975
+ cursor: pointer;
1976
+ /* Enlarge the hit area to >=44px without growing the visual dot. */
1977
+ position: relative;
1304
1978
  }
1305
1979
 
1306
- ._label_1cf1a_22 {
1307
- margin: 0;
1308
- font-size: var(--type-trimmed-sm-size);
1309
- font-weight: var(--font-weight-bold);
1310
- color: var(--color-text-default);
1980
+ ._dot_h8m00_54::after {
1981
+ content: '';
1982
+ position: absolute;
1983
+ inset: -18px;
1311
1984
  }
1312
1985
 
1313
- ._divider_1cf1a_29 {
1314
- height: 1px;
1315
- background: var(--color-border-default);
1986
+ ._dotActive_h8m00_78 {
1987
+ background: var(--color-surface-max);
1316
1988
  }
1317
- ._row_l4edh_1 {
1989
+ /*
1990
+ * Size-guide bottom sheet. Uses the same scrim + rounded-top surface as the other PDP
1991
+ * overlays so it reads as one system. Fixed light surface: the PDP is light in every
1992
+ * theme, and an inverted sheet over a light page would be jarring.
1993
+ */
1994
+ ._scrim_8wz6r_6 {
1995
+ position: fixed;
1996
+ inset: 0;
1997
+ z-index: 60;
1318
1998
  display: flex;
1319
- flex-wrap: wrap;
1320
- gap: var(--space-4) var(--space-6);
1321
- margin: 0;
1322
- padding: 0;
1323
- list-style: none;
1999
+ align-items: flex-end;
2000
+ justify-content: center;
2001
+ background: rgba(8, 14, 18, 0.5);
2002
+ animation: _fade_8wz6r_1 160ms ease-out;
1324
2003
  }
1325
2004
 
1326
- /* mobile/badge/top-pick: soft surface, dark text, uppercase, radius-min. */
1327
- ._badge_l4edh_11 {
1328
- display: inline-flex;
1329
- align-items: center;
1330
- justify-content: center;
1331
- height: 20px;
1332
- padding: var(--space-4) var(--space-6);
1333
- border-radius: var(--radius-2);
1334
- background: var(--color-surface-soft);
1335
- color: var(--color-text-default);
1336
- font-size: var(--type-trimmed-xs-size);
1337
- line-height: var(--type-trimmed-xs-line);
1338
- font-weight: var(--font-weight-bold);
1339
- letter-spacing: 0.12px;
1340
- text-transform: uppercase;
1341
- white-space: nowrap;
2005
+ ._sheet_8wz6r_17 {
2006
+ width: 100%;
2007
+ max-width: 480px;
2008
+ max-height: 80vh;
2009
+ overflow-y: auto;
2010
+ padding: var(--space-8) var(--space-16) var(--space-24);
2011
+ border-radius: var(--radius-18) var(--radius-18) 0 0;
2012
+ background: #ffffff;
2013
+ color: #16181a;
2014
+ animation: _rise_8wz6r_1 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
1342
2015
  }
1343
- /* Figma mobile/pdp/promo: success-soft surface, success text, radius-8, p16, h48. */
1344
- ._banner_1xvw4_2 {
2016
+
2017
+ ._grabber_8wz6r_29 {
2018
+ width: 40px;
2019
+ height: 4px;
2020
+ margin: 0 auto var(--space-12);
2021
+ border-radius: 2px;
2022
+ background: #d8dcde;
2023
+ }
2024
+
2025
+ ._header_8wz6r_37 {
1345
2026
  display: flex;
1346
2027
  align-items: center;
1347
2028
  justify-content: space-between;
1348
2029
  gap: var(--space-12);
1349
- min-height: 48px;
1350
- padding: var(--space-16);
1351
- border-radius: var(--radius-8);
1352
- background: var(--color-state-surface-success-soft);
1353
- color: var(--color-state-text-success);
2030
+ margin-bottom: var(--space-16);
1354
2031
  }
1355
2032
 
1356
- ._message_1xvw4_14 {
1357
- display: inline-flex;
1358
- align-items: center;
1359
- gap: var(--space-6);
1360
- font-size: var(--type-trimmed-sm-size);
2033
+ ._title_8wz6r_45 {
2034
+ margin: 0;
2035
+ font-size: var(--type-heading-sm-size, 20px);
1361
2036
  font-weight: var(--font-weight-bold);
1362
2037
  }
1363
2038
 
1364
- ._icon_1xvw4_22 {
1365
- display: block;
1366
- flex-shrink: 0;
1367
- }
1368
-
1369
- ._timer_1xvw4_27 {
2039
+ ._close_8wz6r_51 {
1370
2040
  display: inline-flex;
1371
2041
  align-items: center;
1372
- gap: var(--space-4);
2042
+ justify-content: center;
2043
+ width: 32px;
2044
+ height: 32px;
1373
2045
  flex-shrink: 0;
1374
- font-size: var(--type-trimmed-sm-size);
2046
+ border: none;
2047
+ border-radius: 50%;
2048
+ background: #f2f4f5;
2049
+ color: #16181a;
2050
+ cursor: pointer;
2051
+ }
2052
+ ._close_8wz6r_51:focus-visible {
2053
+ outline: 2px solid #16181a;
2054
+ outline-offset: 2px;
2055
+ }
2056
+
2057
+ /* The chart can be wider than the sheet, so it scrolls inside its own container
2058
+ rather than making the sheet scroll sideways. */
2059
+ ._tableWrap_8wz6r_71 {
2060
+ overflow-x: auto;
2061
+ margin-bottom: var(--space-16);
2062
+ }
2063
+
2064
+ ._table_8wz6r_71 {
2065
+ width: 100%;
2066
+ border-collapse: collapse;
2067
+ font-size: var(--type-trimmed-sm-size, 14px);
2068
+ }
2069
+
2070
+ ._table_8wz6r_71 th,
2071
+ ._table_8wz6r_71 td {
2072
+ padding: var(--space-8) var(--space-12);
2073
+ text-align: left;
2074
+ border-bottom: 1px solid #e2e6e8;
2075
+ white-space: nowrap;
2076
+ }
2077
+
2078
+ ._table_8wz6r_71 thead th {
1375
2079
  font-weight: var(--font-weight-bold);
1376
- font-variant-numeric: tabular-nums;
2080
+ background: #f7f8f9;
1377
2081
  }
1378
- ._bar_xe5ej_1 {
1379
- position: sticky;
1380
- bottom: 0;
1381
- left: 0;
1382
- right: 0;
2082
+
2083
+ ._notes_8wz6r_95 {
2084
+ margin: 0;
2085
+ padding-left: var(--space-16);
1383
2086
  display: flex;
1384
2087
  flex-direction: column;
1385
2088
  gap: var(--space-6);
1386
- /* Figma mobile/pdp/cta-bar: rounded top, drop-shadow (no border), pt-16 / pb-24. */
1387
- padding: var(--space-16) var(--space-16) calc(var(--space-24) + var(--safe-area-bottom));
1388
- background: var(--color-surface-default);
1389
- border-radius: var(--radius-12) var(--radius-12) 0 0;
1390
- box-shadow: 0 -4px 12px rgba(206, 206, 206, 0.48);
1391
- z-index: 50;
2089
+ font-size: var(--type-trimmed-sm-size, 14px);
2090
+ color: #4a5257;
1392
2091
  }
1393
2092
 
1394
- ._info_xe5ej_17 {
1395
- display: flex;
1396
- align-items: center;
1397
- justify-content: space-between;
1398
- font-weight: var(--font-weight-bold);
2093
+ @keyframes _fade_8wz6r_1 {
2094
+ from { opacity: 0; }
2095
+ to { opacity: 1; }
1399
2096
  }
1400
-
1401
- ._actions_xe5ej_24 {
1402
- display: flex;
1403
- gap: var(--space-8);
2097
+ @keyframes _rise_8wz6r_1 {
2098
+ from { transform: translateY(12%); }
2099
+ to { transform: translateY(0); }
1404
2100
  }
1405
2101
 
1406
- ._actions_xe5ej_24 > * {
1407
- flex: 1 1 0;
2102
+ @media (prefers-reduced-motion: reduce) {
2103
+ ._scrim_8wz6r_6,
2104
+ ._sheet_8wz6r_17 {
2105
+ animation: none;
2106
+ }
1408
2107
  }
1409
- ._page_pdsy4_1 {
2108
+ ._root_9zj90_1 {
1410
2109
  display: flex;
1411
2110
  flex-direction: column;
1412
- gap: var(--space-16);
1413
- /* No bottom padding to clear the CTA bar, and that is deliberate.
1414
- `StickyCtaBar` is `position: sticky`, not `fixed`, so it stays in normal flow and
1415
- already reserves its own height at the end of this column. Padding here does not
1416
- clear anything — it adds an empty band *below* the bar that is only visible once the
1417
- fan scrolls to the very end, which is exactly where it looks like a rendering fault.
1418
- The bar carries its own `--safe-area-bottom` in its padding. */
2111
+ gap: var(--space-12);
1419
2112
  }
1420
2113
 
1421
- /* Sections below the full-bleed image, padded and spaced per the PDP content block. */
1422
- ._content_pdsy4_14 {
2114
+ ._header_9zj90_7 {
1423
2115
  display: flex;
1424
- flex-direction: column;
1425
- gap: var(--space-32);
1426
- padding: 0 var(--space-16);
2116
+ align-items: center;
2117
+ }
2118
+
2119
+ ._title_9zj90_12 {
2120
+ margin: 0;
2121
+ font-size: var(--type-trimmed-md-size);
2122
+ line-height: var(--type-trimmed-md-line);
2123
+ font-weight: var(--font-weight-bold);
2124
+ color: var(--color-text-default);
1427
2125
  }
1428
2126
 
1429
- ._intro_pdsy4_21 {
2127
+ ._chips_9zj90_20 {
1430
2128
  display: flex;
1431
- flex-direction: column;
2129
+ flex-wrap: wrap;
1432
2130
  gap: var(--space-8);
1433
2131
  }
1434
2132
 
1435
- /* Figma product-info: title+price group and the promo banner are spaced 16px apart. */
1436
- ._productInfo_pdsy4_28 {
1437
- display: flex;
1438
- flex-direction: column;
1439
- gap: var(--space-16);
2133
+ /* 48px square chips per the mobile/pdp/size component (radius-xs, not radius-sm). */
2134
+ ._chip_9zj90_20 {
2135
+ display: inline-flex;
2136
+ align-items: center;
2137
+ justify-content: center;
2138
+ width: 48px;
2139
+ height: 48px;
2140
+ padding: 0;
2141
+ border: 1px solid var(--color-border-default);
2142
+ border-radius: var(--radius-6);
2143
+ background: var(--color-surface-default);
2144
+ color: var(--color-text-subtle);
2145
+ font-family: inherit;
2146
+ font-size: var(--type-trimmed-sm-size);
2147
+ font-weight: var(--font-weight-bold);
2148
+ cursor: pointer;
1440
2149
  }
1441
2150
 
1442
- ._titlePrice_pdsy4_34 {
1443
- display: flex;
1444
- flex-direction: column;
1445
- gap: var(--space-8);
2151
+ ._selected_9zj90_44 {
2152
+ border-color: var(--color-surface-max);
2153
+ background: var(--color-surface-max);
2154
+ color: var(--color-text-strong);
1446
2155
  }
1447
2156
 
1448
- ._title_pdsy4_34 {
1449
- margin: 0;
1450
- font-size: var(--type-heading-lg-size);
1451
- line-height: 1.1;
1452
- font-weight: var(--font-weight-bold);
1453
- color: var(--color-text-default);
1454
- /* Reserve two lines and truncate, keeping the price position stable. */
1455
- display: -webkit-box;
1456
- -webkit-line-clamp: 2;
1457
- -webkit-box-orient: vertical;
1458
- overflow: hidden;
2157
+ /*
2158
+ * Sold out: a DASHED border and disabled text — no strike-through and no filled ground.
2159
+ * The design note is explicit that the chip stays legible ("still visible so the size
2160
+ * range reads complete"), so a fan can see the range runs to XXL and that their size is
2161
+ * the one that has gone, rather than seeing a range with a hole in it.
2162
+ */
2163
+ ._disabled_9zj90_56 {
2164
+ cursor: not-allowed;
2165
+ border-style: dashed;
2166
+ color: var(--color-text-disabled);
1459
2167
  }
1460
2168
 
1461
- /* Stock notice shown ONLY when low or out of stock (Figma has no "in stock" line). */
1462
- ._availability_pdsy4_54 {
2169
+ /* Figma: regular weight, critical token. Bold read as a second heading next to the
2170
+ 16px "Size" label above it. */
2171
+ ._error_9zj90_64 {
1463
2172
  margin: 0;
1464
2173
  font-size: var(--type-trimmed-sm-size);
1465
- font-weight: var(--font-weight-bold);
2174
+ font-weight: var(--font-weight-regular);
2175
+ color: var(--color-state-surface-critical-default);
1466
2176
  }
1467
2177
 
1468
- ._low_pdsy4_60 {
1469
- color: var(--color-state-text-warning);
2178
+ ._sizeGuide_9zj90_71 {
2179
+ display: inline-flex;
2180
+ align-items: center;
2181
+ gap: var(--space-2);
2182
+ align-self: flex-start;
2183
+ padding: 0;
2184
+ border: none;
2185
+ background: none;
2186
+ color: var(--color-text-subtle);
2187
+ font-family: inherit;
2188
+ font-size: var(--type-trimmed-sm-size);
2189
+ cursor: pointer;
1470
2190
  }
1471
2191
 
1472
- ._oos_pdsy4_64 {
1473
- color: var(--color-state-surface-critical-default);
2192
+ ._chevron_9zj90_85 {
2193
+ display: block;
1474
2194
  }
1475
- ._hero_czprq_1 {
2195
+ ._root_1j4n8_1 {
1476
2196
  display: flex;
1477
2197
  flex-direction: column;
1478
- gap: var(--space-16);
2198
+ gap: var(--space-12);
1479
2199
  }
1480
2200
 
1481
- ._text_czprq_7 {
2201
+ ._header_1j4n8_7 {
2202
+ margin: 0;
1482
2203
  display: flex;
1483
- flex-direction: column;
1484
- align-items: center;
1485
2204
  gap: var(--space-4);
1486
- padding: var(--space-24) var(--space-16) 0;
1487
- text-align: center;
2205
+ font-size: var(--type-trimmed-md-size);
1488
2206
  }
1489
2207
 
1490
- ._title_czprq_16 {
1491
- font-size: var(--type-heading-lg-size);
1492
- line-height: var(--type-heading-lg-line);
2208
+ ._title_1j4n8_14 {
1493
2209
  font-weight: var(--font-weight-bold);
1494
2210
  color: var(--color-text-default);
1495
2211
  }
1496
2212
 
1497
- ._subtitle_czprq_23 {
1498
- margin: 0;
1499
- font-size: var(--type-trimmed-sm-size);
2213
+ ._value_1j4n8_19 {
2214
+ font-weight: var(--font-weight-regular);
1500
2215
  color: var(--color-text-subtle);
1501
2216
  }
1502
2217
 
1503
- ._image_czprq_29 {
1504
- position: relative;
1505
- width: 100%;
1506
- aspect-ratio: 375 / 360;
1507
- }
1508
-
1509
- ._image_czprq_29 img {
1510
- object-fit: cover;
1511
- }
1512
- ._text_g7ajl_1 {
1513
- margin: 0;
1514
- padding: var(--space-8) var(--space-16);
1515
- font-size: var(--type-heading-md-size);
1516
- line-height: var(--type-heading-md-line);
1517
- font-weight: var(--font-weight-bold);
1518
- color: var(--color-text-default);
2218
+ ._tiles_1j4n8_24 {
2219
+ display: flex;
2220
+ flex-wrap: wrap;
2221
+ gap: var(--space-8);
1519
2222
  }
1520
2223
 
1521
- ._center_g7ajl_10 {
1522
- text-align: center;
2224
+ /*
2225
+ * 79×105 tile, radius-8. The tile itself does NOT clip: the "N left" badge is drawn
2226
+ * overhanging the top edge, so clipping here would shave it in half. `.media` is the
2227
+ * clipped box instead, which keeps the image inside the radius.
2228
+ */
2229
+ ._tile_1j4n8_24 {
2230
+ position: relative;
2231
+ width: 79px;
2232
+ height: 105px;
2233
+ padding: 0;
2234
+ /* Figma color/tile: 1px dark border on the selected tile, none otherwise. Transparent
2235
+ rather than absent so selecting does not resize the tile. */
2236
+ border: 1px solid transparent;
2237
+ border-radius: var(--radius-8);
2238
+ background: transparent;
2239
+ cursor: pointer;
1523
2240
  }
1524
2241
 
1525
- ._left_g7ajl_14 {
1526
- text-align: left;
1527
- }
1528
- ._wrap_88jzo_1 {
1529
- padding: 0 var(--space-16);
2242
+ ._selected_1j4n8_48 {
2243
+ border-color: var(--color-surface-max);
1530
2244
  }
1531
2245
 
1532
- ._box_88jzo_5 {
1533
- position: relative;
1534
- width: 100%;
1535
- aspect-ratio: 343 / 420;
1536
- border-radius: var(--radius-12);
2246
+ ._media_1j4n8_32 {
2247
+ position: absolute;
2248
+ inset: 0;
2249
+ border-radius: var(--radius-8);
1537
2250
  overflow: hidden;
1538
2251
  background: var(--color-surface-soft);
1539
2252
  }
1540
2253
 
1541
- ._image_88jzo_14 {
2254
+ ._image_1j4n8_60 {
1542
2255
  object-fit: cover;
1543
2256
  }
1544
- ._section_yfu87_1 {
1545
- display: flex;
1546
- flex-direction: column;
1547
- gap: var(--space-12);
1548
- }
1549
2257
 
1550
- ._track_yfu87_7 {
1551
- display: flex;
1552
- gap: var(--space-12);
1553
- list-style: none;
1554
- margin: 0;
1555
- padding: 0 var(--space-16);
1556
- overflow-x: auto;
1557
- scroll-snap-type: x mandatory;
1558
- scroll-padding-inline: var(--space-16);
1559
- -webkit-overflow-scrolling: touch;
2258
+ ._placeholder_1j4n8_64 {
2259
+ position: absolute;
2260
+ inset: 0;
2261
+ background: var(--color-surface-soft);
1560
2262
  }
1561
2263
 
1562
- /* Light card island fixed colours so it stays light on the dark PCP. */
1563
- ._card_yfu87_20 {
1564
- flex: 0 0 auto;
1565
- width: calc(100% - var(--space-32));
1566
- scroll-snap-align: start;
2264
+ /* Sold-out: full dark scrim with white label (Figma color/tile state=sold-out). The tile
2265
+ stays tappable — see the component docblock. */
2266
+ ._scrim_1j4n8_72 {
2267
+ position: absolute;
2268
+ inset: 0;
1567
2269
  display: flex;
1568
- flex-direction: column;
1569
- gap: var(--space-12);
1570
- padding: var(--space-24);
1571
- border-radius: var(--radius-12);
1572
- background: #f9fafa;
2270
+ align-items: center;
2271
+ justify-content: center;
2272
+ background: rgba(0, 0, 0, 0.45);
2273
+ color: #ffffff;
2274
+ font-size: var(--type-trimmed-xs-size);
2275
+ line-height: var(--type-trimmed-xs-line);
2276
+ font-weight: var(--font-weight-bold);
2277
+ text-align: center;
1573
2278
  }
1574
2279
 
1575
- ._quote_yfu87_32 {
1576
- margin: 0;
1577
- font-size: var(--type-heading-md-size);
1578
- line-height: 1.25;
2280
+ /*
2281
+ * "N left" low-stock badge (Figma badge/sale/size2). Anchored as drawn — 50px in, 10px
2282
+ * proud of the top edge — so it reads as a tag pinned to the tile rather than a label
2283
+ * inside it. `pointer-events: none` keeps it from swallowing taps meant for the tile.
2284
+ */
2285
+ ._stockBadge_1j4n8_91 {
2286
+ position: absolute;
2287
+ top: -10px;
2288
+ left: 50px;
2289
+ display: inline-flex;
2290
+ align-items: center;
2291
+ height: 20px;
2292
+ padding: var(--space-4);
2293
+ border-radius: var(--radius-4);
2294
+ background: var(--color-state-surface-warning-soft);
2295
+ color: var(--color-state-text-warning);
2296
+ font-size: var(--type-trimmed-xs-size);
2297
+ line-height: var(--type-trimmed-xs-line);
1579
2298
  font-weight: var(--font-weight-bold);
1580
- color: #0c161c;
2299
+ letter-spacing: 0.12px;
2300
+ white-space: nowrap;
2301
+ pointer-events: none;
1581
2302
  }
1582
-
1583
- ._author_yfu87_40 {
2303
+ ._fieldset_1mnz1_1 {
2304
+ border: none;
1584
2305
  margin: 0;
2306
+ padding: 0;
2307
+ min-width: 0;
2308
+ }
2309
+
2310
+ ._legend_1mnz1_8 {
1585
2311
  font-size: var(--type-trimmed-sm-size);
1586
- color: #3d4549;
2312
+ font-weight: var(--font-weight-bold);
2313
+ color: var(--color-text-subtle);
2314
+ margin-bottom: var(--space-8);
2315
+ padding: 0;
1587
2316
  }
1588
2317
 
1589
- ._dots_yfu87_46 {
2318
+ ._options_1mnz1_16 {
1590
2319
  display: flex;
1591
- justify-content: center;
1592
- gap: var(--space-6);
2320
+ flex-wrap: wrap;
2321
+ gap: var(--space-8);
1593
2322
  }
1594
2323
 
1595
- ._dot_yfu87_46 {
1596
- width: 8px;
1597
- height: 8px;
1598
- border-radius: var(--radius-full);
1599
- background: var(--color-border-default);
2324
+ ._option_1mnz1_16 {
2325
+ min-height: var(--tap-target-min);
2326
+ min-width: var(--tap-target-min);
2327
+ padding: 0 var(--space-16);
2328
+ border-radius: var(--radius-8);
2329
+ border: 1px solid var(--color-border-default);
2330
+ background: var(--color-surface-default);
2331
+ color: var(--color-text-default);
2332
+ font-family: inherit;
2333
+ font-size: var(--type-trimmed-sm-size);
2334
+ font-weight: var(--font-weight-bold);
2335
+ cursor: pointer;
2336
+ }
2337
+
2338
+ ._selected_1mnz1_36 {
2339
+ border-color: var(--color-surface-max);
2340
+ background: var(--color-surface-max);
2341
+ color: var(--color-text-strong);
1600
2342
  }
1601
2343
 
1602
- ._dotActive_yfu87_59 {
1603
- background: var(--color-text-default);
2344
+ ._disabled_1mnz1_42 {
2345
+ cursor: not-allowed;
2346
+ color: var(--color-text-disabled);
2347
+ text-decoration: line-through;
2348
+ background: var(--color-surface-soft);
1604
2349
  }
1605
- ._section_ubkar_1 {
2350
+ /* Figma mobile/pdp/details: heading+body group, then more/Less 4px under it. */
2351
+ ._root_t4qba_2 {
1606
2352
  display: flex;
1607
2353
  flex-direction: column;
1608
- gap: var(--space-24);
1609
- padding: var(--space-24) var(--space-16);
2354
+ gap: var(--space-4);
1610
2355
  }
1611
2356
 
1612
- ._row_ubkar_8 {
2357
+ ._group_t4qba_8 {
1613
2358
  display: flex;
1614
- align-items: flex-start;
1615
- gap: var(--space-12);
2359
+ flex-direction: column;
2360
+ gap: var(--space-8);
1616
2361
  }
1617
2362
 
1618
- ._icon_ubkar_14 {
1619
- flex-shrink: 0;
2363
+ ._title_t4qba_14 {
2364
+ margin: 0;
2365
+ font-size: var(--type-trimmed-md-size);
2366
+ line-height: var(--type-trimmed-md-line);
2367
+ font-weight: var(--font-weight-bold);
1620
2368
  color: var(--color-text-default);
1621
2369
  }
1622
2370
 
1623
- ._textCol_ubkar_19 {
1624
- display: flex;
1625
- flex-direction: column;
1626
- gap: var(--space-4);
2371
+ ._body_t4qba_22 {
2372
+ color: var(--color-text-subtle);
1627
2373
  }
1628
2374
 
1629
- ._title_ubkar_25 {
2375
+ ._paragraph_t4qba_26 {
1630
2376
  margin: 0;
1631
- font-size: var(--type-trimmed-sm-size);
1632
- font-weight: var(--font-weight-bold);
1633
- color: var(--color-text-default);
2377
+ font-size: var(--type-paragraph-size);
2378
+ line-height: var(--type-paragraph-line);
1634
2379
  }
1635
2380
 
1636
- ._body_ubkar_32 {
1637
- margin: 0;
1638
- font-size: var(--type-trimmed-sm-size);
2381
+ /* paragraph-spacing/lg between paragraphs, nothing after the last. */
2382
+ ._paragraph_t4qba_26 + ._paragraph_t4qba_26 {
2383
+ margin-top: var(--space-12);
2384
+ }
2385
+
2386
+ /*
2387
+ * Collapsed: three lines.
2388
+ *
2389
+ * A height clamp rather than `-webkit-line-clamp`, because the body is now several
2390
+ * <p> elements and line-clamp only counts lines inside a single -webkit-box — with
2391
+ * multiple block children it clamps nothing and the whole description renders. Three
2392
+ * lines is exactly `font-size × line-height × 3`, so the cut lands on a line boundary
2393
+ * rather than through the middle of one.
2394
+ */
2395
+ ._clamped_t4qba_46 {
2396
+ max-height: calc(var(--type-paragraph-size) * var(--type-paragraph-line) * 3);
2397
+ overflow: hidden;
2398
+ }
2399
+
2400
+ ._toggle_t4qba_51 {
2401
+ align-self: flex-start;
2402
+ padding: 0;
2403
+ border: none;
2404
+ background: none;
1639
2405
  color: var(--color-text-subtle);
2406
+ font-family: inherit;
2407
+ font-size: var(--type-paragraph-size);
2408
+ line-height: var(--type-paragraph-line);
2409
+ /* Figma sets the bold face on this node — it is the affordance, not body copy. */
2410
+ font-weight: var(--font-weight-bold);
2411
+ cursor: pointer;
2412
+ }
2413
+ ._row_l4edh_1 {
2414
+ display: flex;
2415
+ flex-wrap: wrap;
2416
+ gap: var(--space-4) var(--space-6);
2417
+ margin: 0;
2418
+ padding: 0;
2419
+ list-style: none;
2420
+ }
2421
+
2422
+ /* mobile/badge/top-pick: soft surface, dark text, uppercase, radius-min. */
2423
+ ._badge_l4edh_11 {
2424
+ display: inline-flex;
2425
+ align-items: center;
2426
+ justify-content: center;
2427
+ height: 20px;
2428
+ padding: var(--space-4) var(--space-6);
2429
+ border-radius: var(--radius-2);
2430
+ background: var(--color-surface-soft);
2431
+ color: var(--color-text-default);
2432
+ font-size: var(--type-trimmed-xs-size);
2433
+ line-height: var(--type-trimmed-xs-line);
2434
+ font-weight: var(--font-weight-bold);
2435
+ letter-spacing: 0.12px;
2436
+ text-transform: uppercase;
2437
+ white-space: nowrap;
1640
2438
  }
1641
- ._section_3mufa_1 {
2439
+ /* Figma mobile/pdp/promo: success-soft surface, success text, radius-8, p16, h48. */
2440
+ ._banner_1xvw4_2 {
1642
2441
  display: flex;
1643
- flex-direction: column;
1644
- gap: var(--space-8);
2442
+ align-items: center;
2443
+ justify-content: space-between;
2444
+ gap: var(--space-12);
2445
+ min-height: 48px;
1645
2446
  padding: var(--space-16);
2447
+ border-radius: var(--radius-8);
2448
+ background: var(--color-state-surface-success-soft);
2449
+ color: var(--color-state-text-success);
1646
2450
  }
1647
2451
 
1648
- ._title_3mufa_8 {
1649
- font-size: var(--type-heading-md-size);
1650
- line-height: var(--type-heading-md-line);
2452
+ ._message_1xvw4_14 {
2453
+ display: inline-flex;
2454
+ align-items: center;
2455
+ gap: var(--space-6);
2456
+ font-size: var(--type-trimmed-sm-size);
1651
2457
  font-weight: var(--font-weight-bold);
1652
- color: var(--color-text-default);
1653
2458
  }
1654
2459
 
1655
- ._list_3mufa_15 {
1656
- list-style: none;
1657
- margin: 0;
1658
- padding: 0;
2460
+ ._icon_1xvw4_22 {
2461
+ display: block;
2462
+ flex-shrink: 0;
1659
2463
  }
1660
2464
 
1661
- ._row_3mufa_21 {
1662
- display: flex;
2465
+ ._timer_1xvw4_27 {
2466
+ display: inline-flex;
1663
2467
  align-items: center;
1664
- justify-content: space-between;
1665
- min-height: var(--tap-target-min);
1666
- padding: var(--space-12) 0;
1667
- border-bottom: 1px solid var(--color-border-default);
1668
- text-decoration: none;
1669
- color: var(--color-text-default);
1670
- font-size: var(--type-trimmed-md-size);
2468
+ gap: var(--space-4);
2469
+ flex-shrink: 0;
2470
+ font-size: var(--type-trimmed-sm-size);
1671
2471
  font-weight: var(--font-weight-bold);
2472
+ font-variant-numeric: tabular-nums;
1672
2473
  }
1673
-
1674
- ._row_3mufa_21 svg {
1675
- color: var(--color-text-subtle);
2474
+ ._page_1hv16_1 {
2475
+ display: flex;
2476
+ flex-direction: column;
2477
+ gap: var(--space-16);
2478
+ /* No bottom padding to clear the CTA bar, and that is deliberate.
2479
+ `StickyCtaBar` is `position: sticky`, not `fixed`, so it stays in normal flow and
2480
+ already reserves its own height at the end of this column. Padding here does not
2481
+ clear anything — it adds an empty band *below* the bar that is only visible once the
2482
+ fan scrolls to the very end, which is exactly where it looks like a rendering fault.
2483
+ The bar carries its own `--safe-area-bottom` in its padding. */
1676
2484
  }
1677
- /* 4.2 Category (PCP) page layout. Moved here from the Next app's route stylesheet so the
1678
- SDK and the app render the same screen from the same rules — see CategoryContent.tsx. */
1679
2485
 
1680
- /* Background and base typography come from ShopSurface, which this class is applied
1681
- alongside — one owner for the surface, one for the layout. */
1682
- ._pcp_pi5vp_6 {
2486
+ /* Sections below the full-bleed image, padded and spaced per the PDP content block. */
2487
+ ._content_1hv16_14 {
1683
2488
  display: flex;
1684
2489
  flex-direction: column;
1685
2490
  gap: var(--space-32);
1686
- padding-bottom: var(--space-32);
1687
- /* Filling the screen now belongs to `ShopSurface`'s `fill`, which every screen sets and
1688
- which uses `100dvh`. This rule used to be `min-height: 100%` here, with a comment
1689
- arguing against viewport units precisely because a host may render chrome around us
1690
- and a viewport unit overshoots by its height.
2491
+ padding: 0 var(--space-16);
2492
+ }
1691
2493
 
1692
- That argument is right, and it lost to a worse failure. `100%` resolves against the
1693
- parent, so it does nothing at all unless the host's mount chain has a height — and
1694
- DAZN's does not, which is why their cart rendered as a short white card on black.
1695
- Overshooting by the height of a header costs a few pixels of scroll on a short page.
1696
- Not filling costs a screen that looks broken. Hosts that mount a view inside their
1697
- own sized container can still say `fill={false}`. */
2494
+ ._intro_1hv16_21 {
2495
+ display: flex;
2496
+ flex-direction: column;
2497
+ gap: var(--space-8);
1698
2498
  }
1699
2499
 
1700
- ._featured_pi5vp_24 {
1701
- padding: 0 var(--space-16);
2500
+ /* Figma product-info: title+price group and the promo banner are spaced 16px apart. */
2501
+ ._productInfo_1hv16_28 {
2502
+ display: flex;
2503
+ flex-direction: column;
2504
+ gap: var(--space-16);
1702
2505
  }
1703
2506
 
1704
- ._gridBlock_pi5vp_28 {
2507
+ ._titlePrice_1hv16_34 {
1705
2508
  display: flex;
1706
2509
  flex-direction: column;
1707
- gap: var(--space-12);
2510
+ gap: var(--space-8);
1708
2511
  }
1709
2512
 
1710
- ._gridTitle_pi5vp_34 {
1711
- padding: 0 var(--space-16);
1712
- font-size: var(--type-heading-md-size);
1713
- line-height: var(--type-heading-md-line);
2513
+ ._title_1hv16_34 {
2514
+ margin: 0;
2515
+ font-size: var(--type-heading-lg-size);
2516
+ line-height: 1.1;
1714
2517
  font-weight: var(--font-weight-bold);
1715
2518
  color: var(--color-text-default);
2519
+ /* Reserve two lines and truncate, keeping the price position stable. */
2520
+ display: -webkit-box;
2521
+ -webkit-line-clamp: 2;
2522
+ -webkit-box-orient: vertical;
2523
+ overflow: hidden;
1716
2524
  }
1717
2525
 
1718
- ._grid_pi5vp_28 {
1719
- list-style: none;
2526
+ /* Stock notice — shown ONLY when low or out of stock (Figma has no "in stock" line).
2527
+ 16px, matching the `low-stock` node in both PDP frames; it was 14px. */
2528
+ ._availability_1hv16_55 {
1720
2529
  margin: 0;
1721
- padding: 0 var(--space-16);
1722
- display: grid;
1723
- grid-template-columns: repeat(2, 1fr);
1724
- gap: var(--space-16);
2530
+ font-size: var(--type-trimmed-md-size);
2531
+ line-height: var(--type-trimmed-md-line);
2532
+ font-weight: var(--font-weight-bold);
1725
2533
  }
1726
- /*
1727
- * Figma "homepage v2" (node 2795:98786): a full-screen, snap-scrolling editorial
1728
- * showcase. Each panel is a full-bleed photo with a bottom overlay (title + subtitle +
1729
- * outline CTA). Always dark (#080e12), independent of theme. Measurements are taken
1730
- * from Figma: overlay pad 24, gap 16, title 32, subtitle 16/1.55, button h48 radius 8.
1731
- */
1732
2534
 
2535
+ ._low_1hv16_62 {
2536
+ color: var(--color-state-text-warning);
2537
+ }
2538
+
2539
+ ._oos_1hv16_66 {
2540
+ color: var(--color-state-surface-critical-default);
2541
+ }
1733
2542
  /*
1734
- * The height of this scroller is the one measurement a mounted component cannot guess.
1735
- *
1736
- * It used to be `calc(100dvh - 56px)`, and both halves were wrong.
2543
+ * Figma `mobile/hero` (3388:5291): a full-screen editorial image with the category name
2544
+ * read ON TOP of it, not a caption above a picture.
1737
2545
  *
1738
- * **The 56 was a hard-coded guess at somebody else's header.** We are mounted inside a
1739
- * host page whose chrome we do not own and cannot see. Where the host's header was not
1740
- * 56px DAZN's storefront runs 69 the scroller ended up shorter than the space it was
1741
- * given, leaving a dead band of bare surface at the bottom. Because the band sits below
1742
- * the fold until the document itself scrolls, it appeared only after scrolling down and
1743
- * back: exactly the report from 13 Aug 2026.
1744
- *
1745
- * **`dvh` is the wrong unit for a snap container.** It is the *dynamic* viewport, so it
1746
- * grows as a mobile browser hides its URL bar and shrinks as it returns. Every panel then
1747
- * resizes during the very scroll that snapping depends on, and the container is left
1748
- * parked between snap points. `svh` is the stable smallest value and never changes
1749
- * mid-scroll. The cost is a small gap when the chrome is hidden; the alternative is a
1750
- * layout that moves under the reader's thumb.
1751
- *
1752
- * `--showcase-top` is measured at mount and on resize (see HomeShowcase.tsx) rather than
1753
- * assumed: whatever the host puts above us, we take the rest.
2546
+ * 812px is the design's iPhone canvas, so it is expressed as viewport height rather than
2547
+ * copied literally the screen it fills is whatever the fan is holding. `100svh` uses
2548
+ * the SMALL viewport (browser chrome expanded), so the hero never grows taller than the
2549
+ * visible area and push the first rail off-screen on load.
1754
2550
  */
1755
- ._showcase_16alm_30 {
1756
- height: calc(100svh - var(--showcase-top, 0px));
1757
- overflow-y: auto;
1758
- scroll-snap-type: y mandatory;
1759
- /*
1760
- * Do not hand the scroll on to the host page when this container reaches its end.
1761
- * Chaining is what let the outer document sit at a different offset from this one, so a
1762
- * reader who scrolled to the bottom and back found the page in a state neither scroller
1763
- * agreed on.
1764
- */
1765
- overscroll-behavior-y: contain;
1766
- -webkit-overflow-scrolling: touch;
1767
- background: #080e12;
1768
- scrollbar-width: none;
1769
- }
1770
- ._showcase_16alm_30::-webkit-scrollbar {
1771
- display: none;
1772
- }
2551
+ ._hero_2mcnp_10 {
2552
+ --hero-height: clamp(480px, 100svh, 812px);
1773
2553
 
1774
- ._panel_16alm_49 {
1775
2554
  position: relative;
1776
- /* Matches the scroller exactly, or snapping has nothing to align to. */
1777
- height: calc(100svh - var(--showcase-top, 0px));
1778
- scroll-snap-align: start;
1779
- scroll-snap-stop: always;
2555
+ height: var(--hero-height);
1780
2556
  overflow: hidden;
1781
- background: #080e12;
1782
- }
2557
+ background: var(--color-surface-default);
1783
2558
 
1784
- ._image_16alm_59 {
1785
- object-fit: cover;
1786
- object-position: center;
2559
+ /*
2560
+ * The page does NOT start below the picture. In every `pcp` frame the scrolling column
2561
+ * is positioned at y=575 over an 812px hero and carries no fill of its own — the first
2562
+ * rail's heading and the top of its cards read directly on the photograph. (Checked
2563
+ * pixel-for-pixel against the hero rendered alone: identical everywhere the content
2564
+ * does not paint, so the column really is transparent, not a matching dark.)
2565
+ *
2566
+ * So the hero is a full screen of IMAGE and 575/812 of it of LAYOUT, and the fan sees
2567
+ * the first row of product before scrolling. Take the height away here rather than
2568
+ * shortening the box: at 375×575 `cover` would re-crop the 9:16 photograph horizontally
2569
+ * and we would lose the sides of the frame the photographer composed.
2570
+ *
2571
+ * 237px is the hoodies and t-shirts frames, which lead with a rail as our layout does.
2572
+ * The two that lead with a section header sit at 605. Not a different rule — a
2573
+ * different first block.
2574
+ */
2575
+ margin-bottom: calc(var(--hero-height) * -237 / 812);
1787
2576
  }
1788
2577
 
1789
- /* Bottom scrim so overlay text stays legible over any photo. */
1790
- ._panel_16alm_49::after {
1791
- content: '';
2578
+ ._image_2mcnp_37 {
1792
2579
  position: absolute;
1793
2580
  inset: 0;
1794
- background: linear-gradient(to bottom, rgba(8, 14, 18, 0) 45%, rgba(8, 14, 18, 0.85) 100%);
1795
- pointer-events: none;
1796
2581
  }
1797
2582
 
1798
- /* ── Hero ── */
2583
+ ._image_2mcnp_37 img {
2584
+ object-fit: cover;
2585
+ }
2586
+
1799
2587
  /*
1800
- * Unlike the category panels (`edit_6 1`, full-bleed 375x812), the hero photo in Figma
1801
- * (`edit_9 1`) is 375x674 and stops where the opaque "swipe down" bar starts. Giving it
1802
- * its own box keeps the shot at its native aspect instead of cover-cropping the sides.
2588
+ * The scrim the design annotates. Two stops rather than a flat wash: the text sits in the
2589
+ * top third, so darkening the whole frame would only mute the photograph without helping
2590
+ * the words. Fixed colour it darkens a photograph, not a themed surface.
1803
2591
  */
1804
- ._heroImageBox_16alm_79 {
2592
+ ._scrim_2mcnp_51 {
1805
2593
  position: absolute;
1806
- inset: 0 0 82px;
1807
- overflow: hidden;
2594
+ inset: 0;
2595
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0.1) 45%, rgba(0, 0, 0, 0) 70%);
1808
2596
  }
1809
- ._hero_16alm_79 ._heroContent_16alm_84 {
2597
+
2598
+ ._text_2mcnp_57 {
1810
2599
  position: absolute;
1811
- bottom: 15px;
2600
+ top: 106px;
1812
2601
  left: 0;
1813
2602
  right: 0;
1814
- z-index: 1;
1815
- display: flex;
1816
- flex-direction: column;
1817
- gap: 24px;
1818
- align-items: stretch;
1819
- }
1820
- ._heroText_16alm_95 {
1821
2603
  display: flex;
1822
2604
  flex-direction: column;
1823
- gap: 8px;
1824
2605
  align-items: center;
1825
- padding: 0 24px;
2606
+ gap: var(--space-8);
2607
+ padding: var(--space-12) var(--space-24) var(--space-24);
1826
2608
  text-align: center;
1827
2609
  }
1828
- ._logo_16alm_103 {
1829
- width: 48px;
1830
- height: 48px;
1831
- }
1832
- ._heroTitle_16alm_107 {
1833
- margin: 0;
1834
- font-size: 56px;
1835
- line-height: 1.3;
1836
- font-weight: 700;
1837
- letter-spacing: -1.12px;
1838
- color: #f9fafa;
1839
- }
1840
- ._heroSub_16alm_115 {
1841
- margin: 0;
1842
- font-size: 16px;
1843
- line-height: 1.55;
1844
- color: #f9fafa;
1845
- max-width: 327px;
1846
- }
1847
- ._swipe_16alm_122 {
1848
- display: flex;
1849
- flex-direction: column;
1850
- align-items: center;
1851
- gap: 6px;
1852
- height: 82px;
1853
- justify-content: center;
1854
- background: #080e12;
1855
- }
1856
- ._swipeLabel_16alm_131 {
1857
- margin: 0;
1858
- font-size: 14px;
1859
- line-height: 1;
1860
- color: rgba(255, 255, 255, 0.54);
1861
- }
1862
- ._chevron_16alm_137 {
1863
- width: 24px;
1864
- height: 24px;
1865
- color: rgba(255, 255, 255, 0.54);
1866
- animation: _bob_16alm_1 1.8s ease-in-out infinite;
1867
- }
1868
- @keyframes _bob_16alm_1 {
1869
- 0%, 100% { transform: translateY(0); }
1870
- 50% { transform: translateY(4px); }
1871
- }
1872
2610
 
1873
- /* ── Category panels ── */
1874
- ._overlay_16alm_149 {
1875
- position: absolute;
1876
- bottom: 0;
1877
- left: 0;
1878
- right: 0;
1879
- z-index: 1;
1880
- display: flex;
1881
- flex-direction: column;
1882
- gap: 16px;
1883
- align-items: flex-start;
1884
- padding: 0 24px 24px;
1885
- }
1886
- ._copy_16alm_161 {
1887
- display: flex;
1888
- flex-direction: column;
1889
- gap: 8px;
1890
- }
1891
- ._title_16alm_166 {
2611
+ /* Fixed light values on both counts: this text is over a photograph, so it does not
2612
+ follow the surface palette. */
2613
+ ._title_2mcnp_72 {
1892
2614
  margin: 0;
1893
- font-size: 32px;
1894
- line-height: 1.2;
1895
- font-weight: 700;
2615
+ font-size: 40px;
2616
+ line-height: 1;
2617
+ font-weight: var(--font-weight-bold);
1896
2618
  color: #f9fafa;
2619
+ text-wrap: balance;
1897
2620
  }
1898
- ._subtitle_16alm_173 {
2621
+
2622
+ ._subtitle_2mcnp_81 {
1899
2623
  margin: 0;
1900
- font-size: 16px;
1901
- line-height: 1.55;
1902
- color: #f9fafa;
1903
- }
1904
- ._cta_16alm_179 {
1905
- display: inline-flex;
1906
- align-items: center;
1907
- justify-content: center;
1908
- min-height: 48px;
1909
- padding: 8px 12px;
1910
- border: 1px solid #f9fafa;
1911
- border-radius: 8px;
1912
- background: transparent;
1913
- color: #f9fafa;
1914
- font-size: 16px;
1915
- font-weight: 700;
1916
- text-decoration: none;
2624
+ font-size: var(--type-trimmed-md-size);
2625
+ line-height: 1;
2626
+ font-weight: var(--font-weight-regular);
2627
+ color: #f9fafa;
1917
2628
  }
1918
- ._cta_16alm_179:active {
1919
- background: rgba(249, 250, 250, 0.12);
2629
+
2630
+ /*
2631
+ * Without an image there is no photograph to read over, so the hero stops pretending to
2632
+ * be one: it collapses to the height of its own text on the page background rather than
2633
+ * holding a screen-tall empty box.
2634
+ */
2635
+ ._noImage_2mcnp_94 {
2636
+ height: auto;
2637
+ min-height: 0;
2638
+ max-height: none;
2639
+ /* Nothing to overlap, and pulling the page up over the title would eat it. */
2640
+ margin-bottom: 0;
2641
+ }
2642
+
2643
+ ._noImage_2mcnp_94 ._text_2mcnp_57 {
2644
+ position: static;
2645
+ padding-top: var(--space-24);
1920
2646
  }
1921
2647
 
1922
- /* ── Mosaic gallery panel ── */
1923
- ._mosaicPanel_16alm_198 {
2648
+ ._noImage_2mcnp_94 ._title_2mcnp_72,
2649
+ ._noImage_2mcnp_94 ._subtitle_2mcnp_81 {
2650
+ color: var(--color-text-default);
2651
+ }
2652
+ /*
2653
+ * Figma `mobile/lifestyle-text` (3388:4876). The copy rule is two lines at every viewport
2654
+ * from 375 to 430, so the block reserves both — 58px of text inside 24px padding. Letting
2655
+ * it collapse to one line changes the page's rhythm depending on how long the CMS copy is.
2656
+ */
2657
+ ._text_1saw4_6 {
2658
+ margin: 0;
2659
+ padding: var(--space-24);
2660
+ min-height: 106px;
1924
2661
  display: flex;
1925
2662
  align-items: center;
1926
- justify-content: center;
2663
+ font-size: var(--type-heading-lg-size);
2664
+ line-height: 1;
2665
+ font-weight: var(--font-weight-bold);
2666
+ color: var(--color-text-default);
1927
2667
  }
1928
- ._mosaicPanel_16alm_198::after {
1929
- display: none;
2668
+
2669
+ ._center_1saw4_18 {
2670
+ justify-content: center;
2671
+ text-align: center;
1930
2672
  }
1931
- ._mosaic_16alm_198 {
1932
- display: flex;
1933
- flex-direction: column;
1934
- gap: 12px;
1935
- width: 343px;
2673
+
2674
+ ._left_1saw4_23 {
2675
+ justify-content: flex-start;
2676
+ text-align: left;
1936
2677
  }
1937
- ._mRow_16alm_212 {
1938
- display: flex;
1939
- gap: 12px;
1940
- align-items: center;
2678
+ ._wrap_9mquf_1 {
2679
+ padding: var(--space-24) var(--space-16);
1941
2680
  }
1942
- ._mBetween_16alm_217 {
1943
- justify-content: space-between;
2681
+
2682
+ ._box_9mquf_5 {
2683
+ position: relative;
2684
+ width: 100%;
2685
+ /* Figma draws the box 425px tall at 343 wide. */
2686
+ aspect-ratio: 343 / 425;
2687
+ border-radius: var(--radius-18);
2688
+ overflow: hidden;
2689
+ background: var(--color-surface-soft);
1944
2690
  }
1945
- ._mCol_16alm_220 {
2691
+
2692
+ ._track_9mquf_15 {
1946
2693
  display: flex;
1947
- flex-direction: column;
1948
- gap: 10px;
2694
+ list-style: none;
2695
+ margin: 0;
2696
+ padding: 0;
2697
+ height: 100%;
2698
+ overflow-x: auto;
2699
+ scroll-snap-type: x mandatory;
2700
+ -webkit-overflow-scrolling: touch;
2701
+ scrollbar-width: none;
1949
2702
  }
1950
- ._tile_16alm_225 {
2703
+
2704
+ ._track_9mquf_15::-webkit-scrollbar {
2705
+ display: none;
2706
+ }
2707
+
2708
+ ._slide_9mquf_31 {
1951
2709
  position: relative;
1952
- border-radius: 6px;
1953
- overflow: hidden;
1954
- flex-shrink: 0;
2710
+ flex: 0 0 100%;
2711
+ height: 100%;
2712
+ scroll-snap-align: start;
1955
2713
  }
1956
- ._solid_16alm_231 {
1957
- background: #171e23;
2714
+
2715
+ ._image_9mquf_38 {
2716
+ object-fit: cover;
1958
2717
  }
1959
- ._overlay_s4eai_1 {
1960
- position: fixed;
1961
- inset: 0;
1962
- z-index: 900;
2718
+
2719
+ /* Over the photograph, so fixed values rather than surface tokens. */
2720
+ ._dots_9mquf_43 {
2721
+ position: absolute;
2722
+ bottom: 4px;
2723
+ left: 0;
2724
+ right: 0;
2725
+ height: 32px;
1963
2726
  display: flex;
1964
- align-items: flex-end;
2727
+ align-items: center;
1965
2728
  justify-content: center;
2729
+ gap: var(--space-8);
2730
+ pointer-events: none;
1966
2731
  }
1967
2732
 
1968
- ._scrim_s4eai_10 {
1969
- position: absolute;
1970
- inset: 0;
1971
- background: rgba(8, 14, 18, 0.5);
2733
+ ._dot_9mquf_43 {
2734
+ width: 8px;
2735
+ height: 8px;
2736
+ border-radius: var(--radius-full);
2737
+ background: #9ea2a4;
1972
2738
  }
1973
2739
 
1974
- ._sheet_s4eai_16 {
1975
- position: relative;
1976
- width: 100%;
1977
- max-width: 480px;
1978
- max-height: 90dvh;
1979
- background: var(--color-surface-default);
1980
- border-radius: var(--radius-18) var(--radius-18) 0 0;
2740
+ ._dotActive_9mquf_63 {
2741
+ background: #f9fafa;
2742
+ }
2743
+ ._section_iy175_1 {
1981
2744
  display: flex;
1982
2745
  flex-direction: column;
1983
- animation: _slide-up_s4eai_1 200ms ease;
1984
- padding-bottom: var(--safe-area-bottom);
2746
+ gap: var(--space-12);
2747
+ padding: var(--space-24) 0 var(--space-16);
1985
2748
  }
1986
2749
 
1987
- @keyframes _slide-up_s4eai_1 {
1988
- from {
1989
- transform: translateY(100%);
1990
- }
1991
- to {
1992
- transform: translateY(0);
1993
- }
2750
+ ._track_iy175_8 {
2751
+ display: flex;
2752
+ gap: var(--space-12);
2753
+ list-style: none;
2754
+ margin: 0;
2755
+ padding: 0 var(--space-16);
2756
+ overflow-x: auto;
2757
+ scroll-snap-type: x mandatory;
2758
+ scroll-padding-inline: var(--space-16);
2759
+ -webkit-overflow-scrolling: touch;
1994
2760
  }
1995
2761
 
1996
- /* Drag handle strip. `touch-action: none` stops the browser claiming the vertical
1997
- gesture as a page scroll before the pointer handlers see it. Confined to this strip
1998
- so drags starting on the scrollable body still scroll the body. */
1999
- ._grabArea_s4eai_41 {
2000
- padding: var(--space-8) 0 var(--space-4);
2001
- touch-action: none;
2002
- cursor: grab;
2762
+ /*
2763
+ * DELIBERATELY LIGHT, against the design.
2764
+ *
2765
+ * Figma draws this card dark (#0c161c) with the attribution in #3d4549 — about 2:1
2766
+ * contrast, where WCAG AA needs 4.5:1 for body text and AA is a launch gate here. The
2767
+ * same attribution colour on a light card clears it comfortably.
2768
+ *
2769
+ * So this is not drift: matching the design would ship a known accessibility failure. It
2770
+ * is raised with DAZN's designers, and the card flips the moment they retune the token.
2771
+ * Everything else on this block follows Figma exactly.
2772
+ */
2773
+ ._card_iy175_31 {
2774
+ flex: 0 0 323px;
2775
+ max-width: calc(100% - var(--space-20));
2776
+ scroll-snap-align: start;
2777
+ display: flex;
2778
+ flex-direction: column;
2779
+ gap: var(--space-12);
2780
+ padding: 25px var(--space-24);
2781
+ border-radius: var(--radius-8);
2782
+ background: #f9fafa;
2003
2783
  }
2004
2784
 
2005
- ._grabArea_s4eai_41:active {
2006
- cursor: grabbing;
2785
+ ._quote_iy175_43 {
2786
+ margin: 0;
2787
+ font-size: var(--type-heading-lg-size);
2788
+ line-height: 1.2;
2789
+ font-weight: var(--font-weight-bold);
2790
+ color: #0c161c;
2007
2791
  }
2008
2792
 
2009
- ._grabber_s4eai_51 {
2010
- width: 40px;
2011
- height: 4px;
2012
- margin: 0 auto;
2013
- border-radius: 2px;
2014
- background: var(--color-border-default);
2793
+ ._author_iy175_51 {
2794
+ margin: 0;
2795
+ font-size: var(--type-trimmed-sm-size);
2796
+ color: #3d4549;
2015
2797
  }
2016
2798
 
2017
- ._header_s4eai_59 {
2799
+ ._dots_iy175_57 {
2018
2800
  display: flex;
2019
- align-items: center;
2020
- justify-content: space-between;
2021
- padding: var(--space-16);
2022
- border-bottom: 1px solid var(--color-border-default);
2801
+ justify-content: center;
2802
+ gap: var(--space-8);
2023
2803
  }
2024
2804
 
2025
- ._title_s4eai_67 {
2026
- font-size: var(--type-heading-md-size);
2805
+ ._dot_iy175_57 {
2806
+ width: 8px;
2807
+ height: 8px;
2808
+ border-radius: var(--radius-full);
2809
+ background: #9ea2a4;
2027
2810
  }
2028
2811
 
2029
- ._close_s4eai_71 {
2030
- min-width: var(--tap-target-min);
2031
- min-height: var(--tap-target-min);
2032
- display: inline-flex;
2033
- align-items: center;
2034
- justify-content: center;
2035
- border: none;
2036
- background: transparent;
2037
- font-size: var(--type-trimmed-md-size);
2038
- cursor: pointer;
2812
+ ._dotActive_iy175_70 {
2813
+ background: #f9fafa;
2814
+ }
2815
+
2816
+ /* Quiet, but present. Not in the Figma frame — the component's own note makes it
2817
+ mandatory, and staff quotes shown as reviews without one is the failure it guards. */
2818
+ ._disclaimer_iy175_76 {
2819
+ margin: 0;
2820
+ padding: 0 var(--space-16);
2821
+ font-size: var(--type-trimmed-xs-size);
2039
2822
  color: var(--color-text-subtle);
2040
2823
  }
2824
+ ._section_145t0_1 {
2825
+ display: flex;
2826
+ flex-direction: column;
2827
+ gap: var(--space-12);
2828
+ padding: var(--space-24) var(--space-16);
2829
+ }
2041
2830
 
2042
- ._body_s4eai_84 {
2043
- overflow-y: auto;
2044
- padding: var(--space-16);
2045
- flex: 1 1 auto;
2831
+ /* Each benefit is a filled tile, not a bare row (Figma `mobile/why-us`). */
2832
+ ._row_145t0_9 {
2833
+ display: flex;
2834
+ flex-direction: column;
2835
+ gap: var(--space-8);
2836
+ padding: 20px;
2837
+ border-radius: var(--radius-8);
2838
+ background: var(--color-surface-soft);
2046
2839
  }
2047
2840
 
2048
- ._footer_s4eai_90 {
2049
- border-top: 1px solid var(--color-border-default);
2050
- padding: var(--space-16);
2841
+ /* Figma `size/heading/sm`. Written out rather than taken from
2842
+ `--type-heading-sm-size`, which is 20px here and 18px in the library — the token is
2843
+ wrong and correcting it would move the PDP's size guide, which has its own design. */
2844
+ ._title_145t0_21 {
2845
+ margin: 0;
2846
+ font-size: 18px;
2847
+ line-height: 1.38;
2848
+ font-weight: var(--font-weight-bold);
2849
+ color: var(--color-text-default);
2051
2850
  }
2052
- ._wrap_v9onk_1 {
2053
- display: inline-flex;
2851
+
2852
+ ._body_145t0_29 {
2853
+ margin: 0;
2854
+ font-size: var(--type-trimmed-sm-size);
2855
+ color: var(--color-text-subtle);
2856
+ }
2857
+ ._section_opjfc_1 {
2858
+ display: flex;
2054
2859
  flex-direction: column;
2055
- align-items: flex-start;
2056
- gap: var(--space-4);
2860
+ gap: var(--space-16);
2861
+ padding: var(--space-16) var(--space-16) var(--space-24);
2057
2862
  }
2058
2863
 
2059
- ._stepper_v9onk_8 {
2060
- display: inline-flex;
2061
- align-items: center;
2062
- gap: var(--space-4);
2063
- border: 1px solid var(--color-border-default);
2064
- border-radius: var(--radius-full);
2065
- padding: var(--space-2);
2864
+ ._title_opjfc_8 {
2865
+ font-size: var(--type-heading-md-size);
2866
+ line-height: var(--type-heading-md-line);
2867
+ font-weight: var(--font-weight-bold);
2868
+ color: var(--color-text-default);
2066
2869
  }
2067
2870
 
2068
- /* Why the increment stopped. Subtle, but present — never a silently dead control. */
2069
- ._cap_v9onk_18 {
2070
- font-size: var(--type-trimmed-xs-size, 12px);
2071
- font-weight: var(--font-weight-regular, 400);
2072
- color: var(--color-text-subtle);
2871
+ ._list_opjfc_15 {
2872
+ list-style: none;
2873
+ margin: 0;
2874
+ padding: 0;
2073
2875
  }
2074
2876
 
2075
- ._button_v9onk_24 {
2076
- min-width: var(--tap-target-min);
2077
- min-height: var(--tap-target-min);
2078
- /* Centres the icon in the 44px target — without this the svg sits on the text
2079
- baseline and the row reads as misaligned. */
2080
- display: inline-flex;
2877
+ /*
2878
+ * 16px above and below a 14px label clears the 44px tap target on its own, so there is no
2879
+ * explicit floor here. That holds because of the type size — a smaller label would need
2880
+ * the min-height back.
2881
+ */
2882
+ ._row_opjfc_26 {
2883
+ display: flex;
2081
2884
  align-items: center;
2082
- justify-content: center;
2083
- border: none;
2084
- background: transparent;
2085
- border-radius: var(--radius-full);
2086
- font-size: var(--type-trimmed-md-size);
2885
+ justify-content: space-between;
2886
+ padding: var(--space-16) 0;
2887
+ text-decoration: none;
2888
+ color: var(--color-text-default);
2889
+ font-size: var(--type-trimmed-sm-size);
2087
2890
  font-weight: var(--font-weight-bold);
2891
+ }
2892
+
2893
+ /* Between rows only — a rule under the last one reads as a cut-off list. */
2894
+ ._list_opjfc_15 li + li ._row_opjfc_26 {
2895
+ border-top: 1px solid var(--color-border-transparent-soft);
2896
+ }
2897
+
2898
+ ._row_opjfc_26 svg {
2899
+ flex-shrink: 0;
2088
2900
  color: var(--color-text-default);
2089
- cursor: pointer;
2901
+ }
2902
+ /* 4.2 Category (PCP) page layout. Moved here from the Next app's route stylesheet so the
2903
+ SDK and the app render the same screen from the same rules — see CategoryContent.tsx. */
2904
+
2905
+ /* Background and base typography come from ShopSurface, which this class is applied
2906
+ alongside — one owner for the surface, one for the layout. */
2907
+ ._pcp_6gy3c_6 {
2908
+ display: flex;
2909
+ flex-direction: column;
2910
+ /* Filling the screen now belongs to `ShopSurface`'s `fill`, which every screen sets and
2911
+ which uses `100dvh`. This rule used to be `min-height: 100%` here, with a comment
2912
+ arguing against viewport units precisely because a host may render chrome around us
2913
+ and a viewport unit overshoots by its height.
2914
+
2915
+ That argument is right, and it lost to a worse failure. `100%` resolves against the
2916
+ parent, so it does nothing at all unless the host's mount chain has a height — and
2917
+ DAZN's does not, which is why their cart rendered as a short white card on black.
2918
+ Overshooting by the height of a header costs a few pixels of scroll on a short page.
2919
+ Not filling costs a screen that looks broken. Hosts that mount a view inside their
2920
+ own sized container can still say `fill={false}`. */
2921
+ }
2922
+
2923
+ /* The scrolling column, which starts over the hero's lower third. `position` is load-
2924
+ bearing, not cosmetic — CategoryContent.tsx says why. No background: the design lets
2925
+ the photograph show through behind the first rail. */
2926
+ ._blocks_6gy3c_25 {
2927
+ position: relative;
2928
+ display: flex;
2929
+ flex-direction: column;
2090
2930
  }
2091
2931
 
2092
- ._button_v9onk_24:disabled {
2093
- color: var(--color-text-disabled);
2094
- cursor: not-allowed;
2932
+ /*
2933
+ * Blocks stack FLUSH and each owns its own padding, which is how the design is built —
2934
+ * `py-24` on the rail, `p-24` on the editorial line, and so on. This used to be a uniform
2935
+ * `gap: 32` between padding-less blocks. A few totals happened to land near the design's,
2936
+ * but none were derived from it, so changing any one block silently desynced the rhythm
2937
+ * around it.
2938
+ */
2939
+ ._featured_6gy3c_38,
2940
+ ._gridBlock_6gy3c_39 {
2941
+ display: flex;
2942
+ flex-direction: column;
2943
+ gap: var(--space-12);
2944
+ padding: var(--space-24) 0;
2095
2945
  }
2096
2946
 
2097
- ._value_v9onk_46 {
2098
- min-width: 2ch;
2099
- text-align: center;
2100
- font-size: var(--type-trimmed-md-size);
2947
+ ._blockTitle_6gy3c_46 {
2948
+ padding: 0 var(--space-16);
2949
+ font-size: var(--type-heading-md-size);
2950
+ line-height: 1.38;
2101
2951
  font-weight: var(--font-weight-bold);
2952
+ color: var(--color-text-default);
2102
2953
  }
2103
- ._row_stxj2_1 {
2954
+
2955
+ ._grid_6gy3c_39 {
2956
+ list-style: none;
2957
+ margin: 0;
2958
+ padding: 0 var(--space-16);
2104
2959
  display: grid;
2105
- grid-template-columns: 72px 1fr auto;
2106
- gap: var(--space-12);
2107
- align-items: start;
2108
- padding: var(--space-12) 0;
2109
- border-bottom: 1px solid var(--color-border-default);
2960
+ grid-template-columns: repeat(2, 1fr);
2961
+ column-gap: var(--space-12);
2962
+ row-gap: var(--space-24);
2110
2963
  }
2964
+ /*
2965
+ * Figma "homepage v2" (node 2795:98786): a full-screen, snap-scrolling editorial
2966
+ * showcase. Each panel is a full-bleed photo with a bottom overlay (title + subtitle +
2967
+ * outline CTA). Always dark (#080e12), independent of theme. Measurements are taken
2968
+ * from Figma: overlay pad 24, gap 16, title 32, subtitle 16/1.55, button h48 radius 8.
2969
+ */
2111
2970
 
2112
- ._pending_stxj2_10 {
2113
- opacity: 0.6;
2971
+ /*
2972
+ * The height of this scroller is the one measurement a mounted component cannot guess.
2973
+ *
2974
+ * It used to be `calc(100dvh - 56px)`, and both halves were wrong.
2975
+ *
2976
+ * **The 56 was a hard-coded guess at somebody else's header.** We are mounted inside a
2977
+ * host page whose chrome we do not own and cannot see. Where the host's header was not
2978
+ * 56px — DAZN's storefront runs 69 — the scroller ended up shorter than the space it was
2979
+ * given, leaving a dead band of bare surface at the bottom. Because the band sits below
2980
+ * the fold until the document itself scrolls, it appeared only after scrolling down and
2981
+ * back: exactly the report from 13 Aug 2026.
2982
+ *
2983
+ * **`dvh` is the wrong unit for a snap container.** It is the *dynamic* viewport, so it
2984
+ * grows as a mobile browser hides its URL bar and shrinks as it returns. Every panel then
2985
+ * resizes during the very scroll that snapping depends on, and the container is left
2986
+ * parked between snap points. `svh` is the stable smallest value and never changes
2987
+ * mid-scroll. The cost is a small gap when the chrome is hidden; the alternative is a
2988
+ * layout that moves under the reader's thumb.
2989
+ *
2990
+ * `--showcase-top` is measured at mount and on resize (see HomeShowcase.tsx) rather than
2991
+ * assumed: whatever the host puts above us, we take the rest.
2992
+ */
2993
+ ._showcase_ems17_30 {
2994
+ height: calc(100svh - var(--showcase-top, 0px));
2995
+ overflow-y: auto;
2996
+ scroll-snap-type: y mandatory;
2997
+ /*
2998
+ * Do not hand the scroll on to the host page when this container reaches its end.
2999
+ * Chaining is what let the outer document sit at a different offset from this one, so a
3000
+ * reader who scrolled to the bottom and back found the page in a state neither scroller
3001
+ * agreed on.
3002
+ */
3003
+ overscroll-behavior-y: contain;
3004
+ -webkit-overflow-scrolling: touch;
3005
+ background: #080e12;
3006
+ scrollbar-width: none;
3007
+ }
3008
+ ._showcase_ems17_30::-webkit-scrollbar {
3009
+ display: none;
2114
3010
  }
2115
3011
 
2116
- ._media_stxj2_14 {
3012
+ ._panel_ems17_49 {
2117
3013
  position: relative;
2118
- width: 72px;
2119
- height: 72px;
2120
- border-radius: var(--radius-8);
3014
+ /* Matches the scroller exactly, or snapping has nothing to align to. */
3015
+ height: calc(100svh - var(--showcase-top, 0px));
3016
+ scroll-snap-align: start;
3017
+ scroll-snap-stop: always;
2121
3018
  overflow: hidden;
2122
- background: var(--color-surface-soft);
3019
+ background: #080e12;
2123
3020
  }
2124
3021
 
2125
- ._image_stxj2_23 {
3022
+ ._image_ems17_59 {
2126
3023
  object-fit: cover;
3024
+ object-position: center;
2127
3025
  }
2128
3026
 
2129
- ._placeholder_stxj2_27 {
2130
- width: 100%;
2131
- height: 100%;
3027
+ /* Bottom scrim so overlay text stays legible over any photo. */
3028
+ ._panel_ems17_49::after {
3029
+ content: '';
3030
+ position: absolute;
3031
+ inset: 0;
3032
+ background: linear-gradient(to bottom, rgba(8, 14, 18, 0) 45%, rgba(8, 14, 18, 0.85) 100%);
3033
+ pointer-events: none;
2132
3034
  }
2133
3035
 
2134
- ._info_stxj2_32 {
3036
+ /* ── Hero ── */
3037
+ /*
3038
+ * Unlike the category panels (`edit_6 1`, full-bleed 375x812), the hero photo in Figma
3039
+ * (`edit_9 1`) is 375x674 and stops where the opaque "swipe down" bar starts. Giving it
3040
+ * its own box keeps the shot at its native aspect instead of cover-cropping the sides.
3041
+ */
3042
+ ._heroImageBox_ems17_79 {
3043
+ position: absolute;
3044
+ inset: 0 0 82px;
3045
+ overflow: hidden;
3046
+ }
3047
+ ._hero_ems17_79 ._heroContent_ems17_84 {
3048
+ position: absolute;
3049
+ bottom: 15px;
3050
+ left: 0;
3051
+ right: 0;
3052
+ z-index: 1;
2135
3053
  display: flex;
2136
3054
  flex-direction: column;
2137
- gap: var(--space-8);
2138
- min-width: 0;
3055
+ gap: 24px;
3056
+ align-items: stretch;
2139
3057
  }
2140
-
2141
- ._text_stxj2_39 {
3058
+ ._heroText_ems17_95 {
2142
3059
  display: flex;
2143
3060
  flex-direction: column;
2144
- gap: var(--space-4);
2145
- min-width: 0;
3061
+ gap: 8px;
3062
+ align-items: center;
3063
+ padding: 0 24px;
3064
+ text-align: center;
2146
3065
  }
2147
-
2148
- ._title_stxj2_46 {
2149
- font-size: var(--type-trimmed-sm-size);
2150
- font-weight: var(--font-weight-bold);
2151
- /* Two lines, then ellipsis. Product titles are catalogue copy and can run long; an
2152
- unbounded title pushes the stepper and the line total out of alignment down the
2153
- list. */
2154
- display: -webkit-box;
2155
- -webkit-line-clamp: 2;
2156
- -webkit-box-orient: vertical;
2157
- overflow: hidden;
3066
+ ._logo_ems17_103 {
3067
+ width: 48px;
3068
+ height: 48px;
2158
3069
  }
2159
-
2160
- /* "Black · M" — which variant this line is. Without it two sizes of one product are
2161
- the same line twice. */
2162
- ._variant_stxj2_60 {
2163
- font-size: var(--type-trimmed-xs-size);
2164
- color: var(--color-text-subtle);
3070
+ ._heroTitle_ems17_107 {
3071
+ margin: 0;
3072
+ font-size: 56px;
3073
+ line-height: 1.3;
3074
+ font-weight: 700;
3075
+ letter-spacing: -1.12px;
3076
+ color: #f9fafa;
2165
3077
  }
2166
-
2167
- ._unit_stxj2_65 {
2168
- font-size: var(--type-trimmed-xs-size);
2169
- color: var(--color-text-subtle);
3078
+ ._heroSub_ems17_115 {
3079
+ margin: 0;
3080
+ font-size: 16px;
3081
+ line-height: 1.55;
3082
+ color: #f9fafa;
3083
+ max-width: 327px;
2170
3084
  }
2171
-
2172
- ._lineTotal_stxj2_70 {
2173
- font-size: var(--type-trimmed-md-size);
2174
- font-weight: var(--font-weight-bold);
2175
- white-space: nowrap;
3085
+ ._swipe_ems17_122 {
3086
+ display: flex;
3087
+ flex-direction: column;
3088
+ align-items: center;
3089
+ gap: 6px;
3090
+ height: 82px;
3091
+ justify-content: center;
3092
+ background: #080e12;
2176
3093
  }
2177
- ._summary_dmv1d_1 {
3094
+ ._swipeLabel_ems17_131 {
2178
3095
  margin: 0;
3096
+ font-size: 14px;
3097
+ line-height: 1;
3098
+ color: rgba(255, 255, 255, 0.54);
3099
+ }
3100
+ ._chevron_ems17_137 {
3101
+ width: 24px;
3102
+ height: 24px;
3103
+ color: rgba(255, 255, 255, 0.54);
3104
+ animation: _bob_ems17_1 1.8s ease-in-out infinite;
3105
+ }
3106
+ @keyframes _bob_ems17_1 {
3107
+ 0%, 100% { transform: translateY(0); }
3108
+ 50% { transform: translateY(4px); }
3109
+ }
3110
+
3111
+ /* ── Category panels ── */
3112
+ /*
3113
+ * Covers the whole panel, with the content pushed to the bottom — rather than sitting
3114
+ * only at the bottom. That is what gives the stretched CTA below something panel-sized to
3115
+ * stretch against, which is how the design's "whole card = tap target" is honoured.
3116
+ */
3117
+ ._overlay_ems17_154 {
3118
+ position: absolute;
3119
+ inset: 0;
3120
+ z-index: 1;
2179
3121
  display: flex;
2180
3122
  flex-direction: column;
2181
- gap: var(--space-8);
3123
+ justify-content: flex-end;
3124
+ gap: 16px;
3125
+ align-items: flex-start;
3126
+ padding: 0 24px 24px;
2182
3127
  }
2183
-
2184
- ._row_dmv1d_8 {
3128
+ ._copy_ems17_165 {
2185
3129
  display: flex;
2186
- align-items: baseline;
2187
- justify-content: space-between;
2188
- font-size: var(--type-trimmed-sm-size);
2189
- color: var(--color-text-subtle);
3130
+ flex-direction: column;
3131
+ gap: 8px;
2190
3132
  }
2191
-
2192
- ._row_dmv1d_8 dt,
2193
- ._row_dmv1d_8 dd {
3133
+ ._title_ems17_170 {
2194
3134
  margin: 0;
3135
+ font-size: 32px;
3136
+ line-height: 1.2;
3137
+ font-weight: 700;
3138
+ color: #f9fafa;
2195
3139
  }
2196
-
2197
- ._total_dmv1d_21 {
2198
- padding-top: var(--space-8);
2199
- border-top: 1px solid var(--color-border-default);
2200
- font-size: var(--type-trimmed-md-size);
2201
- font-weight: var(--font-weight-bold);
2202
- color: var(--color-text-default);
3140
+ ._subtitle_ems17_177 {
3141
+ margin: 0;
3142
+ font-size: 16px;
3143
+ line-height: 1.55;
3144
+ color: #f9fafa;
3145
+ }
3146
+ ._cta_ems17_183 {
3147
+ display: inline-flex;
3148
+ align-items: center;
3149
+ justify-content: center;
3150
+ min-height: 48px;
3151
+ padding: 8px 12px;
3152
+ border: 1px solid #f9fafa;
3153
+ border-radius: 8px;
3154
+ background: transparent;
3155
+ color: #f9fafa;
3156
+ font-size: 16px;
3157
+ font-weight: 700;
3158
+ text-decoration: none;
3159
+ }
3160
+ /*
3161
+ * The hit area, stretched over the whole card. The button stays exactly where the design
3162
+ * puts it and keeps its own visual state; the tappable region is everything above it.
3163
+ */
3164
+ ._cta_ems17_183::after {
3165
+ content: '';
3166
+ position: absolute;
3167
+ inset: 0;
2203
3168
  }
2204
3169
 
2205
- /* "Free" is a merchandising signal, not a number — success tone, per the design. */
2206
- ._free_dmv1d_30 {
2207
- color: var(--color-state-text-success);
2208
- font-weight: var(--font-weight-bold);
3170
+ ._cta_ems17_183:active {
3171
+ background: rgba(249, 250, 250, 0.12);
2209
3172
  }
2210
3173
 
2211
- /* "Calculated at checkout". Set quieter than a real figure so it never reads as one. */
2212
- ._pending_dmv1d_36 {
2213
- font-size: var(--type-trimmed-xs-size);
2214
- color: var(--color-text-subtle);
3174
+ /* Keyboard users get the outline on the button itself, not on the invisible card-sized
3175
+ region — a focus ring the size of the viewport communicates nothing. */
3176
+ ._cta_ems17_183:focus-visible {
3177
+ outline: none;
3178
+ box-shadow: var(--focus-ring);
2215
3179
  }
2216
3180
  /*
2217
- * Shown while the cart is locked for checkout (contracts 2.11.0 `Cart.locked`).
2218
- *
2219
- * Warning tones rather than critical: nothing has gone wrong, the fan simply cannot edit
2220
- * right now. Critical red here would read as a failure and push people to abandon a
2221
- * checkout that is working.
3181
+ * Figma `mobile/header`, context=shop. The component is 96px: a 40px status bar the
3182
+ * device draws, and the 56px `top.navigation` below it that is the part we can render.
3183
+ * 16px inset both sides, 24px icons.
2222
3184
  */
2223
- ._lockNotice_1f0ae_8 {
2224
- margin: 0;
2225
- padding: var(--space-12) var(--space-16);
2226
- background: var(--color-state-surface-warning-soft);
2227
- color: var(--color-text-default);
2228
- border-bottom: 1px solid var(--color-border-subtle);
2229
- font-size: 14px;
2230
- line-height: 1.4;
2231
- }
2232
- /* Figma mobile/header top.navigation (context=shop): 56px bar, 16px inset, 24px icons. */
2233
- ._header_nbe8a_2 {
3185
+ ._header_7rinq_6 {
2234
3186
  display: flex;
2235
3187
  align-items: center;
2236
3188
  justify-content: space-between;
@@ -2239,7 +3191,27 @@
2239
3191
  background: var(--color-surface-default);
2240
3192
  }
2241
3193
 
2242
- ._right_nbe8a_11 {
3194
+ /*
3195
+ * Overlaid rather than stacked, for screens whose artwork runs to the top of the display.
3196
+ *
3197
+ * `mobile/hero` carries its own `mobile/header` instance at y=0, hidden, and positions the
3198
+ * category title at y=106 — measured from the top of the SCREEN, not from under a bar. In
3199
+ * flow the header pushes the picture down and every measurement inside it lands 56px low,
3200
+ * which is how the category page shipped.
3201
+ *
3202
+ * Declared after `.header` so it wins the background: both are one class, so the later
3203
+ * rule takes it.
3204
+ */
3205
+ ._floating_7rinq_26 {
3206
+ position: absolute;
3207
+ top: 0;
3208
+ left: 0;
3209
+ right: 0;
3210
+ z-index: 5;
3211
+ background: none;
3212
+ }
3213
+
3214
+ ._right_7rinq_35 {
2243
3215
  display: flex;
2244
3216
  align-items: center;
2245
3217
  gap: var(--space-16);
@@ -2252,12 +3224,12 @@
2252
3224
  * header without a back button would silently re-lay-out rather than simply losing one
2253
3225
  * icon. 24px is the icon box it stands in for.
2254
3226
  */
2255
- ._spacer_nbe8a_24 {
3227
+ ._spacer_7rinq_48 {
2256
3228
  width: 24px;
2257
3229
  height: 24px;
2258
3230
  }
2259
3231
 
2260
- ._iconBtn_nbe8a_29 {
3232
+ ._iconBtn_7rinq_53 {
2261
3233
  position: relative;
2262
3234
  display: inline-flex;
2263
3235
  align-items: center;
@@ -2273,35 +3245,59 @@
2273
3245
  }
2274
3246
 
2275
3247
  /* Expand the hit area to >=44px without moving the 24px icon off the 16px inset. */
2276
- ._iconBtn_nbe8a_29::after {
3248
+ ._iconBtn_7rinq_53::after {
2277
3249
  content: '';
2278
3250
  position: absolute;
2279
3251
  inset: -10px;
2280
3252
  }
2281
3253
 
2282
3254
  /*
2283
- * Cart count. Anchored to the icon's top-right corner and allowed to grow leftwards for
2284
- * a two- or three-character value ("99+"), which a fixed-width circle would clip.
3255
+ * The overflow control is drawn in a 32px box against the cart's 24, which is what puts
3256
+ * its glyph on the design's centre line 16px from the right edge, not 12. Its own icon
3257
+ * stays 24px.
3258
+ */
3259
+ ._menuBtn_7rinq_80 {
3260
+ width: 32px;
3261
+ height: 32px;
3262
+ }
3263
+
3264
+ ._menuBtn_7rinq_80::after {
3265
+ inset: -6px;
3266
+ }
3267
+
3268
+ /*
3269
+ * Cart count, anchored where the design anchors it: 14px in and 6px above the icon's own
3270
+ * box, so the circle overhangs the trolley's top-right corner.
3271
+ *
3272
+ * Fixed colours on both counts. The pill is `#dddedf` with `#080e12` on it in the frame —
3273
+ * light on the dark category header — and it reads over a photograph there, so it does not
3274
+ * follow the surface palette any more than the hero title does. Sampled from the frame
3275
+ * rather than taken from the token dump, which resolved the same DAZN token two different
3276
+ * ways depending on which node was asked.
3277
+ *
3278
+ * `min-width` rather than a fixed circle so a two- or three-character value ("99+") grows
3279
+ * leftwards instead of being clipped.
2285
3280
  *
2286
3281
  * Sits above the ::after hit area so the number is never painted underneath it.
2287
3282
  */
2288
- ._badge_nbe8a_57 {
3283
+ ._badge_7rinq_104 {
2289
3284
  position: absolute;
2290
- top: -4px;
2291
- left: 50%;
3285
+ top: -6px;
3286
+ left: 14px;
2292
3287
  z-index: 1;
2293
3288
  min-width: 16px;
2294
3289
  height: 16px;
2295
- padding: 0 4px;
3290
+ padding: 0 var(--space-4);
2296
3291
  box-sizing: border-box;
2297
3292
  display: inline-flex;
2298
3293
  align-items: center;
2299
3294
  justify-content: center;
2300
3295
  border-radius: var(--radius-full);
2301
- background: var(--color-state-surface-critical-default);
2302
- color: #ffffff;
2303
- font-size: 10px;
3296
+ background: #dddedf;
3297
+ color: #080e12;
3298
+ font-size: var(--type-trimmed-xs-size);
2304
3299
  line-height: 1;
3300
+ letter-spacing: 0.12px;
2305
3301
  font-weight: var(--font-weight-bold);
2306
3302
  font-variant-numeric: tabular-nums;
2307
3303
  pointer-events: none;